こんにちわ、みけです。
ここ最近さわってるJava8 Date and Time APIですが、
今日はLocalDateTime
とZonedDateTime
の相互変換についてです。
LocalDateTime
とZonedDateTime
の変換
ZonedDateTime
からLocalDateTime
へはゾーン情報を取り除くだけなので、
変換の種類は#toLocalDateTime
に限られています。
逆に、LocalDateTime
からZonedDateTime
への変換は、
新しいゾーン情報、変換前のゾーン、夏時間などの考慮するべきことがあり、
それぞれに対応するメソッドが存在しています。
@RunWith(Enclosed.class) public class DateTimeTest { private static final DateTimeFormatter withoutZone = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss"); private static final DateTimeFormatter withZone = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss Z"); private static final ZoneId ASIA_TOKYO = ZoneId.of("Asia/Tokyo"); private static final ZoneId GMT = ZoneId.of("GMT"); private static final String DATE_TIME = "2014/10/24 14:09:20 +0900"; //ログをSystem.out.printlnする private synchronized static void log(Queue<Output> outputs) {/*省略*/} //ログ用のクラス(クラス名、出力、メソッド名を出力) private static class Output {/*省略*/} public static class ZonedDateTimeとLocalDateTimeの相互変換 { private static final String CLASS = ZonedDateTimeとLocalDateTimeの相互変換.class.getSimpleName(); private static final Queue<Output> QUEUE = new ConcurrentLinkedQueue<>(); @Rule public TestName testName = new TestName(); @Test public void zonedDateTimeからLocalDateTimeに変換する() { ZonedDateTime parsed = ZonedDateTime.parse(DATE_TIME, withZone); push(withZone.format(parsed)); LocalDateTime converted = parsed.toLocalDateTime(); push(withoutZone.format(converted) + " "); } @Test(expected = DateTimeException.class) public void localDateTimeからゾーン無しでZonedDateTimeに変換できない() { LocalDateTime parsed = LocalDateTime.parse(DATE_TIME, withZone); push(withoutZone.format(parsed)); ZonedDateTime.from(parsed); } @Test public void localDateTimeからゾーン指定してZonedDateTimeに変換する() { LocalDateTime parsed = LocalDateTime.parse(DATE_TIME, withZone); push(withoutZone.format(parsed) + " "); ZonedDateTime converted = ZonedDateTime.of(parsed, ASIA_TOKYO); push(withZone.format(converted)); } @Test public void localDateTimeから異なるゾーンで変換したZonedDateTimeは異なる時刻になる() { LocalDateTime parsed = LocalDateTime.parse(DATE_TIME, withZone); push(withoutZone.format(parsed) + " "); ZonedDateTime asiaTokyo = ZonedDateTime.of(parsed, ASIA_TOKYO); ZonedDateTime gmt = ZonedDateTime.of(parsed, GMT); push(withZone.format(asiaTokyo)); push(withZone.format(gmt)); assertThat(gmt.isAfter(asiaTokyo), is(true)); } @Test public void localDateTimeからゾーンとオフセット指定してZonedDateTimeに変換する() { LocalDateTime parsed = LocalDateTime.parse(DATE_TIME, withZone); push(withoutZone.format(parsed) + " "); ZonedDateTime hawaii = ZonedDateTime.ofInstant(parsed, //変換元のゾーン ZoneOffset.ofHours(-10), //変換後のゾーン ZoneId.of("US/Hawaii")); push(withZone.format(hawaii)); } @Test public void 東京の時刻_Offsetが9時間_をGMTに変換したZonedDateTimeを取得する() { LocalDateTime parsed = LocalDateTime.parse(DATE_TIME, withZone); push(withoutZone.format(parsed) + " "); ZonedDateTime converted = ZonedDateTime.ofInstant(parsed, ZoneOffset.ofHours(9), GMT); ZonedDateTime gmt = ZonedDateTime.of(parsed, GMT); push(withZone.format(converted)); assertThat(converted.isBefore(gmt), is(true)); } @Test public void 夏時間を柔軟に考慮するofLocal() { //Yew York時間 ZoneId newYork = ZoneId.of("EST5EDT"); //EST5EDTで夏時間終了1秒前 String end = "2014/11/02 01:59:59"; LocalDateTime parsed = LocalDateTime.parse(end, withoutZone); push(withoutZone.format(parsed) + " "); //UTC-4(夏時間)で優先して変換 ZonedDateTime utcMinus4 = ZonedDateTime.ofLocal(parsed, newYork, ZoneOffset.ofHours(-4)); push(withZone.format(utcMinus4)); //UTC-5(冬時間)で優先して変換 ZonedDateTime utcMinus5 = ZonedDateTime.ofLocal(parsed, newYork, ZoneOffset.ofHours(-5)); push(withZone.format(utcMinus5)); assertThat(utcMinus4.isBefore(utcMinus5), is(true)); } @Test public void 夏時間を柔軟に考慮するofLocal夏時間終了後() { //Yew York時間 ZoneId newYork = ZoneId.of("EST5EDT"); //EST5EDTで夏時間終了1秒前 String end = "2014/11/02 02:00:00"; LocalDateTime parsed = LocalDateTime.parse(end, withoutZone); push(withoutZone.format(parsed) + " "); //UTC-4(夏時間)で優先して変換 ZonedDateTime utcMinus4 = ZonedDateTime.ofLocal(parsed, newYork, ZoneOffset.ofHours(-4)); push(withZone.format(utcMinus4)); //UTC-5(冬時間)で優先して変換 ZonedDateTime utcMinus5 = ZonedDateTime.ofLocal(parsed, newYork, ZoneOffset.ofHours(-5)); push(withZone.format(utcMinus5)); } @Test public void 夏時間を厳密に判定する_夏時間終了1秒前_minus4() { //YEW YORK時間 ZoneId newYork = ZoneId.of("EST5EDT"); //EST5EDTで夏時間終了1秒前 String end = "2014/11/02 01:59:59"; LocalDateTime parsed = LocalDateTime.parse(end, withoutZone); push(withoutZone.format(parsed) + " "); ZonedDateTime converted = ZonedDateTime.ofStrict(parsed, ZoneOffset.ofHours(-4), newYork); push(withZone.format(converted)); } @Test(expected = DateTimeException.class) public void 夏時間を厳密に判定する_夏時間終了時_minus4でエラー() { //YEW YORK時間 ZoneId newYork = ZoneId.of("EST5EDT"); //EST5EDTで夏時間終了1秒前 String end = "2014/11/02 02:00:00"; LocalDateTime parsed = LocalDateTime.parse(end, withoutZone); push(withoutZone.format(parsed) + " "); //EST5EDTで夏時間終了時は2時から3時に変わるので変換エラー(minus5ならパスする) ZonedDateTime.ofStrict(parsed, ZoneOffset.ofHours(-4), newYork); } private void push(String message) { QUEUE.offer(new Output(message, CLASS, testName)); } @AfterClass public static void outputMessages() { log(QUEUE); } } }
出力結果
2014/10/24 14:09:20 +0900 <- zonedDateTimeからLocalDateTimeに変換する 2014/10/24 14:09:20 <- zonedDateTimeからLocalDateTimeに変換する 2014/10/24 14:09:20 <- localDateTimeからゾーン無しでZonedDateTimeに変換できない 2014/10/24 14:09:20 <- localDateTimeからゾーン指定してZonedDateTimeに変換する 2014/10/24 14:09:20 +0900 <- localDateTimeからゾーン指定してZonedDateTimeに変換する 2014/10/24 14:09:20 <- localDateTimeからゾーンとオフセット指定してZonedDateTimeに変換する 2014/10/24 14:09:20 -1000 <- localDateTimeからゾーンとオフセット指定してZonedDateTimeに変換する 2014/10/24 14:09:20 <- 東京の時刻_Offsetが9時間_をGMTに変換したZonedDateTimeを取得する 2014/10/24 05:09:20 +0000 <- 東京の時刻_Offsetが9時間_をGMTに変換したZonedDateTimeを取得する 2014/10/24 14:09:20 <- localDateTimeから異なるゾーンで変換したZonedDateTimeは異なる時刻になる 2014/10/24 14:09:20 +0900 <- localDateTimeから異なるゾーンで変換したZonedDateTimeは異なる時刻になる 2014/10/24 14:09:20 +0000 <- localDateTimeから異なるゾーンで変換したZonedDateTimeは異なる時刻になる 2014/11/02 01:59:59 <- 夏時間を柔軟に考慮するofLocal 2014/11/02 01:59:59 -0400 <- 夏時間を柔軟に考慮するofLocal 2014/11/02 01:59:59 -0500 <- 夏時間を柔軟に考慮するofLocal 2014/11/02 02:00:00 <- 夏時間を柔軟に考慮するofLocal夏時間終了後 2014/11/02 02:00:00 -0500 <- 夏時間を柔軟に考慮するofLocal夏時間終了後 2014/11/02 02:00:00 -0500 <- 夏時間を柔軟に考慮するofLocal夏時間終了後 2014/11/02 01:59:59 <- 夏時間を厳密に判定する_夏時間終了1秒前_minus4 2014/11/02 01:59:59 -0400 <- 夏時間を厳密に判定する_夏時間終了1秒前_minus4 2014/11/02 02:00:00 <- 夏時間を厳密に判定する_夏時間終了時_minus4でエラー
最初のテストzonedDateTimeからLocalDateTimeに変換する
は
ZonedDateTime#toLocalDateTime
を使って、
ZonedDateTime
からLocalDateTime
に変換します。
変換は単純にゾーン情報がなくなるだけです。
2つ目のテストlocalDateTimeからゾーン無しでZonedDateTimeに変換できない
では
ZonedDateTime#from(TemporalAccessor)
を使って、
LocalDateTime
からZonedDateTime
に変換しようとしますが、
LocalDateTime
にはゾーン情報はないので、
DateTimeException
が発生します。
3つ目のテストlocalDateTimeからゾーン指定してZonedDateTimeに変換する
では、
ZonedDateTime#of(TemporalAccessor, ZoneId)
を使い、
変換するゾーン情報を付与するので、例外は発生しません。
4つ目のテストlocalDateTimeから異なるゾーンで変換したZonedDateTimeは異なる時刻になる
では、ZonedDateTime#of(TemporalAccessor, ZoneId)
の、
ZoneId
が違えば、得られるZonedDateTime
の時刻も
同時刻にならないことを確認しています。
例では東京とGMTとの2014/10/24 14:09:20
に変換されるので、
東京の14:09:20
の方が先に訪れる時間であることを確認しています。
5つ目のテストlocalDateTimeからゾーンとオフセット指定してZonedDateTimeに変換する
では、ZonedDateTime#ofInstant(LocalDateTime, ZoneOffset, ZoneId)
で
変換しています。
最初の引数が日時、第二引数が変換前のゾーン、第三引数が変換後のゾーンです。
この例ではUTC-10(ハワイ)からハワイの時間に変換しているので、
日時は変わっていません。
6つ目のテストは、5つ目のテストの第二引数と第三引数が異なるゾーンを
さすように変更したテストです。
東京の時刻_Offsetが9時間_をGMTに変換したZonedDateTimeを取得する
というテスト名からわかるように、
結果、9時間遅い時刻が返されています。
7つ目のテスト夏時間を柔軟に考慮するofLocal
は夏時間に関わる変換です。
ニューヨークなど一部の国地域では、夏時間が採用されていて、
夏時間から通常時間(冬時間)に切り替わるタイミングによっては、
変換できる時間に複数の候補が出来る場合があります。
その場合にZoneOffset
で与えられたゾーンを優先して、
時間を変更するのが
ZonedDateTime#ofLocal(LocalDateTime, ZoneId, ZoneOffset)
です。
例ではニューヨークの夏時間を採用しています。
ニューヨークの夏時間は2014/11/02 02:00:00
にて
冬時間2014/11/02 03:00:00
になります。
一方夏時間を厳密に判定するメソッドもあります。
ZonedDateTime#ofStrict(LocalDateTime, ZoneOffset, ZoneId)
です。
夏時間を厳密に判定する_夏時間終了1秒前_minus4
では、
夏時間の終了1秒前のLocalDateTime
(2014/11/02 01:59:59
)を
オフセット-4
のニューヨーク時刻で変換します。
夏時間を厳密に判定する_夏時間終了時_minus4でエラー
は
夏時間が終了する時刻(2014/11/02 02:00:00
)を
オフセット-4
のニューヨーク時刻で変換します。
ただし、ニューヨーク時刻で2014/11/02 02:00:00
は存在せず、
2014/11/02 03:00:00
になるので、
この例ではDateTimeException
が発生します。
7つ目以降のテストは1951年以降夏時間がない日本では
特に使うことはないかもしれませんが、
今後夏時間を採用する可能性がないとも言い切れないので、
念のため、こんなのがあるよ程度に覚えておいた方がよいかもしれません。
ただ、なんか引数の順番とか型とかがアレでアレですね。
以上
次回はDate
とLocalDateTime
/ZonedDateTime
への相互変換です。