
date() formatiert einen Zeitstempel, DateTimeImmutable nimmst du, sobald gerechnet wird, und IntlDateFormatter liefert Tages- und Monatsnamen auf Französisch. Setze die Zeitzone explizit: Ohne date.timezone verwendet PHP UTC. strftime() ist seit PHP 8.1 veraltet, und der Ersatz ist nicht date(), das nichts übersetzt.
Ein Datum in PHP auszugeben ist einfach. Das richtige Datum auszugeben, in der richtigen Zeitzone, auf Französisch, und dabei ein Intervall zu berechnen, ohne bei der Zeitumstellung danebenzuliegen, ist deutlich schwieriger. Dieser Artikel behandelt die grundlegenden Funktionen und danach die Klassen DateTimeImmutable und IntlDateFormatter, die alles abdecken, was date() nicht kann. Sämtliche Ausgaben stammen aus PHP 8.5.10.
Zuerst die Zeitzone
Das ist die Fehlerquelle Nummer eins, und sie bleibt stumm. Ist date.timezone in der php.ini nicht gesetzt, fällt PHP auf UTC zurück. Eine französische Website zeigt dann je nach Jahreszeit eine um ein oder zwei Stunden verschobene Uhrzeit an, ohne die geringste Warnung.
echo ini_get('date.timezone'), "\n"; // (leer) auf vielen Installationen
echo date_default_timezone_get(), "\n"; // UTCGesetzt wird sie in der php.ini oder ganz am Anfang der Anwendung:
date_default_timezone_set('Europe/Paris');Die Grundregel: in UTC speichern, in der Zeitzone des Nutzers anzeigen. Eine Datenbank voller Ortszeiten wird unbrauchbar, sobald ein Nutzer die Zeitzone wechselt oder die Uhren umgestellt werden.
date() und time()
time() liefert die Anzahl der Sekunden seit dem 1. Januar 1970 um Mitternacht UTC. date() formatiert einen solchen Zeitstempel, oder den aktuellen Moment, wenn du keinen übergibst.
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/2022Die am häufigsten benutzten Formatzeichen:
| Datum | Uhrzeit | ||
|---|---|---|---|
d | Tag, zweistellig (01 bis 31) | H | Stunde im 24-Stunden-Format (00 bis 23) |
j | Tag ohne führende Null (1 bis 31) | h | Stunde im 12-Stunden-Format (01 bis 12) |
m | Monat, zweistellig (01 bis 12) | i | Minuten (00 bis 59) |
n | Monat ohne führende Null (1 bis 12) | s | Sekunden (00 bis 59) |
Y | Jahr, vierstellig | A | AM oder PM |
N | Wochentag (1 = Montag) | P | Abweichung, zum Beispiel +02:00 |
t | Anzahl der Tage im Monat | U | Unix-Zeitstempel |
L | 1 bei einem Schaltjahr | c | vollständiges ISO-8601-Format |
Ein Zeichen, das zugleich ein Formatcode ist, musst du mit einem Backslash maskieren:
echo date('\L\e d/m/Y'), "\n"; // Le 02/09/2026
// ohne Backslash würde „L“ 0 oder 1 ergeben und „e“ den Namen der Zeitzonedate() spricht kein Französisch
Das ist die zweite klassische Falle. Die Tages- und Monatsnamen, die date() zurückgibt, sind immer englisch, und setlocale() ändert daran nichts.
$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 — unverändertDie Lösung heißt IntlDateFormatter, aus der intl-Erweiterung:
$formateur = new IntlDateFormatter(
'fr_FR',
IntlDateFormatter::FULL,
IntlDateFormatter::NONE,
'Europe/Paris',
IntlDateFormatter::GREGORIAN,
);
echo $formateur->format($ts), "\n"; // vendredi 1 avril 2022Für ein eigenes Format nimmt die Klasse ein ICU-Muster entgegen, dessen Codes sich von denen von date() unterscheiden:
$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 à 21h47Fehlt intl, hilft eine Übersetzungstabelle für das Französische aus, die aber über mehrere Sprachen hinweg nicht skaliert.
DateTimeImmutable, die Klasse der Wahl
Sobald es über die reine Ausgabe hinausgeht, sind die Datumsklassen den Funktionen überlegen. DateTimeImmutable ist DateTime vorzuziehen: Seine Methoden geben ein neues Objekt zurück, statt das vorhandene zu verändern, und schließen damit Fernwirkungen aus.
$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 — unverändert
// Mit DateTime (veränderlich) würde die dritte Zeile 05/09/2026 ausgeben// Zeitspannen addieren und abziehen
$dans30Mois = $date->add(new DateInterval('P30M'));
$ilYa2Sem = $date->sub(new DateInterval('P2W'));
// Abstand zwischen zwei Datumsangaben
$ecart = $date->diff(new DateTimeImmutable('2026-12-25', $paris));
echo $ecart->days, " jours\n";
echo $ecart->format('%m mois et %d jours'), "\n";
// Eine Reihe von Datumsangaben
$periode = new DatePeriod(
$date,
new DateInterval('P1D'),
new DateTimeImmutable('2026-09-07', $paris),
);
foreach ($periode as $jour) {
echo $jour->format('D d/m'), "\n";
}Die Falle am Monatsende
Einen Monat auf ein Monatsende zu addieren ergibt nicht das Erwartete, und mit DateInterval verhält es sich genauso wie mit Arithmetik über mktime().
$fin = new DateTimeImmutable('2026-01-31');
echo $fin->add(new DateInterval('P1M'))->format('d/m/Y'), "\n"; // 03/03/2026Den 31. Februar gibt es nicht: PHP lässt ihn in den März überlaufen. Den letzten Tag des Folgemonats musst du ausdrücklich anfordern:
echo $fin->modify('last day of next month')->format('d/m/Y'), "\n"; // 28/02/2026Ein Tag hat nicht immer 24 Stunden
Bei der Umstellung auf die Sommerzeit hat ein Tag 23 Stunden, bei der Rückstellung 25. 86400 Sekunden auf einen Zeitstempel zu addieren ergibt dann eine falsche Uhrzeit.
$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 rechnet in Kalendertagen und liefert korrekt den Mittag des Folgetags, obwohl nur 23 Stunden vergangen sind. Für eine Erinnerung „morgen um zwölf“ ist das richtig, für eine abgerechnete Dauer falsch. Es sind zwei verschiedene Dinge: P1D für einen Kalendertag, PT24H für vierundzwanzig Stunden.
Ein vom Nutzer eingegebenes Datum einlesen
strtotime() versteht vieles, interpretiert mehrdeutige Formate aber auf amerikanische Art: 03/04/2026 wird zum 4. März, nicht zum 3. April. Bei bekanntem Format ist createFromFormat() eindeutig.
$date = DateTimeImmutable::createFromFormat(
'!d/m/Y', // das „!“ setzt die Uhrzeit auf 00:00:00 zurück
'03/04/2026',
new DateTimeZone('Europe/Paris'),
);
echo $date->format('d F Y'), "\n"; // 03 April 2026Ohne das führende ! übernehmen die nicht angegebenen Felder den aktuellen Zeitpunkt: Zwei Läufe desselben Codes ergeben zwei verschiedene Uhrzeiten.
Der gefährlichste Punkt: Ein ungültiges Datum schlägt nicht fehl, es läuft stillschweigend über.
$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)Nach jeder Auswertung musst du also getLastErrors() prüfen oder das neu formatierte Datum mit der ursprünglichen Eingabe vergleichen:
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;
}Seit PHP 8.2 gibt getLastErrors() false zurück, wenn es nichts zu melden gibt, statt eines Arrays mit Zählern auf null. Der Test muss das berücksichtigen.
Neuere Ergänzungen
PHP 8.4 hat createFromTimestamp() hinzugefügt, das auch Zeitstempel mit Nachkommastellen annimmt:
$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:00Vorher führte der Weg über '@' . $ts oder setTimestamp(), und die Mikrosekunden gingen verloren.
Veraltete Funktionen, die du nicht mehr verwenden solltest
Mehrere noch vorhandene Datumsfunktionen geben einen Deprecation-Hinweis aus. Auf PHP 8.5.10 laufen sie, ihre Entfernung ist aber beschlossen.
| Funktion | Veraltet seit | Ersatz |
|---|---|---|
strftime() | PHP 8.1 | IntlDateFormatter::format() |
gmstrftime() | PHP 8.1 | IntlDateFormatter::format() |
strptime() | PHP 8.2 | date_parse_from_format() oder IntlDateFormatter::parse() |
date_sunrise() / date_sunset() | PHP 8.1 | date_sun_info() |
utf8_encode() | PHP 8.2 | mb_convert_encoding() |
Die genauen Meldungen, aufgenommen auf PHP 8.5.10 wie auf 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() war der übliche Weg zu einem französischen Datum. Sein Ersatz ist nicht date(), das nichts übersetzt, sondern IntlDateFormatter.
Umgekehrt sind mktime(), checkdate(), getdate() und idate() nicht veraltet und funktionieren normal. Für einfache Fälle bleiben sie nützlich, auch wenn die Klassen alles abdecken, was sie können.
Das Wichtigste in Kürze
- Die Zeitzone explizit setzen, in UTC speichern, lokal anzeigen.
date()zum Formatieren eines Zeitpunkts,DateTimeImmutablefür jede Berechnung.IntlDateFormatterfür französische Tages- und Monatsnamen.- Nach
createFromFormat()immergetLastErrors()prüfen: Ein ungültiges Datum läuft stillschweigend über. - Ein Kalendertag sind keine 24 Stunden.
P1DundPT24Hsind nicht austauschbar.
Wenn du Datumsangaben millionenfach aus einer Importdatei liest, siehe große Dateien mit PHP lesen, und die Verbindung zu einer Datenbank, um sie in UTC abzulegen.
Siehe auch ein Datum mit einem regulären Ausdruck validieren, Datumsangaben mit Carbon in Laravel verwalten und den Hub Webentwicklung.
Häufige Fehler
date.timezone in der php.ini fällt PHP auf UTC zurück und zeigt eine verschobene Uhrzeit an, ohne die geringste Warnung.setlocale() hat keinerlei Wirkung auf date(). Der Ersatz für strftime() ist IntlDateFormatter, nicht date().31/02/2026 wird zu 03/03/2026, mit nichts als einer Warnung in getLastErrors(). Das musst du nach jeder Auswertung prüfen.03/04/2026 wird zum 4. März, nicht zum 3. April.31/01/2026 + P1M ergibt 03/03/2026. Für den letzten Tag des Monats nimm modify('last day of next month').P1D korrekt um einen Kalendertag vor, obwohl nur 23 Stunden vergangen sind. P1D und PT24H sind nicht austauschbar.false zurück, wenn es nichts zu melden gibt, statt eines Arrays mit Zählern auf null.

