
date() formatteert een timestamp, DateTimeImmutable gebruik je zodra er gerekend wordt, en IntlDateFormatter geeft dag- en maandnamen in het Frans. Stel de tijdzone expliciet in: zonder date.timezone gebruikt PHP UTC. strftime() is verouderd sinds PHP 8.1 en de vervanger is niet date(), want dat vertaalt niets.
Een datum tonen in PHP is eenvoudig. De juiste datum tonen, in de juiste tijdzone, in het Frans, en ondertussen een interval berekenen zonder de mist in te gaan bij de zomertijd, is een stuk lastiger. Dit artikel behandelt de basisfuncties en daarna de klassen DateTimeImmutable en IntlDateFormatter, die de gevallen afdekken waar date() geen raad mee weet. Alle uitvoer komt van PHP 8.5.10.
Eerst de tijdzone
Dit is bugbron nummer één, en ze houdt zich stil. Staat date.timezone niet in php.ini, dan gebruikt PHP UTC. Een Franse site toont dan een tijd die een of twee uur afwijkt, afhankelijk van het seizoen, zonder ook maar één waarschuwing.
echo ini_get('date.timezone'), "\n"; // (leeg) op veel installaties
echo date_default_timezone_get(), "\n"; // UTCJe stelt de tijdzone in in php.ini, of bovenaan je applicatie:
date_default_timezone_set('Europe/Paris');De grondregel: opslaan in UTC, tonen in de tijdzone van de gebruiker. Een database vol lokale tijden wordt onbruikbaar zodra een gebruiker van tijdzone wisselt of de klok verzet wordt.
date() en time()
time() geeft het aantal seconden sinds de 1e januari 1970 om middernacht UTC. date() formatteert zo’n timestamp, of het huidige moment als je er geen meegeeft.
date_default_timezone_set('Europe/Paris');
echo date('d/m/Y'), "\n"; // 02/09/2026
echo date('d/m/Y H:i:s'), "\n"; // 02/09/2026 21:47:03
echo time(), "\n"; // 1788378423
echo date('d/m/Y', 1645303037), "\n"; // 19/02/2022De meest gebruikte formaattekens:
| Datum | Tijd | ||
|---|---|---|---|
d | dag met 2 cijfers (01 tot 31) | H | uur in 24-uursnotatie (00 tot 23) |
j | dag zonder voorloopnul (1 tot 31) | h | uur in 12-uursnotatie (01 tot 12) |
m | maand met 2 cijfers (01 tot 12) | i | minuten (00 tot 59) |
n | maand zonder voorloopnul (1 tot 12) | s | seconden (00 tot 59) |
Y | jaartal met 4 cijfers | A | AM of PM |
N | dag van de week (1 = maandag) | P | verschuiving, bijvoorbeeld +02:00 |
t | aantal dagen in de maand | U | Unix-timestamp |
L | 1 bij een schrikkeljaar | c | volledig ISO 8601-formaat |
Een teken dat zelf ook een formaatcode is, moet je escapen met een backslash:
echo date('\L\e d/m/Y'), "\n"; // Le 02/09/2026
// zonder backslash zou ‘L’ 0 of 1 geven en ‘e’ de naam van de tijdzonedate() spreekt geen Frans
Dat is de tweede klassieke valkuil. De dag- en maandnamen die date() teruggeeft zijn altijd Engels, en setlocale() verandert daar niets aan.
$ts = mktime(0, 0, 0, 4, 1, 2022);
echo date('l', $ts), "\n"; // Friday
setlocale(LC_TIME, 'fr_FR.UTF-8', 'fr_FR');
echo date('l', $ts), "\n"; // Friday — ongewijzigdDe oplossing is IntlDateFormatter, uit de intl-extensie:
$formateur = new IntlDateFormatter(
'fr_FR',
IntlDateFormatter::FULL,
IntlDateFormatter::NONE,
'Europe/Paris',
IntlDateFormatter::GREGORIAN,
);
echo $formateur->format($ts), "\n"; // vendredi 1 avril 2022Voor een eigen formaat neemt de klasse een ICU-patroon aan, met andere codes dan die van date():
$formateur->setPattern("EEEE d MMMM y '\u{e0}' HH'h'mm");
echo $formateur->format(new DateTimeImmutable('now', new DateTimeZone('Europe/Paris')));
// mercredi 2 septembre 2026 à 21h47Is intl niet beschikbaar, dan helpt een vertaaltabel je uit de brand voor het Frans, maar dat schaalt niet naar meerdere talen.
DateTimeImmutable, de klasse die je wilt gebruiken
Zodra je verder gaat dan tonen alleen, zijn de datumklassen beter dan de functies. DateTimeImmutable verdient de voorkeur boven DateTime: de methodes geven een nieuw object terug in plaats van het huidige aan te passen, wat wijzigingen op afstand uitsluit.
$paris = new DateTimeZone('Europe/Paris');
$date = new DateTimeImmutable('2026-09-02 14:30:00', $paris);
echo $date->format('d/m/Y H:i P'), "\n"; // 02/09/2026 14:30 +02:00
echo $date->modify('+3 days')->format('d/m/Y'), "\n"; // 05/09/2026
echo $date->format('d/m/Y'), "\n"; // 02/09/2026 — ongewijzigd
// Met DateTime (muteerbaar) zou de derde regel 05/09/2026 tonen// Tijdsduur optellen en aftrekken
$dans30Mois = $date->add(new DateInterval('P30M'));
$ilYa2Sem = $date->sub(new DateInterval('P2W'));
// Verschil tussen twee datums
$ecart = $date->diff(new DateTimeImmutable('2026-12-25', $paris));
echo $ecart->days, " jours\n";
echo $ecart->format('%m mois et %d jours'), "\n";
// Een reeks datums
$periode = new DatePeriod(
$date,
new DateInterval('P1D'),
new DateTimeImmutable('2026-09-07', $paris),
);
foreach ($periode as $jour) {
echo $jour->format('D d/m'), "\n";
}De valkuil van de laatste dag van de maand
Een maand optellen bij het einde van een maand geeft niet wat je verwacht, en dat gedrag is met DateInterval hetzelfde als met rekenwerk op mktime().
$fin = new DateTimeImmutable('2026-01-31');
echo $fin->add(new DateInterval('P1M'))->format('d/m/Y'), "\n"; // 03/03/202631 februari bestaat niet: PHP laat de datum doorlopen naar maart. Wil je de laatste dag van de volgende maand, dan moet je daar expliciet om vragen:
echo $fin->modify('last day of next month')->format('d/m/Y'), "\n"; // 28/02/2026Een dag duurt niet altijd 24 uur
Bij de overgang naar de zomertijd duurt een dag 23 uur, bij de wintertijd 25. 86400 seconden bij een timestamp optellen levert dan een verkeerde tijd op.
$paris = new DateTimeZone('Europe/Paris');
$avant = new DateTimeImmutable('2026-03-28 12:00:00', $paris);
$apres = $avant->add(new DateInterval('P1D'));
echo $apres->format('d/m/Y H:i P'), "\n"; // 29/03/2026 12:00 +02:00
echo ($apres->getTimestamp() - $avant->getTimestamp()) / 3600, " heures\n"; // 23DateInterval rekent in kalenderdagen en geeft netjes twaalf uur ’s middags de volgende dag, terwijl er maar 23 uur verstreken zijn. Voor een herinnering ‘morgen om twaalf uur’ is dat het juiste gedrag, voor een gefactureerde duur het verkeerde. Het zijn twee verschillende dingen: P1D voor een kalenderdag, PT24H voor vierentwintig uur.
Een datum inlezen die de gebruiker heeft ingevoerd
strtotime() begrijpt veel, maar leest dubbelzinnige formaten op zijn Amerikaans: 03/04/2026 wordt 4 maart, niet 3 april. Bij een bekend formaat is createFromFormat() ondubbelzinnig.
$date = DateTimeImmutable::createFromFormat(
'!d/m/Y', // de ‘!’ zet de tijd terug op 00:00:00
'03/04/2026',
new DateTimeZone('Europe/Paris'),
);
echo $date->format('d F Y'), "\n"; // 03 April 2026Zonder de ! vooraan nemen de niet-opgegeven velden de waarde van het huidige moment over: twee keer dezelfde code uitvoeren geeft twee verschillende tijden.
Het gevaarlijkste punt: een ongeldige datum mislukt niet, hij loopt stilletjes door.
$saisie = '31/02/2026';
$date = DateTimeImmutable::createFromFormat('!d/m/Y', $saisie, $paris);
echo $date->format('d/m/Y'), "\n"; // 03/03/2026
print_r(DateTimeImmutable::getLastErrors()); // ['warnings' => [10 => 'The parsed date was invalid'], …]
var_dump(checkdate(2, 31, 2026)); // bool(false)Je moet dus na elke parse getLastErrors() controleren, of de opnieuw geformatteerde datum vergelijken met de oorspronkelijke invoer:
function lireDate(string $saisie, DateTimeZone $fuseau): ?DateTimeImmutable
{
$date = DateTimeImmutable::createFromFormat('!d/m/Y', $saisie, $fuseau);
if ($date === false) {
return null;
}
$erreurs = DateTimeImmutable::getLastErrors();
if ($erreurs !== false && ($erreurs['warning_count'] || $erreurs['error_count'])) {
return null;
}
return $date->format('d/m/Y') === $saisie ? $date : null;
}Sinds PHP 8.2 geeft getLastErrors() false terug als er niets te melden valt, in plaats van een array met tellers op nul. Je test moet daar rekening mee houden.
Recente toevoegingen
PHP 8.4 heeft createFromTimestamp() toegevoegd, dat ook timestamps met decimalen aanneemt:
$d = DateTimeImmutable::createFromTimestamp(1645303037.5);
echo $d->format('d/m/Y H:i:s.u P'), "\n"; // 19/02/2022 20:37:17.500000 +00:00Daarvoor moest je via '@' . $ts of setTimestamp() werken, en gingen de microseconden verloren.
Verouderde functies die je niet meer moet gebruiken
Verschillende datumfuncties die er nog zijn, geven een deprecation-melding. Op PHP 8.5.10 werken ze nog, maar hun verwijdering staat gepland.
| Functie | Verouderd sinds | Vervanging |
|---|---|---|
strftime() | PHP 8.1 | IntlDateFormatter::format() |
gmstrftime() | PHP 8.1 | IntlDateFormatter::format() |
strptime() | PHP 8.2 | date_parse_from_format() of IntlDateFormatter::parse() |
date_sunrise() / date_sunset() | PHP 8.1 | date_sun_info() |
utf8_encode() | PHP 8.2 | mb_convert_encoding() |
De exacte meldingen, zowel op PHP 8.5.10 als op PHP 8.4.25:
Deprecated: Function strftime() is deprecated since 8.1,
use IntlDateFormatter::format() instead
Deprecated: Function strptime() is deprecated since 8.2,
use date_parse_from_format() (for locale-independent parsing),
or IntlDateFormatter::parse() (for locale-dependent parsing) instead
Deprecated: Constant SUNFUNCS_RET_STRING is deprecated since 8.4,
as date_sunrise() and date_sunset() were deprecated in 8.1strftime() was de gebruikelijke manier om een datum in het Frans te krijgen. De vervanger is niet date(), dat niets vertaalt, maar IntlDateFormatter.
Omgekeerd zijn mktime(), checkdate(), getdate() en idate() niet verouderd en werken ze gewoon. Voor eenvoudige gevallen blijven ze nuttig, ook al dekken de klassen alles af wat zij doen.
Wat je onthoudt
- Stel de tijdzone expliciet in, sla op in UTC, toon lokaal.
date()om een moment te formatteren,DateTimeImmutablevoor elke berekening.IntlDateFormattervoor dag- en maandnamen in het Frans.- Controleer na
createFromFormat()altijdgetLastErrors(): een ongeldige datum loopt stilletjes door. - Een kalenderdag is geen 24 uur.
P1DenPT24Hzijn niet uitwisselbaar.
Lees je datums bij miljoenen uit een importbestand, kijk dan naar grote bestanden lezen met PHP, en naar de verbinding met een database om ze in UTC op te slaan.
Zie ook een datum valideren met een reguliere expressie, datums beheren met Carbon in Laravel en de hub Webontwikkeling.
Veelgemaakte fouten
date.timezone in php.ini valt PHP terug op UTC en toont het een verschoven tijd, zonder ook maar één waarschuwing.setlocale() heeft geen enkel effect op date(). De vervanging van strftime() is IntlDateFormatter, niet date().31/02/2026 wordt 03/03/2026, met niet meer dan een waarschuwing in getLastErrors(). Je moet dat na elke parse nakijken.03/04/2026 wordt 4 maart, niet 3 april.31/01/2026 + P1M geeft 03/03/2026. Voor de laatste dag van de maand gebruik je modify('last day of next month').P1D netjes één kalenderdag op terwijl er maar 23 uur verstreken zijn. P1D en PT24H zijn niet uitwisselbaar.false terug als er niets te melden valt, in plaats van een array met tellers op nul.

