Timestamp, fusi orari e l'ora che accade due volte

Che cosa conta davvero il tempo Unix, in che cosa gli offset ISO 8601 si distinguono dai fusi orari IANA, che cosa fa l'ora legale ad allarmi e durate, e in quali casi memorizzare in UTC è la risposta sbagliata.

Un timestamp risponde a una di due domande, e la maggior parte dei bug sulle date nasce dal confonderle. Indica o un istante, un punto sulla linea del tempo su cui ogni osservatore concorda, oppure una lettura dell'orologio a muro, che non significa nulla finché non sai di quale muro si tratti. Un pagamento è avvenuto in un istante; un appuntamento dal dentista è una lettura dell'orologio a muro. Memorizza l'uno come se fosse l'altro e si romperà più avanti, di esattamente un'ora, una domenica mattina.

Il tempo Unix conta, non dice l'ora

Il tempo Unix è il numero di secondi trascorsi dal 1970-01-01 00:00:00 UTC. Quel UTC dice da dove è partito il conteggio; il valore in sé non porta con sé alcun fuso orario. Lo stesso istante è lo stesso numero ad Amsterdam, ad Auckland e a Lima.

Il problema è l'unità, quindi conta le cifre.

CifreEsempioUnità
101756080000secondi
131756080000000millisecondi

Ogni data dei nostri giorni ha 10 cifre in secondi e 13 in millisecondi, e sarà così fino al 2286. Entrambe le letture sbagliate sono clamorose: dei millisecondi letti come secondi finiscono nell'anno 57626, dei secondi letti come millisecondi nel gennaio 1970. Un convertitore di timestamp che mostra la data interpretata lo rende evidente.

L'altro limite è il contenitore. Un conteggio di secondi a 32 bit con segno finisce a 2147483647, che corrisponde alle 03:14:07 UTC del 19 gennaio 2038, poi va in negativo e si legge come dicembre 1901. Qualsiasi cosa memorizzi una scadenza a 30 anni calcola già oltre quel limite. I 32 bit senza segno arrivano al 2106 ma perdono ogni data precedente al 1970; i 64 bit non hanno un tetto pratico.

ISO 8601 e RFC 3339

La forma che vale la pena memorizzare è 2026-08-25T14:30:00Z: data, una T, ora, poi un indicatore di fuso. La RFC 3339 è un profilo più rigoroso della ISO 8601 pensato per i protocolli di internet, e quell'indicatore lo richiede. La ISO 8601 in sé ammette anche le date per settimana, una forma senza separatori come 20260825T143000Z e timestamp privi di offset, quindi un'API che promette «ISO 8601» di solito intende il sottoinsieme della RFC 3339.

Z significa offset zero. +02:00 significa che l'orologio locale è due ore avanti rispetto a UTC, quindi UTC si ottiene sottraendo; invertire il verso è un tipico errore di due ore.

StringaChe cosa indica
2026-08-25T14:30:00ZUn istante, senza ambiguità
2026-08-25T16:30:00+02:00Lo stesso istante, su un orologio due ore avanti
2026-08-25T14:30:00Una lettura dell'orologio a muro, nessun istante
2026-08-25Una data, lunga 23, 24 o 25 ore

Una stringa senza Z e senza offset è ambigua, e i parser sono in disaccordo: alcuni presumono UTC, altri il fuso del sistema. È per questo che un valore di sola data come una data di nascita viene spesso mostrato con un giorno di anticipo a ovest di Greenwich.

Un offset non è un fuso orario

+02:00 è un fatto relativo a un solo istante in un solo posto. Europe/Amsterdam è un insieme di regole: +01:00 d'inverno, +02:00 d'estate, e una forma diversa nei decenni precedenti. Memorizzare l'offset conserva la risposta per l'istante in cui è stata misurata e butta via la possibilità di calcolarne qualsiasi altra.

Gli offset non sono neanche univoci. In un pomeriggio d'estate +02:00 copre Amsterdam, Lagos e Johannesburg, quindi dal solo offset non c'è alcuna strada per risalire alle regole né alcun modo di sapere che cosa segnerà quell'orologio a dicembre. Le abbreviazioni sono ancora peggio, dato che CST indica tre fusi diversi. Memorizza l'identificatore IANA nella forma Area/Location.

L'ora che non esiste e l'ora che accade due volte

