Vai al contenuto
OneKitly

Timestamp Unix, secondi intercalari e il problema del 2038

Pubblicato il 20/05/2025 · 18 min di lettura · Strumenti per sviluppatori

Daniel Okonkwo

Daniel OkonkwoSviluppatore front-end e redattore Tech presso OneKitly

Performance web · Formati di file

Verificato su 7 fonti

Vedi il profilo
In breve

Il tempo Unix è il numero di secondi dal 1970-01-01T00:00:00Z, calcolato come se ogni giorno contenesse esattamente 86 400 secondi. I secondi intercalari sono esclusi, quindi non è un conteggio di secondi fisici trascorsi: 86 400 secondi Unix separano 2016-12-31T00:00:00Z da 2017-01-01T00:00:00Z anche se in realtà sono passati 86 401 secondi SI, perché l'UTC ha inserito 23:59:60 in mezzo. È questo il compromesso deliberato: il contatore perde l'accordo con la fisica e guadagna la proprietà che dividere per 86 400 dà sempre la data UTC giusta, senza alcuna tabella di secondi intercalari. Dal 1972 sono stati inseriti ventisette secondi intercalari, l'ultimo il 2016-12-31, e un orologio POSIX non sa rappresentare :60, quindi ripete o congela un secondo. Il problema del 2038 è a parte: un contatore a 32 bit con segno si ferma a 2 147 483 647, cioè 2038-01-19T03:14:07Z. Un secondo dopo salta a −2 147 483 648, ovvero 1901-12-13T20:45:52Z. Un time_t a 64 bit arriva a 9 223 372 036 854 775 807 secondi, circa 292 miliardi di anni, e chiude la questione per sempre. Il bug più comune non è il 2038 ma confondere secondi e millisecondi: per il 2026-08-15, il valore in secondi letto come millisecondi dà il 1970-01-21 e il valore in millisecondi letto come secondi dà l'anno 58589.

Il tempo Unix conta i secondi dal 1970 ignorando deliberatamente i secondi intercalari, ed è per questo che non è un conteggio di secondi fisici trascorsi. Ecco che cosa compra quel compromesso, che cosa fa un secondo intercalare al contatore, l'istante esatto in cui un contatore a 32 bit con segno trabocca, e il bug del fattore 1000 che ti fa atterrare nel 1970 o nell'anno 58589.

Che cosa conta davvero il contatore

Il tempo Unix, o tempo POSIX, è definito come il numero di secondi trascorsi dal 1970-01-01T00:00:00Z, nell'assunto che ogni giorno contenga esattamente 86 400 secondi. La definizione è aritmetica, non osservativa: la specifica dà una formula costruita su anno, giorno dell'anno, ora, minuto e secondo, e quella formula non ha alcun termine per i secondi intercalari, perché al suo interno non esistono.

La conseguenza merita di essere detta senza giri di parole, perché è lì che nasce quasi tutta la confusione: un timestamp Unix non è un conteggio di secondi fisici. È una rappresentazione compressa di una data e ora del calendario UTC. Due timestamp distanti 86 400 sono esattamente a un giorno UTC di distanza per costruzione, sempre, qualunque cosa abbia misurato un orologio al cesio nello stesso intervallo.

Una precisazione, perché la proprietà viene spesso esagerata. Vale in UTC, non nell'ora locale. Misurato su dati reali di fuso orario, da mezzanotte locale a mezzanotte locale in Europe/Paris ci sono stati 82 800 secondi Unix il 2026-03-29 e 90 000 il 2026-10-25, contro 86 400 in un giorno normale; America/New_York ha dato 82 800 l'8 marzo 2026. L'ora legale non cambia il contatore — cambia quanti scatti entrano in un giorno di calendario locale, ed è esattamente per questo che i timestamp vanno conservati come istanti UTC e convertiti solo al momento della visualizzazione.

Il compromesso: niente secondi intercalari, niente tabella

