Od dni do sekund – uniksowy timestamp, problem roku 2038 i strefy czasowe
W dwóch poprzednich wpisach – o wiecznym kalendarzu i o dacie Wielkanocy – zajmowaliśmy się dniami: latami przestępnymi, dniami tygodnia, ruchomymi świętami. Ale komputer, na którym to czytasz, wcale nie myśli w dniach. Pod spodem nie ma żadnego „29 lipca” ani „środy” – jest jedna wielka liczba sekund. I z tej prostoty wynika zaskakująco dużo problemów: data z limitem ważności w 2038 roku, sekundy dopisywane do doby na ostatniej pozycji zegara i strefy czasowe, które trzeba aktualizować jak oprogramowanie.
Uniksowy timestamp: jedna liczba zamiast daty
Czas uniksowy to liczba sekund, które upłynęły od 1 stycznia 1970 roku, 00:00:00 UTC – tzw. epoki Uniksa. Ta konwencja, przyjęta w latach 70. dla wygody jednego systemu operacyjnego, stała się lingua franca całej informatyki:
$ date +%s
1785326400 # ≈ południe UTC, 29 lipca 2026Date.now(); // 1785326400000 – uwaga: JS liczy w MILIsekundach
Math.trunc(Date.now() / 1000); // klasyczny timestamp w sekundachimport time
time.time() # 1785326400.514 – float: sekundy z ułamkiem<?php
time(); // 1785326400Zalety są ogromne. Porównanie dwóch momentów to porównanie dwóch liczb, odstęp między nimi to zwykłe odejmowanie, a jeden moment ma jedną wartość na całym świecie – timestamp nie wie nic o strefach czasowych, bo liczy się względem UTC. Cała arytmetyka kalendarzowa z poprzednich wpisów (lata przestępne, długości miesięcy) jest potrzebna dopiero na samym końcu – przy zamianie liczby na napis „29 lipca 2026” dla człowieka.
Jest jeszcze jedna, mniej oczywista konwencja: w czasie uniksowym doba ma zawsze dokładnie 86 400 sekund. Zapamiętaj to zdanie – wrócimy do niego przy sekundach przestępnych, bo fizyczna doba taka równa nie jest.
Problem roku 2038
Sekundy od 1970 roku trzeba w czymś przechowywać. Historycznie typem time_t w C był 32-bitowy int ze znakiem, a jego maksimum to 2³¹ − 1 = 2 147 483 647. Policzmy, kiedy licznik się skończy:
from datetime import datetime, timezone
datetime.fromtimestamp(2**31 - 1, tz=timezone.utc)
# datetime(2038, 1, 19, 3, 14, 7, ...) – ostatnia sekunda 32-bitowego świata19 stycznia 2038, 03:14:07 UTC. Sekundę później licznik przekręca się na −2 147 483 648, czyli… 13 grudnia 1901 roku. To problem roku 2038 – młodszy brat słynnej pluskwy milenijnej, tyle że wynikający nie z zapisu „dwóch cyfr roku”, lecz z twardego limitu typu danych.
Kogo to naprawdę dotknie? Twój laptop i telefon są od dawna 64-bitowe – 64-bitowy time_t wystarczy na ~292 miliardy lat, więc temat nie istnieje. Ryzyko siedzi gdzie indziej:
- Systemy wbudowane: sterowniki, liczniki, urządzenia przemysłowe i IoT. Sprzęt sprzedawany dziś z 32-bitowym firmware’em będzie w 2038 roku miał kilkanaście lat – czyli dokładnie tyle, ile zwykle żyje.
- Formaty danych i bazy: MySQL-owy typ
TIMESTAMPdo dziś ma zakres kończący się na2038-01-19 03:14:07– daty urodzin, terminy kredytów czy ważność dokumentów lepiej trzymać wDATETIME, który limitu nie ma. - Programy liczące w przyszłość: na błąd 2038 nie trzeba czekać do 2038. Soft wyliczający harmonogram 30-letniego kredytu albo termin ważności długiego certyfikatu przekracza granicę już teraz – pierwsze takie awarie raportowano dekady temu.
Świat open source odrobił lekcję zawczasu: Linux od wersji 5.6 (2020) ma 64-bitowe wywołania czasu także dla systemów 32-bitowych, glibc udostępnia _TIME_BITS=64, a Debian 13 „trixie” (2025) przeprowadził wielką migrację t64 – przebudowę tysięcy pakietów na 64-bitowy time_t również na architekturach 32-bitowych. JavaScript jest bezpieczny niejako przypadkiem: Date trzyma milisekundy w liczbie zmiennoprzecinkowej, co daje zakres do roku 275760.
A najlepsze na koniec: pamiętasz z wpisu o Wielkanocy, że rok 2038 to także najpóźniejsza możliwa Wielkanoc (25 kwietnia) i granica PHP-owego easter_date()? Rok 2038 zbiera nam się w całkiem osobny wpis.
Sekundy przestępne: doba nie jest równa
Wróćmy do konwencji „doba = 86 400 sekund”. Sekunda jest dziś zdefiniowana atomowo (przez częstotliwość przejścia w cezie‑133), a obrót Ziemi – niestety – atomowo równy nie jest: pływy księżycowe go hamują, a procesy we wnętrzu planety potrafią go chwilowo przyspieszać. Astronomiczna doba różni się od 86 400 atomowych sekund o pojedyncze milisekundy – i te milisekundy się kumulują.
Rozwiązanie z 1972 roku to sekunda przestępna: gdy rozjazd zbliża się do 0,9 s, do UTC dokłada się dodatkową sekundę – zegar pokazuje wtedy egzotyczne 23:59:60. Od 1972 roku zrobiono to 27 razy (ostatnio 31 grudnia 2016). Stąd bierze się rodzina skal czasu, które warto umieć rozróżnić:
| Skala | Co robi z sekundami przestępnymi | Stan dziś |
|---|---|---|
| TAI (atomowa) | nie zna ich – płynie jednostajnie | wzorzec |
| UTC (cywilna) | wstawia 23:59:60 | TAI − 37 s |
| GPS | zamrożona kopia UTC z 1980 r. | TAI − 19 s |
| czas uniksowy | udaje, że nie istnieją | ~UTC bez 23:59:60 |
Czas uniksowy sekundy przestępnej nie umie zapisać – 23:59:60 nie ma swojego timestampa. Systemy radzą sobie różnie: klasycznie kernel powtarza ostatnią sekundę doby, a wielkie chmury stosują leap smear – rozsmarowują dodatkową sekundę mikroskopijnymi porcjami na całą dobę, żeby żaden proces nie zobaczył czasu stojącego w miejscu (sekunda z 30 czerwca 2012 położyła wtedy sporo dużych serwisów – od tamtej pory branża woli dmuchać na zimne).
Ten rozdział historii właśnie się kończy: 27. Generalna Konferencja Miar (2022) postanowiła zrezygnować z sekund przestępnych najpóźniej do 2035 roku – rozjazd z obrotem Ziemi będzie się kumulował i zostanie skorygowany kiedyś, większym skokiem. Jest i pikanteria: Ziemia od kilku lat kręci się odrobinę szybciej, więc zanim reforma wejdzie, może być potrzebna pierwsza w historii ujemna sekunda przestępna (23:59:58 → 00:00:00) – scenariusz, którego żaden produkcyjny system nigdy nie przećwiczył.
Strefy czasowe: baza danych, nie matematyka
Timestamp mówi, który to moment; strefa czasowa mówi, jak ten moment nazywa się na czyimś zegarze ściennym. I tu kończy się matematyka, a zaczyna polityka. Offset strefy nie wynika z żadnego wzoru – to decyzja administracyjna, którą państwa potrafią zmienić z tygodniowym wyprzedzeniem. Dlatego strefy czasowe są utrzymywane jako baza danych: tz database od IANA (tzdata), z identyfikatorami w rodzaju Europe/Warsaw, pełną historią zmian od 1970 roku i kilkoma wydaniami rocznie – którą Twój system aktualizuje jak każdy inny pakiet.
Jak bardzo bywa to płynne, pokazują przykłady z ostatnich lat:
- Samoa, 30 grudnia 2011 – dzień, którego nie było. Kraj przeskoczył na zachodnią stronę linii zmiany daty (żeby handlować w rytmie Australii, nie USA): po czwartku 29 grudnia nastąpiła od razu sobota 31 grudnia. Dokładnie ten sam manewr, co wycinanie dni w 1582 i 1752 roku z wpisu o kalendarzu – tyle że współcześnie i z powodów ekonomicznych.
- Liban, marzec 2023: rząd w ostatniej chwili przesunął zmianę czasu, część instytucji się nie podporządkowała – i przez kilka dni w jednym kraju obowiązywały dwa czasy naraz.
- Unia Europejska: projekt zniesienia sezonowej zmiany czasu wisi od 2018 roku i wciąż nie wszedł w życie – co oznacza, że reguły dla
Europe/Warsawmogą się zmienić w każdej chwili.
Sezonowa zmiana czasu funduje nam zresztą lokalne odpowiedniki kalendarzowych ciekawostek: w Polsce 29 marca 2026 godzina 2:30 nie istnieje (zegary skaczą z 2:00 na 3:00), a 25 października 2026 godzina 2:30 zdarzy się dwa razy. Biblioteki standardowe muszą to jakoś rozstrzygać – Python robi to parametrem fold:
from datetime import datetime
from zoneinfo import ZoneInfo # baza tzdata w bibliotece standardowej (Python 3.9+)
warsaw = ZoneInfo("Europe/Warsaw")
datetime(2026, 7, 29, 12, 0, tzinfo=warsaw).utcoffset() # 2 h – CEST (czas letni)
datetime(2026, 12, 29, 12, 0, tzinfo=warsaw).utcoffset() # 1 h – CET
# 25 X 2026, 2:30 istnieje dwa razy – fold wybiera, o którą nam chodzi:
datetime(2026, 10, 25, 2, 30, tzinfo=warsaw).utcoffset() # 2 h – pierwsze 2:30 (CEST)
datetime(2026, 10, 25, 2, 30, fold=1, tzinfo=warsaw).utcoffset() # 1 h – drugie 2:30 (CET)W JavaScript przeglądarka zna strefę użytkownika (Intl.DateTimeFormat().resolvedOptions().timeZone → "Europe/Warsaw"), a nadchodzące Temporal ma pełnoprawny typ ZonedDateTime; w PHP analogiczną rolę pełni DateTimeZone.
Zasada przechowywania dat. Momenty z przeszłości zapisuj jako timestamp/UTC – to fakty, nic ich nie zmieni. Ale przyszłe wydarzenia lokalne (spotkanie „we wtorek o 9:00", lot, wizyta) przechowuj jako czas lokalny + identyfikator strefy (2027-03-30 09:00, Europe/Warsaw), nie jako timestamp. Timestamp zamraża dzisiejsze reguły strefy – jeśli państwo zmieni przepisy między zapisem a terminem (a widzieliśmy, że zmienia), Twoje „9:00" przestanie być dziewiątą.
Podsumowanie
- Komputery liczą sekundy od 1 stycznia 1970 UTC – daty, miesiące i dni tygodnia to tylko warstwa prezentacji nad jedną liczbą (JS: uwaga na milisekundy).
- Problem roku 2038 to limit 32-bitowego
time_t(2³¹ − 1 s → 19 I 2038, 03:14:07 UTC). Desktopy są bezpieczne; ryzyko to embedded/IoT, formaty danych (MySQLTIMESTAMP!) i każdy soft liczący daty w przyszłość – tam błędy zdarzają się już dziś. - Sekundy przestępne łatają różnicę między sekundą atomową a obrotem Ziemi (27 wstawek od 1972 r.; TAI − UTC = 37 s). Czas uniksowy je ignoruje, chmury je „rozsmarowują”, a od 2035 roku mają zniknąć – być może po pierwszej w historii sekundzie ujemnej.
- Strefy czasowe to baza danych IANA, nie wzór: aktualizowana jak oprogramowanie, pełna dni, których nie było (Samoa 2011), i godzin, które bywają dwa razy (25 X, 2:30). Przyszłe wydarzenia lokalne przechowuj jako czas lokalny + strefa.
Tak domyka się mały cykl o czasie: wieczny kalendarz policzył dni, computus – święta, a dziś zeszliśmy do sekund, na których to wszystko stoi.