俺の備忘録

個人的な備忘録です。

javaのZonedDateTimeクラスのサマータイム切り替わり前後の挙動を検証してみた

はじめに

前回はCalendarクラスでサマータイム切り替わり前後の挙動を検証した。 記事を書いた後に気づいたが、Java8で時間まわりの新しいAPIがサポートされていたようだ。 新しいAPIでは、従来のDateとCalendarクラスを合体させたようなZonedDataTimeクラスというものがあり、今回はこのクラスのサマータイム切り替わり前後の挙動を検証した。

ZonedDataTimeクラスのサマータイム切り替わり前後の挙動検証

標準時からサマータイムへの切り替わり前後

省略される時間帯の時刻にセットしてみる

テストコード

// 東部時間(アメリカ/ニューヨーク)でテスト
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss z");

ZonedDateTime zdt0159 = ZonedDateTime.of(2017, 3, 12, 1, 59, 0, 0, ZoneId.of("America/New_York"));
System.out.println("入力:2017/03/12 01:59 => 出力:" + formatter.format(zdt0159));

ZonedDateTime zdt0200 = ZonedDateTime.of(2017, 3, 12, 2, 00, 0, 0, ZoneId.of("America/New_York"));
System.out.println("入力:2017/03/12 02:00 => 出力:" + formatter.format(zdt0200));

ZonedDateTime zdt0230 = ZonedDateTime.of(2017, 3, 12, 2, 30, 0, 0, ZoneId.of("America/New_York"));
System.out.println("入力:2017/03/12 02:30 => 出力:" + formatter.format(zdt0230));

ZonedDateTime zdt0300 = ZonedDateTime.of(2017, 3, 12, 3, 00, 0, 0, ZoneId.of("America/New_York"));
System.out.println("入力:2017/03/12 03:00 => 出力:" + formatter.format(zdt0300));

実行結果

入力:2017/03/12 01:59 => 出力:2017/03/12 01:59:00 EST
入力:2017/03/12 02:00 => 出力:2017/03/12 03:00:00 EDT
入力:2017/03/12 02:30 => 出力:2017/03/12 03:30:00 EDT
入力:2017/03/12 03:00 => 出力:2017/03/12 03:00:00 EDT

省略される時間に設定した場合は+01:00される結果となった。 これは、従来のCalendarクラスと同じ結果である。

時刻を加算してみる

テストコード

// 東部時間(アメリカ/ニューヨーク)でテスト
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss z");

String before = "";
String after = "";

ZonedDateTime zdt0130 = ZonedDateTime.of(2017, 3, 12, 1, 30, 0, 0, ZoneId.of("America/New_York"));
before = formatter.format(zdt0130);
after = formatter.format(zdt0130.plusHours(1));
System.out.println(before + " + 1時間 => " +  after);

ZonedDateTime zdt0230_yesterday = ZonedDateTime.of(2017, 3, 11, 2, 30, 0, 0, ZoneId.of("America/New_York"));
before = formatter.format(zdt0230_yesterday);
after  =  formatter.format(zdt0230_yesterday.plusDays(1));
System.out.println(before + " + 1日 => " +  after);

ZonedDateTime zdt0300_yesterday = ZonedDateTime.of(2017, 3, 11, 3, 00, 0, 0, ZoneId.of("America/New_York"));
before = formatter.format(zdt0300_yesterday);
after  =  formatter.format(zdt0300_yesterday.plusDays(1));
System.out.println(before + " + 1日 => " +  after);

実行結果

2017/03/12 01:30:00 EST + 1時間 => 2017/03/12 03:30:00 EDT
2017/03/11 02:30:00 EST + 1日 => 2017/03/12 03:30:00 EDT
2017/03/11 03:00:00 EST + 1日 => 2017/03/12 03:00:00 EDT

省略される時間タイに入るように時間を加算してみると、省略される時間は飛ばされ、 03:00台の時刻になるようだ。

また、サマータイム前日の02:30から1日足すケースは従来のCalendarクラスとは挙動が異なっている。 (Calendarクラスの場合は、 2017/03/12 01:30 EST)になった。

時刻を減算してみる。

テストコード

// 東部時間(アメリカ/ニューヨーク)でテスト
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss z");

String before = "";
String after = "";

ZonedDateTime zdt0200 = ZonedDateTime.of(2017, 3, 12, 2, 0, 0, 0, ZoneId.of("America/New_York"));
before = formatter.format(zdt0200);
after = formatter.format(zdt0200.minusHours(1));
System.out.println(before + " - 1時間 => " +  after);

ZonedDateTime zdt0230 = ZonedDateTime.of(2017, 3, 12, 2, 30, 0, 0, ZoneId.of("America/New_York"));
before = formatter.format(zdt0230);
after = formatter.format(zdt0230.minusHours(1));
System.out.println(before + " - 1時間 => " +  after);

ZonedDateTime zdt0300 = ZonedDateTime.of(2017, 3, 12, 3, 0, 0, 0, ZoneId.of("America/New_York"));
before = formatter.format(zdt0300);
after = formatter.format(zdt0300.minusHours(1));
System.out.println(before + " - 1時間 => " +  after);