Escludere i secondi intercalari sembra un difetto finché non si guarda l'alternativa. Se il contatore seguisse i secondi fisici, convertire un timestamp in una data richiederebbe di conoscere ogni secondo intercalare inserito fra l'epoca e quel timestamp. I secondi intercalari sono annunciati dall'IERS con qualche mese di anticipo, sulla base di misure della rotazione terrestre, quindi quella tabella non può essere calcolata, solo distribuita. Ogni dispositivo ne avrebbe bisogno, aggiornata, e qualsiasi dispositivo con una tabella scaduta calcolerebbe una data di calendario diversa dallo stesso numero.

Il progetto ha scelto il determinismo anziché l'accuratezza fisica, e il vantaggio è che l'aritmetica dei timestamp funziona ovunque senza stato condiviso. Un telefono che non ha mai avuto una connessione calcola da 1786752000 esattamente la stessa data di un orologio di data center. Dividere per 86 400 è corretto. Aggiungere un giorno significa aggiungere 86 400. Ordinare per timestamp ordina per tempo. Nulla di tutto ciò sopravvivrebbe a una definizione in secondi fisici.

Il prezzo si paga in un unico punto: ogni calcolo di durata fisica che attraversi un secondo intercalare sbaglia del numero di secondi intercalari attraversati. Fra 2016-12-31T00:00:00Z e 2017-01-01T00:00:00Z la differenza Unix è 86 400, mentre sono trascorsi davvero 86 401 secondi SI. Per quasi ogni applicazione quell'errore è irrilevante. Per il telerilevamento satellitare, la sequenziazione di scambi finanziari a granularità inferiore al secondo e la metrologia fisica non lo è, e quei campi usano il TAI o il tempo GPS — scale monotone del tutto prive di secondi intercalari.

Che cosa fa un secondo intercalare al contatore

L'UTC inserisce un secondo intercalare consentendo a un minuto di contenere 61 secondi, con il secondo in più etichettato 23:59:60. Dal 1972 ne sono stati inseriti ventisette — TAI meno UTC valeva 10 secondi al 1972-01-01 e vale 37 dal 2017-01-01, e la differenza è esattamente il conteggio. Tutti positivi; un secondo intercalare negativo, che tolga un secondo, è ammesso dalla norma e non è mai stato usato.

Un orologio POSIX non ha rappresentazione per il secondo numerato 60, quindi qualcosa deve cedere. Il comportamento classico è ripetere: il contatore emette due volte lo stesso valore, un secondo non è monotono, e qualsiasi codice che presuma timestamp strettamente crescenti vede un duplicato. Alcuni sistemi congelano invece il contatore per un secondo. Entrambi sono visibili alle applicazioni, ed entrambi hanno causato guasti reali in sistemi che trattavano un timestamp come chiave univoca o come numero di sequenza strettamente crescente.

La risposta pragmatica su cui i grandi operatori sono convergiti è lo smearing: distribuire il secondo in più su una finestra di ore, così che nessun orologio ripeta o si fermi, al prezzo che ogni orologio nella finestra sia leggermente sbagliato. Questo conta sempre meno, perché nel novembre 2022 la 27ª Conferenza generale dei pesi e delle misure ha deciso di smettere di inserire secondi intercalari entro il 2035, lasciando che l'UTC si discosti dal tempo solare oltre il limite attuale. Il meccanismo non è ancora deciso, ma la direzione sì: il secondo intercalare va in pensione.

Il 2038, calcolato con esattezza

Un intero a 32 bit con segno va da −2 147 483 648 a 2 147 483 647. Interpretato come secondi dall'epoca, il massimo è 2038-01-19T03:14:07Z. Salvare 2 147 483 648 in una vera casella a 32 bit con segno e rileggerla dà −2 147 483 648, cioè 1901-12-13T20:45:52Z. Salvare 2 147 483 649 dà 1901-12-13T20:45:53Z. Il guasto non è un crash né un errore: è una data 136 anni nel passato, consegnata in silenzio e usata.

