Jeśli kiedykolwiek zapisywałeś daty w bazie danych, budowałeś API lub debugowałeś błąd harmonogramu pojawiający się tylko w określonych krajach, na pewno zetknąłeś się z relacją między Unix timestamp a UTC i czasem lokalnym. To zamieszanie jest prawdziwe i prowadzi do poważnych bugów produkcyjnych. W tym artykule wyjaśniamy dokładnie, czym jest UTC, dlaczego Unix timestampy są z definicji oparte na UTC, jak działają offsety stref czasowych oraz jak poprawnie konwertować timestampy w JavaScript, Python i PHP. Znajdziesz tu też najczęstsze błędy popełniane przez deweloperów i sposoby na ich uniknięcie.
Spis treści
Najważniejsze wnioski:
- Unix timestamp zawsze liczy sekundy od 1 stycznia 1970, 00:00:00 UTC - nie zawiera żadnej informacji o strefie czasowej.
- UTC to uniwersalny punkt odniesienia; czas lokalny to po prostu UTC plus lub minus offset.
- Poprawna konwersja timestampa na czas lokalny wymaga znajomości docelowej strefy czasowej, a nie tylko numeru offsetu.
- DST (czas letni) zmienia offset dla danej strefy czasowej - dlatego hardkodowanie offsetów prowadzi do bugów.
Czym jest UTC i dlaczego ma znaczenie?
UTC to skrót od Coordinated Universal Time (Uniwersalny Czas Koordynowany). Jest to główny standard czasu, według którego regulowane są zegary na całym świecie. W przeciwieństwie do stref czasowych, UTC nie ma offsetu - jest punktem zerowym. Nie uwzględnia czasu letniego i nie przesuwa się dla żadnego kraju ani regionu.
Przed UTC istniał GMT (Greenwich Mean Time) i wiele osób nadal używa obu pojęć zamiennie. Technicznie rzecz biorąc, UTC i GMT nieznacznie się różnią, ale w kontekście oprogramowania traktuje się je jako równoważne. UTC zostało formalnie zdefiniowane przez rekomendację ITU-R TF.460 i jest utrzymywane przy użyciu zegarów atomowych.
Dla deweloperów UTC ma kluczowe znaczenie, ponieważ zapewnia jeden, jednoznaczny punkt odniesienia. Jeśli serwer we Frankfurcie i użytkownik w Los Angeles zapisują zdarzenie w UTC, możesz zawsze poprawnie porównać oba timestampy. Jeśli każdy z nich zapisuje czas lokalny bez metadanych - masz problem.
Dlaczego Unix timestampy są zawsze UTC
Unix timestamp (zwany też epoch time lub POSIX time) jest zdefiniowany jako liczba sekund, które upłynęły od 1 stycznia 1970, 00:00:00 UTC. Ten punkt zakotwiczenia - północ 1 stycznia 1970 roku w UTC - jest wbudowany w samą definicję. Nie istnieje wersja Unix time startująca od czasu lokalnego Nowego Jorku czy Tokio.
Odpowiedź na pytanie "czy Unix timestamp jest zawsze UTC?" brzmi: tak, z definicji. Liczba 1700000000 reprezentuje dokładnie ten sam moment w czasie dla każdej osoby na planecie. To, co różni się między użytkownikami, to sposób wyświetlania tego momentu w ich lokalnej strefie czasowej.
Aby lepiej zrozumieć podstawy tego zagadnienia, przeczytaj nasz artykuł o Epoch Time: podstawach Unix timestampów.
Ta zakotwiczona w UTC konstrukcja to jedna z najbardziej użytecznych właściwości Unix time. Ponieważ sama liczba nie zawiera informacji o strefie czasowej, dwa systemy w różnych krajach mogą wymienić timestamp i oba będą wiedzieć dokładnie, do którego momentu się on odnosi - bez żadnych dodatkowych uzgodnień.
UTC a czas lokalny i strefy czasowe
UTC jest punktem odniesienia. Czas lokalny to wynik zastosowania reguł strefy czasowej na UTC. Strefa czasowa to nie tylko stały offset - to nazwany region z zestawem reguł, które mogą się zmieniać w czasie (głównie z powodu czasu letniego, czyli DST).
Na przykład "Eastern Time" w Stanach Zjednoczonych to UTC-5 zimą i UTC-4 latem. Nazwa tej strefy czasowej w bazie danych stref czasowych IANA to "America/New_York". IANA jest autorytatywnym źródłem reguł stref czasowych używanym przez większość języków programowania i systemów operacyjnych.
Praktyczny wniosek: gdy zapisujesz Unix timestamp w bazie danych, przechowujesz wartość UTC. Gdy wyświetlasz ją użytkownikowi, konwertujesz ją na jego lokalną strefę czasową. Nigdy nie przechowuj czasu lokalnego jako surowej liczby i nie zakładaj, że jest to UTC. Właśnie tam zaczynają się bugi.
Jak działają offsety UTC
Offset UTC opisuje, o ile dana strefa czasowa różni się od UTC. Zapisuje się go jako +HH:MM lub -HH:MM. Kilka przykładów:
- UTC+02:00 - Środkowoeuropejski Czas Letni (CEST), stosowany w Niemczech i Francji latem.
- UTC-05:00 - Eastern Standard Time (EST), stosowany na wschodnim wybrzeżu USA zimą.
- UTC+05:30 - India Standard Time (IST), czyli offset z półgodzinną różnicą.
- UTC+00:00 - sam UTC, stosowany również w Wielkiej Brytanii zimą (GMT).
Aby ręcznie przeliczyć epoch time UTC na czas lokalny, dodajesz lub odejmujesz offset w sekundach. Dla UTC+02:00 wynosi to 2 * 3600 = 7200 sekund. Jeśli więc Twój Unix timestamp to 1700000000, czas lokalny w UTC+02:00 odpowiada wartości 1700000000 + 7200 przed formatowaniem.
Jednak ręczne wykonywanie tych obliczeń jest ryzykowne, ponieważ offsety zmieniają się wraz z DST. Zawsze używaj odpowiedniej biblioteki do obsługi stref czasowych zamiast hardkodować offset. Zajrzyj do naszego przewodnika Jak konwertować Unix timestamp na datę, aby poznać szczegóły metod konwersji.
Konwersja Unix timestampów na czas lokalny
Oto konkretny przykład z użyciem Unix timestampa 1700000000, który odpowiada 14 listopada 2023, 22:13:20 UTC. Skonwertujemy go na strefę "America/New_York" (UTC-5 w listopadzie) w trzech językach programowania.
JavaScript
const ts = 1700000000;
// JavaScript Date przyjmuje milisekundy
const date = new Date(ts * 1000);
// Wyświetl w czasie lokalnym Nowego Jorku
const options = {
timeZone: 'America/New_York',
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
timeZoneName: 'short'
};
console.log(date.toLocaleString('en-US', options));
// Output: November 14, 2023 at 05:13:20 PM ESTZwróć uwagę, że obiekt Date w JavaScript przechowuje czas wewnętrznie jako milisekundy UTC. Metoda toLocaleString z opcją timeZone automatycznie obsługuje offset i reguły DST.
Python
from datetime import datetime, timezone
import zoneinfo # Python 3.9+
ts = 1700000000
# Utwórz obiekt datetime ze świadomością UTC na podstawie timestampa
utc_dt = datetime.fromtimestamp(ts, tz=timezone.utc)
# Konwertuj na czas lokalny Nowego Jorku
ny_tz = zoneinfo.ZoneInfo('America/New_York')
local_dt = utc_dt.astimezone(ny_tz)
print(local_dt.strftime('%Y-%m-%d %H:%M:%S %Z'))
# Output: 2023-11-14 17:13:20 ESTKluczowe jest tu użycie datetime.fromtimestamp(ts, tz=timezone.utc). Jeśli pominiesz argument tz, Python użyje lokalnej strefy czasowej serwera do interpretacji timestampa - co jest częstym źródłem bugów.
PHP
$ts = 1700000000;
// Utwórz obiekt DateTime z Unix timestampa (zawsze UTC)
$dt = new DateTime('@' . $ts);
// Ustaw docelową strefę czasową
$dt->setTimezone(new DateTimeZone('America/New_York'));
echo $dt->format('Y-m-d H:i:s T');
// Output: 2023-11-14 17:13:20 ESTW PHP prefiks @ przy tworzeniu obiektu DateTime informuje PHP, że wartość ma być traktowana jako Unix timestamp (UTC). Bez niego PHP może interpretować string przy użyciu domyślnej strefy czasowej serwera.
Najczęstsze błędy deweloperów
Nawet doświadczeni deweloperzy potykają się o obsługę stref czasowych. Oto najczęstsze problemy:
1. Zakładanie, że lokalny czas serwera to UTC
Jeśli Twój serwer jest ustawiony na "Europe/Berlin" i wywołasz time() w PHP lub datetime.now() w Pythonie bez argumentu strefy czasowej, otrzymasz czas lokalny - nie UTC. Zawsze precyzyjnie wskazuj UTC podczas zapisywania timestampów.
2. Hardkodowanie offsetów UTC zamiast nazw stref czasowych
Używanie +02:00 jako stałego offsetu dla Niemiec nie zadziała zimą, gdy Niemcy przełączają się na +01:00. Zawsze używaj nazwanych stref czasowych, takich jak Europe/Berlin, żeby biblioteka mogła automatycznie stosować właściwe reguły DST.
3. Przechowywanie timestampów jako stringów czasu lokalnego bez metadanych strefy
String taki jak 2023-11-14 17:13:20 w bazie danych jest niejednoznaczny. Czy to UTC? EST? IST? Jeśli przechowujesz timestampy jako liczby całkowite Unix lub jako stringi ISO 8601 z offsetem UTC (np. 2023-11-14T22:13:20Z), znaczenie jest jednoznaczne. Przeczytaj nasze porównanie Unix Timestamp Format vs ISO 8601, aby dowiedzieć się, które podejście wybrać.
4. Ignorowanie DST przy harmonogramowaniu zadań
Jeśli planujesz zadanie do uruchomienia "codziennie o 09:00" przez dodawanie 86400 sekund do czasu ostatniego uruchomienia, w dniach zmiany czasu DST zadanie przesunie się o godzinę. Używaj biblioteki cron lub schedulera rozumiejącego reguły stref czasowych.
5. Mylenie milisekund z sekundami
JavaScript używa milisekund dla obiektu Date, ale większość Unix timestampów jest w sekundach. Przekazanie timestampa w sekundach do new Date() bez pomnożenia przez 1000 zwróci datę ze stycznia 1970. Szczegółowo omawiamy to w artykule Sekundy vs milisekundy vs mikrosekundy.
Podsumowanie
Unix timestampy i UTC są nierozłączne z założenia. Timestamp jest zawsze wartością UTC - licznikiem sekund od stałego punktu zakotwiczenia w UTC. Czas lokalny to jedynie format wyświetlania, a nie inny rodzaj timestampa. Najbezpieczniejsze podejście to przechowywanie wszystkiego jako UTC, konwertowanie na czas lokalny wyłącznie w momencie wyświetlania oraz używanie zawsze nazwanych stref czasowych zamiast hardkodowanych offsetów. Stosuj te zasady, a zdecydowana większość bugów związanych ze strefami czasowymi po prostu przestanie się pojawiać. Aby poznać najlepsze praktyki przechowywania tych wartości, zajrzyj do naszego przewodnika Unix Timestampy w bazach danych.
Konwertuj dowolny Unix timestamp na UTC lub czas lokalny - natychmiastowo
Wklej dowolny Unix timestamp i jednym kliknięciem sprawdź jego odpowiednik w UTC oraz Twojej lokalnej strefie czasowej. Bezpłatne, bez rejestracji, obsługuje zarówno sekundy, jak i milisekundy.
Wypróbuj nasze darmowe narzędzie →
Tak. Z definicji Unix epoch timestamp liczy sekundy od 1 stycznia 1970, 00:00:00 UTC. Sama liczba nie zawiera informacji o strefie czasowej - jest wartością UTC. Strefa czasowa staje się istotna dopiero wtedy, gdy konwertujesz timestamp na czytelną dla człowieka datę i godzinę w celach wyświetlania.
Użyj wbudowanej biblioteki do obsługi stref czasowych w swoim języku. W JavaScript użyj toLocaleString z opcją timeZone. W Pythonie użyj datetime.fromtimestamp(ts, tz=timezone.utc).astimezone() z nazwaną strefą czasową. W PHP utwórz obiekt DateTime z timestampa i wywołaj setTimezone(). Zawsze używaj nazwanych stref czasowych, nie surowych offsetów.
Najczęściej dzieje się tak dlatego, że funkcja używa domyślnej strefy czasowej serwera zamiast UTC podczas interpretacji timestampa. W Pythonie pominięcie argumentu tz w datetime.fromtimestamp() powoduje użycie czasu lokalnego systemu. W PHP brak prefiksu @ ma ten sam efekt. Zawsze precyzyjnie wskazuj UTC w momencie tworzenia obiektu.
Offset UTC to liczba godzin (a czasem minut), o którą dana strefa czasowa wyprzedza UTC lub jest za nim opóźniona. Na przykład UTC-05:00 oznacza pięć godzin za UTC. Przy konwersji Unix timestampa na czas lokalny offset jest stosowany do wartości UTC. Ponieważ offsety zmieniają się wraz z DST, powinieneś używać nazwanej strefy czasowej zamiast hardkodowanego offsetu.
Tak, i to jedna z głównych zalet Unix timestampów. Ponieważ każdy timestamp jest wartością UTC, możesz porównywać dowolne dwa timestampy bezpośrednio jako liczby całkowite. Większa liczba zawsze oznacza późniejszy moment w czasie, niezależnie od tego, gdzie timestampy zostały wygenerowane. Do porównania nie jest potrzebna żadna konwersja stref czasowych.