C++20標準ライブラリ <chrono>ヘッダ Tips

C++20標準ライブラリ`<chrono>`ヘッダに追加予定の カレンダー(Calendar)／タイムゾーン(Time Zone) サポートのTips集。

**注意：** 本ページの内容は、2018年3月のJacksonville会合で採用された [P0355R7 Extending `<chrono>` to Calendars and Time Zones](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0355r7.html) にもとづきます。2018年4月現在、後述サンプルコードを実行できるC++処理系はまだ存在しません。

- [C++標準ライブラリの時計(Clock)](http://d.hatena.ne.jp/yohhoy/20180320/p1)
- [C++標準ライブラリのカレンダー(Calendar)](http://d.hatena.ne.jp/yohhoy/20180322/p1)
- [C++標準ライブラリのタイムゾーン(Time Zone)](http://d.hatena.ne.jp/yohhoy/20180326/p1)


# 基本編
## 現在時刻の出力

```cpp:C++20
#include <chrono>
#include <iostream>

auto now_utc = std::chrono::system_clock::now();
std::cout << now_utc << std::endl;
// 出力例: 2018-04-01 01:23:45.678901 UTC

auto now_local = std::chrono::zoned_time{std::chrono::current_zone(), now_utc};
std::cout << now_local << std::endl;
// 出力例: 2018-04-01 10:23:45.678901 JST

auto now_jst = std::chrono::zoned_time{"Asia/Tokyo", now_utc};
std::cout << now_jst << std::endl;
// 出力例: 2018-04-01 10:23:45.678901 JST

// ISO 8601形式
std::cout << format("%FT%RZ", now_utc) << std::endl;
// 出力例: 2018-04-01T01:23Z
std::cout << format("%FT%TZ", floor<std::chrono::seconds>(now_utc)) << std::endl;
// 出力例: 2018-04-01T01:23:45Z
std::cout << format("%FT%TZ", now_utc) << std::endl;
// 出力例: 2018-04-01T01:23:45.678901Z

std::cout << format("%FT%T%Ez", now_jst) << std::endl;
// 出力例: 2018-04-01T10:23:45.678901+09:00
```


## 年月日時分秒フィールドへの分解

```cpp:C++20
#include <cassert>
#include <chrono>

// 2000-09-01 23:45:56 UTC
using namespace std::chrono_literals;  // y,d,h,min,s
using std::chrono::September;
auto tp = std::chrono::sys_days{2000y/September/1d} + 23h + 45min + 56s;

auto dp = floor<std::chrono::days>(tp);

// 年月日
auto ymd = std::chrono::year_month_day{dp};
assert(ymd.year()  == std::chrono::year{2000});
assert(ymd.month() == std::chrono::month{9});
assert(ymd.day()   == std::chrono::day{1});

// 時分秒
auto hms = std::chrono::hh_mm_ss{tp - dp};
assert(hms.hours()   == std::chrono::hours{23});
assert(hms.minutes() == std::chrono::minutes{45});
assert(hms.seconds() == std::chrono::seconds{56});
```

`year`, `month`, `day`, `hours`, `minutes`, `seconds` 型の値は、明示キャストによって符号なし整数型(`unsigned`)へも変換可能。レガシーAPIに渡すなどの特殊ケースを除いて、型安全のためそれぞれの型のまま扱う方が好ましい。


## 日付の正規化

```cpp:C++20
#include <cassert>
#include <chrono>

using namespace std::chrono_literals;  // y
using std::chrono::year_month_day, std::chrono::sys_days;
// 2018-03-32 == 2018-04-01
year_month_day canonical_ymd = sys_days{ 2018y/3/32 };
assert( canonical_ymd == 2018y/4/1 );
```


## 曜日の計算

```cpp:C++20
#include <cassert>
#include <chrono>

using namespace std::chrono_literals;  // y
using std::chrono::weekday, std::chrono::sys_days;
// 2018-04-01 == 日曜日(0 or 7)
auto wd = weekday{sys_days{2018y/4/1}};
assert( wd == std::chrono::Sunday );
assert( wd.c_encoding() == 0 );
assert( wd.iso_encoding() == 7 );
```


## 日数計算

```cpp:C++20
#include <cassert>
#include <chrono>

using namespace std::chrono_literals;  // y
using std::chrono::sys_days;
// 期間(日数)
auto olympic_days = 1 + (sys_days{2020y/8/9} - sys_days{2020y/7/24}).count();
assert( olympic_days == 17 );

// 1000日前の日付
auto olympic_pre1000d = sys_days{2020y/7/24} - std::chrono::days{1000};
assert( olympic_pre1000d == 2017y/10/28 );
```


# 応用編
## 万年カレンダー
```cpp:C++20
#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() };
  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
*/
```

## 年内進捗率の計算

```cpp:C++20
#include <chrono>

double year_progress()
{
  using namespace std::chrono;
    
  auto today = floor<days>(system_clock::now());
  auto this_year = year_month_day{today}.year();
 
  auto elapsed_days = (today - sys_days{this_year/1/1}).count();
  auto days_in_year = 1 + (sys_days{this_year/12/31} - sys_days{this_year/1/1}).count();
    
  return (elapsed_days * 100. / days_in_year);
}

// 2018-03-27現在は 85/365=23.2877%
```


## うるう年の判定
ある年が うるう年(leap year) か否かを判定する。

```cpp:C++20
#include <cassert>
#include <chrono>

inline
bool is_uruu(int y)
{
  return std::chrono::year{y}.is_leap();
}

assert(  is_uruu(2000) );
assert( !is_uruu(2011) );
assert(  is_uruu(2016) );
assert( !is_uruu(2018) );
```


## 海の日の計算
ある年の 7月第3月曜日（[海の日](https://ja.wikipedia.org/wiki/%E6%B5%B7%E3%81%AE%E6%97%A5)）の日付を計算する。

```cpp:C++20
#include <cassert>
#include <chrono>

std::chrono::year_month_day
marine_day(std::chrono::year y)
{
  using std::chrono::Monday;
  // 7月第3月曜日
  return std::chrono::sys_days{ y/7/Monday[3] };
}

using namespace std::chrono_literals;  // y
assert( marine_day(2018y) == 2018y/7/16 );
```


## プレミアムフライデーの計算
ある月の 最終金曜日（aka [プレミアムフライデー](https://premium-friday.com/)）の日付を計算する。

```cpp:C++20
#include <cassert>
#include <chrono>

std::chrono::year_month_day
premium_friday(std::chrono::year_month ym)
{
  using std::chrono::last;
  using std::chrono::Friday;
  // 最終金曜日
  return std::chrono::sys_days{ ym/Friday[last] };
}

using namespace std::chrono_literals;  // y,d
assert( premium_friday(2018y/4) == 2018y/4/27d );
```