C++プログラミング例で学ぶstd::put_time:実践的な日付時刻フォーマット

2025-05-31

C++におけるstd::put_timeは、日付と時刻を特定の書式で出力するためのI/Oマニピュレータです。C++11で導入されました。

std::put_timeは、std::coutなどの出力ストリームに対して、std::tm構造体で表された時刻情報を指定された書式文字列に従って整形し、出力する際に使用します。

使用方法

基本的な使い方は以下のようになります。

#include <iostream>  // std::cout
#include <iomanip>   // std::put_time
#include <ctime>     // std::time_t, struct std::tm, std::localtime

int main() {
    // 現在の時刻を取得
    std::time_t t = std::time(nullptr);
    // tm構造体に変換 (ローカルタイム)
    struct std::tm* tm_info = std::localtime(&t);

    // 日付と時刻を指定された書式で出力
    std::cout << "現在時刻: " << std::put_time(tm_info, "%Y-%m-%d %H:%M:%S") << std::endl;

    // 別の書式で出力
    std::cout << "曜日と日付: " << std::put_time(tm_info, "%A, %B %d, %Y") << std::endl;

    return 0;
}

パラメータ

std::put_timeは2つの引数を取ります。

  1. const std::tm* tmb: 出力したい日付と時刻の情報が格納されたstd::tm構造体へのポインタです。std::localtimestd::gmtime関数などを使って取得します。
  2. const CharT* fmt: 日付と時刻の出力書式を指定する文字列です。この書式文字列は、C言語のstrftime関数で使用されるものとほぼ同じです。

書式指定子 (Format Specifiers)

fmt文字列には、以下のような書式指定子(%で始まる特別な文字)を含めることができます。これらを使って、年、月、日、時、分、秒、曜日などを自由に組み合わせることができます。

指定子説明例 (2025年5月30日 金曜日 20時40分20秒 の場合)
%Y4桁の年2025
%y年の下2桁 (00-99)25
%m月 (01-12)05
%d日 (01-31)30
%H24時間表記の時 (00-23)20
%M分 (00-59)40
%S秒 (00-60)20
%A完全な曜日名 (例: Friday)Friday
%a略称の曜日名 (例: Fri)Fri
%B完全な月名 (例: May)May
%b略称の月名 (例: May)May
%cロケールに応じた標準の日付と時刻Fri May 30 20:40:20 2025
%xロケールに応じた標準の日付05/30/25 (米国の場合)
%Xロケールに応じた標準の時刻20:40:20 (米国の場合)
%j年の日数 (001-366)150 (5月30日は150日目)
%U年の週数 (日曜始まり, 00-53)21 (21週目)
%W年の週数 (月曜始まり, 00-53)21 (21週目)
%w曜日 (0=日曜, 6=土曜)5
%Zタイムゾーン名または略称PDT (環境による)
%%リテラルの%文字%

ロケールとの関係

std::put_timeは、現在設定されているロケール(地域設定)に基づいて日付と時刻の文字列を生成します。例えば、%A (曜日名) や %B (月名) などは、ロケールによって出力される言語が変わります。

注意点

  • std::tm構造体はスレッドセーフではない可能性があるので、マルチスレッド環境で使用する際は注意が必要です。std::localtime_sstd::gmtime_sなどのスレッドセーフな関数、またはC++17以降で導入された<chrono>ライブラリの機能を使用することを検討してください。
  • std::put_timeはI/Oマニピュレータであり、直接文字列を返す関数ではありません。出力ストリーム(std::coutなど)と組み合わせて使用します。文字列として取得したい場合は、std::stringstreamなどと組み合わせて使用します。
#include <iostream>
#include <iomanip>
#include <ctime>
#include <sstream> // std::stringstream

int main() {
    std::time_t t = std::time(nullptr);
    struct std::tm* tm_info = std::localtime(&t);

    std::stringstream ss;
    ss << std::put_time(tm_info, "%Y/%m/%d %H:%M:%S");

    std::string formatted_time_str = ss.str();
    std::cout << "文字列として取得: " << formatted_time_str << std::endl;

    return 0;
}


