利用者の誕生日を新規登録／参照する際に、サマータイムに気をつける

# 1. 概要

フロントサイドでミリ秒変換して
バックエンドのAPIに時間の情報を送信する場合、
サマータイムを考慮する必要があります。

Javaのメソッドで変換すればサマータイムを処理してくれますが、
javascriptでは処理してくれないからです。

この記事では、Javaがどのようにサマータイムを処理するか
変換の細かい仕様をお伝えします。

※サマータイムの詳細については以下の記事をご覧ください。
[夏時間 - Wikipedia](https://ja.wikipedia.org/wiki/%E5%A4%8F%E6%99%82%E9%96%93)

## 問題

サマータイム期間中に生まれた人の誕生日が
一日ずれて表示される事象が発生しました。

## 解決方法

サマータイム期間中は、登録／参照時に1時間分
ミリ秒を足し引きする処理を施す必要があります。


# 2. 詳細

日本でのサマータイム導入期間は以下です。

|年|開始時刻|終了時刻|
|:-:|:------------------:|:------------------:|
|1948年（昭和23年）|5月2日(日)午前2時|9月11日(土)午前2時|
|1949年（昭和24年）|4月3日(日)午前2時|9月10日(土)午前2時|
|1950年（昭和25年）|5月7日(日)午前2時|9月9日(土)午前2時|
|1951年（昭和26年）|5月6日(日)午前2時|9月8日(土)午前2時|

## 日時⇒ミリ秒

### サマータイム開始

5月2日(月)午前2時になると、サマータイムに突入するため
時間が1時間進められて午前3時とミリ秒が同じになります。

**検証用ソース**

```SummerTime.java
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class SummerTime {
	public static void main(String[] args) throws ParseException {
		convertToMilliseconds("1948-05-02 02:00:00");
		convertToMilliseconds("1948-05-02 03:00:00");
	}

	private static void convertToMilliseconds(String dateStr) throws ParseException {
		SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		Date date = formatter.parse(dateStr);
		System.out.println(dateStr + " -> " + date.getTime());
	}
}
```

**出力結果**

```
1948-05-02 02:00:00 -> -683794800000
1948-05-02 03:00:00 -> -683794800000
```

午前2時台の変換は以下のようになっています。

```
1948-05-02 02:00:00 -> -683794800000
1948-05-02 02:00:01 -> -683794799000
1948-05-02 02:00:02 -> -683794798000
・
・
・
1948-05-02 02:59:58 -> -683791202000
1948-05-02 02:59:59 -> -683791201000
1948-05-02 03:00:00 -> -683794800000
```

午前2時59分59秒999から午前3時にかけて
補正される仕様がわかりました。
ただ、サマータイム期間中は午前2時台は存在しないため
2時から3時の間のミリ秒変換が使用される想定はありません。

### サマータイム終了

9月11日(月)午前1時になると、サマータイムから抜けるため
時間を1時間元に戻さなければなりません。

```
1948-09-11 00:59:59 -> -672397201000
1948-09-11 01:00:00 -> -672393600000
```

日時表記では差が1秒ですが、
ミリ秒では補正されて3601000ミリ秒となっています。

## ミリ秒⇒日時

### サマータイム開始

今度はミリ秒から日時表記に戻してみます。

**検証用ソース**

```SummerTime.java
private static void convertToTimeFormat(Long milliSeconds) throws ParseException {
	SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
	String dateStr = formatter.format(milliSeconds);
	System.out.println(milliSeconds + " -> " + dateStr);
}
```

**出力結果**

```
-683794801000 -> 1948-05-02 01:59:59
-683794800000 -> 1948-05-02 03:00:00
-683794799000 -> 1948-05-02 03:00:01
```

### サマータイム終了

```
-672397201000 -> 1948-09-11 00:59:59
-672397200000 -> 1948-09-11 01:00:00
-672397199000 -> 1948-09-11 01:00:01
・
・
・
-672393601000 -> 1948-09-11 01:59:59
-672393600000 -> 1948-09-11 01:00:00
-672393599000 -> 1948-09-11 01:00:01
```

午前1時台が2回くるような仕様となっています。

# 3. 対処方法

フロントで変換する際に、該当する期間のミリ秒を補正して
時間の情報をAPIに送るよう修正しました。

```dateUtil.ts
    /**
     *サマータイムを考慮した時間を取得（APIからフロントへの表示時に変換）
     */
    export function getSummerTimeDate(birthDate){
        let birth = Number(birthDate)
        if ((birth >= -683794800000 && birth <= -672393600001)
            || (birth >= -654764400000 && birth <= -640944000001)
            || (birth >= -620290800000 && birth <= -609494400001)
            || (birth >= -588841200000 && birth <= -578044800001)) {
            birth += 3600000
        }
        return birth
    }

    /**
     *サマータイムを考慮した時間を代入（フロントからAPIへの登録時に変換）
     */
    export function setSummerTimeDate(birthDate){
        let birth = Number(birthDate)
        if ((birth >= -683791200000 && birth <= -672390000001)
            || (birth >= -654760800000 && birth <= -640940400001)
            || (birth >= -620287200000 && birth <= -609490800001)
            || (birth >= -588837600000 && birth <= -578041200001)) {
            birth -= 3600000
        }
        return birth
    }
```

# 4. まとめ

サマータイム導入時期の時間を扱うかどうか、仕様を検討する段階で考慮しておいたほうが良いと思います。
また、タイムゾーンにUTCを使用するなど、回避する方法を別途検討すべきだと思います。

# 5. 参考

[日本のサマータイムとJava](https://qiita.com/sota2502/items/e8df68d9cfebd01af809)
[ほんとうにあったサマータイムについて](https://qiita.com/Kobayashi-asaas/items/adaa3af5c6c705de7223)