C++の基礎 - 時刻

提供:MochiuWiki - SUSE, Electronic Circuit, PCB
ナビゲーションに移動 検索に移動

概要



本日の経過時刻の取得

std::chrono (C++11)

本日の0時0分0秒000からの経過時刻 (mS) を求めるには、std::chrono (C++11以降) を使用する。
なお、C++17以降およびLinux環境では別の手段も存在する。

現在時刻に関する情報は、std::chrononowメソッドで取得できる。
なお、この時刻はUTC (協定世界時) であり日本時間とは異なることに注意する。

 #include <chrono>
 
 std::chrono::system_clock::time_point tp = std::chrono::system_clock::now();


まず、time_since_epocメソッドを使用して、エポックタイムからの経過時刻を取得した後、countメソッドでミリ秒を取得する。
取得した値から現在時刻のミリ秒の部分を得る。

エポックタイムは、未規定ではあるが概ね1970年1月1日から始まる。
ただし、経過時刻 (mS) は1兆を超える値になるため、32ビットのint型では格納できないことに注意する。

 // エポックタイムからの経過時刻
 std::chrono::milliseconds tp_msec = std::chrono::duration_cast<std::chrono::milliseconds>(tp.time_since_epoch());
 
 // 経過時刻をミリ秒 (整数値) に変換する
 long long all_msec = tp_msec.count();
 
 // 1秒未満の値を取り出す
 int msec = all_msec % 1000;


時間、分、秒を取得するため、time_t型に変換する。
さらに、現地時間 (日本時間) に変換する。

 time_t time = std::chrono::system_clock::to_time_t(tp);
 struct tm* t = localtime(&time);


この時、本日の経過時刻 (mS) は計算できるが、std::chrono::system_clock::time_point型は減算ができるため、本日の0時0分0秒000のタイムスタンプを求める。

 // 本日0時0分0秒000のtime_pointを求める
 std::chrono::system_clock::time_point day_tp = tp;
 
 // 0時にする
 day_tp -= std::chrono::hours(t->tm_hour);
 
 // 0分にする
 day_tp -= std::chrono::minutes(t->tm_min);
 
 // 0秒にする
 day_tp -= std::chrono::seconds(t->tm_sec);
 
 // コンマ0秒にする
 day_tp -= std::chrono::milliseconds(msec);


time_point型同士で減算した後、本日の経過時刻 (mS) を求める。

 std::chrono::milliseconds tp_msec_today = std::chrono::duration_cast<std::chrono::milliseconds>(tp - day_tp);
 int msec_today = tp_msec_today.count();


以下の例では、UTC時間、日本時間、フランス時間の3つのタイムゾーンで現在時刻と本日の経過時間を表示している。