ヘッダーファイルの忘れ

エラーの原因
std::put_time を使用するためには、<iomanip> ヘッダーを含める必要があります。また、std::tm 構造体や std::time_tstd::localtime などを使用するためには <ctime> (C互換の <time.h>) が必要です。

よくあるエラーメッセージ

  • no matching function for call to 'put_time...'
  • 'put_time' was not declared in this scope

解決策
必要なヘッダーファイルを忘れずにインクルードします。

#include <iostream>  // std::cout
#include <iomanip>   // std::put_time が含まれる
#include <ctime>     // std::time_t, std::tm, std::localtime が含まれる
// #include <chrono> // C++11以降のより新しい日付/時刻ライブラリ

std::tm* が nullptr である、または不正なポインタである

エラーの原因
std::put_timestd::tm 構造体へのポインタを引数として取ります。std::localtimestd::gmtime といった関数は、失敗した場合(例えば、time_t の値が不正な場合)に nullptr を返すことがあります。また、解放済みのメモリや不正なメモリを指すポインタを渡すと、未定義動作(セグメンテーション違反など)を引き起こします。

よくあるエラーメッセージ

  • 意図しない日付/時刻が表示される
  • 実行時にクラッシュ(セグメンテーション違反など)

解決策
std::localtimestd::gmtime の戻り値が nullptr でないことを常に確認してください。

#include <iostream>
#include <iomanip>
#include <ctime>

int main() {
    std::time_t t = std::time(nullptr);
    struct std::tm* tm_info = std::localtime(&t);

    if (tm_info) { // tm_info が nullptr でないことを確認
        std::cout << "現在時刻: " << std::put_time(tm_info, "%Y-%m-%d %H:%M:%S") << std::endl;
    } else {
        std::cerr << "localtime の取得に失敗しました。" << std::endl;
    }

    return 0;
}
  • C++11以降のスレッドセーフな選択肢
    • localtime_s (Microsoft Visual C++など、一部のコンパイラが提供する非標準関数)
    • gmtime_r (POSIXシステムで利用可能なスレッドセーフ関数)
    • 推奨
      C++20以降で標準化された <chrono> ライブラリ(例: std::chrono::current_zone()->to_local(std::chrono::system_clock::now()) など)を使用するのが最もモダンで安全な方法です。std::chrono::zoned_time を使用すれば、std::tm に依存せずに日付/時刻を整形できます。

不適切な書式指定子 (Format Specifiers)

エラーの原因
strftime スタイルの書式指定子は、% で始まる特定の文字の組み合わせです。誤った指定子を使用したり、% をエスケープせずにリテラルとして出力しようとしたりすると、予期せぬ結果になります。

よくあるエラーメッセージ

  • ランタイムエラー(まれですが、不正な書式指定子によっては発生し得る)。
  • コンパイルエラーは出にくいですが、実行時に期待する出力が得られない。

解決策
strftime の書式指定子のリストを正確に参照し、正しい指定子を使用してください。% をリテラルとして出力したい場合は %% を使用します。

#include <iostream>
#include <iomanip>
#include <ctime>

int main() {
    std::time_t t = std::time(nullptr);
    struct std::tm* tm_info = std::localtime(&t);

    // 正しい書式指定子の例
    std::cout << "年-月-日: " << std::put_time(tm_info, "%Y-%m-%d") << std::endl;

    // リテラル % を出力したい場合
    std::cout << "パーセンテージ: " << std::put_time(tm_info, "%%") << std::endl; // %% を使用

    // 存在しない指定子(例: %q)を使用すると、そのまま出力されるか、未定義動作
    // std::cout << std::put_time(tm_info, "%q") << std::endl; // これは避けるべき

    return 0;
}

ロケールの影響

