web-dev-qa-db-ja.com

LocalDateTimeからZonedDateTime

Java 8複数の地域をサポートするSpring Webアプリ。顧客の場所のカレンダーイベントを作成する必要があります。だから、私のWebとPostgresサーバーはMSTタイムゾーンでホストされています。しかし、顧客はESTにいるため、読んだいくつかのベストプラクティスに従って、すべての日付時刻をUTC形式で保存すると考えました。データベースのすべての日付時刻フィールドはTIMESTAMPとして宣言されます。

LocalDateTimeを取得してUTCに変換する方法は次のとおりです。

ZonedDateTime startZonedDT = ZonedDateTime.ofLocal(dto.getStartDateTime(), ZoneOffset.UTC, null);
//appointment startDateTime is a LocalDateTime
appointment.setStartDateTime( startZonedDT.toLocalDateTime() );

さて、たとえば、日付の検索が入ったら、要求された日付時刻からUTCに変換し、結果を取得し、ユーザーのタイムゾーン(dbに保存されている)に変換する必要があります:

ZoneId  userTimeZone = ZoneId.of(timeZone.getID());
ZonedDateTime startZonedDT = ZonedDateTime.ofLocal(appointment.getStartDateTime(), userTimeZone, null);
dto.setStartDateTime( startZonedDT.toLocalDateTime() );

今、これが正しいアプローチであるかどうかはわかりません。また、LocalDateTimeからZonedDateTimeへ、またはその逆に移動するため、タイムゾーン情報が失われる可能性があるのではないかと思います。

ここに私が見ているものがありますが、私には正しくないようです。 UIからLocalDateTimeを受け取ると、次のようになります。

2016-04-04T08:00

ZonedDateTime =

dateTime=2016-04-04T08:00
offset="Z"
zone="Z"

そして、その変換された値を保存する予定LocalDateTimeに割り当てると、

2016-04-04T08:00

LocalDateTimeに格納しているため、ZonedDateTimeに変換したタイムゾーンが失われているように感じます。

Postgresがその情報を失わないように、エンティティ(予定)にLocalDateTimeではなくZonedDateTimeを使用する必要がありますか?

----------------Edit--------------- -

バジルの優れた答えの後、私はユーザーのタイムゾーンを気にしないという贅沢があることに気付きました-すべての予定は特定の場所に対するものであるため、すべての日付時刻をUTCとして保存し、取得時に場所のタイムゾーンに変換できます。次のフォローアップを行いました question

25
sonoerin

Postgresには、TIMESTAMPなどのデータ型はありません。 Postgresには、日付と時刻を表す 2つのタイプ があります:TIMESTAMP WITH TIME ZONEおよびTIMESTAMP WITHOUT TIME ZONE。これらのタイプは、タイムゾーン情報に関して非常に異なる動作をします。

  • WITH typeは、任意のオフセットまたはタイムゾーン情報を使用して日時を [〜#〜] utc [〜#〜] に調整し、そのオフセットまたはタイムゾーンを破棄します。 Postgresはオフセット/ゾーン情報を保存しません。
    • このタイプは、瞬間、タイムライン上の特定のポイントを表します。
  • WITHOUT typeは、存在する可能性のあるオフセットまたはゾーン情報を無視します。
    • このタイプは、 not は瞬間を表します。これは、 potential 約26-27時間の範囲(世界中のタイムゾーンの範囲)のあいまいな考えを表しています。

ここで説明します エキスパートのDavid E. Wheelerのように、実質的に常にWITH型が必要です。 WITHOUTは、タイムライン上の固定点ではなく、日時のあいまいな考えがある場合にのみ意味があります。たとえば、「今年のクリスマスは2016-12-25T00:00:00に開始されます」は、 any タイムゾーンに適用されるときにWITHOUTに格納されますが、まだ適用されていませんタイムライン上の実際の瞬間を取得する単一のタイムゾーン。サンタのエルフがEugene Oregon USの開始時間を追跡している場合、彼らはWITHタイプと、2016-12-25T00:00:00-08:00ZはZuluまたはUTCを意味する)としてPostgresに保存される2016-12-25T08:00.00Zなどのオフセットまたはタイムゾーンを含む入力を使用します。

