C++20 Calender and timezone library
- 2018/03/27 01:50
-
本エントリは, 元々 P0355R5 を参考にまとめを行った記事であるが, その後 P0355R7 で
sun
といった曜日を表すリテラルが全てSunday
,may
といった月を表すリテラルが全てMay
といった形式に変更され, またこれらとstd::chrono::last_spec
型のlast
1がstd::literals::chrono_literals
名前空間下からstd::chrono
名前空間下に移動された.system_clock::to_time_t
とsystem_clock::from_time_t
を Deprecated としていたが, Deprecated でなくなった.
といった他に, 細かい文面の改修や, constexpr
がつけられるなどの変更が加えられたため, 本エントリにおいても,
それに従い該当箇所を改変している (差分を示すことも考えたが, ただ見難くなるように感じたため,
そのようなことはしなかった).
これらの変更には対応したつもりだが, 細かい厳密な記述に関しては,
やはり P0355 の最新リビジョンを追って確かめてほしい(そして, 間違った箇所があれば指摘くださると嬉しい).
要旨
先週, 米国フロリダ州ジャクソンビルで ISO C++ 委員会によって, C++技術仕様(TS, 実験的機能ブランチ) と次の国際標準(IS) C++20 に関する作業が行われた. 同会議で Reddit で紹介されているように, C++20 にいくつかの機能が追加された. そのうちの 1 つである, Calender and timezone library に関する新機能, 概要のメモ.
- 全機能の網羅性を主軸としたエントリではない(が, 独断と偏見により重要に感じた内容は結果的に網羅してしまっている部分もある)ため注意
- 以下, 特に断らない限り, 全てのコード片において
using namespace std::chrono;
,using namespace std::chrono_literals;
がされているとする. - 以下, 特に断らない限り, 各コードブロック以外で言及される
内容
といった記述については, 名前空間std::chrono
を省略する.
設計
以下の内容が追加される.
<chrono>
に対するカレンダーおよびタイムゾーンライブラリをサポートするための最小限の拡張- Proleptic Gregorian calendar(civil calendar)
- IANA Time Zone Database を基にしたタイムゾーンライブラリ
- 分数秒, タイムゾーンの略語, UTC オフセットの完全サポートおよび
strftime
のようなフォーマッティング機能 - IANA Time Zone Database でサポートされている, 閏秒を計算するための複数のクロック
カレンダー
本ライブラリ機能によって, 例えば 2016 年を次のように表現することができる.
auto y = std::chrono::year{2016};
auto y = 2016y;
このとき, year
は partial-calendar-type (部分カレンダー型) である.
他の部分カレンダー型と組み合わせることで, year_month_day
などの full-calendar-type (フルカレンダー型) を生成することができる.
フルカレンダー型は, 1 日の制度を持つタイムポイントであり, 部分カレンダー型であるyear
, month
, day
で構成される.
これらからyear_month_day
が構築されると, 内部で計算は一切起きず, 唯一起きることは, year_month_day
が内部でそれらを格納するということだけである.
std::chrono::year_month_day ymd1{2016y, month{5}, day{29}};
この場合, すべての入力がコンパイル時定数であるので, constexpr
にすることができる. さらに, operator/
がオーバーロードされているため, 従来の日付の構文が利用できる.
constexpr std::chrono::year_month_day ymd1{2016y, month{5}, day{29}};
constexpr auto ymd2 = 2016y/May/29d;
constexpr auto ymd3 = May/29d/2016y;
constexpr auto ymd4 = 29d/May/2016y;
static_assert(ymd1 == ymd2);
static_assert(ymd2 == ymd3);
static_assert(ymd3 == ymd4);
static_assert(ymd1.year() == 2016y);
static_assert(ymd1.month() == May);
static_assert(ymd1.day() == 29d);
May
は月を表現するmonth
型のオブジェクトであり, 他にJan
, Feb
, Mar
, Apr
, Jun
, Jul
, Aug
, Sep
, Oct
, Nov
, Dec
が,
次のように定義される(クリックで展開).
カレンダーライブラリは, 例えばスカラ型で直接日付を指定するといったことはなく, 明示的な型指定による表現によって実現する.
なお, 部分カレンダー型(year
, month
に加えてday
型)は, すべて Strong ordering2 を満たし, 加えて以下のメンバ関数をもつ.
- デフォルトコンストラクタ,
unsigned
型(year
型のみint
型)の値を受け付けるコンストラクタ - 各型の単位においてそれを前後に進める, {前|後}置{イン|デ}クリメント演算子
- 各型の単位で計算を行う
+
,-
の二項演算子 - 二項演算子と同様の計算を行い自身に代入する
+=
,-=
の複合代入演算子 unsigned
型(year
型のみint
型)の値への明示的な変換(constexpr explicit operator unsigned() const noexcept;
)- 指定された日付が各単位で適切であるかどうかをチェックする
ok
year
型は, これに加えて, is_leap
, min
, max
メンバ関数をもつ. is_leap
は, 指定された年が閏年であるか判定できる. min
, max
は内部型の最小値と最大値を返す.
また, 上記の通りy
といったリテラル接尾語が定義される.
またday
型は, d
といったリテラル接尾語が定義される.
フルカレンダー型は, sys_days
という型へ変換できる. これは, 次のように定義されている.
constexpr year_month_day::operator sys_days() const noexcept;
sys_days
型に変換することで,system_clock::time_point
ファミリとの間で変換でき, これにより完全な相互運用が可能である. sys_days
は
次のように定義される(クリックで展開).
加えて, sys_days
には次の特性がある.
sys_days
は,system_clock::time_point
がマイクロ秒, またはナノ秒のカウントだけであるのと同様に,system_clock
の基点(エポック)からの日数を示す.sys_days
は, 切り捨てエラーなしで暗黙的にsystem_clock::time_point
に変換される.system_clock::time_point
は, 切り捨てエラーが含まれるため, 暗黙的にsys_days
に変換されない.system_clock::time_point_cast
またはfloor
を使用した明示的な変換によってsystem_clock::time_point
からsys_days
へ変換することができる.
内部で保持する部分カレンダー型year
, month
, day
をそれぞれy_
, m_
, d_
としたとき,
year_month_day
からsys_days
への変換時には(すなわち上記のoperator sys_days()
の呼び出し),
year_month_day::ok()
がtrue
の場合,sys_days
の基点から*this
までの日数を保持するsys_days
を返す- そうでない場合,
y_.ok() && m_.ok() == true
ならばsys_days{y_/m_/last}
からdays
(duration<int32_t, ratio_multiply<ratio<24>, hours::period>>
) の数だけsys_days{y_,m_,last}.day()
からオフセットされたsys_days
を返す - そうでない場合, 未規定である
ここでyear_month_day::ok
は,
y_.ok() && m_.ok() == true
1d
d_
(y_/m_/last).day()
であるとき true
を, そうでない場合, false
を返すメンバ関数である.
constexpr std::chrono::system_clock::time_point tp = std::chrono::sys_days{2016y/May/29d}; // Convert date to time_point
static_assert(tp.time_since_epoch() == 1'464'480'000'000'000us);
constexpr auto ymd = std::chrono::year_month_day{std::chrono::floor<days>(tp)}; // Convert time_point to date
static_assert(ymd == 2016y/May/29d);
constexpr auto tp = std::chrono::sys_days{2016y/May/29d} + 7h + 30min; // 2016-05-29 07:30 UTC
static_assert(year_month_day{sys_days{2017y/January/0}} == 2016y/December/31);
static_assert(year_month_day{sys_days{2017y/January/31}} == 2017y/January/31);
static_assert(year_month_day{sys_days{2017y/January/32}} == 2017y/February/1);
days
の他に, weeks
, months
, years
が
次のように定義される(クリックで展開).
days
,weeks
,months
,years
型は, それぞれ少なくとも $$40000 年の範囲をカバーする.hours
のリテラル接尾語がh
,minutes
のリテラル接尾語がmin
, というように, 今までのリテラル接尾語はduration
型へ対応していたが, 新規に追加されるy
,d
といったリテラル接尾語は,years
,days
に対応するリテラル接尾語ではなく, 上述したように,year
,day
の部分カレンダー型に対応するリテラル接尾語である.- 1 年を, 365.2425 日(グレゴリオ暦の平均長)と定義し, 1 月を, 30.436875 日 と定義するため, システム時刻(
time_point
)を利用した算出結果と,year_month_day
を利用した算出結果は異なる.
constexpr auto date1 = sys_days{1997y/May/30d} - months{5}; // 1996-12-28 19:34:30
constexpr auto date2 = sys_days{1997y/December/29d} - years{1}; // 1996-12-28 18:10:48
現実のカレンダーの利用方法として, たとえば「2016 年の 5 月 29 日」を, 「2016 年の 5 月第 5 日曜日」ということもよくあり, これを表現することもできる.
constexpr std::chrono::system_clock::time_point tp = std::chrono::sys_days{Sunday[5]/May/2016}; // Convert date to time_point
static_assert(tp.time_since_epoch() == 1'464'480'000'000'000us);
constexpr auto ymd = std::chrono::year_month_weekday{std::chrono::floor<days>(tp)}; // Convert time_point to date
static_assert(ymd == Sunday[5]/std::chrono::May/2016);
static_assert(2016y/May/29d == std::chrono::year_month_day{Sunday[5]/May/2016});
constexpr auto wdi = Sunday[5]; // wdi is the 5th Sunday of an as yet unspecified month
static_assert(wdi.weekday() == Sunday);
static_assert(wdi.index() == 5);
static_assert(std::is_same<decltype(Sunday), std::chrono::weekday>::value);
static_assert(std::is_same<decltype(wdi.index()), std::chrono::weekday_indexed>::value);
Sunday
はweekday
型であり, 日曜日を表現するリテラルとして定義され, 他にもMonday
, Tuesday
, Wednesday
, Thursday
, Friday
, Saturday
が
次のように定義される(クリックで展開).
weekday
型は, Strong equality3 を満たし, 加えて, 次のメンバ関数を持つ.
- デフォルトコンストラクタ,
unsigned
,sys_days
, 後に取り上げているlocal_days
型のオブジェクトを受け付けるコンストラクタ - 曜日を前後に進める{前|後}置{イン|デ}クリメント演算子
weekday
,days
型のオブジェクトを受け付けて曜日の計算を行う+
,-
の二項演算子- 二項演算子と同様の計算を行い自身に代入する
+=
,-=
の複合代入演算子 - 指定された曜日が適切であるかどうかをチェックする
ok
operator []
operator []
は, unsigned
型を引数として呼び出すと, weekday_indexed
型のオブジェクトが返され, last_spec
型のオブジェクトを引数として呼び出すと, weekday_last
型のオブジェクトが返される.
weekday_indexed
型は, 月の第 1, 第 2, 第 3, 第 4 または第 5 曜日を表すために使用され, 上記の通り,weekday
メンバ関数,index
メンバ関数を持つ他,ok
メンバ関数を持つ.weekday_last
型は, 月の最後のweekday
を表すために使用され,weekday
メンバ関数,ok
メンバ関数を持つ,last_spec
型は, 最終日を表すために使用され, 同型のオブジェクトlast
がchrono
名前空間下に定義される.
例えば次のようにして, ある月の最終日, 最終 曜日などを表現することができる.
auto today = std::chrono::year_month_day{std::chrono::floor<std::chrono::days>(std::chrono::system_clock::now())};
auto last_day = today.year()/today.month()/last; // last day of this month
auto last_Sunday = today.year()/today.month()/Sunday[last]; // last Sundayday of this month
static_assert(std::is_same<decltype(Sunday[5]), std::chrono::weekday_indexed>::value);
static_assert(std::is_same<decltype(Sunday[last]), std::chrono::weekday_last>::value);
他に, 年を未指定とし, 特定の月日を表す, month_day
, 月の最終日を表すmonth_day_last
, 番目の曜日を表すmonth_weekday
, 月の最終曜日を表すmonth_weekday_last
と,
日を未指定とし, 特定の年月を表す, year_month
, 前述したyear_month_day
, 特定の年月の最終日を表すyear_month_day_last
, 特定の年月の 番目の曜日を表すyear_month_weekday
,
特定の年月の最終曜日を表すyear_month_weekday_last
が提供される.
constexpr auto md = February/1d;
static_assert(std::is_same<decltype(md), std::chrono::month_day>::value);
constexpr auto mdl = February/last; // mdl is the last day of February of an as yet unspecified year
static_assert(mdl.month() == February);
static_assert(std::is_same<decltype(mdl), std::chrono::month_day_last>::value);
auto mw = February/Sunday[5];
constxpr static_assert(std::is_same<decltype(mw), std::chrono::month_weekday>::value);
constexpr auto mwl = February/Sunday[last];
static_assert(std::is_same<decltype(mwl), std::chrono::month_weekday_last>::value);
constexpr auto ym = 2016y/February;
static_assert(std::is_same<decltype(ym), std::chrono::year_month>::value);
constexpr auto ymd = 2016y/February/1d;
static_assert(std::is_same<decltype(ymd), std::chrono::year_month_day>::value);
constexpr auto ymdl = 2016y/February/last;
static_assert(std::is_same<decltype(ymdl), std::chrono::year_month_day_last>::value);
constexpr auto ymw = 2016y/February/Sunday[1];
static_assert(std::is_same<decltype(ymw), std::chrono::year_month_weekday>::value);
constexpr auto ymwl = 2016y/February/last;
static_assert(std::is_same<decltype(ymwl), std::chrono::year_month_day_last>::value);
フルカレンダー型, 部分カレンダー型, sys_days
型の全てで, operator <<
のオーバーロードによる IO ストリームへの出力機能が提供される他, 非メンバ関数として, to_stream
, from_stream
が提供される.
これらはそれぞれ, 指定されたフォーマットの通りに出力する機能と, 指定されたフォーマットを使用して入力ストリームから解析する機能を持つ.
std::cout << std::chrono::sys_days{Sunday[5]/May/2016} << std::endl; // 2016-05-29
std::chrono::to_stream(std::cout, "%b/%d/%Y %A %T", std::chrono::sys_days{2016y/May/29d} + 30min); // May/29/2016 Sunday 00:30:00
auto is = std::istringstream{"2016-5-26"};
auto tp = std::chrono::sys_days{};
std::chrono::from_stream(in, "%F", tp);
if (!is.fail()) std::cout << tp << std::endl; // 2016-05-26
また, time_of_day
クラスが提供される. これは, hours
, minutes
, seconds
, duration<Rep, Period>
の 4 つに対する特殊化が行われており,
それぞれ午前 0 時からの時間, 時間:分, 時間:分:秒, 時間:分:秒: といった書式設定ができる.
std::chrono::time_of_day<std::chrono::hours> todh(1h);
.make12();
todhstd::cout << todh << '\n'; // 1am
.make24();
todhstd::cout << todh << '\n'; // 0100
std::chrono::time_of_day<std::chrono::minutes> todm(1h + 30min);
.make12();
todmstd::cout << todm << '\n'; // 1:30am
.make24();
todmstd::cout << todm << '\n'; // 01:30
std::chrono::time_of_day<std::chrono::seconds> tods(1h + 30min + 30s);
.make12();
todsstd::cout << tods << '\n'; // 1:30:30am
.make24();
todsstd::cout << tods << '\n'; // 01:30:30
std::chdono::time_of_day<std::chrono::milliseconds> todms(1h + 30min + 30s + 30ms);
.make12();
todmsstd::cout << todms << '\n'; // 1:30:30.030am
.make24();
todmsstd::cout << todms << '\n'; // 01:30:30.030
タイムゾーン
タイムゾーンライブラリは, IANA Time Zone Database のパーサーとして提供される4. IANA Time Zone Database には, UTC からのオフセットと地域の省略名5が含まれており, さらに該当する場合, 夏時間(サマータイム)のルールも含まれる. これを表現した,tzdb
, またバージョンごとのtzdb
のリストとなっているtzdb_list
を介して, 任意のtzdb
にアクセスすることができる.
tzdb_list
はシングルトンであり, 非メンバ関数get_tzdb_list
からその参照を得て利用する.
関連する宣言を下記に抜粋する(クリックで展開).
local_time
はlocal_t
という空の擬似クロック型が指定されており, これは当然 C++ の Clock ライブラリコンセプトを満たしていないが, 未定義のタイムゾーンに関するローカル時刻であることを示す.sys_info
構造体は,time_zone
とsys_time
, またはlocal_time
の組み合わせ, およびzoned_time
から取得することができる. 実質的には,time_zone
とsys_time
のペアであり, 低レベル API を表現する.sys_time
からlocal_time
への通常の変換では, 暗黙的にこの構造体が使用される.begin
,end
フィールドは,time_zone
およびtime_point
についてoffset
とabbrev
がbegin
,end
であることを示す.offset
フィールドは, 関連するtime_zone
およびtime_point
に有効な UTC オフセットを示す(offset = local_time - sys_time
).save
フィールドは, 通常local_time
とsys_time
の変換では必要のない”余分な”情報であるが, サマータイムの対応で必要となる.save != 0min
の場合, このsys_info
はサマータイムの時間帯にあると判断する.offset - save
によって, このtime_zone
がサマータイムに対応できていない可能性を導出できる. しかし, この情報は正式なものではなく, そのような情報を確実に取得する唯一の方法は,save == 0min
であるsys_info
を返すtime_point
と, 確認したいtime_zone
を照会することである.abbrev
フィールドは, 関連するtime_zone
およびtime_point
に使用される現在の略語を示す. 略語は,time_zone
間で一意でないため, 略語をtime_zone
と UTC のオフセットに確実にマッピングすることはできない- IO ストリームに対応している.
zoned_time zt = { "Asia/Tokyo", system_clock::now() }; std::cout << zt.get_info() << '\n';
local_info
構造体は, 低レベル API を表す.local_time
からsys_time
への通常の変換では, 暗黙的にこの構造体が使用される.local_time
からsys_time
への変換が唯一(サマータイムでない)で,result == unique
である場合,first
が正しいsys_info
がセットされ,second
が 0 で初期化される.- 変換が存在しない(
result == noexistent
)6場合,first
はlocal_time
の直前で終了するsys_info
がセットされ,second
はlocal_time
の直後に開始するsys_info
がセットされる. - 変換が曖昧(
result == ambiguous
)7な場合,first
はlocal_time
の直後に終了するsys_info
がセットされ,second
はlocal_time
の直前で開始するsys_info
がセットされる. - IO ストリームに対応している.
std::cout << get_tzdb().current_zone()->get_info(local_days{2016y/May/29d}) << '\n';
time_zone
構造体は, 特定の地域の全てのタイムゾーン遷移を表現する. データベースの初期化の過程で, 現在地のタイムゾーン, およびタイムゾーンの情報をなんらかの方法89で構築する. Strong ordering10 を満たす.name
メンバ関数によって,time_zone
の名前11を取得できる.get_info
メンバ関数によって,sys_info
,local_info
を取得できる.to_sys
メンバ関数によって,sys_time
,local_time
を取得できる.time_zone::to_sys(local_time<Duration> tp) const;
: 少なくともseconds
と同じぐらいのsys_time
であり, 引数の精度がさらに高ければそれに合わせられる.tp
からsys_time
への変換が曖昧12である場合,ambiguous_local_time
例外をスローする13.tp
からsys_time
への変換が存在しない14場合,nonexistent_local_time
例外をスローする15.time_zone::to_sys(local_time<Duration> tp, choose z) const;
: 少なくともseconds
と同じぐらいのsys_time
であり, 引数の精度がさらに高ければそれに合わせられる.tp
からsys_time
への変換が曖昧16があいまいである場合,z == choose::earliest
の場合は, サマータイム以前のsys_time
を返し,z == choose::latest
の場合は, サマータイム以後のsys_time
を返す.tp
が 2 つの UTCtime_point
の間に存在しない時間を表す場合, 2 つの UTCtime_point
は同じになり, UTCtime_point
が返される.time_zone::to_local(sys_time<Duration> tp) const;
:tp
と自身のtime_zone
に関連づけられたlocal_time
を返す.
tzdb
は, 前述した通り, タイムゾーンデータベースを表現する.version
は, そのデータベースバージョンを表す.zones
,links
,leaps
は, 検索の高速化のために, 構築時に昇順ソートされる.locate_zone
メンバ関数から, 与えられたstring_view
オブジェクトとname()
が等価であるtime_zone
が見つかった場合, そのtime_zone
へのポインタを取得できる. そうでない場合, 与えられたstring_view
とlink.name()
(ここで,link
は後述しているtime_zone
の代替名を表現するクラスである)が等価であるlink
が見つかった場合,zone.name() == link.target()
のtime_zone
ポインタが取得できる. そうでない場合,runtime_error
例外を送出する. 例外送出以外でこの関数から処理が返るとき, 返される戻り値は必ず有効なtime_zone
へのポインタである.current_zone
メンバ関数から, コンピューターに設定されたローカルタイムゾーンを取得できる.
tzdb_list
は,tzdb
のアトミックポインターをもつ,tzdb
のシングルトンリストである. 複数のバージョンのデータベースを, 同リストを介して一度に使用することができる. 例:for (auto&& v : get_tzdb_list()) { std::cout << v << '\n'; }
front
メンバ関数によって, 先頭tzdb
の参照を得ることができる. これは,reload_tzdb
非メンバ関数に対してスレッドセーフである.erase_after
メンバ関数によって, 与えられたイテレータの後に参照するtzdb
を消去する. 消去された要素の次の要素を指すイテレータが返される. そのような要素が存在しない場合, メンバ関数end
を呼び出し, その結果を返す. なお, ここで消去されたtzdb
を参照することを除いて, ポインター, 参照, イテレータは無効にならない. また, メンバ関数begin
を呼び出し, それによって参照されるtzdb
を消去することはできない.begin
メンバ関数によって, コンテナ内の最初のtzdb
を参照するイテレータ取得できる.cbegin
メンバ関数はbegin
メンバ関数のconst
版である.end
メンバ関数によって, コンテナ内の最後のtzdb
より 1 つ後ろの位置を参照するイテレータを取得できる.cend
メンバ関数はend
メンバ関数のconst
版である.get_tzdb_list
非メンバ関数によって, 同リストの参照を得ることができる. 同メンバ関数への呼び出しがデータベースへの最初のアクセスである場合, データベースを初期化する. この呼び出しによってデータベースが初期化された場合,tzdb
を一つ持つtzdb_list
が構築される. 同メンバ関数を一度に複数のスレッドから呼び出しても競合せず, スレッドセーフである. 何らかの理由で有効なリストの参照を返せず, 1 つ以上の有効なtzdb
を含む場合,runtime_error
例外を送出する.get_tzdb
非メンバ関数によって, 同リストの先頭tzdb
の参照を得ることができる(get_tzdb_list().front()
).locate_zone
非メンバ関数によって, 次の値を得ることができる. なお, これがデータベースへの最初のアクセスである場合, データベースを初期化する.get_tzdb().locate_zone(tz_name);
current_zone
非メンバ関数によって, 次の値を得ることができる.get_tzdb().current_zone();
current_zone()
を介して実装によって提供される.
アプリケーションが実行されている間, 実装はタイムゾーンデータベースの更新を選択することがある.
このアップデートは, アプリケーションによって次に挙げる関数を呼び出さない限り, アプリケーションに影響を与えることはない.
この潜在的に更新されたタイムゾーンデータベースは, リモートタイムゾーンデータベースと呼ぶ.
次のように定義される(クリックで展開).
reload_tzdb
非メンバ関数は, 最初にリモートタイムゾーンデータベースのチェックを行い, ローカルデータベースとリモートデータベースのバージョンが同じである場合はなにもしない. それ以外の場合, リモートデータベースは,get_tzdb_list
非メンバ関数によってアクセスされるtzdb_list
の先頭にプッシュされる. いずれの場合も,get_tzdb_list().front()
が返される. この関数は,get_tzdb_list().front()
とget_tzdb_list().erase_after()
に対してスレッドセーフである. 何らかの理由で有効なtzdb
の参照が戻されない場合,runtime_error
例外が送出される.remote_version
非メンバ関数は, 最新のリモートデータベースバージョンの文字列(std::string
)を返す. リモートバージョンが利用できない場合, 空の文字列が返される. 空でない場合, これをget_tzdb_list().version
と比較して, ローカルデータベースとリモートデータベースが同等かどうかをチェックできる.
zoned_traits
, zoned_time
を利用することで, sys_days
, local_days
といったtime_point
をtzdb
データベースと関連付けることができる.
次のように定義される(クリックで展開).
zoned_traits
によって, zoned_time
のデフォルトコンストラクタの動作をカスタマイズすることができる.
zoned_traits<const time_zone*>::default_zone();
は,std::chrono::locate_zone("UTC")
を返す.zoned_traits<const time_zone*>::locate_zone(string_view name);
は,std::chrono::locate_zone(name)
を返す.
zoned_time
は, Duration
の精度で, time_zone
とtime_point
の論理区切りを表す. Strong equality17を満たす.
次のように定義される(クリックで展開).
- invariant な
zoned_time<Duration>
は常に有効なtime_zone
を参照し, 曖昧でない時間を表す. - デフォルコンストラクタは,
zone_
をzoned_traits::default_zone()
で初期化し,tp_
をデフォルト構築してzoned_time
を構築する. - コピーコンストラクタは, 関連する
time_zone
を転送する.Duration
がnoexcept
コピーコンストラクタである場合,zoned_time<Duration>
はnoexcept
コピーコンストラクタである. zoned_time(const sys_time<Duration>& st)
:zone_
をzoned_traits::default_zone
で初期化し,tp_
をst
で初期化してzoned_time
を構築する.zoned_time(TimeZonePtr z)
:std::move(z)
でzone_
を初期化し,zoned_time
を構築する. このとき,z
は有効なtime_zone
を指していなければならない.zoned_time(string_view name)
:zoned_traits::locate_zone(name)
でzone_
を初期化し,tp_
をデフォルト構築してzoned_time
を構築する.zoned_time(const zoned_time<Duration2, TimeZonePtr>& y) noexcept
:x == y
となるzoned_time
,x
を構築する.zoned_time(TimeZonePtr z, const sys_time<Duration>& st)
:zone_
をstd::move(z)
で初期化し,tp_
をst
で初期化してzoned_time
を構築する. このとき,z
は有効なtime_zone
を指していなければならない.zoned_time(string_view name, const sys_time<Duration>& st)
:{zoned_traits<TimeZonePtr>::locate_zone(name), st}
と同等の構築を行う.zoned_time(TimeZonePtr z, const local_time<Duration>& tp)
:zone_
をstd::move(z)
で初期化し,tp_
をzone_->to_sys(t)
で初期化してzoned_time
を構築する. このとき,z
は有効なtime_zone
を指していなければならない.zoned_time(string_view name, const local_time<Duration>& tp)
:{zoned_traits<TimeZonePtr>::locate_zone(name), tp}
と同等の構築を行う.zoned_time(TimeZonePtr z, const local_time<Duration>& tp, choose c)
:zoned_
をstd::moev(z)
で初期化し,tp
をzone_->to_sys(t, c)
で初期化してzoned_time
を構築する. このとき,z
は有効なtime_zone
を指していなければならない.zoned_time(string_view name, const local_time<Duration>& tp, choose c)
:{zoned_traits<TimeZonePtr>::locate_zone(name), tp, c}
と同等の構築を行う.zoned_time(TimeZonePtr z, const zoned_time<Duration2, TimeZonePtr2>& y)
:zone_
をstd::move(z)
で初期化し,tp_
をz.tp_
で初期化してzoned_time
を構築する. このとき,z
は有効なtime_zone
を指していなければならない.zoned_time(TimeZonePtr z, const zoned_time<Duration2, TimeZonePtr2>& y, choose)
:{z, y}
と同等の構築を行う. このとき,z
は有効なtime_zone
を指していなければならない.choose
パラメータを渡すことができるが, これによって挙動が変わることはない.zoned_time(string_view name, const zoned_time<Duration>& y)
:{zoned_traits<TimeZonePtr>::locate_zone(name), y}
と同等の構築を行う.zoned_time(string_view name, const zoned_time<Duration>& y, choose c)
:{locate_zone(name), y, c}
と同等の構築を行う.choose
パラメータを渡すことができるが, これによって挙動が変わることはない.operator=(const local_time<Duration>& lt)
: 代入後,get_local_time() == lt
となるよう代入し*this
を返す. この代入は,get_time_zone
の戻り値には影響しない.operator sys_time<duration>() const
:get_sys_time()
を返す.operator local_time<duration>() const
:get_local_time()
を返す.get_time_zone
メンバ関数によって,zone_
のポインタを取得できる.get_local_time
メンバ関数によって, 構築時に設定されたタイムゾーンでのlocal_time
オブジェクトを取得できる(retunr zone_->to_local(tp_);
).get_sys_time
メンバ関数によって, 構築時に設定されたタイムゾーンでのsys_time
オブジェクトを取得できる(return tp_;
).get_info
メンバ関数によって, 構築時に設定されたタイムゾーンでのsys_info
オブジェクトを取得できる(return zone_->get_info(tp_);
).
同ライブラリを利用して, 例えば次のように, ある日時の東京の時間帯を得ることができる18.
auto tp1 = std::chrono::sys_days{2016y/May/29d} + 7h + 30min + 6s + 153ms; // 2016-05-29 07:30:06.153 UTC
std::chrono::zoned_time zt1 = {"Asia/Tokyo", tp1};
std::cout << zt1 << '\n'; // 2016-05-29 16:30:06.153 JST
auto tp2 = std::chrono::local_days{2016y/May/29d} + 7h + 30min + 6s + 153ms; // 2016-05-29 07:30:06.153 JTC
auto zt2 = std::chrono::zoned_time{"Asia/Tokyo", tp2};
std::cout << zt << '\n'; // 2016-05-29 07:30:06.153 JST
leap
は, タイムゾーンデータベースの初期化時に構築され, タイムゾーンデータベースに格納されるクラスであり, 主に閏秒を扱うクラスである. 同クラスは, Strong ordering19を満たす.
次のように定義される(クリックで展開).
date
メンバ関数によって,date_
を取得できる.date_
には閏秒挿入の日付が格納されている.- 閏秒挿入の全ての日付を
for (auto& l : get_tzdb().leaps) std::cout << l.date() << '\n';
で確認できる.
またタイムゾーンデータベースの構築時に作成される, time_zone
の代替名を表現するlink
というクラスも提供される.
Clock
新たに utc_clock
, tai_clock
, gps_clock
, file_clock
の 4 つのクロック,
また, そのtime_point
型のエイリアス(utc_time
, utc_seconds
, tai_time
, tai_seconds
, gps_time
, gps_seconds
) が提供される.
utc_clock
は, 協定世界時(UTC)を表現するクロックであり, 1970 年 1 月 1 日木曜日午後 00:00:00 分からの時間を測定する. これには, 閏秒が含まれる.tai_clock
は, 国際原始時計(TAI)を表現するクロックであり, 1958 年 1 月 1 日 00:00:00 からの時間を測定し, この日の UTC(1957-12-31 23:59:50 UTC)よりも 10 秒前にオフセットされている. これには, 閏秒が含まれない20.gps_clock
は GPS 時刻を表現するクロックであり, UTC 1980 年 1 月 6 日 00:00:00 からの時間を測定する. 閏秒は含まれない21.file_clock
は, C++20 で追加されたエイリアス,using file_time_type = std::chrono::time_point<std::chrono::file_clock>;
で利用されるファイルクロックである22.
試用
同ライブラリを利用した任意月のカレンダーを出力するプログラムは, 既にあったのだが, 特別何か別のものは思いつかないので, とりあえず, 任意年の全ての月のカレンダーを出力するプログラムを書いて試用.
#include <algorithm>
#include <array>
#include <chrono>
#include <iostream>
#include <iomanip>
#include <utility>
namespace ns {
template <class> struct weeks_init;
template <std::size_t... s>
struct weeks_init<std::index_sequence<s...>> {
constexpr weeks_init() = default;
constexpr std::array<std::chrono::weekday, sizeof...(s)> operator()() const noexcept { return {{ std::chrono::weekday{s}... }}; }
};
constexpr auto weeks = weeks_init<std::make_index_sequence<7>>()();
inline void print_weeks(std::ostream& os)
{
namespace sc = std::chrono;
std::copy(std::begin(weeks), std::end(weeks), std::ostream_iterator<sc::weekday>(os, " "));
<< '\n';
os }
} // namespace ns
struct print_calendar_year {
explicit constexpr print_calendar_year(std::chrono::year y) noexcept
: y_(y) {}
friend std::ostream& operator<<(std::ostream& os, const print_calendar_year& this_)
{
using namespace std::chrono_literals;
namespace sc = std::chrono;
constexpr int width = 5;
for (unsigned i = 1u, uweek = static_cast<unsigned>(sc::weekday{sc::sys_days{this_.y_/sc::January/1d}}); i <= 12u; ++i) {
auto lastday = (this_.y_/sc::month{i}/sc::last).day();
<< std::setw(ns::weeks.size() * width / 2) << sc::month{i} << '\n';
os ::print_weeks(os << std::setw(width));
ns
unsigned k = 0;
for (; k < uweek; ++k) os << std::setw(width) << " ";
for (sc::day d{1}; d <= lastday; ++d) {
<< std::setw(width) << static_cast<unsigned>(d);
os if (++k > 6) {
= 0;
k << '\n';
os }
}
if (k) os << '\n';
= k;
uweek }
return os;
}
private:
std::chrono::year y_;
};
int main()
{
std::cout << print_calendar_year{std::chrono::year{2000}} << std::endl;
}
実行結果.
タイムゾーンに関するサンプルは, 元の実装のドキュメントで多く取り上げられている. フライトタイムの計算や, IANA タイムゾーンデータベースを利用しないカスタムタイムゾーンを作成する例などが掲示されている.
感想
- とてもよく作り込まれていて, 使いやすそうに感じる.
- C++ にこのような高レベル API が導入されるのは, 少し新鮮.
この一つ前の ISO C++ 委員会による国際会議で C++20 に追加された P0515 Consistent comparison で挙げられている comparison category types での呼称を用いている. 参照: Consistent/three-way comparison↩︎
この一つ前の ISO C++ 委員会による国際会議で C++20 に追加された P0515 Consistent comparison で挙げられている comparison category types での呼称を用いている. 参照: Consistent/three-way comparison↩︎
サマータイムの開始と終了で, 存在しないローカル時間(
nonexistent_local_time
)と重複するローカル時間(ambiguous_local_time
)という概念が生じる.↩︎サマータイムの開始と終了で, 存在しないローカル時間(
nonexistent_local_time
)と重複するローカル時間(ambiguous_local_time
)という概念が生じる.↩︎元の実装を見ると, 現在地のタイムゾーン取得においては, Linux および Mac では特定ファイル(/usr/share/zoneinfo, /usr/share/zoneinfo/uclibc) を読み込み, Windows ではレジストリ値を読み込んでいる. レジストリ値から取得されたネイティブな現在のタイムゾーン名が標準のものと一致する保証はなく, 特に Windows の場合, 得られる名前は必ず標準と異なるものであるため, 標準の名前と関連づけるマッピングが行われる. 元の実装では, Windows の場合ではタイムゾーンデータベースの取得の際に, xml ファイル を取得している.↩︎
元の実装を見ると, OS のタイムゾーンデータベースを利用せず, リモート API があるときは OS 依存またはサードパーティ製のライブラリを利用してデータを取得し, そうでないとき OS のタイムゾーンデータベースを利用する. 元の実装で OS のタイムゾーンデータベースを試用する際には,
DUSE_OS_TZDB=1
をセットしてビルドする. Windows 環境がないので筆者にはわからないが, Windows では OS のタイムゾーンデータベースを利用できないようだ.↩︎この一つ前の ISO C++ 委員会による国際会議で C++20 に追加された P0515 Consistent comparison で挙げられている comparison category types での呼称を用いている. 参照: Consistent/three-way comparison↩︎
サマータイムの開始と終了で, 存在しないローカル時間(
nonexistent_local_time
)と重複するローカル時間(ambiguous_local_time
)という概念が生じる.↩︎2016-11-06 01:30:00 EDT は, 2016-11-06 05:30:00 UTC と 2016-11-06 06:30:00 UTC になりうる.
try { auto zt = zoned_time{"America/New_York", local_days{Sunday[1]/November/2016} + 1h + 30min}; } catch (const ambiguous_local_time&) { }
↩︎サマータイムの開始と終了で, 存在しないローカル時間(
nonexistent_local_time
)と重複するローカル時間(ambiguous_local_time
)という概念が生じる.↩︎2016-03-13 02:30:00 EDT は, 2016-03-13 02:00:00 EST と 2016-03-13 03:00:00 EDT の間にあるため存在しない. どちらも, 2016-03-13 07:00:00 UTC と等価である.
try { auto zt = zoned_time{"America/New_York", local_days{Sunday[2]/March/2016} + 2h + 30min}; } catch (const noexistent_local_time&) {}
↩︎サマータイムの開始と終了で, 存在しないローカル時間(
nonexistent_local_time
)と重複するローカル時間(ambiguous_local_time
)という概念が生じる.↩︎この一つ前の ISO C++ 委員会による国際会議で C++20 に追加された P0515 Consistent comparison で挙げられている comparison category types での呼称を用いている. 参照: Consistent/three-way comparison↩︎
元の実装を見ると, OS のタイムゾーンデータベースを利用せず, リモート API があるときは OS 依存またはサードパーティ製のライブラリを利用してデータを取得し, そうでないとき OS のタイムゾーンデータベースを利用する. 元の実装で OS のタイムゾーンデータベースを試用する際には,
DUSE_OS_TZDB=1
をセットしてビルドする. Windows 環境がないので筆者にはわからないが, Windows では OS のタイムゾーンデータベースを利用できないようだ.↩︎この一つ前の ISO C++ 委員会による国際会議で C++20 に追加された P0515 Consistent comparison で挙げられている comparison category types での呼称を用いている. 参照: Consistent/three-way comparison↩︎
閏秒が UTC に挿入される度に, UTC は TAI の 1 秒遅れとなる. 1961 年発祥の旧 UTC, 1972 年の特別調整, 1972 年から 2017 年 1 月まで行われた27 回の閏秒調整の過程を踏み, UTC は現在 TAI に対して 37 秒遅れている. 参考: http://jjy.nict.go.jp/mission/page1.html↩︎
tai_clock
同様, UTC に閏秒が挿入されるたびに UTC の 1 秒後を表現することとなる. 2017 年時点で, UTC は GPS の 18 秒前 にある. 余談: GPS が UTC との差分を計算する方法に関して.↩︎file_time_type
は C++17 時点で既にエイリアスとして追加されているが,using file_time_type = std::chrono::time_point</*trivial-clock*/>;
と記されており, 具体的なクロック型は明記されていなかった.↩︎
活動継続のためのご支援を募集しています