Prima di tutto, la gestione del tempo e l'aritmetica di PostgreSQL è fantastica e l'opzione 3 va bene nel caso generale. Tuttavia, è una visione incompleta dell'ora e dei fusi orari e può essere completata:
- Memorizza il nome del fuso orario di un utente come preferenza dell'utente (ad es
America/Los_Angeles
. No -0700
).
- Fai in modo che i dati sugli eventi / orari degli utenti vengano inviati localmente al loro frame di riferimento (molto probabilmente un offset dall'UTC, come
-0700
).
- Nell'applicazione, convertire l'ora in
UTC
e memorizzata utilizzando una TIMESTAMP WITH TIME ZONE
colonna.
- L'ora di ritorno richiede locale al fuso orario di un utente (ad esempio, conversione da
UTC
a America/Los_Angeles
).
- Imposta il tuo database
timezone
su UTC
.
Questa opzione non funziona sempre perché può essere difficile ottenere il fuso orario di un utente e quindi il consiglio di copertura da utilizzare TIMESTAMP WITH TIME ZONE
per applicazioni leggere. Detto questo, lasciatemi spiegare alcuni aspetti di fondo di questa opzione 4 in modo più dettagliato.
Come l'opzione 3, il motivo WITH TIME ZONE
è perché il momento in cui è successo qualcosa è un momento assoluto nel tempo. WITHOUT TIME ZONE
produce un parente fuso orario . Non mischiare mai, mai, MAI TIMESTAMP assoluti e relativi.
Da una prospettiva programmatica e di coerenza, assicurati che tutti i calcoli vengano effettuati utilizzando UTC come fuso orario. Questo non è un requisito di PostgreSQL, ma aiuta quando si integra con altri linguaggi o ambienti di programmazione. Impostazione di unCHECK
sulla colonna per assicurarsi che la scrittura nella colonna timestamp abbia uno scostamento del fuso orario 0
è una posizione difensiva che previene alcune classi di bug (ad esempio uno script scarica i dati in un file e qualcos'altro ordina i dati dell'ora usando un ordinamento lessicale). Ancora una volta, PostgreSQL non ha bisogno di questo per eseguire correttamente i calcoli della data o per convertire tra fusi orari (ad esempio PostgreSQL è molto abile nel convertire gli orari tra due fusi orari arbitrari). Per garantire che i dati che entrano nel database siano archiviati con un offset pari a zero:
CREATE TABLE my_tbl (
my_timestamp TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
CHECK(EXTRACT(TIMEZONE FROM my_timestamp) = '0')
);
test=> SET timezone = 'America/Los_Angeles';
SET
test=> INSERT INTO my_tbl (my_timestamp) VALUES (NOW());
ERROR: new row for relation "my_tbl" violates check constraint "my_tbl_my_timestamp_check"
test=> SET timezone = 'UTC';
SET
test=> INSERT INTO my_tbl (my_timestamp) VALUES (NOW());
INSERT 0 1
Non è perfetto al 100%, ma fornisce una misura anti-colpo abbastanza forte che assicura che i dati siano già convertiti in UTC. Ci sono molte opinioni su come farlo, ma questa sembra essere la migliore nella pratica dalla mia esperienza.
Le critiche alla gestione del fuso orario del database sono ampiamente giustificate (ci sono molti database che gestiscono questo con grande incompetenza), tuttavia la gestione dei timestamp e dei fusi orari di PostgreSQL è piuttosto impressionante (nonostante alcune "caratteristiche" qua e là). Ad esempio, una di queste caratteristiche:
-- Make sure we're all working off of the same local time zone
test=> SET timezone = 'America/Los_Angeles';
SET
test=> SELECT NOW();
now
-------------------------------
2011-05-27 15:47:58.138995-07
(1 row)
test=> SELECT NOW() AT TIME ZONE 'UTC';
timezone
----------------------------
2011-05-27 22:48:02.235541
(1 row)
Tieni presente che AT TIME ZONE 'UTC'
elimina le informazioni sul fuso orario e crea un parente TIMESTAMP WITHOUT TIME ZONE
utilizzando il quadro di riferimento del target (UTC
).
Quando si converte da incompleto TIMESTAMP WITHOUT TIME ZONE
a a TIMESTAMP WITH TIME ZONE
, il fuso orario mancante viene ereditato dalla connessione:
test=> SET timezone = 'America/Los_Angeles';
SET
test=> SELECT EXTRACT(TIMEZONE_HOUR FROM NOW());
date_part
-----------
-7
(1 row)
test=> SELECT EXTRACT(TIMEZONE_HOUR FROM TIMESTAMP WITH TIME ZONE '2011-05-27 22:48:02.235541');
date_part
-----------
-7
(1 row)
-- Now change to UTC
test=> SET timezone = 'UTC';
SET
-- Create an absolute time with timezone offset:
test=> SELECT NOW();
now
-------------------------------
2011-05-27 22:48:40.540119+00
(1 row)
-- Creates a relative time in a given frame of reference (i.e. no offset)
test=> SELECT NOW() AT TIME ZONE 'UTC';
timezone
----------------------------
2011-05-27 22:48:49.444446
(1 row)
test=> SELECT EXTRACT(TIMEZONE_HOUR FROM NOW());
date_part
-----------
0
(1 row)
test=> SELECT EXTRACT(TIMEZONE_HOUR FROM TIMESTAMP WITH TIME ZONE '2011-05-27 22:48:02.235541');
date_part
-----------
0
(1 row)
La linea di fondo:
- memorizzare il fuso orario di un utente come etichetta con nome (ad es
America/Los_Angeles
) e non come un offset dall'UTC (ad es-0700
)
- usa UTC per tutto a meno che non ci sia un motivo valido per memorizzare uno scostamento diverso da zero
- considera tutti gli orari UTC diversi da zero come un errore di input
- non mischiare e abbinare timestamp relativi e assoluti
- utilizzare anche
UTC
come timezone
nel database, se possibile
Nota sul linguaggio di programmazione casuale: il datetime
tipo di dati di Python è molto bravo a mantenere la distinzione tra tempi assoluti e relativi (anche se all'inizio frustrante finché non lo si integra con una libreria come PyTZ ).
MODIFICARE
Lascia che ti spieghi un po 'di più la differenza tra relativo e assoluto.
Il tempo assoluto viene utilizzato per registrare un evento. Esempi: "L'utente 123 ha effettuato l'accesso" o "una cerimonia di consegna dei diplomi inizia alle 2pm PST 2011-05-28". Indipendentemente dal fuso orario locale, se potessi teletrasportarti nel luogo in cui si è verificato l'evento, potresti assistere all'evento. La maggior parte dei dati temporali in un database è assoluta (e quindi dovrebbe essereTIMESTAMP WITH TIME ZONE
, idealmente con un offset +0 e un'etichetta testuale che rappresenta le regole che governano il particolare fuso orario - non un offset).
Un evento relativo sarebbe registrare o programmare l'ora di qualcosa dal punto di vista di un fuso orario ancora da determinare. Esempi: "le porte della nostra attività aprono alle 8:00 e chiudono alle 21:00", "incontriamoci ogni lunedì alle 7:00 per un incontro settimanale per la colazione" o "ogni Halloween alle 20:00". In generale, il tempo relativo viene utilizzato in un modello o fabbrica per gli eventi e il tempo assoluto viene utilizzato per quasi tutto il resto. C'è una rara eccezione che vale la pena sottolineare che dovrebbe illustrare il valore dei tempi relativi. Per eventi futuri sufficientemente lontani nel futuro in cui potrebbe esserci incertezza sul momento assoluto in cui potrebbe verificarsi qualcosa, utilizzare un timestamp relativo. Ecco un esempio del mondo reale:
Supponiamo che sia l'anno 2004 e che sia necessario programmare una consegna il 31 ottobre 2008 alle 13:00 sulla costa occidentale degli Stati Uniti (ovvero America/Los_Angeles
/ PST8PDT
). Se lo hai memorizzato utilizzando l'ora assoluta ’2008-10-31 21:00:00.000000+00’::TIMESTAMP WITH TIME ZONE
, la consegna sarebbe arrivata alle 14:00 perché il governo degli Stati Uniti ha approvato l' Energy Policy Act del 2005 che ha modificato le regole che regolano l'ora legale. Nel 2004, quando era prevista la consegna, la data 10-31-2008
sarebbe stata Pacific Standard Time (+8000
solare del ), ma a partire dall'anno 2005+ i database del fuso orario hanno riconosciuto che 10-31-2008
sarebbe stata l'ora legale del Pacifico (+0700
). La memorizzazione di un timestamp relativo con il fuso orario avrebbe portato a un programma di consegna corretto perché un timestamp relativo è immune alle manomissioni mal informate del Congresso. Dove il limite tra l'utilizzo dei tempi relativi e assoluti per la pianificazione è, è una linea sfocata, ma la mia regola pratica è che la pianificazione per qualsiasi cosa in futuro oltre 3-6 mesi dovrebbe fare uso di timestamp relativi (programmato = assoluto vs pianificato = parente ???).
L'altro / ultimo tipo di tempo relativo è il INTERVAL
. Esempio: "la sessione scadrà 20 minuti dopo che un utente ha effettuato l'accesso". Un INTERVAL
può essere utilizzato correttamente con timestamp assoluti ( TIMESTAMP WITH TIME ZONE
) o relativi (TIMESTAMP WITHOUT TIME ZONE
). È altrettanto corretto dire "una sessione utente scade 20 minuti dopo un accesso riuscito (login_utc + session_duration)" o "la nostra colazione mattutina può durare solo 60 minuti (recurring_start_time + meeting_length)".
Ultimi pezzi di confusione: DATE
, TIME
, TIME WITHOUT TIME ZONE
e TIME WITH TIME ZONE
sono tutti i tipi di dati relativi. Ad esempio: '2011-05-28'::DATE
rappresenta una data relativa poiché non si dispone di informazioni sul fuso orario che potrebbero essere utilizzate per identificare la mezzanotte. Allo stesso modo, '23:23:59'::TIME
è relativo perché non conosci né il fuso orario né quello DATE
rappresentato dall'ora. Anche con '23:59:59-07'::TIME WITH TIME ZONE
, non sai cosa DATE
sarebbe. Infine, DATE
con un fuso orario in realtà non è a DATE
, è un TIMESTAMP WITH TIME ZONE
:
test=> SET timezone = 'America/Los_Angeles';
SET
test=> SELECT '2011-05-11'::DATE AT TIME ZONE 'UTC';
timezone
---------------------
2011-05-11 07:00:00
(1 row)
test=> SET timezone = 'UTC';
SET
test=> SELECT '2011-05-11'::DATE AT TIME ZONE 'UTC';
timezone
---------------------
2011-05-11 00:00:00
(1 row)
Mettere date e fusi orari nei database è una buona cosa, ma è facile ottenere risultati leggermente errati. È richiesto uno sforzo aggiuntivo minimo per memorizzare le informazioni sul tempo in modo corretto e completo, tuttavia ciò non significa che sia sempre richiesto uno sforzo aggiuntivo.