C++20でサマータイムを謳歌しよう

# 前置き
サマータイムを導入するかしないかで巷が賑わっています。C++20では`std::chrono`の拡張として[Date and time utilities - cppreference.com](https://en.cppreference.com/w/cpp/chrono)が導入されました。Date and time utilitiesは、Time Zone Databaseを利用してサマータイムにも対応しているのでC++でサマータイムを謳歌してみようと思います。

日本でも過去、1948年から1952年までの４年間サマータイムを導入していたことがあります。
[夏時刻法 - Wikipedia](https://ja.wikipedia.org/wiki/%E5%A4%8F%E6%99%82%E5%88%BB%E6%B3%95)の記事によると、1948年は、5月2日0:00時に１時間時刻を進めて、9月の第2土曜日の深夜24:00に１時間時計の針を戻したそうです。

他の記事([サマータイム導入には反対だが「日本夏時間」を試してみた - Qiita](https://qiita.com/belgianbeer/items/f678ae6264a674d33887))で
言及されているように、Time Zone Databaseには、この４年間のサマータイムは記録されているようで、今回は過去のサマータイムの記録からC++でどのようにサマータイムを扱うべきか見ていこうと思います。

タイムゾーンやサマータイムについては[タイムゾーン呪いの書 - Qiita](https://qiita.com/dmikurube/items/15899ec9de643e91497c)で詳しい説明がされているのでご覧ください。

# Date and time utilitiesの基礎
サマータイムの扱い方を見る前にDate and time utilitiesの基本的な使い方を見ていきます。このライブラリには5つの基本的なコンセプトがあります。

- calendar: 日付を表現する型。
- sys_time: Unix時間を表現する型。タイムゾーンとは独立。(`java.time.Instant`相当)
- local_time: ローカル時間（あるタイムゾーンでの時間）を表現する型。タイムゾーンとは独立。(`java.time.LocalDateTime`相当)
- time_zone: タイムゾーン。
- zoned_time: 時間とタイムゾーンの組。(`java.time.ZonedDateTime`相当)

## calendar
calendarは日付を表現することができ、日付の比較等が行うことができます。

```cpp
void basic_calendar() {
	using namespace std::literals;
	using namespace date;

	constexpr year y{ 2018_y };
	constexpr month m{ aug };
	constexpr day d{ 9_d };
	constexpr year_month_day ymd{ 2018_y / aug / 9_d };
	static_assert(ymd == y / m / d);
	static_assert(2020_y / aug / 9_d != ymd);
	static_assert(2018_y <= 2020_y);
}
``` 

## sys_time & local_time
`sys_time` & `local_time`は、ほぼ同じものですが、`sys_time`はUnix時間を、`local_time`はローカルの時間を表現しています。両者とも同じ型同士は比較可能ですが、`sys_time`と`local_time`はタイムゾーン情報が無いため比較できません。また、`sys_time/local_time`ともに実体はクラス・テンプレートで時間精度でパラメータ化されています。

```cpp
void basic_sys_or_local_time() {
	using namespace std::literals;
	using namespace date;

	constexpr auto hour_precision = sys_days{ 2018_y / aug / 9_d } + 2h; // hour単位の精度。
	std::cout << hour_precision << "\n"; // 2018-08-09 0200
	constexpr auto second_precision = sys_days{ 2018_y / aug / 9_d } + 2h + 2s; // second単位の精度。
	std::cout << second_precision << "\n"; // 2018-08-09 02:00:02
	constexpr auto hour_precision_local = local_days{ 2018_y / aug / 9_d } + 2h;
	std::cout << hour_precision_local << "\n"; // 2018-08-09 0200
	constexpr auto second_precision_local = local_days{ 2018_y / aug / 9_d } + 2h + 2s;
	std::cout << second_precision_local << "\n"; // 2018-08-09 02:00:02
	static_assert(hour_precision != second_precision);
	// static_assert(hour_precision != second_precision_local); sys_timeとlocal_timeは比較できず、コンパイル・エラー。
}
```

## time_zone
`time_zone`は、タイムゾーン情報を扱うためのIF群です。Timezone databaseファイルを指定したりリロードする機能も提供されています。

```cpp
void basic_time_zone() {
	date::tzdb const& tz = date::get_tzdb(); // Timezone database
	date::time_zone const* const japan_time_zone = date::locate_zone("Asia/Tokyo"); // 日本の標準タイムゾーン。
}
```

## zoned_time
`zoned_time`は、`sys_time/local_time`に`time_zone`情報を組み合わせたものです。`zoned_time`型のオブジェクトを生成するためには、`make_zoned`というファクトリ関数を使用します。`make_zoned`は、第1引数にタイムゾーン情報を、第2引数に時刻情報を取ります。`zoned_time`はタイムゾーン情報を持つため、`sys_time`から生成された`zoned_time`と`local_time`から生成された`zoned_time`は比較することができます。

`sys_time`と`local_time`では表現する時間が違うことに注意してください。`sys_time`はUnix Timeです。そのため`sys_time`で生成した時刻と`local_time`(日本標準時間)で生成した時刻には9時間のオフセットがあります。

```cpp
void basic_zoned_time() {
	using namespace std::literals;
	using namespace date;

	auto z0 = make_zoned("Asia/Tokyo", sys_days{ 2018_y / aug / 9_d });   // Unix時刻の2018年8月9日00:00 == 日本時刻の2018年8月9日09:00
	auto z1 = make_zoned("Asia/Tokyo", local_days{ 2018_y / aug / 9_d }); // 日本時刻の2018年8月9日00:00。
	assert(z0 != z1);
	std::cout << z0 << "\n"; // 2018-08-09 09:00:00 JST
	std::cout << z1 << "\n"; // 2018-08-09 00:00:00 JST
}
```

Date and time utilitiesのより詳細な例は作者のGithub Wikiページでみることができます。[Examples and Recipes · HowardHinnant/date Wiki](https://github.com/HowardHinnant/date/wiki/Examples-and-Recipes#localtime)

# サマータイム突入時

いよいよC++でサマータイムを扱っていこうと思います。サマータイムに突入した1948年5月1日から2日にかけての時刻の変化を見ていこうと思います。以下のコードでは日本時間1948年5月1日23時から1時間づつ時間を足しています。

```cpp
void begining_day_light_saving_time() {
	using namespace std::literals;
	using namespace date;

	auto z = make_zoned("Asia/Tokyo", local_days{ 1948_y / may / 1 } +23h);

	for (int i = 0; i < 4; ++i) {
		std::cout << z << " = " << format("%F %T %Z\n", z.get_sys_time());
		z = z.get_local_time() + 1h;
	}
}
```

このコードを実行すると23時の表示がされた後、`date::nonexistent_local_time`例外でプログラムが終了していまいます。例外のメッセージを読むと、日本時間で5月2日0時から5月2日の1時は存在しないギャップだと言っています。日本時間の1948年5月2日0時はサマータイムの開始時です。サマータイムの調整のため、この瞬間に日本時間は1時間早くなりました。そのため日本時間上、1948年5月2日0時から1時までは存在しません。`date::nonexistent_local_time`例外が投げられました。

```console:実行結果
1948-05-01 23:00:00 JST = 1948-05-01 14:00:00 UTC

例外で終了。例外のメッセージは、
1948-05-02 00:00:00 is in a gap between
1948-05-02 00:00:00 JST and
1948-05-02 01:00:00 JDT which are both equivalent to
1948-05-01 15:00:00 UTC
```

Unix時間はエポックからの連続した時間でギャップが存在しません。次は、同じく日本時間5月2日23時からUnix時間を進めてみようと思います。今度は例外は発生しません。Unix時間が連続して変化しているのに対して、日本時間はJSTからJDTに変わるタイミングで時間が飛んでいることが確認できます。

```cpp
void begining_day_light_saving_time_with_sys_time() {
	using namespace std::literals;
	using namespace date;

	auto z = make_zoned("Asia/Tokyo", local_days{ 1948_y / may / 1 } +23h);

	for (int i = 0; i < 4; ++i) {
		std::cout << z << " = " << format("%F %T %Z\n", z.get_sys_time());
		z = z.get_sys_time() + 1h; // Unix時間を進める。
	}
}
```

```console:実行結果
1948-05-01 23:00:00 JST = 1948-05-01 14:00:00 UTC
1948-05-02 01:00:00 JDT = 1948-05-01 15:00:00 UTC // 1時間飛んでいる。
1948-05-02 02:00:00 JDT = 1948-05-01 16:00:00 UTC
1948-05-02 03:00:00 JDT = 1948-05-01 17:00:00 UTC
```

# サマータイム離脱時
次は、1948年9月第2土曜日の深夜24:00、サマータイム終了時の近辺の挙動を見てみます。日本時間、9月第2土曜日（11日）の22時から、1時間づつ時間を進めてみます。Date and time utilsでは、第2土曜（`sat[2]`）のようなカレンダーの記法もサポートしています。

```cpp
void finishing_day_light_saving_time() {
	using namespace std::literals;
	using namespace date;

	auto z = make_zoned("Asia/Tokyo", local_days{ 1949_y / sep / sat[2] } +22h);

	for (int i = 0; i < 4; ++i) {
		std::cout << z << " = " << format("%F %T %Z\n", z.get_sys_time());
		z = z.get_local_time() + 1h;
	}
}
```

このコードを実行すると、また例外でプログラムが終了します。今度は`date::ambiguous_local_time`という先ほどとは異なる例外です。例外のメッセージを読むと、日本時間の"1948年9月11日23時"という表現はあいまいであると言っています。日本時間の1948年9月11日24時はサマータイムの終了の瞬間です。サマータイムの時刻調整のため深夜24時に時計の針は1時間戻されました。日本時間11日の23~24時の間は、サマータイム終了前と終了後で2回同じ時刻を刻んでいることになります。

```console:実行結果
1948-09-11 22:00:00 JDT = 1948-09-11 12:00:00 UTC

例外で終了。例外のメッセージ。
1948-09-11 23:00:00 is ambiguous.  It could be
1948-09-11 23:00:00 JDT == 1948-09-11 13:00:00 UTC or
1948-09-11 23:00:00 JST == 1948-09-11 14:00:00 UTC
```

同じサマータイムの終了近辺でUnix時間を進めて確認します。サマータイムの終了時点(JDTからJSTに切り替わる時点)時計の針が23:00時に戻っていることが確認できます。

```cpp
void finishing_day_light_saving_time_with_sys_time() {
	using namespace std::literals;
	using namespace date;

	auto z = make_zoned("Asia/Tokyo", local_days{ 1948_y / sep / sat[2] } +22h);

	for (int i = 0; i < 4; ++i) {
		std::cout << z << " = " << format("%F %T %Z\n", z.get_sys_time());
		z = z.get_sys_time() + 1h;
	}
}
```

```console:実行時間
1948-09-11 22:00:00 JDT = 1948-09-11 12:00:00 UTC
1948-09-11 23:00:00 JDT = 1948-09-11 13:00:00 UTC
1948-09-11 23:00:00 JST = 1948-09-11 14:00:00 UTC // 23時からもう一度時計を進める。
1948-09-12 00:00:00 JST = 1948-09-11 15:00:00 UTC
```

# 最後に

今回はC++20でサマータイムを扱う方法を見てきました。

ローカル時間`local_time`を使用して時刻の調整を行うと、ローカル時間のギャップや曖昧さのため例外が発生することが分かりました。また、以下のようにサマータイムの開始・終了を跨いだ時間の計算では、`sys_time`を使用した計算と`local_time`を使用した計算では計算結果が異なります。速度など単位時間当たりの計算で`local_time`を使用している場合は意図しない値になる可能性があるので注意が必要です。

```cpp
void diff_across_day_light_saving() {
	using namespace std::literals;
	using namespace date;

	auto beforeJDT = make_zoned("Asia/Tokyo", local_days{ 1948_y / may / 1 } + 23h);
	auto afterJDT = make_zoned("Asia/Tokyo", local_days{ 1948_y / may / 2 }  + 1h);
	
	std::cout << date::format("%H:%M\n", afterJDT.get_sys_time() - beforeJDT.get_sys_time()); // 1時間差。
	std::cout << date::format("%H:%M\n", afterJDT.get_local_time() - beforeJDT.get_local_time()); // 2時間差。
}
```

まとめると時刻に対する演算では`sys_time`を使用して、ユーザへの表示では`local_time`を使用するのが良いようです。

**Date and time utilsは表現力が高く、型安全なライブラリです。サマータイムも扱えることが分かったので、サマータイム導入を隠れ蓑にしてC++20のツールチェインを導入していきましょう！**

# 参考リンク
- [Date and time utilities - cppreference.com](https://en.cppreference.com/w/cpp/chrono)
- [WELCOME TO THE TIME ZONE](http://schd.ws/hosted_files/cppcon2016/0f/Welcome%20To%20The%20Time%20Zone%20-%20Howard%20Hinnant%20-%20CppCon%202016.pdf)
- [Howard Hinnant/date: A date and time library based on the C++11/14/17 <chrono> header](https://github.com/HowardHinnant/date)
- [ぼくたちのかんがえたさいきょうのi18n国家 - Qiita](https://qiita.com/yugui/items/55f2529c5a731badeff7)
- [タイムゾーン呪いの書 - Qiita](https://qiita.com/dmikurube/items/15899ec9de643e91497c)
- [夏時刻法 - Wikipedia](https://ja.wikipedia.org/wiki/%E5%A4%8F%E6%99%82%E5%88%BB%E6%B3%95)
- [ソースコード@gist](https://gist.github.com/mitsutaka-takeda/95ff723ec481ce34326ad6e3654a0ec9)