Java.timeのPostgresのTIMESTAMP WITHOUT TIME ZONEに相当するものはJava.time.LocalDateTimeです。 UTCで作業すること(良いこと)を意図していたため、 not を使用してLocalDateTime(悪いこと)を使用する必要があります。それが混乱とトラブルの主なポイントかもしれません。 LocalDateTimeまたはZonedDateTimeの使用について考え続けていますが、どちらも使用しないでください。代わりにInstantを使用する必要があります(以下で説明します)。

また、LocalDateTimeからZonedDateTimeへ、またはその逆に移動するため、タイムゾーン情報が失われる可能性があるのではないかと思います。

確かにあなたは。 LocalDateTimeの全体のポイントは lose タイムゾーン情報です。したがって、ほとんどのアプリでこのクラスを使用することはほとんどありません。繰り返しますが、クリスマスの例です。または別の例、「会社の方針:世界中のすべての工場が午後12時30分に昼食を取ります」。それはLocalTimeであり、特定の日付の場合はLocalDateTimeです。ただし、タイムゾーンを適用して ZonedDateTime を取得するまで、タイムライン上の実際のポイントではなく、実際の意味はありません。その昼休みは、デリー工場のタイムライン上のデュッセルドルフ工場とは異なり、デトロイト工場では再び異なります。

LocalDateTimeの「Local」という単語は、 no specific localityを意味するため、直感に反する場合があります。クラス名の「ローカル」を読むときは、「一瞬ではなく、タイムライン上ではなく、ちょっとした日時に関する曖昧なアイデア」と考えてください。

サーバーは、ほとんど常にオペレーティングシステムのタイムゾーンでUTCに設定する必要があります。ただし、システム管理者がそれを変更したり、他のJavaアプリがJVM内の現在のデフォルトタイムゾーンを変更したりするのは非常に簡単なので、プログラミングはこの外部性に決して依存しないでください。したがって、常に希望の/予想されるタイムゾーンを指定してください。 (ちなみに、Localeも同じです。)

結果:

  • あなたは一生懸命働いています。
  • プログラマ/システム管理者は、「グローバルに考え、ローカルに存在する」ことを学ばなければなりません。

オタクのヘルメットを着用した勤務時間中は、UTCで考えてください。普通の人に切り替えた日の終わりに、あなたは自分の町の現地時間について考えることに戻ります。

ビジネスロジックはUTCに焦点を合わせる必要があります。データベースストレージ、ビジネスロジック、データ交換、シリアル化、ロギング、および独自の思考はすべて、UTCタイムゾーン(およびちなみに24時間時計)で行う必要があります。データをユーザーに提示するときは、特定のタイムゾーンを適用するだけです。ゾーン化された日時は、アプリの内部の機能部分ではなく、外部のものと考えてください。

Java側では、多くのビジネスロジックで Java.time.Instant (UTCのタイムライン上の瞬間)を使用します。

Instant now = Instant.now();

