Data i godzina w PHP: jak je pobrać i poprawnie sformatować

W tym artykule pokazuję, jak pobrać i sformatować datę oraz godzinę w PHP. Funkcja PHP date() Funkcja date() zamienia znacznik czasu na datę i godzinę czytelne dla użytkownika. Komputer przechowuje daty i godziny w formacie zwanym uniksowym znacznikiem czasu, który mierzy czas

Data i godzina w PHP: jak je pobrać i poprawnie sformatować
Szybka odpowiedź

date() formatuje znacznik czasu, DateTimeImmutable przydaje się przy każdym obliczeniu, a IntlDateFormatter daje francuskie nazwy dni i miesięcy. Ustaw strefę wprost: bez date.timezone PHP używa UTC. strftime() jest wycofane od PHP 8.1 i nie zastępuje go date(), które niczego nie tłumaczy.

Wyświetlenie daty w PHP jest proste. Wyświetlenie właściwej daty, we właściwej strefie, po francusku, i policzenie odstępu bez pomyłki przy zmianie czasu, już znacznie mniej. Ten artykuł omawia podstawowe funkcje, a potem klasy DateTimeImmutable i IntlDateFormatter, które rozwiązują przypadki, z jakimi date() sobie nie radzi. Wszystkie wyniki powstały na PHP 8.5.10.

Najpierw strefa czasowa

To źródło błędów numer jeden i działa po cichu. Jeśli date.timezone nie jest ustawione w php.ini, PHP używa UTC. Francuska strona pokazuje wtedy godzinę przesuniętą o jedną albo dwie, zależnie od pory roku, i nigdzie nie pojawia się ostrzeżenie.

php
echo ini_get('date.timezone'), "\n";        // (puste) na wielu instalacjach
echo date_default_timezone_get(), "\n";     // UTC

Ustawia się to w php.ini albo na starcie aplikacji:

php
date_default_timezone_set('Europe/Paris');

Zasada jest jedna: przechowuj w UTC, wyświetlaj w strefie użytkownika. Baza danych pełna czasów lokalnych staje się bezużyteczna, gdy tylko użytkownik zmieni strefę albo nadejdzie czas letni.

date() i time()

time() zwraca liczbę sekund, które upłynęły od 1. stycznia 1970 roku, od północy czasu UTC. date() formatuje taki znacznik czasu, albo bieżącą chwilę, jeśli nic mu nie podasz.

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

Najczęściej używane znaki formatu:

Data Godzina
d dzień na 2 cyfrach (01 do 31) H godzina w cyklu 24-godzinnym (00 do 23)
j dzień bez zera (1 do 31) h godzina w cyklu 12-godzinnym (01 do 12)
m miesiąc na 2 cyfrach (01 do 12) i minuty (00 do 59)
n miesiąc bez zera (1 do 12) s sekundy (00 do 59)
Y rok na 4 cyfrach A AM albo PM
N dzień tygodnia (1 = poniedziałek) P przesunięcie, na przykład +02:00
t liczba dni w miesiącu U znacznik czasu Unix
L 1, jeśli rok jest przestępny c pełny format ISO 8601

Żeby wstawić znak, który jest zarazem kodem formatu, trzeba go poprzedzić lewym ukośnikiem:

php
echo date('\L\e d/m/Y'), "\n";      // Le 02/09/2026
// bez lewego ukośnika „L” dałoby 0 albo 1, a „e” nazwę strefy

date() nie mówi po francusku

To druga klasyczna pułapka. Nazwy dni i miesięcy zwracane przez date() są zawsze angielskie, a setlocale() niczego tu nie zmienia.

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 — bez zmian

Rozwiązaniem jest IntlDateFormatter z rozszerzenia intl:

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

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

Do własnego formatu klasa przyjmuje wzorzec ICU, którego kody różnią się od tych z date():

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

Jeśli intl jest niedostępne, tablica odpowiedników ratuje sytuację po francusku, ale nie da się jej rozciągnąć na wiele języków.

DateTimeImmutable, właściwy wybór

Do wszystkiego, co wykracza poza samo wyświetlanie, klasy dat sprawdzają się lepiej niż funkcje. DateTimeImmutable jest lepsze od DateTime: jego metody zwracają nowy obiekt zamiast modyfikować bieżący, co eliminuje zmiany na odległość.

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 — bez zmian

// Z DateTime (mutowalnym) trzeci wiersz pokazałby 05/09/2026
php
// Dodawanie i odejmowanie okresów
$dans30Mois = $date->add(new DateInterval('P30M'));
$ilYa2Sem   = $date->sub(new DateInterval('P2W'));

// Różnica między dwiema datami
$ecart = $date->diff(new DateTimeImmutable('2026-12-25', $paris));
echo $ecart->days, " jours\n";
echo $ecart->format('%m mois et %d jours'), "\n";

// Seria dat
$periode = new DatePeriod(
    $date,
    new DateInterval('P1D'),
    new DateTimeImmutable('2026-09-07', $paris),
);
foreach ($periode as $jour) {
    echo $jour->format('D d/m'), "\n";
}

Pułapka ostatniego dnia miesiąca

Dodanie miesiąca do końca miesiąca nie daje tego, czego się spodziewasz, i działa tak samo z DateInterval, jak z arytmetyką na mktime().

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

31 lutego nie istnieje: PHP przelewa go na marzec. Żeby dostać ostatni dzień następnego miesiąca, trzeba o niego poprosić wprost:

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

Doba nie zawsze ma 24 godziny

