sql >> Databáze >  >> RDS >> Oracle

Zpracování časového pásma ve webové aplikaci

Přečtěte si otázku Doporučené postupy pro letní čas a časové pásmo. Váš je v podstatě duplikát.

Servery v UTC

Ano, obecně by servery měly mít OS nastavený na UTC jako časové pásmo, nebo pokud není uvedeno, použijte GMT nebo časové pásmo Reykjavík Island. Vaše implementace Java pravděpodobně používá toto nastavení jako své vlastní aktuální výchozí časové pásmo.

Upřesněte časové pásmo

Ale nezávisí na časovém pásmu nastaveném na UTC. Systémový správce by to mohl změnit. A jakýkoli kód Java v libovolném vláknu jakékoli aplikace v rámci vašeho JVM může změnit aktuální výchozí časové pásmo JVM za běhu voláním TimeZone.setDefault . Místo toho si zvykněte vždy specifikovat požadované/očekávané časové pásmo předáním volitelného argumentu v kódu Java.

Považuji za konstrukční chybu, že jakýkoli rámec data a času by časové pásmo učinil volitelným. Být volitelný vytváří nekonečné množství zmatků, protože programátoři, stejně jako všichni ostatní, nevědomě přemýšlejí v podmínkách svého vlastního časového pásma, pokud k tomu nejsou vyzváni. Takže v práci s datem se příliš často tomuto problému nevěnuje žádná pozornost. Přidejte k problému, že výchozí nastavení JVM se liší. Mimochodem, stejně jako Locale , stejné problémy, by měly být vždy výslovně uvedeny.

UTC

Vaše obchodní logika, ukládání dat a výměna dat by se měly téměř vždy provádět v UTC. Téměř každá databáze má funkci pro úpravu jakéhokoli vstupu do UTC a uložení v UTC.

Při předkládání data a času uživateli nastavte očekávané časové pásmo. Při serializaci hodnoty data a času použijte formát řetězce ISO 8601. Viz odpověď VickyArora konkrétně pro Oracle (jsem Postgres person). Ujistěte se, že jste si pečlivě přečetli dokument a procvičte si experimentováním, abyste plně porozuměli chování vaší databáze. Specifikace SQL se v tomto ohledu příliš nevysvětluje a chování se velmi liší.

java.sql

Pamatujte, že když používáte Java a JDBC, budete používat java.sql.Timestamp a související datové typy. Jsou vždy v UTC, automaticky. Očekávejte, že v budoucnu budou ovladače JDBC aktualizovány tak, aby přímo používaly nové datové typy definované v rámci java.time zabudovaném do Java 8 a novějších.

java.time

Staré třídy jsou zastaralé pomocí java.time. Naučte se používat java.time a zároveň se vyhnete starému java.util.Date/.Calendar a váš programátorský život bude mnohem příjemnější.

Dokud nebude váš ovladač JDBC aktualizován, můžete používat metody pro usnadnění převodu zabudované do java.time. Viz příklady dále, kde Instant je okamžik v UTC a ZonedDateTime je Okamžité přizpůsobené časovému pásmu.

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

Jít opačným směrem.

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

Pokud potřebujete původní časové pásmo, uložte jej

Pokud vaše obchodní požadavky považují časové pásmo původních vstupních dat za důležité, je třeba si je zapamatovat, pak to explicitně uložte jako samostatný sloupec v databázové tabulce. Můžete použít offset-from-UTC, ale to neposkytuje úplné informace. Časové pásmo je posun plus soubor pravidel pro minulé, současné a budoucí řešení anomálií, jako je letní čas. Nejvhodnější je tedy správný název časového pásma, například America/Montreal .

Pouze datum je nejednoznačné

Řekl jste, že shromažďujete mnoho hodnot pouze pro datum, bez denního času a bez časového pásma. Třída pro to v java.time je LocalDate . Stejně jako u LocalTime a LocalDateTime , část „Místní…“ neznamená žádnou konkrétní lokalitu, tedy žádné časové pásmo, a tedy ani bod na časové ose – nemá žádný skutečný význam.

Mějte na paměti, že hodnota pouze pro datum je z definice nejednoznačná. V každém okamžiku se datum v různých částech světa liší. Například těsně po půlnoci v Paříži Francie je nový den, ale v Montrealu Québec je datum stále „včera“.

Obvykle je v podnikání určité časové pásmo implicitní, dokonce i nevědomě intuitivní. Nevědomá intuice o datových bodech obvykle nefunguje dobře z dlouhodobého hlediska, zejména v softwaru. Je lepší jasně uvést, jaké časové pásmo bylo zamýšleno. Můžete uložit zamýšlenou zónu vedle data, jako je jiný sloupec v databázové tabulce, nebo můžete přidat komentář do programovacího kódu. Věřím, že by bylo mnohem lepší a bezpečnější uložit hodnotu data a času. Jak tedy transformujeme pouze datum na datum a čas?

Často je nový den okamžik po půlnoci, první okamžik dne. Možná si myslíte, že to znamená denní dobu 00:00:00.0 ale ne vždy. Letní čas (DST) a případně další anomálie mohou posunout první okamžik na jiný čas nástěnných hodin. Nechte java.time určit správnou denní dobu pro první okamžik pomocí LocalDate třída a její atStartOfDay metoda.

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

V některých obchodních kontextech může být nový den definován (nebo předpokládán) jako pracovní doba. Řekněme například, že vydavatel v New Yorku znamená 9:00 místního času, když říkají, že „návrh knihy je splatný do 2. ledna“. Zjistíme, že čas dne pro toto datum v daném časovém pásmu.

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

Co to znamená pro autora působícího na Novém Zélandu? Voláním withZoneSameInstant se přizpůsobte jejímu konkrétnímu časovému pásmu pro prezentaci .

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

Databáze

Pro ukládání databáze se transformujeme na Instant (okamžik na časové ose v UTC) a předat jako java.sql.Timestamp jak je vidět výše.

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

Po načtení z databáze se transformujte zpět na datum a čas v New Yorku. Převést z java.sql.Timestamp na Instant a poté použijte časové pásmo ZoneId získat ZonedDateTime .

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

Pokud váš databázový ovladač vyhovuje JDBC 4.2 nebo novější, můžete být schopni předávat/načítat typy java.time přímo, spíše než převádět na/z typů java.sql. Vyzkoušejte PreparedStatement::setObject a ResultSet::getObject metody.



  1. MariaDB JSON_CONTAINS() Vysvětleno

  2. Laravel-5 ekvivalent „LIKE“ (výmluvný)

  3. Jednoduché uvozovky, dvojité uvozovky a zpětné uvozovky v MySQL

  4. Jak spustím PHP, které je uloženo v databázi MySQL?