うまくいけば [〜#〜] jdbc [〜#〜] ドライバーは最終的に更新され、InstantのようなJava.time型を直接処理できるようになります。それまでは、Java.sql型を使用する必要があります。古いJava.sqlクラスには、Java.timeとの間の変換のための新しいメソッドがあります。

Java.sql.TimeStamp ts = Java.sql.TimeStamp.valueOf( instant );

ここで、 Java.sql.TimeStamp オブジェクトを setTimestampPreparedStatement経由で渡し、PostgresでTIMESTAMP WITH TIME ZONEとして定義された列に保存します。

別の方向に進むには:

Instant instant = ts.toInstant();

したがって、InstantからJava.sql.TimestampTIMESTAMP WITH TIME ZONE、すべてUTCで、簡単に実行できます。 タイムゾーンは含まれません。サーバーOS、JVM、およびクライアントの現在のデフォルトタイムゾーンはすべて無関係です。

ユーザーに提示するには、タイムゾーンを適用します。 適切なタイムゾーン名 を使用します。ESTISTなどの3〜4文字のコードは使用しないでください。

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

必要に応じて別のゾーンに調整できます。

ZonedDateTime zdtKolkata = zdt.withZoneSameInstant( ZoneId.of( "Asia/Kolkata" ) );

UTCのタイムライン上の瞬間、Instantに戻るには、ZonedDateTimeから抽出できます。

Instant instant = zdt.toInstant();

どこで LocalDateTime を使用していません。

2016-04-04T08:00など、UTCからのオフセットやタイムゾーンなしでデータを取得した場合、そのデータはまったく役に立ちません(上記で説明したクリスマスや会社ランチタイプのシナリオについては話していないと仮定します)。オフセット/ゾーン情報のない日時は、通貨を示すことのない金額のようなものです:142.70または$142.70-役に立たない。しかし、USD 142.70、またはCAD 142.70、またはMXN 142.70…これらは便利です。

その2016-04-04T08:00値を取得し、目的のオフセット/ゾーンコンテキストが絶対に確実である場合:

  1. その文字列をLocalDateTimeとして解析します。
  2. UTCからのオフセットを適用してOffsetDateTimeを取得するか、タイムゾーンを適用してZonedDateTimeを取得します。

このコードのように。

LocalDateTime ldt = LocalDateTime.parse( "2016-04-04T08:00" );
ZoneId zoneId = ZoneId.of( "Asia/Kolkata" ); // Or "America/Montreal" etc.
ZonedDateTime zdt = ldt.atZone( zoneId ); // Or atOffset( myZoneOffset ) if only an offset is known rather than a full time zone.

あなたの質問は本当に他の多くの質問の複製です。これらの問題は、他の質問と回答で何度も議論されてきました。このトピックの詳細については、Stack Overflowを検索して調査することをお勧めします。

JDBC 4.2

JDBC 4.2では、 Java.time オブジェクトをデータベースと直接交換できます。 Java.sql.Timestampや関連するクラスを再度使用する必要はありません。

JDBC仕様で定義されている OffsetDateTime を使用した保存。

myPreparedStatement.setObject( … , instant.atOffset( ZoneOffset.UTC ) ) ;  // The JDBC spec requires support for `OffsetDateTime`. 

…またはJDBCドライバでサポートされている場合は、Instantを直接使用することもできます。

myPreparedStatement.setObject( … , instant ) ;  // Your JDBC driver may or may not support `Instant` directly, as it is not required by the JDBC spec. 

取得しています。

OffsetDateTime odt = myResultSet.getObject( … , OffsetDateTime.class ) ;

Java.timeについて

Java.time フレームワークは、Java 8以降に組み込まれています。これらのクラスは、 Java.util.DateCalendar 、& SimpleDateFormat などの厄介な古い legacy 日時クラスに取って代わります。

Joda-Time プロジェクトは、現在 メンテナンスモード であり、 Java.time クラスへの移行を推奨しています。

詳細については、 Oracle Tutorial を参照してください。また、Stack Overflowで多くの例と説明を検索してください。仕様は JSR 31 です。

Java.timeオブジェクトをデータベースと直接交換できます。 JDBC 4.2 以降に準拠する JDBCドライバー を使用します。文字列やJava.sql.*クラスは必要ありません。

Java.timeクラスはどこで入手できますか?

  • Java SE 8Java SE 9 、およびそれ以降
    • ビルトイン。
    • 実装がバンドルされた標準Java AP​​Iの一部。
    • Java 9は、いくつかのマイナーな機能と修正を追加します。
  • Java SE 6 および Java SE 7
    • Java.time機能の多くは、 ThreeTen-Backport のJava 6および7にバックポートされています。
  • Android
    • Androidの後のバージョンは、Java.timeクラスの実装をバンドルします。
    • 以前のAndroid(<26)の場合、 ThreeTenABP プロジェクトは ThreeTen-Backport(前述)。 ThreeTenABPの使用方法… を参照してください。

ThreeTen-Extra プロジェクトは、追加のクラスでJava.timeを拡張します。このプロジェクトは、Java.timeに将来追加される可能性のある証明の場です。ここで、 IntervalYearWeekYearQuartermore などの便利なクラスを見つけることができます。

66
Basil Bourque