Tidsstämplar, tidszoner och timmen som inträffar två gånger

Vad Unix-tid faktiskt räknar, hur ISO 8601-offsets skiljer sig från IANA-tidszoner, vad sommartid gör med larm och tidsintervall, och när det är fel svar att lagra UTC.

En tidsstämpel besvarar en av två frågor, och de flesta datumbuggar kommer av att de blandas ihop. Den namnger antingen en tidpunkt, en punkt på tidslinjen som alla observatörer är eniga om, eller en väggklocksavläsning, som inte betyder något förrän du vet vems vägg. En betalning skedde vid en tidpunkt; en tandläkartid är en väggklocksavläsning. Lagra den ena som den andra så går det sönder senare, med exakt en timme, en söndagsmorgon.

Unix-tid räknar, den visar inte tiden

Unix-tid är antalet sekunder som förflutit sedan 1970-01-01 00:00:00 UTC. Det UTC:t säger var räkningen började; värdet självt bär ingen tidszon. Samma tidpunkt är samma tal i Amsterdam, Auckland och Lima.

Problemet är enheten, så räkna siffrorna.

SiffrorExempelEnhet
101756080000sekunder
131756080000000millisekunder

Varje nutida datum har 10 siffror i sekunder och 13 i millisekunder, och kommer att ha det fram till 2286. Båda feltolkningarna är högljudda: millisekunder lästa som sekunder hamnar år 57626, sekunder lästa som millisekunder i januari 1970. En tidsstämpelkonverterare som visar det tolkade datumet gör det uppenbart.

Den andra gränsen är behållaren. En sekundräknare på 32 bitar med tecken tar slut vid 2147483647, vilket är 03:14:07 UTC den 19 januari 2038, slår sedan runt till negativt och läses som december 1901. Allt som lagrar en giltighetstid på 30 år räknar redan bortom det. 32 bitar utan tecken når 2106 men förlorar varje datum före 1970; 64 bitar har inget praktiskt tak.

ISO 8601 och RFC 3339

Formen värd att lära sig utantill är 2026-08-25T14:30:00Z: datum, ett T, klockslag, sedan en zonbeteckning. RFC 3339 är en striktare profil av ISO 8601 för internetprotokoll, och den kräver den beteckningen. ISO 8601 självt tillåter också veckodatum, en form utan avgränsare som 20260825T143000Z, och tidsstämplar helt utan offset, så ett API som lovar "ISO 8601" menar oftast delmängden RFC 3339.

Z betyder en offset på noll. +02:00 betyder att den lokala klockan går två timmar före UTC, så UTC hittas genom subtraktion; att vända på det är ett vanligt fel på två timmar.

SträngVad den namnger
2026-08-25T14:30:00ZEn tidpunkt, entydigt
2026-08-25T16:30:00+02:00Samma tidpunkt, på en klocka som går två timmar före
2026-08-25T14:30:00En väggklocksavläsning, ingen tidpunkt
2026-08-25Ett datum, 23, 24 eller 25 timmar långt

En sträng med varken Z eller offset är tvetydig, och parsrarna är oense: vissa antar UTC, andra systemets zon. Det är därför ett värde som bara är ett datum, som en födelsedag, ofta renderas en dag för tidigt väster om Greenwich.

En offset är inte en tidszon

+02:00 är ett faktum om en tidpunkt på en plats. Europe/Amsterdam är en regeluppsättning: +01:00 på vintern, +02:00 på sommaren, och en annan form under tidigare decennier. Att lagra offseten bevarar svaret för den tidpunkt då den mättes och kastar bort förmågan att beräkna någon annan.

Offsets är inte heller unika. En sommareftermiddag täcker +02:00 Amsterdam, Lagos och Johannesburg, så enbart från offseten finns ingen väg tillbaka till reglerna och inget sätt att veta vad den klockan visar nästa december. Förkortningar är ännu värre, eftersom CST namnger tre olika zoner. Lagra IANA-identifieraren på formen Area/Location.

Timmen som inte finns och timmen som inträffar två gånger

