本題に生える前に
Javaでjava.util.Dateを利用してタイムゾーンを取り扱う際には日時がエポックタイムからの経過時刻(ミリ秒)でかつ、タイムゾーンがGMTで管理されていることは一般的かと思いますが、ここでは日時とタイムゾーンが何かの理由で別に保持されているような場合にを想定しています。
例えば、以下のような感じです
項目 | 値 |
---|---|
日時 | 2016-12-01 15:00:00 |
タイムゾーン | Australia/Sydney |
これをSystem.out.printlnなどで表示したときに以下のようにDate型の保持する値が、違和感を覚える場合です。言い換えると、本来は15:00はAustralia/Sydneyの時間であってほしいのになぜかローカルのタイムゾーンになってる。といった感じです。
Thu Dec 01 15:00:00 JST 2016
sun.util.calendar.ZoneInfo[id="Asia/Tokyo",offset=32400000,dstSavings=0,useDaylight=false,transitions=10,lastRule=null]
また、ここで行われている変換を利用した場合、バグに直面しますので、反面教師的にこのロジックはたどり着かないようにしてください。解決策が見つかりましたら改めて投稿します。シチュエーションの回避となった場合は定かではありません。
本題
とにかく、上記のような状態になった時にどうやって正しいデータとして保持しなおそうかという事を考えています。このようなシチュエーションを回避することが一番の解決策かもしれませんけど。
Java8 以前の場合
ちょっとトリッキーなコードです。文字列に変換するという苦肉の策ですね。
static Date convertWithBeforeJava7(Date dateTime, TimeZone timeZone) throws ParseException {
String format = "yyyy/MM/dd HH:mm:ss.SSS";
SimpleDateFormat local = new SimpleDateFormat(format);
SimpleDateFormat convert = new SimpleDateFormat(format);
convert.setTimeZone(timeZone);
return convert.parse(local.format(dateTime));
}
Java8
なれないですが、Java8のクラスでも試してみました。なんとなくスッキリしていますが、わざわざDate型に戻すのかという気分でもあります。
テスト結果
テストは2種類やりました。入力は以下です。
環境条件
項目 | 値 |
---|---|
ローカルタイムゾーン | Australia/Sydney |
入力
テスト番号 | 日時 | タイムゾーン |
---|---|---|
1 | 2016-10-02 02:00 | Australia/Sydney |
2 | 2016-10-02 02:00 | America/Montreal |
十分なテストではないので注意が必要ですが。以下のような結果になってます。
java.lang.AssertionError: It would be same date
Expected: is <Sun Oct 02 17:00:00 EST 2016>
but: was <Sun Oct 02 18:00:00 EST 2016>
at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:20)
at org.junit.Assert.assertThat(Assert.java:956)
at com.nekoscape.java.sample.TimezoneConversionTest.convertWithOtherTimezone_test_for_Java8Method(TimezoneConversionTest.java:76)
(省略)
java.lang.AssertionError: It would be same date
Expected: is <Sun Oct 02 17:00:00 EST 2016>
but: was <Sun Oct 02 18:00:00 EST 2016>
at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:20)
at org.junit.Assert.assertThat(Assert.java:956)
at com.nekoscape.java.sample.TimezoneConversionTest.convertWithOtherTimezone_test_for_beforeJava7Method(TimezoneConversionTest.java:62)
(省略)
Process finished with exit code -1
テストが2件失敗してます。どちらもローカルタイムゾーンではないAmerica/Montrealを指定した場合に発生しています。何が起きているのでしょうか?
結論から書きますと、現在のこのロジックではローカルのタイムゾーンがサマータイム(Daylight saving)になったときに問題が発生します。日時として指定した2016-10-02 02:00はAustralia/Sydneyのサマータイムの開始日時です。そのため、文字列からDate型として保持した時にはすでに2016-10-02 03:00という意味になります。
そのため、1時間の変換ミスが発生しています。
最初にも記載しましたが、バグです。解決できていません。
誤ってもコピーしないでください。試しに動かせるようにソースは以下に置きます。
ソースコード: timezone-conversion