Przy przejściu na czas letni doba ma ich 23, przy przejściu na zimowy, 25. Dodanie 86400 sekund do znacznika czasu daje wtedy złą godzinę.

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 liczy w dniach kalendarzowych i poprawnie zwraca południe następnego dnia, mimo że minęły tylko 23 godziny. To zachowanie właściwe dla przypomnienia „jutro w południe”, a błędne dla rozliczanego czasu. To dwa różne pojęcia: P1D to dzień kalendarzowy, PT24H to dwadzieścia cztery godziny.

Odczyt daty wpisanej przez użytkownika

strtotime() rozumie bardzo dużo, ale formaty niejednoznaczne interpretuje po amerykańsku: 03/04/2026 to 4 marca, a nie 3 kwietnia. Przy znanym formacie createFromFormat() nie zostawia miejsca na domysły.

php
$date = DateTimeImmutable::createFromFormat(
    '!d/m/Y',                        // „!” resetuje godzinę do 00:00:00
    '03/04/2026',
    new DateTimeZone('Europe/Paris'),
);
echo $date->format('d F Y'), "\n";   // 03 April 2026

Bez początkowego ! pola, których nie podasz, przyjmują wartości z bieżącej chwili: dwa uruchomienia tego samego kodu dają dwie różne godziny.

Najgroźniejsze jest to, że niepoprawna data nie kończy się błędem, tylko po cichu się przelewa.

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)

Po każdym parsowaniu trzeba więc sprawdzić getLastErrors() albo porównać datę sformatowaną z powrotem z tym, co przyszło z formularza:

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;
}

Od PHP 8.2 getLastErrors() zwraca false, kiedy nie ma nic do zgłoszenia, zamiast tablicy z wyzerowanymi licznikami. Test musi to uwzględniać.

Co nowego

PHP 8.4 dorzucił createFromTimestamp(), które przyjmuje ułamkowe znaczniki czasu:

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

Wcześniej trzeba było iść przez '@' . $ts albo setTimestamp(), a mikrosekundy ginęły po drodze.

Przestarzałe funkcje, których nie należy już używać

Kilka wciąż obecnych funkcji daty wypisuje ostrzeżenie o wycofaniu. Na PHP 8.5.10 działają, ale ich usunięcie jest już zaplanowane.

Funkcja Wycofana od Zamiennik
strftime() PHP 8.1 IntlDateFormatter::format()
gmstrftime() PHP 8.1 IntlDateFormatter::format()
strptime() PHP 8.2 date_parse_from_format() albo IntlDateFormatter::parse()
date_sunrise() / date_sunset() PHP 8.1 date_sun_info()
utf8_encode() PHP 8.2 mb_convert_encoding()

Dokładne komunikaty, zebrane na PHP 8.5.10 i na 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() było zwykłym sposobem na datę po francusku. Jego zamiennikiem nie jest date(), które niczego nie tłumaczy, tylko właśnie IntlDateFormatter.

Odwrotnie niż one, mktime(), checkdate(), getdate() i idate() nie są wycofane i działają normalnie. W prostych przypadkach wciąż się przydają, nawet jeśli klasy pokrywają wszystko, co robią.

Do zapamiętania

  • Ustaw strefę wprost, przechowuj w UTC, wyświetlaj lokalnie.
  • date() do sformatowania chwili, DateTimeImmutable do każdego obliczenia.
  • IntlDateFormatter do francuskich nazw dni i miesięcy.
  • Po createFromFormat() sprawdź getLastErrors(): niepoprawna data przelewa się po cichu.
  • Dzień kalendarzowy to nie 24 godziny. P1D i PT24H nie są wymienne.

Do parsowania milionów dat w pliku importu zobacz czytanie dużych plików w PHP, a żeby zapisać je w UTC, połączenie z bazą danych.

Zobacz też walidację daty wyrażeniem regularnym, obsługę dat z Carbonem w Laravelu i hub Programowanie webowe.

Częste błędy

Nieustawiona strefa czasowa Bez date.timezone w php.ini PHP wraca do UTC i pokazuje przesuniętą godzinę bez najmniejszego ostrzeżenia.
date('l') zostaje po angielsku setlocale() nie ma żadnego wpływu na date(). Zamiennikiem strftime() jest IntlDateFormatter, a nie date().
createFromFormat przyjmuje niepoprawną datę 31/02/2026 staje się 03/03/2026 ze zwykłym ostrzeżeniem w getLastErrors(). Trzeba je sprawdzać po każdym parsowaniu.
createFromFormat bez „!” Niepodane pola przyjmują bieżącą godzinę: dwa uruchomienia tego samego kodu dają dwa wyniki.
strtotime czyta daty po amerykańsku 03/04/2026 to 4 marca, a nie 3 kwietnia.
Dodanie miesiąca do końca miesiąca 31/01/2026 + P1M daje 03/03/2026. Po ostatni dzień miesiąca sięgnij po modify('last day of next month').
Doba nie ma 24 godzin Przy zmianie czasu P1D poprawnie przesuwa o dzień kalendarzowy, choć minęły tylko 23 godziny. P1D i PT24H nie są wymienne.
getLastErrors zwraca false Od PHP 8.2 metoda zwraca false, kiedy nie ma nic do zgłoszenia, zamiast tablicy z wyzerowanymi licznikami.

DateDateTimeIntlPHP

Damien Flandrin Web developer od 2010 roku, twórca Gekkode i Email Impact. Każdy artykuł jest sprawdzany na prawdziwym projekcie przed publikacją. Kontakt
Newsletter

Nowe testy, poradniki i projekty — e-mailem.

Powtarzalne testy, wersjonowany kod, datowane wyniki. Nigdy spamu.