In tutta l'Unione Europea gli orologi vanno avanti l'ultima domenica di marzo e indietro l'ultima domenica di ottobre. Ad Amsterdam, in primavera le 02:00 diventano le 03:00, quindi ogni ora locale dalle 02:00:00 alle 02:59:59 quel giorno non esiste. In autunno le 03:00 diventano le 02:00, quindi le 02:30 accadono due volte, una a +02:00 e un'altra un'ora più tardi a +01:00.

  • Allarmi ricorrenti. Un job impostato per le 02:30 locali non ha alcun orario valido nella domenica di primavera. Gli schedulatori si comportano in modo diverso: lo saltano, lo spostano alle 03:00 oppure alle 01:30. Nella domenica d'autunno lo stesso job può scattare una o due volte, quindi controlla i prossimi orari di esecuzione con un lettore di espressioni cron.
  • File di log. Un'ora di timestamp locali senza offset non si può ordinare, e due eventi distanti un'ora portano lo stesso identico testo. Registra in UTC, oppure metti l'offset su ogni riga.
  • Durate. Dalle 20:00 del 24 ottobre 2026 alle 20:00 del giorno dopo, ora locale di Amsterdam, passano 25 ore. «Aggiungi un giorno» e «aggiungi 24 ore» sono operazioni diverse, e una differenza fra date in giorni di calendario risponde a una domanda diversa rispetto a un convertitore di durata che lavora in ore.

Le regole cambiano, e l'archiviazione deve reggere il colpo

Il database dei fusi orari di IANA, tzdata, contiene queste regole e viene pubblicato più volte all'anno, perché le regole sono leggi: i governi aboliscono l'ora legale, la adottano oppure spostano le date del cambio, a volte con poche settimane di preavviso. Un'immagine di container bloccata a una versione, un browser e il runtime di un linguaggio portano ciascuno la propria copia e possono essere in disaccordo su una stessa macchina. Convertire un'ora locale futura in un istante è una previsione, che si risolve solo quando quel momento arriva.

Tipo di valoreChe cosa memorizzarePerché
Qualcosa che è accadutoL'istante in UTCIl passato non si può cambiare per legge
Appuntamento futuro su un orologio a muroData e ora locali più l'identificatore di fuso IANAL'intenzione è la lettura dell'orologio
Istante futuro, fisso in tutti i fusiUTCL'intenzione è l'istante
Data senza oraLa data e basta, senza fuso e senza mezzanotteAttaccarle un fuso la fa scivolare

È per questo che «basta memorizzare in UTC» è sbagliato per una riunione dell'anno prossimo. Converti oggi le 09:00 del 30 marzo 2027 in Europe/Amsterdam verso UTC e la risposta è 07:00Z. Se prima di allora la data del cambio si sposta, la riunione resta alle 09:00 locali ma 07:00Z viene ora mostrato come le 08:00: il valore memorizzato ha congelato una previsione e buttato via l'intenzione. Tieni l'ora locale più il fuso come fonte di verità; qualsiasi colonna UTC accanto a essa è una cache.

Mostrare le date alle persone

03/04 non si può disambiguare a partire dalla stringa. Per la maggior parte del mondo è il 3 aprile e negli Stati Uniti il 4 marzo; 03/04/05 aggiunge una terza incognita. Scrivi il mese in lettere per l'output destinato alle persone (4 Apr 2026) e usa ISO per lo scambio fra macchine. Un tag di timestamp Discord aggira il problema in chat, mostrando il valore nel fuso e nella locale di chi legge: memorizza un valore non ambiguo, formatta al bordo.

I numeri di settimana hanno una regola tutta loro. Le settimane ISO iniziano di lunedì, e la settimana 1 è quella che contiene il primo giovedì di gennaio, o equivalentemente quella che contiene il 4 gennaio. Quindi il 1° gennaio può cadere nella settimana 52 o 53 dell'anno ISO precedente, e il 29 dicembre nella settimana 1 di quello successivo. Una convenzione nordamericana molto diffusa numera invece a partire dalla settimana che inizia di domenica e contiene il 1° gennaio, quindi la stessa data riceve due numeri, e vale la pena confrontare la settimana ISO di un calcolatore del giorno della settimana con quella di un foglio di calcolo.

Durate, mesi e anni bisestili

«Un mese dopo» non è un numero fisso di giorni; è 28, 29, 30 o 31. Aggiungere un mese al 31 gennaio non ha una risposta corretta, e le librerie in genere fermano il risultato alla fine di febbraio, il che rende l'operazione non reversibile: aggiungi un mese, sottrailo di nuovo, e il 31 gennaio è diventato il 28 gennaio. L'arrotondamento è la stessa trappola in miniatura: troncare un orario al quarto d'ora più vicino e arrotondarlo allo stesso quarto d'ora possono differire fino a un intervallo intero.

La regola degli anni bisestili per intero: un anno è bisestile se è divisibile per 4, tranne che gli anni divisibili per 100 non lo sono, a meno che non siano divisibili anche per 400. Quindi il 1900 non lo era, il 2000 sì, e il 2100 non lo sarà, il che dà un anno medio di 365,2425 giorni. Il codice che verifica solo la divisibilità per 4 è corretto per ogni anno a memoria d'uomo, ed è il motivo per cui sopravvive fino al 2100. Un anniversario che cade il 29 febbraio è la seccatura collegata: l'anno successivo offre il 28 febbraio e il 1° marzo, e nessuna regola sceglie fra i due, quindi scegline uno di proposito.