エラーの原因
std::put_time は、現在設定されているC++ストリームのロケール設定に影響を受けます。特に、%A (完全な曜日名)、%B (完全な月名)、%x (日付のロケール表現)、%X (時刻のロケール表現) などは、ロケールによって出力される言語や書式が変わります。意図しない言語で出力されたり、期待する書式にならないことがあります。

よくある問題

  • 日付の区切り文字が異なる(例: MM/DD/YY の代わりに DD.MM.YY)。
  • 曜日や月が英語でなく、システムのデフォルト言語で表示される。

解決策
明示的にロケールを設定するか、またはロケールに依存しない書式指定子のみを使用します。

#include <iostream>
#include <iomanip>
#include <ctime>
#include <locale> // std::locale, std::locale::classic(), std::locale::global()

int main() {
    std::time_t t = std::time(nullptr);
    struct std::tm* tm_info = std::localtime(&t);

    // デフォルトロケールでの出力(システム設定に依存)
    std::cout << "デフォルトロケール: " << std::put_time(tm_info, "%c") << std::endl;

    // ロケールを "C" (デフォルトのPOSIXロケール、通常は英語) に設定
    // std::locale::global(std::locale("")); // システムのデフォルトロケール
    std::locale::global(std::locale("C")); // Cロケール (英語)
    std::cout.imbue(std::locale("C"));     // cout ストリームにロケールを設定

    // ロケール変更後の出力
    std::cout << "Cロケール: " << std::put_time(tm_info, "%c") << std::endl;
    std::cout << "Cロケール (曜日): " << std::put_time(tm_info, "%A") << std::endl;

    // 日本語ロケールを設定(システムに日本語ロケールがインストールされている場合)
    try {
        std::locale::global(std::locale("ja_JP.UTF-8")); // Linux/macOS
        std::cout.imbue(std::locale("ja_JP.UTF-8"));
        // または Windows の場合: std::locale::global(std::locale("Japanese_Japan.932"));
        // std::cout.imbue(std::locale("Japanese_Japan.932"));

        std::cout << "日本語ロケール: " << std::put_time(tm_info, "%c") << std::endl;
        std::cout << "日本語ロケール (曜日): " << std::put_time(tm_info, "%A") << std::endl;
    } catch (const std::runtime_error& e) {
        std::cerr << "ロケール設定エラー: " << e.what() << std::endl;
    }


    return 0;
}

注意
std::locale の設定はグローバルな状態を変更するため、注意深く使用する必要があります。特にマルチスレッド環境では、スレッドごとにロケールを管理する方が安全です。

std::stringstream との組み合わせでの問題

エラーの原因
std::put_time は出力ストリームに作用するマニピュレータです。std::stringstream を使って文字列に変換する際に、stringstream のロケール設定がデフォルトの "C" ロケールであるため、期待通りの出力にならないことがあります。

よくある問題

解決策
std::stringstream オブジェクトに、明示的に目的のロケールを imbue (設定) します。

#include <iostream>
#include <iomanip>
#include <ctime>
#include <sstream>
#include <locale>

int main() {
    std::time_t t = std::time(nullptr);
    struct std::tm* tm_info = std::localtime(&t);

    std::stringstream ss;

    // std::stringstream に現在のグローバルロケールを設定
    // または特定のロケールを設定 (例: std::locale("ja_JP.UTF-8"))
    ss.imbue(std::locale()); // 現在のグローバルロケールを設定

    ss << std::put_time(tm_info, "%A, %Y年%m月%d日 %H時%M分%S秒");

    std::string formatted_time_str = ss.str();
    std::cout << "stringstream 出力: " << formatted_time_str << std::endl;

    return 0;
}

時刻の精度(ミリ秒、マイクロ秒)が扱えない

問題
std::put_timestd::tm 構造体は、秒までの精度しか持ちません。ミリ秒やマイクロ秒といったより細かい精度が必要な場合、これらの機能では対応できません。

