1. 概要

夏時間(DST)は、自然光をさらに1時間活用するために、夏の間に時計を進める方法です(暖房電力、照明電力の節約、気分の向上など)。

いくつかの国で使用されており、日付とタイムスタンプを操作する際に考慮する必要があります。

このチュートリアルでは、さまざまな場所に応じてJavaでDSTを正しく処理する方法を説明します。

2. JREおよびDSTの可変性

まず、世界中のDSTゾーンが非常に頻繁に変更され、それを調整する中央機関がないことを理解することが非常に重要です。

国、場合によっては都市でさえ、それを適用または取り消すかどうか、またどのように取り消すかを決定できます。

変更が発生するたびに、変更は IANAタイムゾーンデータベースに記録され、更新はJREの将来のリリースで展開されます。

待つことができない場合は、 Javaタイムゾーンアップデータツールと呼ばれる公式のOracleツールを使用して、新しいDST設定を含む変更されたタイムゾーンデータをJREに強制できます。 JavaSEダウンロードページで入手できます。

3. 間違った方法:3文字のタイムゾーンID

JDK 1.1の時代には、APIは3文字のタイムゾーンIDを許可していましたが、これによりいくつかの問題が発生しました。

まず、これは、同じ3文字のIDが複数のタイムゾーンを参照する可能性があるためです。 たとえば、CSTはUSである可能性があります 「中部標準時」だけでなく、「中国標準時」。 その場合、Javaプラットフォームはそのうちの1つしか認識できませんでした。

もう1つの問題は、標準のタイムゾーンでは夏時間が考慮されないことでした。 複数のエリア/リージョン/都市が同じ標準タイムゾーン内にローカルDSTを持つことができるため、標準時間はそれを監視しません。

下位互換性のため、 java.util.Timezoneを3文字のIDでインスタンス化することは引き続き可能です。 ただし、このメソッドは非推奨であり、使用しないでください。

4. 正しい方法:TZDBタイムゾーンID

JavaでDSTを処理する正しい方法は、タイムゾーンを特定のTZDBタイムゾーンIDでインスタンス化することです。 「ヨーロッパ/ローマ」。

次に、これを java .util.Calendar などの時間固有のクラスと組み合わせて使用し、 TimeZoneの生オフセット(GMTタイムゾーンへ)の適切な構成を取得します。 )、および自動DSTシフト調整。

GMT +1からGMT+ 2 (2018年3月25日午前2時のイタリアで発生)へのシフトが、右を使用すると自動的に処理される方法を見てみましょう TimeZone:

TimeZone tz = TimeZone.getTimeZone("Europe/Rome");
TimeZone.setDefault(tz);
Calendar cal = Calendar.getInstance(tz, Locale.ITALIAN);
DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.ITALIAN);
Date dateBeforeDST = df.parse("2018-03-25 01:55");
cal.setTime(dateBeforeDST);
 
assertThat(cal.get(Calendar.ZONE_OFFSET)).isEqualTo(3600000);
assertThat(cal.get(Calendar.DST_OFFSET)).isEqualTo(0);

ご覧のとおり、 ZONE_OFFSET は60分(イタリアは GMT + 1 であるため)ですが、DST_OFFSETはその時点で0です。

カレンダーに10分を追加しましょう。

cal.add(Calendar.MINUTE, 10);

現在、 DST_OFFSET も60分になり、国は現地時間を CET (中央ヨーロッパ時間)から CEST (中央ヨーロッパ夏時間)に移行しました。 GMT + 2

Date dateAfterDST = cal.getTime();
 
assertThat(cal.get(Calendar.DST_OFFSET))
  .isEqualTo(3600000);
assertThat(dateAfterDST)
  .isEqualTo(df.parse("2018-03-25 03:05"));

コンソールに2つの日付を表示すると、タイムゾーンも変更されます。

Before DST (00:55 UTC - 01:55 GMT+1) = Sun Mar 25 01:55:00 CET 2018
After DST (01:05 UTC - 03:05 GMT+2) = Sun Mar 25 03:05:00 CEST 2018

最後のテストとして、2つの Date の間の距離、1:55と3:05を測定できます。

Long deltaBetweenDatesInMillis = dateAfterDST.getTime() - dateBeforeDST.getTime();
Long tenMinutesInMillis = (1000L * 60 * 10);
 
assertThat(deltaBetweenDatesInMillis)
  .isEqualTo(tenMinutesInMillis);

予想通り、距離は70分ではなく10分です。

TimeZoneLocaleを正しく使用することで、Dateを操作するときに発生する可能性のある一般的な落とし穴に陥らないようにする方法を見てきました。

5. 最善の方法:Java8日付/時刻API

これらのスレッドセーフではなく、常にユーザーフレンドリーであるとは限らない java.util クラスの操作は、特に互換性の問題により適切にリファクタリングできなかったため、常に困難でした。

このため、Java8はまったく新しいパッケージjava.time と、まったく新しいAPIセット Date / Time APIを導入しました。これはISO中心で、完全にスレッド化されています-安全で、有名なライブラリJoda-Timeに大きく影響を受けています。

java .util.Date java .time.LocalDateTime の後継から始めて、この新しいクラスを詳しく見てみましょう。

LocalDateTime localDateTimeBeforeDST = LocalDateTime
  .of(2018, 3, 25, 1, 55);
 
assertThat(localDateTimeBeforeDST.toString())
  .isEqualTo("2018-03-25T01:55");

LocalDateTimeが標準で広く採用されている日時表記であるISO8601プロファイルにどのように準拠しているかを確認できます。

ZonesOffsetsを完全に認識していませんが、完全にDST対応のjava.time.ZonedDateTimeに変換する必要があるのはそのためです。

ZoneId italianZoneId = ZoneId.of("Europe/Rome");
ZonedDateTime zonedDateTimeBeforeDST = localDateTimeBeforeDST
  .atZone(italianZoneId);
 
assertThat(zonedDateTimeBeforeDST.toString())
  .isEqualTo("2018-03-25T01:55+01:00[Europe/Rome]"); 

ご覧のとおり、日付には2つの基本的な末尾の情報が組み込まれています。 +01:00ZoneOffset であり、[ヨーロッパ/ローマ]ZoneId

前の例のように、10分を追加してDSTをトリガーしましょう。

ZonedDateTime zonedDateTimeAfterDST = zonedDateTimeBeforeDST
  .plus(10, ChronoUnit.MINUTES);
 
assertThat(zonedDateTimeAfterDST.toString())
  .isEqualTo("2018-03-25T03:05+02:00[Europe/Rome]");

ここでも、時間とゾーンオフセットの両方が前方にシフトし、同じ距離を維持していることがわかります。

Long deltaBetweenDatesInMinutes = ChronoUnit.MINUTES
  .between(zonedDateTimeBeforeDST,zonedDateTimeAfterDST);
assertThat(deltaBetweenDatesInMinutes)
  .isEqualTo(10);

6. 結論

夏時間とは何か、そしてJavaコアAPIのさまざまなバージョンでのいくつかの実用的な例を通してそれを処理する方法を見てきました。

Java 8以降を使用する場合は、使いやすさと標準のスレッドセーフな性質により、新しいjava.timeパッケージの使用をお勧めします。

いつものように、完全なソースコードはGithub利用できます。