DanLevy.net

Il Tuo Timestamp è una Bugia

Cosa mi ha insegnato un biglietto del treno sulla memorizzazione del tempo nei database

Sto prenotando un treno da New York a Chicago quando mi è diventato chiaro perché i tipi timestamp in Postgres sono così confusi. Il biglietto mostrava:

Tre modi diversi di parlare del tempo, tutti sullo stesso biglietto. E ognuno deve essere memorizzato in modo diverso in un database.

La Domanda che Nessuno Fa per Prima

Sia TIMESTAMP che TIMESTAMPTZ in Postgres occupano esattamente 8 byte con la stessa precisione al microsecondo. Allora perché avere due tipi?

Perché “che ora è?” dipende interamente da cosa stai cercando di dire a qualcuno.

Quando salgo su quel treno a New York, devo sapere che parte alle 8:00 Eastern. Quello è il numero sull’orologio della stazione che devo trovare. Quando la mia amica viene a prendermi a Chicago, deve sapere che arrivo alle 19:30 Central—quello è il numero sul suo orologio. E se sto cercando di capire se avrò tempo per leggere il mio libro, devo sapere che si tratta di un viaggio di undici ore e mezza.

Stesso treno. Stesso viaggio. Tre rappresentazioni completamente diverse del tempo.

Cosa Fa Davvero TIMESTAMPTZ

Ecco il trucco con TIMESTAMPTZ—e non è quello che la maggior parte delle persone pensa. Non memorizza il fuso orario. Il nome è fuorviante.

Quello che fa è convertire qualsiasi ora gli fornisci in UTC prima di memorizzarla, per poi riconvertirla nel fuso orario della tua sessione quando la leggi. La parte “TZ” non riguarda la memorizzazione, riguarda il supporto alla conversione.

Diciamo che stai memorizzando quella partenza del treno. Qualcuno a Tokyo interroga il tuo database e vede la partenza in JST. Qualcuno a Londra la vede in GMT. Tutti guardano lo stesso momento assoluto, semplicemente espresso nel loro fuso orario configurato. Questo è perfetto per registrare eventi: “quando è stato elaborato questo pagamento?” o “quando è avvenuta questa richiesta API?”

Ma quel biglietto del treno? Non vuoi che l’ora di partenza cambi solo perché qualcuno la interroga da un fuso orario diverso. Il treno parte alle 8:00 Eastern, punto. Quello non è un momento assoluto nel tempo—è una promessa su cosa dirà l’orologio alla stazione.

Memorizzare Quello che Intendi Davvero

Per quel viaggio in treno, devi memorizzare cose diverse per scopi diversi:

Ora la tua applicazione può fare quello che fa il biglietto del treno: mostrare “Partenza 8:00 EST” convertendo il momento assoluto nel fuso orario di origine, mostrare “Arrivo 19:30 CST” convertendo nel fuso orario di destinazione, e mostrare “Durata: 11h 30m” direttamente dall’intervallo.

La persona che prenota il biglietto da Tokyo vede gli stessi orari locali in ogni stazione. Ecco cosa deve sapere.

Perché la Tua App di Monitoraggio Voli ha Sbagliato

Hai mai notato come alcune app di monitoraggio voli mostrano il tuo fuso orario durante il volo? Tipo che sei sopra l’Atlantico e dice “Ora corrente: 16:32 GMT.” A chi importa? Non sei a Greenwich, sei a 11.000 metri da qualche parte sopra l’oceano.

Quello che vuoi davvero vedere:

Nessuno di questi è una conversione di fuso orario. I primi due sono intervalli—durate, non momenti. L’ultimo è una conversione di fuso orario, ma verso un luogo specifico, non “il tuo fuso orario corrente.”

Vedi? Due calcoli di intervallo (NOW() - actual_departure e estimated_arrival - NOW()), una conversione di fuso orario verso un luogo specifico (AT TIME ZONE destination_timezone). Il tuo fuso orario corrente non c’entra.

Quando l’Ora del Muro è Quello di cui Hai Davvero Bisogno

Agli hotel non importano i momenti assoluti nel tempo. Gli interessano le letture dell’orologio nella loro posizione.

“Check-in dopo le 15:00” non significa “check-in 15 ore dopo la mezzanotte UTC.” Significa “quando l’orologio nella nostra lobby segna le 15:00, puoi fare il check-in.” Se i tuoi server sono in Virginia ma l’hotel è a Parigi, vuoi comunque che quella regola si attivi alle 15:00 ora di Parigi.

Il tipo TIME (senza data o fuso orario) rappresenta esattamente questo: “una lettura su un orologio.” Abbinalo a un campo di testo per il fuso orario (“Europe/Paris”), e puoi applicare politiche basate sull’ora locale indipendentemente da dove vivono i tuoi server. Ma vorrai anche colonne TIMESTAMPTZ per quando gli ospiti specifici fanno effettivamente check-in e check-out—quelli sono momenti assoluti che il tuo backend deve tracciare.

Il Problema del Calendario