解決策
C++11で導入された <chrono> ライブラリを使用します。これはより高精度で柔軟な日付/時刻操作を提供します。

#include <iostream>
#include <chrono>
#include <iomanip> // C++11 以降の put_time には必要
// #include <format> // C++20 以降で推奨される日付/時刻整形

int main() {
    // 現在のシステム時刻を取得 (高精度)
    auto now = std::chrono::system_clock::now();

    // ミリ秒、マイクロ秒を含む時刻の表示 (C++17までは手動で計算)
    // C++20 の std::format や std::chrono::format を使うのが最も簡潔
    // 今は C++11 の put_time の制約を説明するため、その範囲で考える

    // put_time は std::tm* を必要とするため、一度変換する必要がある
    std::time_t t = std::chrono::system_clock::to_time_t(now);
    struct std::tm* tm_info = std::localtime(&t);

    // 秒以下の精度が必要な場合は、put_time は使えないので手動で計算
    auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()) % 1000;

    if (tm_info) {
        std::cout << "現在時刻 (秒まで): " << std::put_time(tm_info, "%Y-%m-%d %H:%M:%S") << std::endl;
        std::cout << "ミリ秒: " << ms.count() << std::endl;
        std::cout << "合計 (擬似): " << std::put_time(tm_info, "%Y-%m-%d %H:%M:%S") << "." << std::setw(3) << std::setfill('0') << ms.count() << std::endl;
    }

    // C++20 の std::format を使用するとより簡単
    // #if __cplusplus >= 202002L
    //     std::cout << "C++20 format: " << std::format("{:%Y-%m-%d %H:%M:%S}", now) << std::endl; // 秒まで
    //     std::cout << "C++20 format (ミリ秒): " << std::format("{:%Y-%m-%d %H:%M:%S}.{:03}", now, ms.count()) << std::endl;
    // #endif

    return 0;
}


例1: 基本的な日付と時刻の出力

最も基本的な例で、現在のシステム時刻を取得し、特定の書式で標準出力に出力します。

#include <iostream>  // std::cout, std::endl
#include <iomanip>   // std::put_time
#include <ctime>     // std::time_t, struct std::tm, std::localtime, std::time

int main() {
    // 1. 現在のシステム時刻を取得
    //    std::time(nullptr) は現在の時刻を time_t 型で返す
    std::time_t current_time_t = std::time(nullptr);

    // 2. time_t を tm 構造体に変換
    //    std::localtime は time_t をローカルタイムゾーンの tm 構造体に変換する
    //    戻り値は静的ストレージを指すポインタであるため、スレッドセーフではない点に注意
    struct std::tm* time_info = std::localtime(&current_time_t);

    // 3. tm 構造体が有効であることを確認
    if (time_info == nullptr) {
        std::cerr << "エラー: 日付/時刻情報の取得に失敗しました。" << std::endl;
        return 1; // エラー終了
    }

    // 4. std::put_time を使用して整形して出力
    //    第1引数: tm 構造体へのポインタ
    //    第2引数: 書式指定文字列 (strftime の書式に準拠)
    std::cout << "現在の日付と時刻: "
              << std::put_time(time_info, "%Y-%m-%d %H:%M:%S")
              << std::endl;

    // 別の書式で出力
    std::cout << "曜日と日付: "
              << std::put_time(time_info, "%A, %B %d, %Y")
              << std::endl;

    // 時刻のみ出力
    std::cout << "時刻のみ: "
              << std::put_time(time_info, "%H:%M:%S")
              << std::endl;

    return 0;
}

出力例 (実行時の日付と時刻による)

現在の日付と時刻: 2025-05-30 20:42:53
曜日と日付: Friday, May 30, 2025
時刻のみ: 20:42:53

例2: 異なる書式指定子の使用

std::put_time で使用できる様々な書式指定子を組み合わせて、多様な出力形式を試します。

#include <iostream>
#include <iomanip>
#include <ctime>

