Javaと時間:パート2

この記事は第1部で続き、Java 8で導入された新しいDate Time APIに専念します。このトピックは非常に大きく深刻なので、最初はこのトピックを別のものにしたかったのです。 私自身はプロジェクトでこのAPIの使用を完全には開始していないので、一緒に対処します。 原則として、新しいAPIに切り替える緊急の必要性はありません。また、多くはJava 8でプロジェクトをまだ開始していないため、開発の時間が残っています。



記事では、スタッフのドキュメンテーションの平凡な翻訳に手を出さないようにしますが、特に重要だと思われることにもっと集中したいと思います。



物語



時間の処理に関しては、長い間、標準Javaライブラリに関する苦情がありました。 批判されたバージョンのAPIは、かなり前に開発されたもので、その設計に重大な誤りがありました。 別の方法として、多くはサードパーティのJoda-timeライブラリを使用しました。 私自身は、いくつかの理由により、Joda-timeの大ファンではありません。



比較



おそらく、古いAPIの多くに合わなかったものから始める価値があります。 そして、時間を無駄にしないために、新しいAPIが改善されたことをすぐに示します。



クラスをパッケージに分類:





クラス名:





不変性とスレッドの安全性:





精度:





時刻と日付のスタンプの保存:





タイムゾーン表示:





テスト:





月の番号付け:





タグ付け:





期間指定:



恐怖



また、「劣化」とは絶対に呼べないものについて話す価値があるかもしれませんが、慎重に「恐怖」と呼んでいます。





これらの事例や、他の事例で私に警告を発した事例について詳しく説明します。



タイムゾーン



通常どおりタイムゾーンから始めましょう。 新しいjava.time.ZoneIdクラスはタイムゾーンを指定します。 2つのサブクラスjava.time.ZoneRegionおよびjava.time.ZoneOffsetは、2つのタイプのタイムゾーンを実装します。地理的タイムゾーンと、UTC、UTまたはGMTを基準とした単純なオフセットを持つタイムゾーンです。 矢印を変換するためのルールは、別個のクラスjava.time.zone.ZoneRulesで作成され、そのインスタンスはjava.time.ZoneId#getRulesメソッドを介して利用できます。