Ho un promemoria ricorrente impostato per le 9:00: “Rivedere le priorità giornaliere.” Voglio quel promemoria alle 9:00 ovunque io sia. Se sto viaggiando, dovrebbe comunque attivarsi alle 9:00 ora locale.

Ma ho anche un evento nel calendario: “Standup del team alle 10:00 EST.” Il mio collega a Berlino deve vedere “16:00 CET” per quello stesso evento. Stessa riunione, diversi orari di visualizzazione, perché questo è un momento assoluto a cui partecipiamo tutti.

Due tipi diversi di eventi, due strategie di memorizzazione diverse. La riunione ottiene un TIMESTAMPTZ. Il promemoria ottiene un TIME più la mia impostazione del fuso orario corrente. Evita di cercare di forzare entrambi nello stesso campo.

Le Cose che si Rompono in Produzione

Anche con i tipi giusti, la precisione può morderti. Postgres memorizza microsecondi: 10:00:00.123456. L’oggetto Date di JavaScript usa millisecondi: 10:00:00.123.

Quindi questa query potrebbe misteriosamente non restituire righe:

SELECT * FROM orders WHERE created_at = '2026-01-15 10:00:00.123';

Il database ha 10:00:00.123456 e il tuo codice passa 10:00:00.123. A seconda di come il tuo driver lo gestisce, quelli potrebbero non corrispondere.

Non usare l’uguaglianza esatta per i timestamp. Usa query per intervallo, o—meglio—non cercare record per il loro timestamp di creazione. Usa un vincolo univoco appropriato o una chiave di idempotenza.

Regole Pratiche

Usa TIMESTAMPTZ come predefinito. Quando sei in dubbio, usa TIMESTAMPTZ. Gestisce deployment multi-regione, ora legale e cambiamenti futuri dei fusi orari automaticamente. Ha la stessa dimensione di memorizzazione di TIMESTAMP, quindi non c’è penalità.

Memorizza il contesto separatamente. Se devi mostrare “Partenza 8:00 EST” accanto al momento effettivo, memorizza sia TIMESTAMPTZ che origin_timezone come colonne separate. Non cercare di codificare tutto in un unico campo.

Pensa agli intervalli. Molti requisiti legati al tempo riguardano in realtà la durata, non i momenti. “Da quanto tempo è in attesa?” “Quando scadrà?” Usa operazioni INTERVAL, non conversioni di fuso orario.

Esegui tutto in UTC. I tuoi server dovrebbero essere impostati su UTC. Le sessioni del database dovrebbero avere UTC come predefinito. Converti ai fusi orari locali solo quando visualizzi agli utenti, e solo quando sai quale fuso orario conta.

Richiedi informazioni sul fuso orario dai client. Se un client invia 2026-01-15T10:00:00 senza un offset, rifiutalo. Richiedi il formato ISO-8601 con Z o un offset esplicito come -05:00. Non indovinare.

Applicare Buoni Predefiniti

Se TIMESTAMPTZ è il tuo predefinito (e dovrebbe esserlo), considera di applicarlo a livello di database. Un trigger che rifiuta colonne TIMESTAMP WITHOUT TIME ZONE sembra estremo, ma catturare “dimenticato di aggiungere TZ” al momento della creazione dello schema è meglio che fare il debug sei mesi dopo quando qualcuno aggiunge una nuova tabella e se ne dimentica.

Cosa Mi Ha Insegnato Quel Biglietto del Treno

Il tempo nei database non è difficile perché i timestamp sono complicati. È difficile perché di solito memorizziamo più preoccupazioni in un unico campo, o non pensiamo a cosa stiamo effettivamente cercando di mostrare agli utenti.

Quel biglietto del treno aveva ragione: ora di partenza nel fuso orario di origine, ora di arrivo nel fuso orario di destinazione, e durata come cosa completamente separata. Tre pezzi diversi di informazione, ognuno significativo a modo suo.

Il tuo database può fare la stessa cosa. Memorizza i momenti assoluti come TIMESTAMPTZ. Memorizza il contesto di visualizzazione (fusi orari, posizioni) come colonne separate. Usa tipi INTERVAL per le durate. Lascia che Postgres faccia le conversioni quando ne hai bisogno, ma sii esplicito su quale fuso orario conta per quale scopo.

La maggior parte delle volte, questo significa TIMESTAMPTZ e UTC ovunque, con conversioni di fuso orario solo al momento della visualizzazione. Ma quando hai bisogno di orari del muro o programmi ricorrenti, i tipi TIMESTAMP o TIME esistono esattamente per questo motivo.

La chiave è sapere quale domanda stai cercando di rispondere: “Quando è successo?” vs. “A che ora dovrei essere lì?” vs. “Quanto tempo ci vorrà?” Sono tutte domande diverse sul tempo, e spesso hanno bisogno di strategie di memorizzazione diverse.

Pensa a cosa hanno bisogno di vedere i tuoi utenti. Poi memorizza i dati che ti permettono di mostrare loro esattamente quello.

Risorse