int main() {
    std::time_t t = std::time(nullptr);
    struct std::tm* tm_info = std::localtime(&t);

    if (tm_info == nullptr) {
        std::cerr << "エラー: 日付/時刻情報の取得に失敗しました。" << std::endl;
        return 1;
    }

    std::cout << "--- 様々な書式指定子の例 ---" << std::endl;

    // 年 (4桁 vs 2桁)
    std::cout << "年 (YYYY): " << std::put_time(tm_info, "%Y") << std::endl; // 2025
    std::cout << "年 (YY):   " << std::put_time(tm_info, "%y") << std::endl; // 25

    // 月 (数字 vs 名前)
    std::cout << "月 (MM):   " << std::put_time(tm_info, "%m") << std::endl; // 05
    std::cout << "月 (Full): " << std::put_time(tm_info, "%B") << std::endl; // May
    std::cout << "月 (Abbr): " << std::put_time(tm_info, "%b") << std::endl; // May

    // 日
    std::cout << "日 (DD):   " << std::put_time(tm_info, "%d") << std::endl; // 30

    // 時 (24時間 vs 12時間)
    std::cout << "時 (HH - 24h): " << std::put_time(tm_info, "%H") << std::endl; // 20
    std::cout << "時 (II - 12h): " << std::put_time(tm_info, "%I") << std::endl; // 08
    std::cout << "午前/午後 (AM/PM): " << std::put_time(tm_info, "%p") << std::endl; // PM

    // 分, 秒
    std::cout << "分 (MM):   " << std::put_time(tm_info, "%M") << std::endl; // 42
    std::cout << "秒 (SS):   " << std::put_time(tm_info, "%S") << std::endl; // 53

    // 曜日
    std::cout << "曜日 (Full): " << std::put_time(tm_info, "%A") << std::endl; // Friday
    std::cout << "曜日 (Abbr): " << std::put_time(tm_info, "%a") << std::endl; // Fri

    // 年の日数
    std::cout << "年間の日数: " << std::put_time(tm_info, "%j") << std::endl; // 150 (5月30日は150日目)

    // ロケール依存の書式
    std::cout << "ロケール日付時刻 (%c): " << std::put_time(tm_info, "%c") << std::endl; // Fri May 30 20:42:53 2025
    std::cout << "ロケール日付 (%x): " << std::put_time(tm_info, "%x") << std::endl;   // 05/30/25
    std::cout << "ロケール時刻 (%X): " << std::put_time(tm_info, "%X") << std::endl;   // 20:42:53

    // リテラル % を出力
    std::cout << "リテラル %: " << std::put_time(tm_info, "%%") << std::endl;

    return 0;
}

出力例 (実行時の日付と時刻、およびシステムのロケール設定による)

--- 様々な書式指定子の例 ---
年 (YYYY): 2025
年 (YY):   25
月 (MM):   05
月 (Full): May
月 (Abbr): May
日 (DD):   30
時 (HH - 24h): 20
時 (II - 12h): 08
午前/午後 (AM/PM): PM
分 (MM):   42
秒 (SS):   53
曜日 (Full): Friday
曜日 (Abbr): Fri
年間の日数: 150
ロケール日付時刻 (%c): Fri May 30 20:42:53 2025
ロケール日付 (%x): 05/30/25
ロケール時刻 (%X): 20:42:53
リテラル %: %

例3: std::stringstream を使って文字列として取得する

std::put_time は直接文字列を返す関数ではないため、文字列として整形された日付/時刻を取得したい場合は std::stringstream と組み合わせて使用します。

#include <iostream>
#include <iomanip>
#include <ctime>
#include <sstream>   // std::stringstream
#include <string>    // std::string

