Timestamp, fusi orari e l'ora che accade due volte
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.
| Cifre | Esempio | Unità |
|---|---|---|
| 10 | 1756080000 | secondi |
| 13 | 1756080000000 | millisecondi |
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.
| Stringa | Che cosa indica |
|---|---|
2026-08-25T14:30:00Z | Un istante, senza ambiguità |
2026-08-25T16:30:00+02:00 | Lo stesso istante, su un orologio due ore avanti |
2026-08-25T14:30:00 | Una lettura dell'orologio a muro, nessun istante |
2026-08-25 | Una 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 valore | Che cosa memorizzare | Perché |
|---|---|---|
| Qualcosa che è accaduto | L'istante in UTC | Il passato non si può cambiare per legge |
| Appuntamento futuro su un orologio a muro | Data e ora locali più l'identificatore di fuso IANA | L'intenzione è la lettura dell'orologio |
| Istante futuro, fisso in tutti i fusi | UTC | L'intenzione è l'istante |
| Data senza ora | La data e basta, senza fuso e senza mezzanotte | Attaccarle 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.