一般に、示されたリファクタリングを除いて、ここで重要な基本的な変更は見つかりませんでした。ただし、矢印を変換するためのルールが情報を要求するためのより多くのメソッドを提供することを除きます。 したがって、 古い記事で書かれたものはすべて、新しいクラスのタイムゾーンにも当てはまりますが、メソッドの名前が若干異なる点が異なります。



  @Test public void testZoneId() throws Exception { // case-1 ZoneId zid1 = ZoneId.of("Europe/Moscow"); Assert.assertEquals("ZoneRegion", zid1.getClass().getSimpleName()); // case-2 ZoneId zid2 = ZoneId.of("UTC+4"); Assert.assertEquals("ZoneRegion", zid2.getClass().getSimpleName()); // case-3 ZoneId zid3 = ZoneId.of("+03:00:00"); Assert.assertEquals("ZoneOffset", zid3.getClass().getSimpleName()); // case-4 ZoneId zid4 = ZoneId.ofOffset("UTC", ZoneOffset.of("+03:00:00")); Assert.assertEquals("ZoneRegion", zid4.getClass().getSimpleName()); }
      
      







実際にケース3と同じを要求するケース4が、結果としてjava.time.ZoneOffsetではなくjava.time.ZoneRegionを作成する理由はあまり明確ではありません。



  @Test public void testZoneUTC() throws Exception { ZoneId zid1 = ZoneOffset.UTC; Assert.assertEquals("ZoneOffset", zid1.getClass().getSimpleName()); ZoneId zid2 = ZoneId.of("Z"); Assert.assertEquals("ZoneOffset", zid2.getClass().getSimpleName()); Assert.assertSame(ZoneOffset.UTC, zid2); ZoneId zid3 = ZoneId.of("UTC"); Assert.assertEquals("ZoneRegion", zid3.getClass().getSimpleName()); }
      
      







UTCタイムゾーンに対して特別な定数java.time.ZoneOffset#UTCが設定されていますが、それにもかかわらず、新しいAPIのZoneId.of( "UTC")の要求は、この定数ではなくjava.util.ZoneRegionクラスのオブジェクトを既に発行しています。



時計



時間は時計です 」-一部の物理学者が主張するように。 そして、そのフレーズは、 java.time.Clockクラスが基盤となる新しいAPIの鍵となります。 時計の一部と同様に、時間は次のようになります:一定(非アクティブ)、遅い、さまざまな精度で実行、異なるタイムゾーンで矢印を動かす 一般に、新しいAPIでは、テストのテストを含め、ほぼすべての時間を使用(または自分で定義)できます。



標準のjava.time.Clockインスタンスは、ファクトリの静的メソッドによってのみ作成できます(クラス自体は抽象クラスです)。



java.time.Clockの標準インスタンスは、作成されたタイムゾーンを常に認識しています(これも不要です)。



ファクトリメソッドを見てみましょう。





java.time.Clockをオーバーライドして、時間を発行するための独自のロジックを作成できます。たとえば、リクエストごとにランダムな時間を提供するクロックなどです。



java.time.Clockオブジェクトには、次の3つの作業メソッドのみがあります。





今少し批判:





インスタント



java.time.Instantは新しいjava.util.Dateであり、ナノ秒の精度と正しい名前でのみ不変です。 内部では、 Unix時間を2つのフィールドの形式で保存します。長さは秒数で、整数は現在の秒内のナノ秒数です。



両方のフィールドの値は直接要求することができ、古いAPIのミリ秒のより馴染みのあるUnix時間表現を計算するように依頼することもできます。

  @Test public void testInstantFields() throws Exception { Instant instant = Clock.systemDefaultZone().instant(); System.out.println(instant.getEpochSecond()); System.out.println(instant.getNano()); System.out.println(instant.toEpochMilli()); }
      
      







java.util.Dateと同様に(正しく使用した場合)、java.time.Instantクラスオブジェクトはタイムゾーンについて何も知りません。



それとは別に、java.time.Instant.toString()メソッドに言及する価値があります。 以前のjava.util.Date.toString()が現在のロケールとデフォルトのタイムゾーンを考慮して機能する場合、新しいjava.time.Instant.toString()は常にUTCタイムゾーンと同じISO-8601形式でテキスト表現を生成します-これが適用されますデバッグ時に変数をIDEに出力する:

  @Test public void testInstantString() throws Exception { Instant instant1 = Clock.system(ZoneId.of("Europe/Paris")).instant(); System.out.println(instant1.toString()); Instant instant2 = Clock.systemUTC().instant(); System.out.println(instant2.toString()); Instant instant3 = Clock.systemDefaultZone().instant(); System.out.println(instant3.toString()); }
      
      





 2016-01-06T15:22:53.403Z
 2016-01-06T15:22:53.417Z
 2016-01-06T15:22:53.423Z




基本的なインターフェース



基本インターフェースjava.time.temporal.TemporalAccessorを見てみましょう。 TemporalAccessorインターフェースは、現在のポイントまたはラベルに関する個別の部分情報を要求するための参照であり、新しいAPIのすべての一時クラスによって実装されます。



java.time.InstantからUnix-time値を聞いてみましょう。

  @Test(expected = DateTimeException.class) public void testTemporalAccessor2() throws Exception { TemporalAccessor ta = Clock.systemUTC().instant(); // java.time.DateTimeException: Invalid value for InstantSeconds \ // (valid values -9223372036854775808 - 9223372036854775807): 1451983908 System.out.println(ta.get(ChronoField.INSTANT_SECONDS)); }
      
      







完全に説明できないメッセージで例外が発生します。

 java.time.DateTimeException:InstantSecondsの無効な値\
                          (有効な値-9223372036854775808-9223372036854775807):1451983908




少しのお金を稼いだことで、例外の理由が明らかになりました:結果は理論的にはintの範囲に収まらないかもしれません(現在のところ)。 INSTANT_SECONDSフィールドは、長い間リクエストする必要があります。 リクエストを修正し、途中で追加のメタ情報をリクエストします:

  @Test public void testTemporalAccessor3() throws Exception { TemporalAccessor ta = Clock.systemUTC().instant(); System.out.println(ta.getLong(ChronoField.INSTANT_SECONDS)); ValueRange vr = ta.range(ChronoField.INSTANT_SECONDS); System.out.println(vr.getMinimum()); System.out.println(vr.getMaximum()); System.out.println(ta.isSupported(ChronoField.INSTANT_SECONDS)); System.out.println(ta.isSupported(ChronoField.CLOCK_HOUR_OF_DAY)); }
      
      





 1452094053
 -9223372036854775808
 9223372036854775807
本当
偽




CLOCK_HOUR_OF_DAYフィールドは、インスタントタイプではサポートされていません。 これは完全に予想されることです。ある時点で時間を見つけるために、java.time.Instantにないタイムゾーンを指定する必要があるからです。 この値をすべて同じように要求してみましょう。

  @Test(expected = UnsupportedTemporalTypeException.class) public void testTemporalAccessor1() throws Exception { TemporalAccessor ta = Clock.systemUTC().instant(); // java.time.temporal.UnsupportedTemporalTypeException: Unsupported field: ClockHourOfDay System.out.println(ta.getLong(ChronoField.CLOCK_HOUR_OF_DAY)); }
      
      







すべてが正しい-1日の1時間をリクエストすると、例外が発生します。 リクエストメソッドがデフォルトのタイムゾーンを使用しなかったのは素晴らしいことです(新しいAPIにはありません)。



個々のフィールドのクエリに加えて、 java.time.TemporalQueryインターフェースを継承するより複雑なアルゴリズム戦略を使用して値をクエリできます。

  @Test public void testTemporalAccessor4() throws Exception { TemporalAccessor ta = Clock.systemUTC().instant(); ZoneId zoneId1 = ta.query(TemporalQueries.zone()); ZoneId zoneId2 = TemporalQueries.zone().queryFrom(ta); Assert.assertEquals(zoneId1, zoneId2); TemporalUnit unit1 = ta.query(TemporalQueries.precision()); TemporalUnit unit2 = TemporalQueries.precision().queryFrom(ta); Assert.assertEquals(unit1, unit2); }
      
      







java.time.temporal.Temporal-インターフェースはTemporalAccessorインターフェースの子孫です。 時刻/マークを前後に移動する操作、時刻情報の一部を置き換える操作、および別の時刻/マークまでの距離を計算する操作を紹介します。 これは、新しいAPIのほぼすべての「フルタイム」クラスによって実装されます。



ラベルを1日前にシフトして、差を計算しようとします。

  @Test public void testTemporal1() throws Exception { Temporal t1 = Clock.systemUTC().instant(); Temporal t2 = t1.plus(1, ChronoUnit.DAYS); Assert.assertEquals(Duration.ofDays(1).getSeconds(), t2.getLong(ChronoField.INSTANT_SECONDS) - t1.getLong(ChronoField.INSTANT_SECONDS)); Assert.assertEquals(24, t1.until(t2, ChronoUnit.HOURS)); Assert.assertEquals(24, Duration.between(t1, t2).get(ChronoUnit.HOURS)); }
      
      







すべてのクラスが最終的に不変になったため、操作中に元の変数が変更されないため、操作の結果を別の変数に割り当てることを忘れないでください。すべてがjava.lang.Stringまたはjava.math.BigDecimalと同じです。



java.time.Instantで時刻を変更してみましょう。

  @Test(expected = UnsupportedTemporalTypeException.class) public void testTemporal2() throws Exception { Temporal t = Clock.systemUTC().instant(); // java.time.temporal.UnsupportedTemporalTypeException: Unsupported field: HourOfDay t.with(ChronoField.HOUR_OF_DAY, 2); }
      
      







この操作にはタイムゾーンが必要なので、私たちはそれを手に入れることが期待されています。



java.time.temporal.TemporalAdjuster-時点/ラベル修正戦略のインターフェース。たとえば、現在のコードの初日に移動します。 以前は、java.util.Calendarフィールドを操作するための独自のヘルパークラスを作成する必要がありました。必要なコードがまだ標準配信にない場合、すべてのコードを戦略の形式で作成できるようになりました。

  @Test public void testTemporalAdjuster() throws Exception { ZonedDateTime zdt = ZonedDateTime.of(2005, 10, 30, 0, 0, 0, 0, ZoneId.of("Europe/Moscow")); ZonedDateTime zdt1 = zdt.with(TemporalAdjusters.firstDayOfYear()); ZonedDateTime zdt2 = (ZonedDateTime) TemporalAdjusters.firstDayOfYear().adjustInto(zdt); Assert.assertEquals(zdt1, zdt2); Assert.assertEquals(2005, zdt1.get(ChronoField.YEAR)); Assert.assertEquals(1, zdt1.get(ChronoField.MONTH_OF_YEAR)); Assert.assertEquals(1, zdt1.get(ChronoField.DAY_OF_MONTH)); }
      
      







これで一時クラスに行くことができます。



LocalTime、LocalDate、LocalDateTime



java.time.LocalTimeはタプル(時間、分、秒、ナノ秒)です

java.time.LocalDateはタプル(年、月、日)です

java.time.LocalDateTime-両方のタプルを一緒に



これらのクラスには、情報の一部を格納するための特定のクラスjava.time.MonthDayjava.time.Yearjava.time.YearMonth参照し ます



これらのクラスはすべて、タイムスタンプまたはその部分が含まれているという事実によって結合されていますが、タイムゾーンまたはオフセットさえ持っていないため、それらだけでは(LocalDateTimeでさえ)時間軸上のタイムポイントを決定できません。



これらのクラスは、他のすべてのクラスと同様にjava.lang.Comparableインターフェースをサポートしますが、これはタイムポイントではなくタイムスタンプの比較であることを理解する必要があります。

  @Test public void testLocalDateTime() throws Exception { ZonedDateTime zdt1 = ZonedDateTime.of(2015, 1, 10, 15, 0, 0, 0, ZoneId.of("Europe/Moscow")); ZonedDateTime zdt2 = ZonedDateTime.of(2015, 1, 10, 14, 0, 0, 0, ZoneId.of("Europe/London")); Assert.assertEquals(-1, zdt1.compareTo(zdt2)); LocalDateTime ldt1 = zdt1.toLocalDateTime(); LocalDateTime ldt2 = zdt2.toLocalDateTime(); Assert.assertEquals(+1, ldt1.compareTo(ldt2)); }
      
      







java.time.LocalTimeとjava.sql.Timeの間、およびjava.time.LocalDateとjava.sql.Dateの間では避けられない類似性があるにもかかわらず、これらは完全に異なるクラスであると言わなければなりません。 古いAPIでは、クラスjava.sql.Timeとjava.sql.Dateはjava.util.Dateの継承者です。つまり、それらの解釈(たとえば、時間の値を取得する)は、このクラスのオブジェクトが作成されたタイムゾーンと時間に依存します。このオブジェクトが読み取られるゾーン。 新しいAPIでは、クラスjava.time.LocalTimeおよびjava.time.LocalDateは値の正直なタプルであり、時間帯は時間の値の書き込みおよび読み取りに関与しません。



ただし、時間帯の解釈は時間に依存するため、ある時点から作成する場合はタイムゾーンが必要です。

  @Test(expected = DateTimeException.class) public void testLocalDateTimeCreate1() throws Exception { Clock clock = Clock.system(ZoneId.of("Europe/Moscow")); // java.time.DateTimeException: Unable to obtain LocalDateTime \ // from TemporalAccessor: 2016-01-11T15:15:03.180Z of type java.time.Instant LocalDateTime ldt = LocalDateTime.from(clock.instant()); }
      
      







タイムゾーンを取得する場所がないという事実のために例外がスローされます(インスタントではそうではありませんが、デフォルトではゾーンを取得しません)。 ただし、java.time.Clockクロックから取得するか、さらに転送することができます。

  @Test public void testLocalDateTimeCreate2() throws Exception { Clock clock = Clock.system(ZoneId.of("Europe/Moscow")); LocalDateTime ldt1 = LocalDateTime.ofInstant(clock.instant(), ZoneId.of("UTC")); System.out.println(ldt1); LocalDateTime ldt2 = LocalDateTime.now(clock); System.out.println(ldt2); }
      
      







これですべてが機能するようになりましたが、間違いを犯しやすいのは少し不安です。



前の記事へのコメントで、彼らは本当の偏執病はカレンダー値での操作の際にカレンダーをも示すべきであると述べました(インスタントを除くすべての時間クラスのオブジェクトの作成を含む)。 新しいAPIには、年表と呼ばれるいくつかのカレンダーがあります。

  @Test public void testChronology() throws Exception { Clock clock = Clock.system(ZoneId.of("Europe/Moscow")); ZonedDateTime zdt = ZonedDateTime.now(clock); ChronoLocalDateTime dt1 = IsoChronology.INSTANCE.localDateTime(zdt); System.out.println(dt1); // 2016-01-11T18:48:15.145 ChronoLocalDateTime dt2 = JapaneseChronology.INSTANCE.localDateTime(zdt); System.out.println(dt2); // Japanese Heisei 28-01-11T18:48:15.145 ChronoLocalDateTime dt3 = ThaiBuddhistChronology.INSTANCE.localDateTime(zdt); System.out.println(dt3); // ThaiBuddhist BE 2559-01-11T18:48:15.145 }
      
      







一般に、ISO-8601(グレゴリオ暦とほぼ同等)以外のIsoChronology年表が必要なる場合を想像するのは困難ですが、新しいAPIはこれをサポートします。



ZonedDateTime



java.time.ZonedDateTime -java.util.Calendarの類似物。これは、タイムゾーンを含む時間コンテキストに関する完全な情報を持つ最も強力なクラスです。したがって、このクラスは、シフトを含むすべての操作を正しく実行します。



LocalDateTimeからZonedDateTimeを作成してみましょう。

  @Test(expected = DateTimeException.class) public void testZoned1() throws Exception { LocalDateTime ldt = LocalDateTime.of(2015, 1, 10, 0, 0, 0, 0); // java.time.DateTimeException: Unable to obtain ZonedDateTime from TemporalAccessor: 2015-01-10T00:00 of type java.time.LocalDateTime ZonedDateTime zdt = ZonedDateTime.from(ldt); }
      
      







操作(LocalDateTime内)にタイムゾーンがなく、新しいAPIが再びデフォルトのタイムゾーンの使用を拒否するという事実をすぐに理解できます(これは非常に良いことです)。



正しいオプション:

  @Test public void testZoned2() throws Exception { LocalDateTime ldt = LocalDateTime.of(2015, 1, 10, 0, 0, 0, 0); ZonedDateTime zdt = ZonedDateTime.of(ldt, ZoneId.of("Europe/Moscow")); }
      
      







誤って指定された日付に関して、ZonedDateTimeがどれほど厳密かを見てみましょう。java.util.Calendarには寛容なスイッチがあり、「厳格」モードと「ソフト」モードの両方に設定できます。新しいAPIにはそのような切り替えはありません。



うるう年ではない2月29日は過ぎません。

  @Test(expected = DateTimeException.class) public void testLenient2() throws Exception { // java.time.DateTimeException: Invalid date 'February 29' as '2005' is not a leap year ZonedDateTime.of(2005, 2, 29, 2, 30, 0, 0, ZoneId.of("Europe/Moscow")); }
      
      







60秒は指定できません。

  @Test(expected = DateTimeException.class) public void testLenient3() throws Exception { // java.time.DateTimeException: Invalid value for SecondOfMinute (valid values 0 - 59): 60 ZonedDateTime.of(2005, 2, 20, 2, 30, 60, 0, ZoneId.of("Europe/Moscow")); }
      
      







しかし、夏時間に針が切り替わった時点でのラベルの表示は正常に経過し、結果は予想とは異なります。strictモードでは、java.util.Calendarはこれを見逃していません(前の記事を参照)。

  @Test public void testLenient1() throws Exception { ZonedDateTime zdt = ZonedDateTime.of(2005, 3, 27, 2, 30, 0, 0, ZoneId.of("Europe/Moscow")); Assert.assertEquals(3, zdt.getLong(ChronoField.HOUR_OF_DAY)); Assert.assertEquals(30, zdt.getLong(ChronoField.MINUTE_OF_HOUR)); }
      
      







ZonedDateTimeの操作については何も書きません。ドキュメントをご覧ください。



OffsetTime、OffsetDateTime



java.time.OffsetTimeはLocalTime + ZoneOffsetです

java.time.OffsetDateTimeはLocalDateTime + ZoneOffset



オフセットがタイムゾーンではない(タイムゾーンはオフセットの履歴であり、さらに多くの情報がある)ので、OffsetDateTimeも少ないZonedDateTimeよりも情報。 OffsetDateTimeは時間軸上の時点を完全に示すことができますが、このクラスは矢印の将来および過去の転送について何も知らないため、完全に正しいシフトを生成することはできません。



現在のユーザーオフセットのみが状況によって認識されている場合(たとえば、JavaScriptを介して、これらのクラスを使用できます。完全に正しいシフト操作は許可されていないため、ユーザーのフルタイムゾーンを確認する方法がある場合は、ZonedDateTimeを使用することをお勧めします。一方、OffsetDateTimeの2つのインスタンス間では、常に秒単位の差を正常かつ正確に計算できます。



時間変更



新しいAPIのすべてのクラスのうち、時間軸上の時点を一意に決定するのは、java.time.Instant、java.time.ZonedDateTime、およびjava.time.OffsetTimeの3つだけです。



タイムゾーンについて知っているのは彼だけであるため、シフト操作と時刻変更操作は通常java.time.ZonedDateTimeでのみ正しく実行されます。



手を冬時間に切り替えた日の経過時間を計算して例を実行してみましょう。

  @Test public void testWinterDay() throws Exception { ZonedDateTime zdt1 = ZonedDateTime.of(2005, 10, 30, 0, 0, 0, 0, ZoneId.of("Europe/Moscow")); // case #1 - ok ZonedDateTime zdt2 = zdt1.plusDays(1); Assert.assertEquals(25, Duration.between(zdt1, zdt2).toHours()); // case #2 - ok ZonedDateTime zdt3 = zdt1.plus(1, ChronoUnit.DAYS); Assert.assertEquals(25, Duration.between(zdt1, zdt3).toHours()); // case #3 - ok OffsetDateTime odt1 = zdt1.toOffsetDateTime(); OffsetDateTime odt2 = zdt2.toOffsetDateTime(); Assert.assertEquals(25, Duration.between(odt1, odt2).toHours()); // case #4 - ??? OffsetDateTime odt3 = zdt1.toOffsetDateTime(); OffsetDateTime odt4 = odt3.plus(1, ChronoUnit.DAYS); Assert.assertEquals(24, Duration.between(odt3, odt4).toHours()); // case #5 - ok Instant instant1 = Instant.from(zdt1); Instant instant2 = Instant.from(zdt2); Assert.assertEquals(25, Duration.between(instant1, instant2).toHours()); // case #6 - ??? Instant instant3 = Instant.from(zdt1); Instant instant4 = instant3.plus(1, ChronoUnit.DAYS); Assert.assertEquals(24, Duration.between(instant3, instant4).toHours()); // case #7 - ??? LocalDateTime localDateTime1 = LocalDateTime.from(zdt1); LocalDateTime localDateTime2 = localDateTime1.plus(1, ChronoUnit.DAYS); Assert.assertEquals(24, Duration.between(localDateTime1, localDateTime2).toHours()); // case #8 - ??? LocalDateTime localDateTime3 = LocalDateTime.from(zdt1); LocalDateTime localDateTime4 = LocalDateTime.from(zdt2); Assert.assertEquals(24, Duration.between(localDateTime3, localDateTime4).toHours()); }
      
      







ケースケース#1とケース#2は本格的なZonedDateTimeクラスで実行され、正しい結果を提供します。この日には矢印が戻ったため、結果として25時間になります。



ケースケース#3は、OffsetDateTimeが時間軸上のポイントに関する情報を完全に格納することを示していますが、ケース#4は、タイムゾーンが失われると、このクラスが異なる計算を実行することを示します。



ケース#5とケース#6でも同じです-Instantは時間軸上のポイントを完全に定義しているにもかかわらず、タイムゾーンなしで計算を実行します。



ケースケース#7およびケース#8-LocalDateTimeが時点を完全に反映せず、タイムゾーンなしで計算を実行できないことを示します。



これらの例が新しいAPIのエラーを示しているとは決して言いたくない(誰かがそう思った場合)。すべての効果は期待され、説明可能です。別のことは緊張します-この振る舞いはJava開発者の軍隊によってどれほど実現されるでしょうか。古いAPIでは、1つのjava.util.Calendarクラスのみがすべての計算に関係していたため、このような潜在的な問題は不可能でした。



おそらく、ZonedDateTimeを除くすべてのクラスで時間の経過とともにほとんどの操作を禁止する価値があったのは、矢印の翻訳の過程で彼だけだからです。タイムゾーンがないと時刻を定義しないため、LocalDateTimeを使用したDurationの計算を禁止する価値があったのでしょう。このようなソリューションの可能性や不可能性について、どういうわけか真剣に議論する準備はできていませんが、新しいAPIには危険を感じています。



期間、期間



新しいAPIには、期間を決定するための2つのクラスがあります。



java.time.Period-タプル(年、月、日)の形式でのカレンダー期間(期間)の説明。



java.time.Duration-正確な継続時間の説明(整数秒)およびナノ秒形式の現在の秒の小数部。



2つの違いは、例が冬時間に切り替えられた日の例に示されています。針が戻るため、この暦日は25時間です。

  @Test public void testDuration() throws Exception { Period period = Period.of(0, 0, 1); Duration duration = Duration.of(1, ChronoUnit.DAYS); ZonedDateTime zdt1 = ZonedDateTime.of(2005, 10, 30, 0, 0, 0, 0, ZoneId.of("Europe/Moscow")); ZonedDateTime ztd2 = zdt1.plus(period); Assert.assertEquals(ZonedDateTime.of(2005, 10, 31, 0, 0, 0, 0, ZoneId.of("Europe/Moscow")), ztd2); ZonedDateTime ztd3 = zdt1.plus(duration); Assert.assertEquals(ZonedDateTime.of(2005, 10, 30, 23, 0, 0, 0, ZoneId.of("Europe/Moscow")), ztd3); }
      
      







Period.of(0、0、1)を追加すると、次の暦日に正しく移動します。Duration.of(1、ChronoUnit.DAYS)を追加する場合、実際には24時間を追加し、次の暦日に移動しません。



フォーマットと解析



古いAPIでは、java.text.SimpleDateFormatがスレッドセーフではないことは常に驚きでした。SimpleDateFormatは状態を保存する必要がないようであるため、直感的なスレッドセーフが期待されていました。



新しいAPIはこの問題を解決しました。



java.time.format.DateTimeFormatter-クラスは書式設定と解析設定を定義します。



  @Test public void testFormat() throws Exception { DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:dd z", Locale.ENGLISH); ZonedDateTime zdt1 = ZonedDateTime.of(2005, 10, 30, 0, 0, 0, 0, ZoneId.of("Europe/Moscow")); String text = zdt1.format(formatter); System.out.println(text); TemporalAccessor ta = formatter.parse(text); // java.time.format.Parsed ZonedDateTime zdt2 = ZonedDateTime.from(ta); Assert.assertEquals(zdt1, zdt2); }
      
      







JavaDocを見ると、新しいAPIにフォーマットのオプションが追加されていることがわかります。解析が特定の時間クラスを返さず、java.time.Temporal(実装としてjava.time.format.Parsed)を抽象化し、それから、スペアパーツのバッグから、クラスのオブジェクトを組み立てることができることも興味深い必要です。



クラス図



新しいAPIのクラス図を示します。いくつかのマイナークラス、およびjava.util.Serializableやjava.lang.Comparableなどのインターフェイスの実装は提供されていません。



基本的なインターフェース






時代






タイムゾーン






期間と期間






タイムライン








一時クラス








互換性



古いAPIと新しいAPIの間で情報を交換するために、いくつかのメソッドが実装されています。さらに、十分な能力で実装されています。古いAPIは新しいAPIを知っていますが、新しいAPIは古いAPIについて何も知りません。純粋に理論的には、これによりいつかすべての古いクラスを破棄することができますが、これは私たちの生涯で起こるとは思いません。







  @Test public void testTimeZoneCompat() throws Exception { ZoneId zoneId1 = ZoneId.of("Europe/Moscow"); TimeZone timeZone = TimeZone.getTimeZone(zoneId1); ZoneId zoneId2 = timeZone.toZoneId(); Assert.assertEquals(zoneId1, zoneId2); } @Test public void testDateCompat() throws Exception { Instant instant1 = Clock.systemUTC().instant(); Date date = Date.from(instant1); Instant instant2 = date.toInstant(); Assert.assertEquals(instant1, instant2); }
      
      







繰り返しますが、ニュアンスがあります。java.util.Dateで時刻を駆動する場合、またはその逆の場合、古いAPIはミリ秒単位で動作し、新しいAPIはナノ秒単位で動作するため、精度は回復不能なほど失われます。 java.lang.System#currentTimeMillisの形式で現在時刻の単一ミリ秒ソースがある限り、これは重要ではありませんが、将来、特にテストの場合、これが問題になる可能性があります。



結論



私はまだ新しいAPIから複雑な気持ちを持っています。一方では、大幅な改善がありますが、他方では、2つの主な問題がありました:ZonedDateTime以外のクラスを使用する場合に予期しない誤ったタイムシフトを実行する機能、およびタイムゾーンが操作で利用できないときにランタイムで予期せず例外を取得する機能です。さらに、新しいAPIは古いAPIよりもやや複雑です。これがどれほど重要であるかは、大衆的実践によって示されます。



All Articles