int main() {
    std::time_t t = std::time(nullptr);
    struct std::tm* tm_info = std::localtime(&t);

    if (tm_info == nullptr) {
        std::cerr << "エラー: 日付/時刻情報の取得に失敗しました。" << std::endl;
        return 1;
    }

    // std::stringstream オブジェクトを作成
    std::stringstream ss;

    // std::put_time を使って ss に出力
    ss << "ログ時刻: " << std::put_time(tm_info, "%Y/%m/%d %H:%M:%S");

    // stringstream から std::string を取得
    std::string formatted_time_string = ss.str();

    std::cout << "整形された文字列: " << formatted_time_string << std::endl;

    // 別の文字列ストリームを使って、異なる書式で取得
    std::stringstream ss2;
    ss2 << "シンプル: " << std::put_time(tm_info, "%x %X");
    std::string simple_time_string = ss2.str();
    std::cout << simple_time_string << std::endl;

    return 0;
}

出力例

整形された文字列: ログ時刻: 2025/05/30 20:42:53
シンプル: 05/30/25 20:42:53

std::put_time のロケール依存の書式指定子 (%A, %B, %c, %x, %X など) は、現在のロケール設定に影響されます。std::locale を使用してロケールを変更し、その影響を確認します。

#include <iostream>
#include <iomanip>
#include <ctime>
#include <locale>    // std::locale, std::locale::global, std::cout.imbue

int main() {
    std::time_t t = std::time(nullptr);
    struct std::tm* tm_info = std::localtime(&t);

    if (tm_info == nullptr) {
        std::cerr << "エラー: 日付/時刻情報の取得に失敗しました。" << std::endl;
        return 1;
    }

    // 1. デフォルトのロケールでの出力 (通常は "C" ロケールか、システム設定)
    //    例えば、英語環境であれば May, Friday などと表示される
    std::cout << "--- デフォルトロケール ---" << std::endl;
    std::cout << "日時: " << std::put_time(tm_info, "%c") << std::endl;
    std::cout << "月名: " << std::put_time(tm_info, "%B") << std::endl;
    std::cout << "曜日: " << std::put_time(tm_info, "%A") << std::endl;
    std::cout << std::endl;

    // 2. 日本語ロケールに設定 (システムに日本語ロケールがインストールされている必要あり)
    //    Windows と Linux/macOS でロケール名の書式が異なる場合がある
    try {
        // グローバルロケールを設定
        // Linux/macOS の場合: "ja_JP.UTF-8"
        // Windows の場合: "Japanese" または "Japanese_Japan.932" など
        std::locale::global(std::locale("ja_JP.UTF-8")); // 例: Linux/macOS

        // 標準出力ストリームに新しいロケールを適用
        std::cout.imbue(std::locale()); // 現在のグローバルロケールを imbue する

        std::cout << "--- 日本語ロケール ---" << std::endl;
        std::cout << "日時: " << std::put_time(tm_info, "%c") << std::endl;
        std::cout << "月名: " << std::put_time(tm_info, "%B") << std::endl;
        std::cout << "曜日: " << std::put_time(tm_info, "%A") << std::endl;
        std::cout << std::endl;

    } catch (const std::runtime_error& e) {
        std::cerr << "警告: 日本語ロケールの設定に失敗しました (" << e.what() << ")" << std::endl;
        std::cerr << "ロケール依存の出力は英語のままかもしれません。" << std::endl;
    }

    // 3. "C" ロケールに明示的に設定 (英語表示に戻す)
    std::locale::global(std::locale("C"));
    std::cout.imbue(std::locale("C"));

    std::cout << "--- C ロケール ---" << std::endl;
    std::cout << "日時: " << std::put_time(tm_info, "%c") << std::endl;
    std::cout << "月名: " << std::put_time(tm_info, "%B") << std::endl;
    std::cout << "曜日: " << std::put_time(tm_info, "%A") << std::endl;

    return 0;
}

出力例 (システムのロケール設定とインストール状況による)

--- デフォルトロケール ---
日時: Fri May 30 20:42:53 2025
月名: May
曜日: Friday

