Oracle
 sql >> Database >  >> RDS >> Oracle

Gestione del fuso orario nell'applicazione web

Leggi la domanda Best practice relative all'ora legale e al fuso orario. Il tuo è fondamentalmente un duplicato.

Server in UTC

Sì, in genere i server dovrebbero avere il loro sistema operativo impostato su UTC come fuso orario o, se non fornito, utilizzare GMT o il fuso orario di Reykjavík Islanda. La tua implementazione Java probabilmente acquisisce questa impostazione come il proprio fuso orario predefinito corrente.

Specifica il fuso orario

Ma non dipende dal fuso orario impostato su UTC. Un amministratore di sistema potrebbe cambiarlo. E qualsiasi codice Java in qualsiasi thread di qualsiasi app all'interno della tua JVM può modificare il fuso orario predefinito corrente della JVM in fase di esecuzione chiamando TimeZone.setDefault . Quindi, prendi l'abitudine di specificare sempre il fuso orario desiderato/previsto passando l'argomento facoltativo nel tuo codice Java.

Considero un difetto di progettazione che qualsiasi framework data-ora renderebbe il fuso orario opzionale. Essere facoltativi crea una quantità infinita di confusione perché i programmatori, come tutti gli altri, pensano inconsciamente in termini di fuso orario personale a meno che non venga richiesto. Quindi troppo spesso nel lavoro su data e ora non viene prestata attenzione al problema. Aggiungi il problema che l'impostazione predefinita JVM varia. A proposito, idem per Locale , stessi problemi, dovrebbero essere sempre specificati in modo esplicito.

UTC

La logica aziendale, l'archiviazione dei dati e lo scambio di dati dovrebbero quasi sempre essere eseguiti in UTC. Quasi tutti i database hanno una funzione per regolare qualsiasi input in UTC e archiviare in UTC.

Quando si presenta una data e ora a un utente, regolare il fuso orario previsto. Quando si serializza un valore di data e ora, utilizzare i formati di stringa ISO 8601. Vedi la risposta di VickyArora per Oracle in particolare (sono una persona Postgres). Assicurati di leggere attentamente il documento ed esercitati sperimentando per comprendere appieno il comportamento del tuo database. Le specifiche SQL non sono molto esplicite al riguardo e il comportamento varia ampiamente.

java.sql

Ricorda che quando utilizzi Java e JDBC, utilizzerai java.sql.Timestamp e relativi tipi di dati. Sono sempre in UTC, automaticamente. In futuro aspettatevi di vedere i driver JDBC aggiornati per utilizzare direttamente i nuovi tipi di dati definiti nel framework java.time integrato in Java 8 e versioni successive.

java.time

Le vecchie classi sono superate da java.time. Impara a utilizzare java.time evitando il vecchio java.util.Date/.Calendar e rendi la tua vita di programmazione molto più piacevole.

Fino all'aggiornamento del driver JDBC, è possibile utilizzare i metodi pratici di conversione incorporati in java.time. Vedi gli esempi di seguito, dove Instant è un momento in UTC e ZonedDateTime è un istante adattato a un fuso orario.

Instant instant = myJavaSqlTimestamp.toInstant();
ZoneId zoneId = ZoneId.of( "America/Montreal" );
ZonedDateTime zdt = ZonedDateTime.ofInstant( instant , zoneId );

Per andare nell'altra direzione.

java.sql.Timestamp myJavaSqlTimestamp = java.sql.Timestamp.from( zdt.toInstant() );

Se hai bisogno del fuso orario originale, salvalo

Se i requisiti aziendali considerano importante, da ricordare, il fuso orario dei dati di input originali, archiviarlo esplicitamente come colonna separata nella tabella del database. È possibile utilizzare un offset da UTC, ma ciò non fornisce informazioni complete. Un fuso orario è un offset più un insieme di regole per la gestione passata, presente e futura di anomalie come l'ora legale. Quindi un nome di fuso orario corretto è più appropriato come America/Montreal .

La sola data è ambigua

Hai detto di raccogliere molti valori di sola data, senza ora del giorno e senza fuso orario. La classe per questo in java.time è LocalDate . Come con LocalTime e LocalDateTime , la parte "Locale..." non significa nessuna località particolare, quindi nessun fuso orario, e quindi nessun punto sulla linea temporale -- non ha un vero significato.

Tieni presente che un valore di sola data è ambiguo per definizione. In un dato momento, la data varia in tutto il mondo. Ad esempio, poco dopo la mezzanotte a Parigi la Francia è un nuovo giorno ma a Montréal Québec la data è ancora “ieri”.

Di solito negli affari un certo fuso orario è implicito, anche inconsciamente intuito. L'intuizione inconscia sui punti dati tende a non funzionare bene a lungo termine, specialmente nel software. Meglio rendere esplicito quale fuso orario fosse previsto. È possibile memorizzare la zona prevista insieme alla data, ad esempio un'altra colonna nella tabella del database, oppure inserire un commento nel codice di programmazione. Credo che sarebbe molto meglio e più sicuro memorizzare un valore data-ora. Quindi, come trasformiamo una data solo in una data e ora?

Spesso un nuovo giorno è il momento dopo la mezzanotte, il primo momento della giornata. Potresti pensare che significhi l'ora del giorno 00:00:00.0 ma non sempre. L'ora legale (DST) ed eventualmente altre anomalie possono spingere il primo momento a un'ora diversa dell'orologio da parete. Lascia che java.time determini l'ora del giorno corretta per il primo momento attraverso il LocalDate class e il relativo atStartOfDay metodo.

ZoneId zoneId = ZoneId.of( "America/Montreal" );
LocalDate today = LocalDate.now( zoneId );
ZonedDateTime todayStart = today.atStartOfDay( zoneId );

In alcuni contesti lavorativi un nuovo giorno può essere definito (o assunto) come orario lavorativo. Ad esempio, supponiamo che un editore a New York significhi le 9:00 nella sua ora locale quando dice "la bozza del libro è prevista entro il 2 gennaio". Prendiamo quell'ora del giorno per quella data in quel fuso orario.

ZoneId zoneId = ZoneId.of( "America/New_York" );
ZonedDateTime zdt = ZonedDateTime.of( 2016 , 1 , 2 , 9 , 0 , 0 , 0 , zoneId );

Cosa significa per l'autore che lavora in Nuova Zelanda? Regola il suo fuso orario particolare per la presentazione chiamando withZoneSameInstant .

ZoneId zoneId_Pacific_Auckland = ZoneId.of( "Pacific/Auckland" );
ZonedDateTime zdt_Pacific_Auckland = zdt.withZoneSameInstant( zoneId_Pacific_Auckland );

Banca dati

Per l'archiviazione del database ci trasformiamo in un Instant (un momento sulla timeline in UTC) e passare come java.sql.Timestamp come visto in precedenza.

java.sql.Timestamp ts = java.sql.Timestamp.from( zdt.toInstant() );

Quando viene recuperato dal database, trasforma di nuovo in una data e ora di New York. Converti da java.sql.Timestamp a un Instant , quindi applica un fuso orario ZoneId per ottenere un ZonedDateTime .

Instant instant = ts.toInstant();
ZoneId zoneId = ZoneId.of( "America/New_York" );
ZonedDateTime zdt = ZonedDateTime.ofInstant( instant , zoneId );

Se il driver del database è conforme a JDBC 4.2 o versioni successive, potresti essere in grado di passare/recuperare direttamente i tipi java.time anziché convertire in/da tipi java.sql. Prova PreparedStatement::setObject e ResultSet::getObject metodi.