日付と時刻を含む文字列を特定の時点(Javaでは「Instant
」と呼びます)に解析するのは非常に複雑です。Javaはこれに何度も取り組んでいます。最新のもの、java.time
およびはjava.time.chrono
、ほぼすべてのニーズをカバーします(時間拡張 :) を除く)。
ただし、その複雑さは多くの混乱をもたらします。
日付の解析を理解するための鍵は次のとおりです。
なぜJavaには日付を解析するための非常に多くの方法があるのですか
- 時間を測定するシステムはいくつかあります。たとえば、歴史的な日本の暦は、それぞれの皇帝または王朝の治世の時間範囲から導き出されました。次に、UNIXタイムスタンプなどがあります。幸いなことに、(ビジネス)世界全体で同じように使用することができました。
- 歴史的に、システムはさまざまな理由で切り替えられていました。たとえば、1582年のユリウス暦からグレゴリオ暦までです。そのため、それより前の「西洋」の日付は異なる方法で処理する必要があります。
- そしてもちろん、変化はすぐには起こりませんでした。一部の宗教の本部や他の食生活を信じるヨーロッパの他の地域からカレンダーが来たため、たとえばドイツは1700年まで切り替わりませんでした。
...そしてなぜLocalDateTime
、ZonedDateTime
他は とても複雑
タイムゾーンがあります。タイムゾーンは基本的には、地球の表面の「ストライプ」* [1]であり、その当局は、どの時間オフセットがあるかについて同じ規則に従います。これには夏時間のルールが含まれます。
時間帯は、主に誰が誰を征服したかに基づいて、さまざまな地域で時間とともに変化します。また、1つのタイムゾーンのルールも時間とともに変化します。
時間オフセットがあります。タイムゾーンはたとえば「プラハ」の可能性があるため、これはタイムゾーンと同じではありませんが、夏時間のオフセットと冬時間のオフセットがあります。
タイムゾーン付きのタイムスタンプを取得する場合、オフセットは年のどの部分にあるかによって異なります。うるう時の間、タイムスタンプは2つの異なる時間を意味する可能性があるため、追加の情報がないと確実に信頼できません。変換されました。
注:タイムスタンプとは、「オプションでタイムゾーンやタイムオフセットを含む、日付や時刻を含む文字列」を意味します。
いくつかのタイムゾーンは、特定の期間の同じ時間オフセットを共有する場合があります。たとえば、サマータイムオフセットが有効でない場合、GMT / UTCタイムゾーンは「ロンドン」タイムゾーンと同じです。
もう少し複雑にするには(ただし、ユースケースではそれほど重要ではありません)。
- 科学者たちは、時間とともに変化する地球のダイナミックを観察します。それに基づいて、彼らは個々の年の終わりに秒を追加します。(したがって
2040-12-31 24:00:00
、有効な日時である可能性があります。)これには、日付変換を正しく行うためにシステムが使用するメタデータの定期的な更新が必要です。たとえばLinuxでは、これらの新しいデータを含むJavaパッケージが定期的に更新されます。
更新では、履歴と将来の両方のタイムスタンプの以前の動作が常に維持されるわけではありません。そのため、いくつかのタイムゾーンの変更の前後の2つのタイムスタンプを比較すると、異なるバージョンのソフトウェアで実行すると、異なる結果が得られる場合があります。これは、影響を受けるタイムゾーンと他のタイムゾーンとの比較にも適用されます。
これによりソフトウェアにバグが発生する場合は、UNIXタイムスタンプのような複雑なルールがないタイムスタンプの使用を検討してください。
7のため、将来の日付については、確実に日付を正確に変換することはできません。したがって、たとえば、の現在の解析は8524-02-17 12:00:00
、将来の解析から数秒ずれる可能性があります。
このためのJDKのAPIは、現代のニーズとともに進化しました
- 初期のJavaリリースには
java.util.Date
、年、月、日、時間しかないという前提で、どちらかというと単純な方法がありました。これはすぐには不十分でした。
- また、データベースのニーズは異なっていたため、かなり早い段階で
java.sql.Date
導入されましたが、独自の制限があります。
- どちらも異なるカレンダーとタイムゾーンをうまくカバーしていないため、
Calendar
APIが導入されました。
- これはまだタイムゾーンの複雑さをカバーしていませんでした。それでも、上記のAPIを組み合わせるのは本当に大変な作業でした。そのため、Java開発者がグローバルなWebアプリケーションに取り組み始めたとき、JodaTimeなどのほとんどのユースケースを対象としたライブラリが急速に普及しました。JodaTimeは、約10年間、事実上の標準でした。
- しかし、JDKはJodaTimeと統合していなかったため、JDKでの作業は少し面倒でした。したがって、この問題への取り組み方について非常に長い議論の末、JSR-310は主にJodaTimeに基づいて作成されました。
Javaでそれを処理する方法 java.time
タイムスタンプを解析するタイプを決定します
タイムスタンプ文字列を使用する場合は、それに含まれる情報を知る必要があります。これが決定的なポイントです。これを正しく行わないと、「インスタントを作成できません」、「ゾーンオフセットが見つかりません」、「不明なゾーンID」などの不可解な例外が発生します。
日付と時刻が含まれていますか?
時間オフセットはありますか?
時間オフセットは+hh:mm
部品です。時には、「ズールー時間」、協定世界時、またはグリニッジ標準時として+00:00
置き換えられる場合があります。これらはタイムゾーンも設定します。
これらのタイムスタンプには、を使用します。Z
UTC
GMT
OffsetDateTime
タイムゾーンはありますか?
これらのタイムスタンプには、を使用しますZonedDateTime
。
ゾーンは、
- 名前(「プラハ」、「太平洋標準時」、「PST」)、または
- で表される"ゾーンID"( "アメリカ/ Los_Angeles"、 "ヨーロッパ/ロンドン")、java.time.ZoneId。
タイムゾーンのリストは、ICAANが支援する「TZデータベース」によって編集されます。
ZoneId
のjavadocによると、ゾーンIDもZ
オフセットとして指定できます。これが実際のゾーンにどのようにマッピングされるかはわかりません。TZしかないタイムスタンプがうるう時の時間オフセット変更に該当する場合は、あいまいであり、解釈の対象となりResolverStyle
ます。以下を参照してください。
どちらもない場合、不足しているコンテキストが想定されるか無視されます。そして消費者は決定しなければなりません。したがって、それを解析し、不足している情報を追加してLocalDateTime
変換する必要がありますOffsetDateTime
。
- UTC時間であると想定できます。0時間のUTCオフセットを追加します。
- それは変換が起こっている場所の時であると想定することができます。システムのタイムゾーンを追加して変換します。
- 無視してそのまま使用できます。これは、たとえば2回比較または減算する場合(参考文献を参照
Duration
)、または知らない場合に本当に重要ではない場合(ローカルバスのスケジュールなど)に役立ちます。
部分的な時間情報
- タイムスタンプが含まれているものに基づいて、あなたが取ることができ
LocalDate
、LocalTime
、OffsetTime
、MonthDay
、Year
、またはYearMonth
それから。
完全な情報がある場合は、を取得できますjava.time.Instant
。これは内部で使用され、OffsetDateTime
とのZonedDateTime
ます。
それを解析する方法を理解する
に関する広範なドキュメントがあります DateTimeFormatter
タイムスタンプ文字列の解析と文字列へのフォーマットの両方が可能あります。
事前に作成されたDateTimeFormatter
のは、詳細表示を隠すすべての標準的なタイムスタンプのフォーマットをカバーする必要があります。たとえば、ISO_INSTANT
解析できます2011-12-03T10:15:30.123457Z
。
特別なフォーマットがある場合は、独自のDateTimeFormatter(これもパーサーです)を作成できます。
private static final DateTimeFormatter TIMESTAMP_PARSER = new DateTimeFormatterBuilder()
.parseCaseInsensitive()
.append(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SX"))
.toFormatter();
のソースコードを見て、DateTimeFormatter
を使用してビルドする方法を学ぶことをお勧めしますDateTimeFormatterBuilder
。そこにいる間ResolverStyle
、パーサーが形式とあいまいな情報に関してLENIENT、SMART、またはSTRICTのどれであるかを制御する方法も見てください。
TemporalAccessor
さて、よくある間違いは、の複雑さに陥ることですTemporalAccessor
。これは、開発者がで作業する方法に由来しますSimpleDateFormatter.parse(String)
。そう、DateTimeFormatter.parse("...")
あなたに与えますTemporalAccessor
。
// No need for this!
TemporalAccessor ta = TIMESTAMP_PARSER.parse("2011-... etc");
しかし、前のセクションの知識を備えていれば、必要なタイプに簡単に解析できます。
OffsetDateTime myTimestamp = OffsetDateTime.parse("2011-12-03T10:15:30.123457Z", TIMESTAMP_PARSER);
実際にはDateTimeFormatter
どちらも必要ありません。解析する型にはparse(String)
メソッドがあります。
OffsetDateTime myTimestamp = OffsetDateTime.parse("2011-12-03T10:15:30.123457Z");
に関してTemporalAccessor
は、文字列にどのような情報が含まれているかが曖昧で、実行時に決定したい場合に使用できます。
私はあなたの魂にいくつかの理解の光を当てることを望みます:)
注:java.time
Java 6および7へのバックポート:ThreeTen-Backportがあります。Androidの場合、ThreeTenABPがあります。
[1]それらは縞模様ではないというだけでなく、いくつかの奇妙な極端なものもあります。たとえば、一部の隣接する太平洋諸島には、+ 14:00と-11:00のタイムゾーンがあります。つまり、ある島では5月1日の午後3時があり、別の島ではそれほど遠くない場合でも、4月30日は午後12時のままです(正しくカウントした場合:))。
ZonedDateTime
場合、ではなくを望んでいLocalDateTime
ます。名前は直感に反しています。これLocal
は、特定のタイムゾーンではなく、一般的に地域を意味します。そのため、LocalDateTime
オブジェクトはタイムラインに関連付けられていません。意味を持たせるには、タイムラインで特定の瞬間を取得するには、タイムゾーンを適用する必要があります。