ZonedDateTime zdt0200Tomo = ZonedDateTime.of(2017, 3, 13, 2, 0, 0, 0, ZoneId.of("America/New_York"));
before = formatter.format(zdt0200Tomo);
after = formatter.format(zdt0200Tomo.minusDays(1));
System.out.println(before + " - 1日 => " +  after);

ZonedDateTime zdt0230Tomo = ZonedDateTime.of(2017, 3, 13, 2, 30, 0, 0, ZoneId.of("America/New_York"));
before = formatter.format(zdt0230Tomo);
after = formatter.format(zdt0230Tomo.minusDays(1));
System.out.println(before + " - 1日 => " +  after);

ZonedDateTime zdt0300Tomo = ZonedDateTime.of(2017, 3, 13, 3, 0, 0, 0, ZoneId.of("America/New_York"));
before = formatter.format(zdt0300Tomo);
after = formatter.format(zdt0300Tomo.minusDays(1));
System.out.println(before + " - 1日 => " +  after);

実行結果

2017/03/12 03:00:00 EDT - 1時間 => 2017/03/12 01:00:00 EST
2017/03/12 03:30:00 EDT - 1時間 => 2017/03/12 01:30:00 EST
2017/03/12 03:00:00 EDT - 1時間 => 2017/03/12 01:00:00 EST
2017/03/13 02:00:00 EDT - 1日 => 2017/03/12 03:00:00 EDT
2017/03/13 02:30:00 EDT - 1日 => 2017/03/12 03:30:00 EDT
2017/03/13 03:00:00 EDT - 1日 => 2017/03/12 03:00:00 EDT

1時間引いた時は、省略される02:00台が飛ばされ、01:00台になる。 サマータイム開始の翌日から1日引いたときは、-23時間されている。 この結果はCalendarクラスのときと同じ結果である。

サマータイムから標準時への切り替わり前後

重複する時間帯前後に時刻をセットしてみる

テストコード

// 東部時間(アメリカ/ニューヨーク)でテスト
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss z");

ZonedDateTime zdt0030 = ZonedDateTime.of(2017, 11, 5, 0, 30, 0, 0, ZoneId.of("America/New_York"));
System.out.println("入力:2017/11/5 00:30 => 出力:" + formatter.format(zdt0030));

ZonedDateTime zdt0100 = ZonedDateTime.of(2017, 11, 5, 1, 00, 0, 0, ZoneId.of("America/New_York"));
System.out.println("入力:2017/11/5 01:00 => 出力:" + formatter.format(zdt0100));

ZonedDateTime zdt0130 = ZonedDateTime.of(2017, 11, 5, 1, 30, 0, 0, ZoneId.of("America/New_York"));
System.out.println("入力:2017/11/5 01:30 => 出力:" + formatter.format(zdt0130));

ZonedDateTime zdt0200 = ZonedDateTime.of(2017, 11, 5, 2, 00, 0, 0, ZoneId.of("America/New_York"));
System.out.println("入力:2017/11/5 02:00 => 出力:" + formatter.format(zdt0200));

実行結果

入力:2017/11/5 00:30 => 出力:2017/11/05 00:30:00 EDT
入力:2017/11/5 01:00 => 出力:2017/11/05 01:00:00 EDT
入力:2017/11/5 01:30 => 出力:2017/11/05 01:30:00 EDT
入力:2017/11/5 02:00 => 出力:2017/11/05 02:00:00 EST

重複する01:00台をセットした場合は、夏時間の01:00台になる。 Calendarクラスの場合は、標準時の01:00台になっていたので挙動が異なる。

時刻を加算してみる

テストコード

// 東部時間(アメリカ/ニューヨーク)でテスト
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss z");

String before = "";
String after = "";

ZonedDateTime zdt0030 = ZonedDateTime.of(2017, 11, 5, 0, 30, 0, 0, ZoneId.of("America/New_York"));
before = formatter.format(zdt0030);
after = formatter.format(zdt0030.plusHours(1));
System.out.println(before + " + 1時間 => " +  after);

ZonedDateTime zdt0030_2 = ZonedDateTime.of(2017, 11, 5, 0, 30, 0, 0, ZoneId.of("America/New_York"));
before = formatter.format(zdt0030_2);
after  =  formatter.format(zdt0030_2.plusHours(2));
System.out.println(before + " + 2時間 => " +  after);

ZonedDateTime zdt0130_yesterday = ZonedDateTime.of(2017, 11, 4, 1, 30, 0, 0, ZoneId.of("America/New_York"));
before = formatter.format(zdt0130_yesterday);
after  =  formatter.format(zdt0130_yesterday.plusDays(1));
System.out.println(before + " + 1日 => " +  after);

ZonedDateTime zdt0230_yesterday = ZonedDateTime.of(2017, 11, 4, 2, 30, 0, 0, ZoneId.of("America/New_York"));
before = formatter.format(zdt0230_yesterday);
after  =  formatter.format(zdt0230_yesterday.plusDays(1));
System.out.println(before + " + 1日 => " +  after);