以下の例は夏時間を考慮していないため、実務では、タイムゾーンライブラリ (例: Howard Hinnant's dateライブラリ等) の使用を検討すること。
(フランスの時差は、一般的に、UTC+1 であるが、夏時間中は UTC+2 となる)

 #include <iostream>
 #include <chrono>
 #include <ctime>
 #include <iomanip>
 
 // タイムゾーンごとの時差 (秒)
 const int JAPAN_OFFSET  = 9 * 3600;  // 日本時間
 const int FRANCE_OFFSET = 1 * 3600;  // フランス時間 (ただし、夏時間を考慮していないことに注意する)
 
 // 指定されたタイムポイントを文字列形式に変換する
 // オフセットを適用してタイムゾーンを調整する
 std::string format_time(const std::chrono::system_clock::time_point &tp, int offset = 0)
 {
    auto time = std::chrono::system_clock::to_time_t(tp) + offset;
    auto msec = std::chrono::duration_cast<std::chrono::milliseconds>(tp.time_since_epoch()).count() % 1000;
    std::tm* t = std::gmtime(&time);
 
    std::ostringstream oss;
    oss << std::put_time(t, "%Y-%m-%d %H:%M:%S") << '.' << std::setfill('0') << std::setw(3) << msec;
    return oss.str();
 }
 
 // 指定されたタイムポイントの日の開始時刻 (00:00:00.000) を計算する
 // タイムゾーンのオフセットも考慮する
 std::chrono::system_clock::time_point get_day_start(const std::chrono::system_clock::time_point &tp, int offset = 0)
 {
    auto time = std::chrono::system_clock::to_time_t(tp) + offset;
    std::tm* t = std::gmtime(&time);
    return tp - std::chrono::hours(t->tm_hour) - std::chrono::minutes(t->tm_min) - std::chrono::seconds(t->tm_sec) 
              - std::chrono::milliseconds(std::chrono::duration_cast<std::chrono::milliseconds>(tp.time_since_epoch()).count() % 1000)
              - std::chrono::seconds(offset);
 }
 
 // 指定されたタイムゾーンの現在時刻と経過時間を表示する
 void print_time_info(const std::string &zone_name, const std::chrono::system_clock::time_point &tp, int offset = 0)
 {
    auto day_start = get_day_start(tp, offset);
    auto msec_today = std::chrono::duration_cast<std::chrono::milliseconds>(tp - day_start).count();
 
    std::cout << zone_name << ":" << std::endl;
    std::cout << "  現在時刻: " << format_time(tp, offset) << std::endl;
    std::cout << "  経過時間: " << msec_today << " ms" << std::endl;
    std::cout << std::endl;
 }
 
 int main()
 {
    auto now = std::chrono::system_clock::now();
 
    print_time_info("UTC", now);
    print_time_info("日本時間", now, JAPAN_OFFSET);
    print_time_info("フランス時間", now, FRANCE_OFFSET);  // フランス時間で
 
    return 0;
 }


# 出力例

UTC:
  現在時刻: 2024-09-05 05:30:45.123
  経過時間: 19845123 ms

日本時間:
  現在時刻: 2024-09-05 14:30:45.123
  経過時間: 52245123 ms

フランス時間:
  現在時刻: 2024-09-05 06:30:45.123
  経過時間: 23445123 ms


※注意
より正確な時間を取得する場合は、タイムゾーンライブラリ (例: Howard Hinnant's dateライブラリ等) の使用を推奨する。

std::chrono (C++17)

C++17以降では、<chrono>ライブラリに大幅な拡張が加えられており、タイムゾーンやカレンダーの扱いが簡単になった。
特に、std::chrono::zoned_timeクラスの導入により、タイムゾーンを考慮した時間の扱いが可能になった。

以下の例では、C++17の新機能を使用して、UTC時間、日本時間、フランス時間の3つのタイムゾーンで現在時刻と本日の経過時間を表示している。

処理の流れを以下に示す。

  1. zoned_timeクラスを使用して、特定のタイムゾーンでの時間を表現する。
  2. floor<days>を使用して、その日の開始時刻 (午前0時) を計算する。
  3. duration_castを使用して、経過時間をミリ秒単位で計算する。
  4. タイムゾーンは文字列で指定する。 ("UTC", "Asia/Tokyo", "Europe/Paris")
    これにより、夏時間なども自動的に考慮される。
  5. タイムゾーンが見つからない場合等はエラーとする。


 #include <iostream>
 #include <chrono>
 #include <iomanip>
 
 using namespace std::chrono;
 
 template<typename Clock, typename Duration>
 void print_time_info(const std::string &zone_name, const zoned_time<Duration> &zt)
 {
    auto now = zt.get_local_time();
    auto today = floor<days>(now);
    auto time_since_midnight = now - today;
 
    std::cout << zone_name << ":\n";
    std::cout << "  現在時刻: " << now << "\n";
    std::cout << "  経過時間: " << duration_cast<milliseconds>(time_since_midnight).count() << " ms\n\n";
 }
 
 int main()
 {
    try {
       // 現在のUTC時間を取得
       auto now = system_clock::now();
 
       // 各タイムゾーンでの時間を計算
       auto utc = zoned_time<milliseconds>("UTC", now);
       auto tokyo = zoned_time<milliseconds>("Asia/Tokyo", now);
       auto paris = zoned_time<milliseconds>("Europe/Paris", now);
 
       // 各タイムゾーンの情報を表示
       print_time_info("UTC", utc);
       print_time_info("日本時間", tokyo);
       print_time_info("フランス時間", paris);
    }
    catch (const std::exception &e) {
       std::cerr << "エラー: " << e.what() << std::endl;
       return -1;
    }
 
    return 0;
 }


# 出力例

UTC:
  現在時刻: 2024-09-05 05:30:45.123456
  経過時間: 19845123 ms

日本時間:
  現在時刻: 2024-09-05 14:30:45.123456
  経過時間: 52245123 ms

フランス時間:
  現在時刻: 2024-09-05 07:30:45.123456
  経過時間: 27045123 ms


std::chrono (C++20)

C++20では、std::chrono::monthstd::chrono::yearが追加されたため、月初めからや年頭からの経過時刻 (mS) を取得することができる。
また、C++20では、現地時刻用のtime_point型、今日の時間情報を取得するtime_of_dayクラスも追加されるため、time_t型に変換せずに簡単に取得できる。

C++20での主な改善点を以下に示す。

  • カレンダーと時間帯のサポートの強化された。
  • 日付の操作がより直感的になった。
  • 時間帯データベースへのアクセスが改善された。
  • 書式化機能が強化されて、日付と時間の出力がより柔軟になった。


C++20の時間関連機能において、複雑な日付と時間の操作を含むアプリケーションの開発が大幅に簡素化された。

 #include <iostream>
 #include <chrono>
 #include <format>
 
 using namespace std::chrono;
 
 template<typename Clock, typename Duration>
 void print_time_info(const std::string &zone_name, const zoned_time<Duration> &zt)
 {
    auto now = zt.get_local_time();
    auto today = floor<days>(now);
    auto time_since_midnight = now - today;
 
    auto ymd = year_month_day{today};
    auto time = hh_mm_ss{floor<milliseconds>(time_since_midnight)};
 
    std::cout << std::format("{}: \n", zone_name);
    std::cout << std::format("  現在時刻: {:%Y-%m-%d %H:%M:%S}.{:03d}\n", now, time.subseconds().count());
    std::cout << std::format("  経過時間: {} ms\n\n", duration_cast<milliseconds>(time_since_midnight).count());
 }
 
 int main()
 {
    try {
       // 現在のシステム時間を取得
       auto now = system_clock::now();
 
       // タイムゾーンオブジェクトを取得
       const auto utc = locate_zone("UTC");
       const auto tokyo = locate_zone("Asia/Tokyo");
       const auto paris = locate_zone("Europe/Paris");
 
       // 各タイムゾーンでの時間を計算
       auto utc_time = zoned_time{utc, now};
       auto tokyo_time = zoned_time{tokyo, now};
       auto paris_time = zoned_time{paris, now};
 
       // 各タイムゾーンの情報を表示
       print_time_info("UTC", utc_time);
       print_time_info("日本時間", tokyo_time);
       print_time_info("フランス時間", paris_time);
  
       // 日付操作の例
       auto today = floor<days>(utc_time.get_local_time());
       auto this_year = year_month_day{today}.year();
       auto new_years_day = sys_days{this_year/January/1};
       auto days_since_new_year = floor<days>(utc_time.get_sys_time() - new_years_day);
 
       std::cout << std::format("今年の元旦から今日までの日数: {} 日\n", days_since_new_year.count());
    }
    catch (const std::exception &e) {
       std::cerr << "エラー: " << e.what() << std::endl;
       return -1;
    }
 
    return 0;
 }


# 出力例

UTC: 
  現在時刻: 2024-09-05 05:30:45.123
  経過時間: 19845123 ms

日本時間: 
  現在時刻: 2024-09-05 14:30:45.123
  経過時間: 52245123 ms

フランス時間: 
  現在時刻: 2024-09-05 07:30:45.123
  経過時間: 27045123 ms

今年の元旦から今日までの日数: 249 日


※注意
一部の機能 (特にタイムゾーン関連) は、使用する実装によっては、まだ完全にはサポートされていない可能性がある。

Howard Hinnant's dateライブラリ

Howard Hinnant's dateライブラリとは

Howard Hinnant's dateライブラリは強力で柔軟性が高いため、C++20の標準ライブラリと同様の機能を提供している。
また、C++20の標準ライブラリと非常に互換性が高く、追加の機能も使用できる。

特に、C++20のサポートがまだ完全でない環境やより高度なタイムゾーン機能が必要な場合に適している。

Howard Hinnant's dateライブラリのメリットを以下に示す。

  • C++20の標準ライブラリとよく似たインターフェースを提供しているため、将来的にC++20への移行時の変更が最小限で済む。
  • 広範なプラットフォームとコンパイラでサポートされている。
  • タイムゾーンデータベースの更新が容易である。
  • 追加の機能 (例: タイムゾーン間の変換) が利用可能である。


なお、Howard Hinnant's dateライブラリのライセンスは、MITライセンスに準拠している。

Howard Hinnant's dateライブラリのインストール

Howard Hinnant's dateライブラリのビルドに必要なライブラリをインストールする。

# RHEL
sudo dnf install libcurl-devel

# SUSE
sudo zypper install libcurl-devel


Howard Hinnant's dateライブラリのGithubにアクセスして、ソースコードをダウンロードする。
ダウンロードしたファイルを解凍する。

tar xf date-<バージョン>.tar.gz
cd date-<バージョン>


または、git cloneコマンドを実行して、ソースコードをダウンロードすることもできる。

git clone https://github.com/HowardHinnant/date.git
cd date


Howard Hinnant's dateライブラリをビルドおよびインストールする。

mkdir build && cd build

cmake -DCMAKE_BUILD_TYPE=Release \
      -DCMAKE_INSTALL_PREFIX=<Howard Hinnant's dateライブラリのインストールディレクトリ> \
      -DBUILD_SHARED_LIBS=ON     \
      -DBUILD_TZ_LIB=ON          \  # タイムゾーンライブラリをビルドする場合 (推奨)
      -DMANUAL_TZ_DB=ON          \  # 開発者がソースコード内で set_install を呼び出して手動で TZ DB を設定する場合
      -DENABLE_DATE_TESTING=ON   \  # ユニットテストを有効にする場合
      ..
make -j $(nproc)
make install


サンプルコード

以下の例では、Howard Hinnant's dateライブラリを使用して、UTC時間、日本時間、フランス時間の3つのタイムゾーンで現在時刻と本日の経過時間を表示している。

 #include <iostream>
 #include <iomanip>
 #include "date/date.h"
 #include "date/tz.h"
 
 using namespace date;
 using namespace std::chrono;
 
 template<typename Clock, typename Duration>
 void print_time_info(const std::string &zone_name, const zoned_time<Duration> &zt)
 {
    auto now = zt.get_local_time();
    auto today = floor<days>(now);
    auto time_since_midnight = now - today;
 
    auto ymd = year_month_day{today};
    auto time = make_time(floor<milliseconds>(time_since_midnight));
 
    std::cout << zone_name << ": \n";
    std::cout << "  現在時刻: " << ymd << ' ' << time << '\n';
    std::cout << "  経過時間: " << duration_cast<milliseconds>(time_since_midnight).count() << " ms\n\n";
 }
 
 int main()
 {
    try {
       // 現在のシステム時間を取得
       auto now = system_clock::now();
 
       // 各タイムゾーンでの時間を計算
       auto utc_time = zoned_time{"UTC", now};
       auto tokyo_time = zoned_time{"Asia/Tokyo", now};
       auto paris_time = zoned_time{"Europe/Paris", now};
 
       // 各タイムゾーンの情報を表示
       print_time_info("UTC", utc_time);
       print_time_info("日本時間", tokyo_time);
       print_time_info("フランス時間", paris_time);
 
       // 日付操作の例
       auto today = floor<days>(utc_time.get_local_time());
       auto this_year = year_month_day{today}.year();
       auto new_years_day = sys_days{this_year/January/1};
       auto days_since_new_year = floor<days>(utc_time.get_sys_time() - new_years_day);
 
       std::cout << "今年の元旦から今日までの日数: " << days_since_new_year.count() << " 日\n";
 
       // タイムゾーン間の変換例
       auto tokyo_local = tokyo_time.get_local_time();
       auto paris_local = zoned_time{"Europe/Paris", tokyo_local};
       std::cout << "\n東京時間 " << tokyo_local << std::endl;
       std::cout << "パリ時間 " << paris_local.get_local_time() << std::endl;
    }
    catch (const std::exception &e) {
       std::cerr << "エラー: " << e.what() << std::endl;
       return -1;
    }
 
    return 0;
 }


# 出力例

UTC: 
  現在時刻: 2024-09-05 05:30:45.123
  経過時間: 19845123 ms

日本時間: 
  現在時刻: 2024-09-05 14:30:45.123
  経過時間: 52245123 ms

フランス時間: 
  現在時刻: 2024-09-05 07:30:45.123
  経過時間: 27045123 ms

今年の元旦から今日までの日数: 249 日

東京時間 2024-09-05 14:30:45.123 は、
パリ時間 2024-09-05 07:30:45.123 です。



比較 : C++標準タイムゾーンライブラリとHoward Hinnant's dateライブラリ

C++20の標準タイムゾーンライブラリとHoward Hinnant's dateライブラリでは、互いに長所・短所があり、プロジェクトの要件により適切な選択が異なる場合がある。

  • C++20の標準タイムゾーンライブラリ
    • メリット
      標準ライブラリの一部であるため、追加のライブラリをインストールする必要がない。
      将来的には、全てのC++20準拠のコンパイラでサポートされる。
      標準化されているため、長期的なサポートが期待できる。
    • デメリット
      現時点では、全てのコンパイラやプラットフォームで完全にサポートされているわけではない。
      初期の実装では、機能が制限されている可能性がある。
      タイムゾーンデータベースの更新方法が明確でない場合がある。


  • Howard Hinnant's dateライブラリ
    • メリット
      長年にわたって開発されてきた成熟したライブラリである。
      より広範な機能セットを提供している。
      タイムゾーンデータベースの更新が容易である。
    • 短所
      追加のライブラリをプロジェクトに組み込む必要がある。
      プロジェクトに外部依存関係を追加することになる。


C++20の標準ライブラリを選択する場合は、長期的なプロジェクトであり標準化された解決策を求める場合や外部依存関係を最小限に抑えたい場合等がある。
Howard Hinnant's dateライブラリを選択する場合は、より高度なタイムゾーン機能が必要な場合やタイムゾーンデータベースの頻繁な更新が必要な場合等がある。

ただし、長期的なプロジェクトや標準化された解決策を求める場合は、C++20の標準ライブラリの使用を推奨する。
使用しているコンパイラとプラットフォームが完全にサポートしていることを確認する必要がある。