--- 日本語ロケール ---
日時: 2025年05月30日 20時42分53秒
月名: 5月
曜日: 金曜日

--- C ロケール ---
日時: Fri May 30 20:42:53 2025
月名: May
曜日: Friday

注意
std::locale::global() はプログラム全体のロケール設定を変更します。マルチスレッド環境では注意が必要です。ストリームごとに imbue() を使用してロケールを設定することもできます。



C言語の strftime 関数

std::put_time は、内部的にC言語の strftime 関数を使用しています。そのため、C++でも直接 strftime を使うことができます。

特徴

  • std::tm 構造体へのポインタを引数に取ります。
  • std::put_time が導入されるC++11より前のバージョンでも利用可能です。
  • 文字列バッファとバッファサイズを明示的に指定する必要があります。これにより、バッファオーバーフローのリスクを管理できます。
  • std::put_time と同じ書式指定子を使用します。

利点

  • 出力先のバッファを制御できるため、特定のメモリ管理シナリオで有利。
  • C++の古いバージョンでも利用可能。

欠点

  • std::put_time と同様に、std::tm がスレッドセーフではない可能性(特に localtimegmtime の使用時)があります。
  • Cスタイルの文字列 (char*) を扱うため、C++の std::string との相互変換が必要になる場合がある。
  • バッファの確保と解放、サイズの管理が必要。

コード例

#include <iostream>
#include <ctime>    // time_t, tm, localtime, strftime
#include <string>   // std::string
#include <vector>   // std::vector (動的なバッファ確保のため)

int main() {
    std::time_t t = std::time(nullptr);
    struct std::tm* tm_info = std::localtime(&t);

    if (tm_info == nullptr) {
        std::cerr << "エラー: 日付/時刻情報の取得に失敗しました。" << std::endl;
        return 1;
    }

    // バッファサイズを適切に設定する
    // 十分なサイズを確保しないと、切り捨てられたり、バッファオーバーフローのリスクがある
    // 一般的には、最大で256バイトあればほとんどのケースをカバーできる
    std::vector<char> buffer(256); // charの動的配列としてバッファを確保

    // strftime を使用して時刻を整形
    // 第1引数: 出力バッファ
    // 第2引数: バッファサイズ
    // 第3引数: 書式指定文字列
    // 第4引数: tm 構造体へのポインタ
    size_t len = std::strftime(buffer.data(), buffer.size(), "%Y-%m-%d %H:%M:%S", tm_info);

    if (len > 0) { // 整形が成功した場合
        std::cout << "strftime で出力: " << buffer.data() << std::endl;
    } else {
        std::cerr << "strftime での整形に失敗しました。" << std::endl;
    }

    // ロケール依存の出力
    len = std::strftime(buffer.data(), buffer.size(), "%A, %B %d, %Y (%c)", tm_info);
    if (len > 0) {
        std::cout << "strftime (ロケール依存): " << buffer.data() << std::endl;
    }

    return 0;
}

C++20 の <chrono> ライブラリと std::format (推奨)

C++20 で導入された <chrono> ライブラリと <format> ライブラリ (std::format) を組み合わせる方法は、現代のC++で日付と時刻を扱う最も推奨される方法です。

特徴

  • std::tm 構造体への依存が少なくなります。
  • スレッドセーフ
    設計上、スレッドセーフに配慮されています。
  • タイムゾーン対応
    std::chrono::zoned_time を使用することで、タイムゾーンを意識した日付/時刻操作と整形が可能です。
  • 柔軟なフォーマット
    std::format は、Pythonの str.format や C# の複合書式指定機能に似ており、非常に柔軟な書式指定が可能です。
  • 型安全性
    std::chrono::system_clock::time_pointstd::chrono::zoned_time など、専用の型を使用するため、型安全性が高いです。
  • 高い精度
    ナノ秒レベルまでの時刻を扱えます。