Due fatti vicini vale la pena conoscerli. Un contatore a 32 bit senza segno arriva a 4 294 967 295, cioè 2106-02-07T06:28:15Z — soluzione comune nel firmware embedded, che si limita a rinviare il problema rendendo per giunta irrappresentabili le date precedenti al 1970. E un time_t a 64 bit con segno arriva a 9 223 372 036 854 775 807 secondi, circa 292 miliardi di anni, all'incirca ventuno volte l'età attuale dell'universo. Non è un rinvio: è una correzione permanente.

La data da temere non è il 2038 ma oggi, perché i primi sistemi a rompersi sono quelli che calcolano istanti futuri. Dal 2026-08-15 il traboccamento dista 360 731 647 secondi — 4 175 giorni, cioè 11,43 anni. Un orizzonte di dieci anni da quella data finisce il 2036-08-14 e ci sta ancora. Uno di quindici anni finisce il 2041-08-14 e non ci sta. Tutto ciò che memorizza una scadenza, un piano di ammortamento, una finestra di conservazione o una validità di certificato superiore a circa undici anni e mezzo produce già valori che un campo a 32 bit non può contenere.

Che cosa è a rischio e che cosa no nel 2026

I sistemi operativi a 64 bit mainstream stanno bene, e da anni. Sulla macchina usata per questo articolo, sizeof(time_t) vale 8 byte e il comando date -u -r 2147483648 stampa Tue Jan 19 03:14:08 UTC 2038 senza alcun traboccamento. Lo stesso vale per ogni Linux, macOS e Windows a 64 bit attuale. JavaScript non è mai stato esposto: un Date contiene un conteggio float64 di millisecondi e la specifica limita il suo intervallo a più o meno 8 640 000 000 000 000 millisecondi, cioè da −271821-04-20 a +275760-09-13.

L'esposizione che resta è stretta ma reale, e sta soprattutto nei campi, non nelle CPU. I tipi di colonna con intervallo a 32 bit documentato sono i più comuni: il TIMESTAMP di MySQL è specificato per finire il 2038-01-19 03:14:07 UTC, mentre il suo tipo DATETIME non è interessato. I formati di rete e di file che specificano un campo a 32 bit non possono essere allargati senza un salto di versione. Il firmware embedded su microcontrollori a 32 bit usa spesso un contatore a 32 bit di proposito, per ragioni di memoria. E qualsiasi base di codice che conservi un secondo d'epoca in un tipo esplicitamente a 32 bit — una colonna int32, un record binario a larghezza fissa, una struct C compilata per un target a 32 bit — porta con sé il limite indipendentemente dal sistema operativo sottostante.

L'audit pratico è breve. Cerca colonne int32 e INTEGER che contengano secondi d'epoca; controlla l'intervallo documentato di ogni tipo di colonna temporale che usi; individua record binari a larghezza fissa e qualsiasi struct compilata per un target a 32 bit; e prova con il valore 2147483648 invece di aspettare. Se un campo non può essere allargato, conservare una stringa ISO 8601 o un conteggio di millisecondi a 64 bit sono entrambe alternative valide, ed entrambe costano più byte di quanti risparmino discussioni.

Secondi o millisecondi: il bug del fattore 1000

È di gran lunga il bug più comune sui timestamp, ed è interamente un problema di unità. Gli strumenti Unix, la maggior parte delle API e la definizione POSIX usano secondi. JavaScript, Java e moltissime API web usano millisecondi. I due sono lo stesso numero scalato di 1 000, e nessuno è etichettato nel formato di trasporto, quindi una discordanza è invisibile finché non si mostra una data.