Inom EU flyttas klockan fram sista söndagen i mars och tillbaka sista söndagen i oktober. I Amsterdam blir 02:00 till 03:00 på våren, så varje lokal tid från 02:00:00 till 02:59:59 inträffar inte den dagen. På hösten blir 03:00 till 02:00, så 02:30 inträffar två gånger, en gång på +02:00 och igen en timme senare på +01:00.

  • Återkommande larm. Ett jobb satt till 02:30 lokal tid har ingen giltig tid på vårsöndagen. Schemaläggarna gör olika: hoppar över det, flyttar det till 03:00, eller flyttar det till 01:30. På höstsöndagen kan samma jobb gå igång en eller två gånger, så kontrollera de närmaste körtiderna med en cron-parser.
  • Loggfiler. En timme med lokala tidsstämplar utan offset går inte att sortera, och två händelser med en timmes mellanrum bär identisk text. Logga i UTC, eller sätt offseten på varje rad.
  • Tidsintervall. Från 20:00 den 24 oktober 2026 till 20:00 dagen därpå, lokal tid i Amsterdam, är 25 timmar. "Lägg till en dag" och "lägg till 24 timmar" är olika operationer, och en datumdifferens i kalenderdagar besvarar en annan fråga än en tidsintervallskonverterare som räknar i timmar.

Reglerna ändras, så lagringen måste överleva dem

IANA:s tidszonsdatabas, tzdata, håller dessa regler och publiceras flera gånger om året, eftersom reglerna är lagstiftning: regeringar avskaffar sommartid, inför den, eller flyttar omställningsdatumen, ibland med några veckors varsel. En fastnaglad containeravbildning, en webbläsare och en språkkörning bär var sin kopia och kan vara oense på en och samma maskin. Att konvertera en framtida lokal tid till en tidpunkt är en förutsägelse, avgjord först när den infaller.

Typ av värdeLagraSkäl
Något som har häntTidpunkt i UTCDet förflutna kan inte lagstiftas om
Framtida möte på en väggklockaLokalt datum med tid plus IANA-zon-idAvsikten är klockavläsningen
Framtida tidpunkt, fast över alla zonerUTCAvsikten är tidpunkten
Datum utan klockslagRent datum, ingen zon, ingen midnattAtt fästa en zon får det att driva

Det är därför "lagra bara UTC" är fel för ett möte nästa år. Konvertera 09:00 den 30 mars 2027 i Europe/Amsterdam till UTC i dag och svaret är 07:00Z. Om omställningsdatumet flyttas innan dess är mötet fortfarande 09:00 lokal tid, men 07:00Z renderas nu som 08:00: det lagrade värdet frös en förutsägelse och kastade bort avsikten. Behåll lokal tid plus zon som sanningskällan; varje UTC-kolumn bredvid den är en cache.

Att visa datum för människor

03/04 går inte att reda ut ur strängen. Det är 3 april för större delen av världen och 4 mars i USA; 03/04/05 lägger till en tredje okänd. Skriv månaden med bokstäver i utdata för människor (4 Apr 2026) och använd ISO för maskinutbyte. En Discord-tidsstämpeltagg kringgår detta i chatt genom att rendera i zonen och språkinställningen hos den som läser: lagra ett entydigt värde, formatera i ytterkanten.

Veckonummer har sin egen regel. ISO-veckor börjar på måndag, och vecka 1 är den vecka som innehåller den första torsdagen i januari, likvärdigt den vecka som innehåller den 4 januari. Så 1 januari kan hamna i vecka 52 eller 53 av föregående ISO-år, och 29 december kan hamna i vecka 1 av nästa. En vanlig nordamerikansk konvention numrerar i stället från den söndagsbörjande vecka som innehåller 1 januari, så samma datum får två nummer, och ISO-veckan i en veckodagsräknare är värd att stämma av mot ett kalkylblad.

Tidsintervall, månader och skottår

"En månad senare" är inte ett fast antal dagar; det är 28, 29, 30 eller 31. Att lägga till en månad till 31 januari har inget korrekt svar, och bibliotek begränsar i allmänhet till slutet av februari, vilket gör operationen icke vändbar: lägg till en månad, dra bort den igen, och 31 januari är nu 28 januari. Avrundning är samma fälla i miniatyr: att kapa en klocktid till närmaste kvart och att avrunda till den skiljer sig med upp till ett helt intervall.

Skottårsregeln i sin helhet: ett år är skottår om det är delbart med 4, utom att år delbara med 100 inte är det, om de inte också är delbara med 400. Så 1900 var det inte, 2000 var det, och 2100 kommer inte att vara det, vilket ger ett genomsnittsår på 365,2425 dagar. Kod som bara testar delbarhet med 4 är korrekt för varje år i mannaminne, vilket är varför den överlever ända till 2100. Ett årsdagsfirande den 29 februari är den besläktade olägenheten: nästa år erbjuder 28 februari och 1 mars, och ingen regel väljer mellan dem, så välj en medvetet.