利点

  • エラーハンドリングが容易。
  • std::format の非常に柔軟な書式指定により、様々なニーズに対応できる。
  • タイムゾーンの概念が組み込まれている。
  • 高精度な時刻情報を扱える。
  • 最もモダンで強力な機能を提供。

欠点

  • 新しい概念(タイムポイント、デュレーション、タイムゾーンなど)を学習する必要がある。
  • C++20以降のコンパイラと標準ライブラリが必要。

コード例

#include <iostream>
#include <chrono>   // std::chrono::system_clock, std::chrono::current_zone, etc.
#include <format>   // std::format (C++20)

int main() {
    // 1. 現在のシステム時刻を取得
    auto now = std::chrono::system_clock::now();

    // 2. std::format を使用して整形して出力
    //    C++20 の chrono フォーマット指定子を使用
    std::cout << "std::format (C++20) で出力 (秒まで): "
              << std::format("{:%Y-%m-%d %H:%M:%S}", now)
              << std::endl;

    // ミリ秒を含めて出力する例 (C++20 の chrono フォーマット拡張)
    // {:.[precision]S} など
    std::cout << "std::format (C++20) で出力 (ミリ秒): "
              << std::format("{:%Y-%m-%d %H:%M:%S}.{:03}", now,
                             std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()) % 1000)
              << std::endl;

    // タイムゾーンを考慮した出力 (C++20 の chrono と format)
    // 現在のタイムゾーンを取得し、ローカル時刻に変換
    auto zoned_now = std::chrono::current_zone()->to_local(now);
    std::cout << "std::format (タイムゾーン付き): "
              << std::format("{:%Y-%m-%d %H:%M:%S %Z}", zoned_now) // %Z はタイムゾーン名
              << std::endl;
    
    // 曜日と月名もロケールに応じて出力(localeモジュールがサポートしていれば)
    // 通常、formatはグローバルロケールに影響される
    std::cout << "std::format (ロケール依存の曜日と月): "
              << std::format("{:%A, %B %d, %Y}", zoned_now)
              << std::endl;


    return 0;
}

外部ライブラリの利用

Boost.Date_Time などの外部ライブラリも、日付と時刻の操作と整形に非常に強力な機能を提供します。

特徴

  • クロスプラットフォーム。
  • 豊富な機能(期間、インターバル、タイムゾーン、カスタムカレンダーなど)。
  • C++標準ライブラリの機能が不十分だった時代から使われてきた実績のあるライブラリ。

利点

  • 古いC++標準でも、モダンな日付/時刻機能を利用できる。
  • std::put_timestrftime では難しい複雑な日付/時刻計算や操作が可能。

欠点

  • C++20の <chrono><format> が利用可能な場合、Boost.Date_Time を使う必要性は減りました。
  • 学習コストがある。
  • Boostライブラリの導入とビルドが必要。
// #include <boost/date_time/posix_time/posix_time.hpp>
// #include <iostream>

// int main() {
//     // namespace pt = boost::posix_time;
//     // pt::ptime now = pt::second_clock::local_time(); // 現在のローカル時刻
//     // std::cout << "Boost.Date_Time で出力: " << pt::to_simple_string(now) << std::endl;
//     // std::cout << "カスタムフォーマット: " << pt::to_iso_string(now) << std::endl;
//     return 0;
// }
  • C++20より前の環境で、std::put_time や strftime では不十分な複雑な日付/時刻操作が必要な場合
    Boost.Date_Time などの外部ライブラリを検討する。

  • C++20以降のモダンなプロジェクト、高精度な時刻、タイムゾーン対応、安全性を重視する場合
    <chrono>std::format の組み合わせが最も推奨されます。 これが将来のC++開発の標準的なアプローチとなるでしょう。

  • C++11以降で手軽にストリーム出力したい場合
    std::put_time が最も簡潔で便利。std::tm のスレッドセーフティに留意。

  • 簡単な整形、古いC++標準、Cスタイルに慣れている場合
    strftime を直接使用する。ただし、バッファオーバーフローに注意。