Java の DateFormat を使うときは setCalendar または setTimeZone を忘れない

## 倫敦はいま何時頃だろうか？

日本とロンドンの時刻を調べるプログラムを書く。

```java
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;

public class Foo {

  public static void main(String[] args) {

    // 日時オブジェクトの準備
    Calendar utcCal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); // 倫敦は協定世界時
    Calendar jstCal = Calendar.getInstance(TimeZone.getTimeZone("JST")); // 日本標準時
    Date utcDate = utcCal.getTime();
    Date jstDate = jstCal.getTime();

    // フォーマッターを準備
    SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    // 年月日時分秒に整形して出力
    System.out.println("倫敦: " + format.format(utcDate.getTime()));
    System.out.println("日本: " + format.format(jstDate.getTime()));
  }
}
```

実行結果。

```
倫敦: 2019-09-05 21:00:00
日本: 2019-09-05 21:00:00
```

（・3・）ｱﾙｪｰ?
9時間ずれるかと思ったら同じ時刻になってしまった……

## APIリファレンスを確認する

[Calendar \(Java SE 11 & JDK 11 \)](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/util/Calendar.html)

> Calendarクラスは、特定の時点とYEAR、MONTH、DAY_OF_MONTH、HOURなどのカレンダ・フィールド・セット間の変換、および次週の日付の取得などのカレンダ・フィールド操作を行うための抽象クラスです。 特定のインスタントは、1970年1月1日00:00:00.000 GMT (グレゴリオ暦)を元期とするミリ秒単位のオフセットで表現できます。

[Calendar \(Java SE 11 & JDK 11 \)](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/util/Calendar.html)

> public final Date getTime()
> このCalendarの時間値(元期からのミリ秒単位のオフセット)を表すDateオブジェクトを返します。

Calendar#getTime で取得できる Date オブジェクトは UNIX エポック(1970年1月1日午前0時0分0秒) からのミリ秒をベースにしているようだ。Calendar のタイムゾーンは関係ないらしい。

[DateFormat \(Java SE 11 & JDK 11 \)](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/text/DateFormat.html)

> public void setCalendar(Calendar newCalendar)
> この日付フォーマットで使用するカレンダを設定します。最初は、指定されたロケールまたはデフォルトのロケールに対し、デフォルトのカレンダが使用されます。

[DateFormat \(Java SE 11 & JDK 11 \)](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/text/DateFormat.html)

> public void setTimeZone​(TimeZone zone)
> このDateFormatオブジェクトのカレンダのタイムゾーンを設定します。

DateFormat#setCalendar または DateFormat#setTimeZone を使う必要があったらしい。

## フォーマッター側にタイムゾーンを設定する

```java
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;

public class Bar {

  public static void main(String[] args) {

    // 日時オブジェクトの準備
    Calendar utcCal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); // 倫敦は協定世界時
    Calendar jstCal = Calendar.getInstance(TimeZone.getTimeZone("JST")); // 日本標準時
    Date utcDate = utcCal.getTime();
    Date jstDate = jstCal.getTime();

    // フォーマッターを準備
    SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    // フォーマッターがデフォルトでセットしているタイムゾーンは？
    System.out.println(format.getCalendar().getTimeZone().getID());

    // タイムゾーンUTCをセット
    format.setTimeZone(utcCal.getTimeZone());
    System.out.println("倫敦: " + format.format(utcDate.getTime()));

    // タイムゾーンJSTをセット
    format.setTimeZone(jstCal.getTimeZone());
    System.out.println("日本: " + format.format(jstDate.getTime()));
  }
}
```

実行してみたら、今度はちゃんと9時間の時差のある日時が出力できた。
デフォルトのタイムゾーンは「Asia/Tokyo」だった。

```
Asia/Tokyo
倫敦: 2019-09-05 12:01:00
日本: 2019-09-05 21:01:00
```

日本にいて、かつ日本標準時を出力するようなプログラムしか書いていなかったので、この問題に遭遇したことが無かった。

## Java 1.8 からは java.time パッケージもある

Java 1.8 からは標準ライブラリに新しい日時APIが用意されたのでそちらを使うのも良い。

[Java日付時間API](https://docs.oracle.com/javase/jp/8/docs/technotes/guides/datetime/)

> JDK 8で導入された日付時間APIは、日付と時間に関するもっとも重要な側面をモデル化したパッケージの集まりです。java.timeパッケージのコア・クラスは、ISO-8601に定義されている暦体系(グレゴリオ暦に基づく)をデフォルトのカレンダとして使用します。他のISO以外の暦体系はjava.time.chronoパッケージを使用して表現でき、ヒジュラ暦やタイ仏暦などの事前に定義された暦がいくつか提供されています。

```java
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;

public class Baz {

  public static void main(String[] args) {

    // 日時オブジェクトの準備
    ZonedDateTime utcDateTime = ZonedDateTime.now(ZoneId.of("UTC"));
    ZonedDateTime jstDateTime = ZonedDateTime.now(ZoneId.of("Japan"));

    // フォーマッターを準備
    DateTimeFormatter format = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    System.out.println("倫敦: " + utcDateTime.format(format));
    System.out.println("日本: " + jstDateTime.format(format));
  }
}
```

実行してみたら、ちゃんと9時間の時差で出力できている。

```
倫敦: 2019-09-05 12:10:11
日本: 2019-09-05 21:10:11
```

## 今回の動作確認環境
- macOS mojave
- OpenJDK 11.0.2

## 参考資料
- [Calendar \(Java Platform SE 8 \)](https://docs.oracle.com/javase/jp/8/docs/api/java/util/Calendar.html)
- [DateFormat \(Java Platform SE 8 \)](https://docs.oracle.com/javase/jp/8/docs/api/java/text/DateFormat.html)
- [Java日付時間API](https://docs.oracle.com/javase/jp/8/docs/technotes/guides/datetime/)
- [ZonedDateTime \(Java Platform SE 8 \)](https://docs.oracle.com/javase/jp/8/docs/api/java/time/ZonedDateTime.html)