Entrambe le direzioni producono un risultato assurdo, e questa è la buona notizia. Prendi 2026-08-15T00:00:00Z: in secondi è 1786752000, in millisecondi 1786752000000. Passa il valore in secondi a qualcosa che si aspetta millisecondi e ottieni 1970-01-21T16:19:12Z — tre settimane dopo l'epoca, perché 1,79 miliardi di millisecondi sono solo una ventina di giorni. Passa il valore in millisecondi a qualcosa che si aspetta secondi e ottieni l'anno 58589, precisamente +058589-12-01T00:00:00Z. Entrambi sono così lontani dal plausibile che un solo controllo li scova.

L'euristica che funziona: un timestamp attuale in secondi ha dieci cifre, in millisecondi tredici. Dieci cifre resteranno corrette fino al 2286. Meglio di un'euristica è dare al campo un nome che renda l'unità impossibile da fraintendere — expiresAtSeconds anziché expiresAt — oppure far passare una stringa RFC 3339 attraverso il confine e analizzarla all'arrivo, cosa autodescrittiva che costa qualche decina di byte.

Conservare il tempo perché sopravviva

Conserva l'istante, non la resa. Un istante è un punto sulla linea del tempo ed è pienamente specificato da un timestamp UTC di larghezza sufficiente — un secondo d'epoca a 64 bit, un conteggio di millisecondi a 64 bit, o una stringa RFC 3339 che termina in Z. Una resa è ciò che vedrebbe una persona in un luogo preciso, e dipende da regole di fuso che i governi cambiano con qualche settimana di preavviso. Conservare la resa significa conservare una risposta che può diventare sbagliata retroattivamente.

Un'eccezione merita di essere nominata, perché conservare in UTC viene spesso venduto come consiglio universale. Un appuntamento futuro in un luogo con un nome non è un istante: è un'ora da orologio a parete in una giurisdizione, e se quella giurisdizione sposta gli orologi l'istante corretto cambia. Una riunione alle 09:00 a Berlino il prossimo novembre va conservata come data locale, ora locale e identificatore di fuso IANA, e risolta in istante solo quando serve. Conserva UTC per ciò che è accaduto, e ora locale più nome di fuso per ciò che è programmato.

Oltre a ciò, tre abitudini tolgono gran parte del dolore restante. Dai a ogni campo timestamp un'unità nel nome, così chi legge non deve mai indovinare fra secondi e millisecondi. Usa un tipo a 64 bit ovunque, colonna di database compresa, così il 2038 è una curiosità storica e non una scadenza. E non trattare mai un timestamp come identificatore univoco o numero di sequenza monotono, perché la gestione dei secondi intercalari, le correzioni d'orologio e le migrazioni di macchine virtuali possono far comparire due volte lo stesso valore o far tornare il tempo indietro per un attimo.

Rappresentazioni del tempo e i loro limiti, ogni istante di confine calcolato e non citato
RappresentazioneUnitàPiù anticaPiù recenteDove la si incontra ancora
time_t a 32 bit con segnoSecondi1901-12-13T20:45:52Z2038-01-19T03:14:07ZTarget embedded a 32 bit, colonne int32, record binari a larghezza fissa
Contatore a 32 bit senza segnoSecondi1970-01-01T00:00:00Z2106-02-07T06:28:15ZSoluzioni firmware; nessuna data precedente al 1970 possibile
time_t a 64 bit con segnoSecondiCirca 292 miliardi di anni prima del 1970Anno 292 277 026 596 (2^63 − 1 secondi)Ogni build attuale a 64 bit di Linux, macOS e Windows
Date di JavaScriptMillisecondi (float64)−271821-04-20+275760-09-13 (±8 640 000 000 000 000 ms)Browser e Node; non hanno mai avuto un problema 2038
TIMESTAMP di MySQLSecondi1970-01-01 00:00:01 UTC2038-01-19 03:14:07 UTCMolto diffuso; DATETIME è l'alternativa non interessata
Stringa RFC 3339Testo, autodescrittivoNessun limite inferiore nel formatoNessun limite superiore nel formatoAPI e log; costa byte, elimina l'ambiguità secondi/millisecondi
Convertitore di tempo UnixConverti un timestamp Unix (secondi o millisecondi) in una data leggibile e viceversa, con il timestamp attuale dal vivo.Prova lo strumento

