Datum und Uhrzeit in PHP: formatieren, Zeitzonen, DateTimeImmutable

Datum und Uhrzeit in PHP abrufen und formatieren: die Zeitzone setzen, mit date() ausgeben, mit DateTimeImmutable rechnen, Tages- und Monatsnamen auf Französisch mit IntlDateFormatter und die veralteten Funktionen, die du hinter dir lassen solltest.

Datum und Uhrzeit in PHP: formatieren, Zeitzonen, DateTimeImmutable
Schnelle Antwort

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.

php
echo ini_get('date.timezone'), "\n";        // (leer) auf vielen Installationen
echo date_default_timezone_get(), "\n";     // UTC

Gesetzt wird sie in der php.ini oder ganz am Anfang der Anwendung:

php
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.

php
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/2022

Die 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:

php
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 Zeitzone

date() 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.

php
$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ändert

Die Lösung heißt IntlDateFormatter, aus der intl-Erweiterung:

php
$formateur = new IntlDateFormatter(
    'fr_FR',
    IntlDateFormatter::FULL,
    IntlDateFormatter::NONE,
    'Europe/Paris',
    IntlDateFormatter::GREGORIAN,
);

echo $formateur->format($ts), "\n";   // vendredi 1 avril 2022

Für ein eigenes Format nimmt die Klasse ein ICU-Muster entgegen, dessen Codes sich von denen von date() unterscheiden:

php
$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 à 21h47

Fehlt 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.

php
$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
php
// 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().

php
$fin = new DateTimeImmutable('2026-01-31');
echo $fin->add(new DateInterval('P1M'))->format('d/m/Y'), "\n";  // 03/03/2026

Den 31. Februar gibt es nicht: PHP lässt ihn in den März überlaufen. Den letzten Tag des Folgemonats musst du ausdrücklich anfordern:

php
echo $fin->modify('last day of next month')->format('d/m/Y'), "\n";  // 28/02/2026

Ein 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.

php
$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";  // 23

DateInterval 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.

php
$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 2026

Ohne 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.

php
$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:

php
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:

php
$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:00

Vorher 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:

code
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.1

strftime() 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, DateTimeImmutable für jede Berechnung.
  • IntlDateFormatter für französische Tages- und Monatsnamen.
  • Nach createFromFormat() immer getLastErrors() prüfen: Ein ungültiges Datum läuft stillschweigend über.
  • Ein Kalendertag sind keine 24 Stunden. P1D und PT24H sind 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

Zeitzone nicht gesetzt Ohne date.timezone in der php.ini fällt PHP auf UTC zurück und zeigt eine verschobene Uhrzeit an, ohne die geringste Warnung.
date('l') bleibt englisch setlocale() hat keinerlei Wirkung auf date(). Der Ersatz für strftime() ist IntlDateFormatter, nicht date().
createFromFormat nimmt ein ungültiges Datum an 31/02/2026 wird zu 03/03/2026, mit nichts als einer Warnung in getLastErrors(). Das musst du nach jeder Auswertung prüfen.
createFromFormat ohne das „!“ Nicht angegebene Felder übernehmen die aktuelle Uhrzeit: Zwei Läufe desselben Codes ergeben zwei Ergebnisse.
strtotime liest Daten auf amerikanische Art 03/04/2026 wird zum 4. März, nicht zum 3. April.
Einen Monat auf ein Monatsende addieren 31/01/2026 + P1M ergibt 03/03/2026. Für den letzten Tag des Monats nimm modify('last day of next month').
Ein Tag hat keine 24 Stunden Bei der Zeitumstellung rückt P1D korrekt um einen Kalendertag vor, obwohl nur 23 Stunden vergangen sind. P1D und PT24H sind nicht austauschbar.
getLastErrors gibt false zurück Seit PHP 8.2 gibt die Methode false zurück, wenn es nichts zu melden gibt, statt eines Arrays mit Zählern auf null.

DateDateTimeIntlPHP

Damien Flandrin Webentwickler seit 2010, Gründer von Gekkode und Email Impact. Jeder Artikel wird vor der Veröffentlichung an einem echten Projekt getestet. Kontakt
Newsletter

Neue Tests, Tutorials und Projekte, per E-Mail.

Reproduzierbare Tests, versionierter Code, datierte Ergebnisse. Niemals Spam.