実行結果

2017/11/05 00:30:00 EDT + 1時間 => 2017/11/05 01:30:00 EDT
2017/11/05 00:30:00 EDT + 2時間 => 2017/11/05 01:30:00 EST
2017/11/04 01:30:00 EDT + 1日 => 2017/11/05 01:30:00 EDT
2017/11/04 02:30:00 EDT + 1日 => 2017/11/05 02:30:00 EST

時を足すと、EDTの01:00台、ESTの01:00台の順に進む。 前日から1日を足すケースは02:00が境界になっているようで、 前日の01:30から1日足すと、24時間加算され、前日の02:30から1日足すと 25時間足されている。 この挙動はCalendarクラスと同じである。

時間を減算してみる

テストコード

// 東部時間(アメリカ/ニューヨーク)でテスト
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss z");

String before = "";
String after = "";

ZonedDateTime zdt0230 = ZonedDateTime.of(2017, 11, 5, 2, 30, 0, 0, ZoneId.of("America/New_York"));
before = formatter.format(zdt0230);
after = formatter.format(zdt0230.minusHours(1));
System.out.println(before + " - 1時間 => " +  after);

ZonedDateTime zdt0230_2 = ZonedDateTime.of(2017, 11, 5, 2, 30, 0, 0, ZoneId.of("America/New_York"));
before = formatter.format(zdt0230_2);
after = formatter.format(zdt0230_2.minusHours(2));
System.out.println(before + " - 2時間 => " +  after);

ZonedDateTime zdt0030Tomo = ZonedDateTime.of(2017, 11, 6, 0, 30, 0, 0, ZoneId.of("America/New_York"));
before = formatter.format(zdt0030Tomo);
after  =  formatter.format(zdt0030Tomo.minusDays(1));
System.out.println(before + " - 1日 => " +  after);

ZonedDateTime zdt0100Tomo = ZonedDateTime.of(2017, 11, 6, 1, 00, 0, 0, ZoneId.of("America/New_York"));
before = formatter.format(zdt0100Tomo);
after  =  formatter.format(zdt0100Tomo.minusDays(1));
System.out.println(before + " - 1日 => " +  after);


ZonedDateTime zdt0130Tomo = ZonedDateTime.of(2017, 11, 6, 1, 30, 0, 0, ZoneId.of("America/New_York"));
before = formatter.format(zdt0130Tomo);
after  =  formatter.format(zdt0130Tomo.minusDays(1));
System.out.println(before + " - 1日 => " +  after);

ZonedDateTime zdt0230Tomo = ZonedDateTime.of(2017, 11, 6, 2, 30, 0, 0, ZoneId.of("America/New_York"));
before = formatter.format(zdt0230Tomo);
after  =  formatter.format(zdt0230Tomo.minusDays(1));
System.out.println(before + " - 1日 => " +  after);

実行結果

2017/11/05 02:30:00 EST - 1時間 => 2017/11/05 01:30:00 EST
2017/11/05 02:30:00 EST - 2時間 => 2017/11/05 01:30:00 EDT
2017/11/06 00:30:00 EST - 1日 => 2017/11/05 00:30:00 EDT
2017/11/06 01:00:00 EST - 1日 => 2017/11/05 01:00:00 EST
2017/11/06 01:30:00 EST - 1日 => 2017/11/05 01:30:00 EST
2017/11/06 02:30:00 EST - 1日 => 2017/11/05 02:30:00 EST

時を減算する場合は、ESTの01:00台、EDTの01:00台の順に遷移する。 標準時2日目から1日減算するケースでは、01:00が境界になっていて、 01:00より前だと25時間引かれ、01:00以降だと24時間引かれる結果となっている。 この挙動はCalendarクラスと同じである。

まとめ

まとめるとZonedDateTimeは以下の挙動らしい。

  • 省略される時間帯(02:00台)にセットすると、夏時間の03:00台として扱われる。Calendarクラスと同じ。  

  • 省略される時間帯に入るように時刻を加算すると、省略された時刻がスキップされる。 サマータイム前日の02:30から1日足した場合は、ZonedDateTimeでは、EDTの03:30になるが、Calendarでは、ESTの1:30であったため、挙動が異なる。   

  • 省略される時間帯に入るように時刻を減算した場合の挙動はCalendarクラスと同じ。   

  • 時刻をを重複される時間帯(01:00台)にセットすると、夏時間の01:00台として扱われる。Calendarクラスでは標準時の01:00台だったため、挙動が異なる。
      

  • 時刻を重複される時間帯(01:00台)に入るように加減算すると、時を加減算した場合は、重複する時間帯(01:00)がそれぞれ別の1時間として扱われる。 日を加減算場合は、境界前後で24時間加減算されるか25時間加減算されるかが切り替わる。この挙動はCalendarクラスと同じ。   

Calendarクラスと概ね同じように扱えるが、サマータイム切り替わり前後の時間の挙動が若干異なるため、新APIに乗り換る場合は注意が必要そうだ。