Domande frequenti

Un timestamp Unix è in UTC o nel mio fuso locale?
Nessuna delle due, a rigore, ed è questo il modo utile di pensarci. Un timestamp Unix individua un punto sulla linea del tempo. Non porta alcun fuso orario, perché non ne ha bisogno — il numero 1786752000 indica lo stesso momento ovunque sulla Terra. Ciò che è vero è che convertirlo in una data leggibile richiede un fuso, e che l'epoca è ancorata a 1970-01-01T00:00:00 UTC, quindi convertire senza fuso indicato dà UTC. Ecco perché un timestamp è la cosa giusta da conservare e trasmettere e quella sbagliata da mostrare: il livello di archiviazione ha bisogno di un istante inequivocabile, quello di presentazione di un fuso, di una locale e di un calendario. Il bug comune è convertire in ora locale a metà di una pipeline e poi conservare il risultato, cosa che fissa il fuso di una macchina e sposta in silenzio ogni valore del suo scarto. Converti una volta sola, all'ultimo momento possibile, nell'interfaccia.
Che cosa succede esattamente alle 03:14:07 del 19 gennaio 2038?
In qualsiasi sistema che conservi il timestamp in un intero a 32 bit con segno, il contatore arriva a 2 147 483 647 e l'incremento successivo trabocca. Simulato in una vera casella a 32 bit con segno, salvare 2 147 483 648 si rilegge come −2 147 483 648, che si converte in 1901-12-13T20:45:52Z. Il comportamento non è un'eccezione né un crash: il valore diventa semplicemente una data 136 anni nel passato e viene usato. Quel che ne consegue dipende dal codice sopra. Gli ordinamenti si invertono. I calcoli di età e durata diventano enormemente negativi. Certificati e sessioni sembrano scaduti da tempo, o non scadere mai. Gli scheduler in stile cron scattano di continuo o si fermano. I record finiscono nella partizione sbagliata. Il pericolo nasce proprio dal fatto che nulla solleva un errore: ogni livello riceve un numero ben formato e si comporta correttamente per il numero ricevuto. È anche per questo che testare è facile — metti 2147483648 in un campo oggi e rileggilo, invece di aspettare la data.
I miei server sono a 64 bit. Sono al sicuro dal problema del 2038?
Il tuo sistema operativo lo è, la tua applicazione forse no. Su una macchina moderna a 64 bit sizeof(time_t) vale 8 byte — verificato qui — e la shell stampa Tue Jan 19 03:14:08 UTC 2038 per il valore 2147483648, senza traboccare. Ma il kernel è raramente il luogo del limite. L'esposizione sta nei campi che hai scelto: una colonna INTEGER o int32 con secondi d'epoca, un protocollo o formato di file con campo timestamp fisso a 32 bit, una struct compilata per un target embedded a 32 bit, un record binario a larghezza fissa scritto anni fa. Il tipo di colonna TIMESTAMP di MySQL è un esempio documentato, specificato per finire il 2038-01-19 03:14:07 UTC a prescindere da quanti bit abbia il server, mentre il suo tipo DATETIME non è interessato. Una seconda esposizione, meno ovvia, è qualsiasi dispositivo di terze parti nel parco — stampanti, telecamere, controllori, sensori — il cui firmware è a 32 bit per progetto e forse non verrà mai aggiornato. L'audit conviene farlo ora e non nel 2037, perché i guasti cominciano dai valori futuri e il 2026 è già dentro la finestra di undici anni e mezzo.
Come faccio a capire se un numero è in secondi o in millisecondi?
Conta le cifre. Un timestamp attuale in secondi ha dieci cifre e continuerà ad averne dieci fino al 2286; lo stesso istante in millisecondi ne ha tredici. Per 2026-08-15T00:00:00Z i due valori sono 1786752000 e 1786752000000. Nel dubbio converti e guarda il risultato, perché entrambi gli errori producono qualcosa di palesemente assurdo: leggere il valore in secondi come millisecondi dà 1970-01-21T16:19:12Z, tre settimane dopo l'epoca, e leggere il valore in millisecondi come secondi dà l'anno 58589. Una data del gennaio 1970 o di un futuro lontano è quasi sempre questo bug e non dati sbagliati. La correzione duratura non è il rilevamento ma la denominazione. Chiama il campo expiresAtSeconds o createdAtMillis così l'unità viaggia col valore, oppure invia una stringa RFC 3339 come 2026-08-15T00:00:00Z fra i servizi — è autodescrittiva, si ordina correttamente come testo, sopravvive all'incollaggio in un log e costa una ventina di byte.
I secondi intercalari rendono sbagliati i miei calcoli di durata?
Tecnicamente sì, e in pratica quasi mai abbastanza da contare. Sottrarre due timestamp Unix dà la differenza del contatore, che omette ogni secondo intercalare intermedio. Attraverso il secondo intercalare del 2016, il contatore dice che fra 2016-12-31T00:00:00Z e 2017-01-01T00:00:00Z sono passati 86 400 secondi, mentre ne sono trascorsi 86 401 SI. Poiché in tutto sono stati inseriti solo ventisette secondi intercalari, l'errore massimo possibile su una durata che copra l'intero periodo dal 1972 è di ventisette secondi — irrilevante per fatturazione, durata di sessione, scadenza di cache, latenza al millisecondo o qualsiasi misura aziendale. Diventa rilevante nel telerilevamento satellitare, nella fisica di alta precisione e nella sequenziazione finanziaria sotto il secondo, e quei campi usano il TAI o il tempo GPS, del tutto privi di secondi intercalari. C'è però un pericolo pratico molto più grande: misurare il tempo trascorso con l'orologio da parete. Correzioni NTP, migrazioni di macchine virtuali e modifiche manuali possono spostare un orologio da parete di ben più di un secondo, avanti o indietro. Per misurare durate usa un orologio monotono — quello che il tuo linguaggio espone come qualcosa tipo performance.now o un orologio stabile — e riserva i timestamp da parete per registrare quando qualcosa è accaduto.
Devo conservare i timestamp in UTC o con un fuso orario?
Dipende dal fatto che tu stia registrando qualcosa di accaduto o programmando qualcosa che accadrà. Per tutto ciò che è passato — una riga di log, un ordine, un pagamento, una scrittura di audit — conserva l'istante in UTC e converti solo alla visualizzazione. L'istante è un fatto e non cambia mai; il modo in cui si presenta nell'ora locale di un utente è una questione di presentazione ricalcolabile in qualsiasi momento. Per tutto ciò che è programmato nel futuro, UTC è la scelta sbagliata, ed è il caso che si sbaglia più spesso. Una riunione alle 09:00 a Berlino il prossimo novembre non è un istante: è un'ora da orologio a parete in una giurisdizione. Se la Germania cambia le sue regole orarie da qui ad allora, l'istante corretto si sposta, e un valore UTC salvato oggi diventerebbe un appuntamento all'ora locale sbagliata. Conserva la data locale, l'ora locale e l'identificatore di fuso IANA come Europe/Berlin, e risolvi in istante quando ti serve. I governi cambiano davvero le regole di fuso, di solito con qualche settimana di preavviso, e il database tz viene aggiornato più volte l'anno per seguirle.

Articoli che potrebbero interessarti

Tutte le guide

Strumenti correlati

Fonti

Hai notato un errore in questo articolo?