C++

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

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

注意: 本ページの内容は、2018年3月のJacksonville会合で採用された P0355R7 Extending <chrono> to Calendars and Time Zones にもとづきます。2018年4月現在、後述サンプルコードを実行できるC++処理系はまだ存在しません。

基本編

現在時刻の出力

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

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

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::time_of_day{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に渡すなどの特殊ケースを除いて、型安全のためそれぞれの型のまま扱う方が好ましい。

日付の正規化

C++20
#include <cassert>
#include <chrono>

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

曜日の計算

C++20
#include <cassert>
#include <chrono>

using namespace std::chrono_literals;  // y
// 2018-04-01 == 日曜日(0)
auto wd = weekday{sys_days{2018y/4/1}};
assert( wd == std::chrono::Sunday );
assert( (unsigned)wd == 0 );

日数計算

C++20
#include <cassert>
#include <chrono>

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

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

応用編

万年カレンダー

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}} };
  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
*/

年内進捗率の計算

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) か否かを判定する。

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月曜日(海の日)の日付を計算する。

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 プレミアムフライデー)の日付を計算する。

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 );