C++2a(C++20)標準ライブラリ <chrono> ヘッダに追加される カレンダー(Calendar) サポートについてざっくりメモ。
本記事では簡単のため名前空間std::chrono
を省略する。またタイムゾーン(Time Zone)サポートには言及しない。
まとめ:
- 型安全(Type safety): 年(
year
), 月(month
), 日(day
), 年月日(year_month_day
)を型システム上で表現する。数値からの暗黙変換は禁止。- 例: 2020年7月24日*1は
year_month_day{2020, 7, 24}
ではなくyear_month_day{year{2020}, month{7}, day{24}}
のように記述する。
- 例: 2020年7月24日*1は
- 既存chronoライブラリとの統合: 年月日(
year_month_day
) と システム時刻(system_clock::time_point
) は相互変換可能。年月日はフィールド単純保持、システム時刻は実在するTime pointを表す。*2- 例1:
auto tp = sys_days{2020y/July/24} + 9h + 30min + 15s; year_month_day ymd{floor
(tp)}; - 例2: 2018年3月32日は2018年4月1日
sys_days{2018y/3/32} == sys_days{2018y/4/1}
- 例1:
- 日付リテラルの表記順は 年/月/日, 月/日/年[アメリカ式], 日/月/年[イギリス式] をサポート。*3
- 例: 2020年7月24日は
2020y/July/24
,2020y/7/24
,July/24/2020
,24d/July/2020
,24d/7/2020
*4
- 例: 2020年7月24日は
- 曜日(
weekday
), 最終日(last_spec
)を用いて、“ある月の最終日”、“ある月の第N X曜日”、“ある月の最終X曜日”も表現可能。*5- 例: 2020年7月の最終日
2020y/7/last == 2020y/7/31
、2020年7月の第1月曜日2020y/7/Monday[1] == 2020y/7/6
、2020年7月の最終月曜日2020y/7/Monday[last] == 2020y/7/27
- 例: 2020年7月の最終日
- 日付計算サポート: N年(
years
), N月(months
), N週(weeks
), N日(days
) 前/後を計算可能。- 注意: 一年
years{1}
は 146097/400 日(≒365.24日)、一ヶ月months{1}
は 1/12 年(≒30.43日) のDurationと定義される。年月日(year_month_day
)またはシステム時刻(Time point)のいずれの型で計算するかで結果が異なる。 - 例: 3ヶ月前(2020-04-24)
2020y/7/24 - months{3}
、16日後(2020-08-09)sys_days{2020y/7/24} + days{16}
*6
- 注意: 一年
- IOストリーム出力(
operator<<
)、書式指定出力(to_stream
)、書式指定入力(from_stream
)を追加提供。- 例:
std::cout << 2020y/7/24;
は "2020-07-24" を出力。
- 例:
- グレゴリオ歴(Gregorian calendar)のみサポート。要件を満たせば独自の暦との相互運用可。
- (当然ながら)国・地域・文化で異なる “祝日” のサポートはない。
おまけ:任意月のカレンダーを出力するサンプルプログラム。
// C++2a #include <chrono> #include <iostream> #include <iomanip> void print_calendar(std::ostream& os, const std::chrono::year_month& ym) { using namespace std::chrono; unsigned weekday_offset{ weekday{sys_days{ym/1}}.c_encoding() }; // P1466R3 unsigned lastday_in_month{ (ym/last).day() }; os << " " << ym << "\n" << "Su Mo Tu We Th Fr Sa\n"; unsigned wd = 0; while (wd++ < weekday_offset) os << " "; for (unsigned d = 1; d <= lastday_in_month; ++d, ++wd) os << std::setw(2) << d << (wd % 7 == 0 ? '\n' : ' '); } int main() { using namespace std::chrono_literals; print_calendar(std::cout, 2018y/3); } /* 2018/Mar Su Mo Tu We Th Fr Sa 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 */
2020-10-23追記:おまけプログラムにP1466R3を反映。std::chrono::weekday
から整数値(unsigned
)への変換では、日曜始まりの範囲[0, 6]を返すc_encoding
または月曜始まりの範囲[1, 7]を返すiso_encoding
のいずれかを明示する必要がある。
関連URL
- P0355R7 Extending <chrono> to Calendars and Time Zones
- 2018 Jacksonville ISO C++ Committee Reddit Trip Report : cpp
- https://github.com/HowardHinnant/date
- C++標準ライブラリの時計(Clock) - yohhoyの日記
*1:2020-07-24 は東京オリンピックの開会式予定日ですって。特例法で祝日にする(他祝日を変更)とかなんとか。
*2:本文中の floor<D> は名前空間 std::chrono で定義されるDuration変換関数(C++17で追加)。days は日単位を表すDuration型、sys_days はシステムクロックの日単位Time point型(いずれもC++2aで追加)。
*3:ユーザ定義リテラルは名前空間 std::literals::chrono_literals 以下で定義される。本文中の例では年(year)を返す operator""y 、日(day)を返す operator""d 、7番目の月(month)を表す定数 July を利用している。年月日区切りは除算演算子 / オーバーロードにより実現される。
*4:月は2桁表記 2020y/07/24 できなくもないが、8月(08)と9月(09)がill-formedな8進数リテラルとなってしまう。おすすめできない。
*5:本文中の例では、月曜日を表す定数 Monday と、 last_spec 型のタグ定数 last を利用している。いずれも名前空間 std::chrono 以下で定義される。
*6:月単位の加減算をTime point型で計算 sys_days{2020y/7/24} - months{3} すると、3ヶ月前は 2020-04-23 16:32:42 となってしまう。一方で、日単位の加減算は日単位Time point型(sys_days)へ変換してから計算する必要がある。