Seaside Laboratory

Post

C++ でビルド日付を取得する方法

プログラム上でビルド日付を扱う処理が出てきたので、おぼろげな記憶を頼りに Microsoft Docs の「定義済みマクロ」を調べたところ、それらしき用途の __DATE__ というマクロがあった。

__DATE__
現在のソースファイルのコンパイル日付。日付は Mmm dd yyyy 形式の固定長の文字列リテラルです。月の名前 Mmm は、C ランタイムライブラリ (CRT) asctime 関数によって生成される省略形の月名と同じものです。日付 dd の最初の文字は、値が 10 未満の場合は空白になります。

今回必要なのは yyyymmdd 形式だが、__DATE__ には "Aug 15 2020" といった形式で入っているのでそのまま使うことができない。

プログラム実行時に __DATE__ を任意の形式に変換して使うというやり方もあるが、本来であれば何のコストもかけずに取得できていた情報に CPU リソースを割くのは癪なのでマクロを使ってコンパイル時に解決することにした。

変換プログラム

__DATE__ をマクロで加工するという手法は結構使われているようで、GitHub あたりを検索すると似たようなプログラムを見つけることができる。

// 指定オフセットの日付文字を取り出す
#define BUILD_DATE_CHR( i ) (__DATE__[i])

// 指定オフセットの日付文字と指定文字を比較する
#define CMP_BUILD_DATE_CHR( i, c ) (BUILD_DATE_CHR( i ) == c)

// 月名を比較する
#define CMP_BUILD_MON_STR( c0, c1, c2 ) \
(\
    CMP_BUILD_DATE_CHR( 0, c0 ) &&\
    CMP_BUILD_DATE_CHR( 1, c1 ) &&\
    CMP_BUILD_DATE_CHR( 2, c2 )\
)

// 月名を月数に変換
#define BUILD_MON_NUM \
(\
    CMP_BUILD_MON_STR( 'J', 'a', 'n' ) ?  1 :\
    CMP_BUILD_MON_STR( 'F', 'e', 'b' ) ?  2 :\
    CMP_BUILD_MON_STR( 'M', 'a', 'r' ) ?  3 :\
    CMP_BUILD_MON_STR( 'A', 'p', 'r' ) ?  4 :\
    CMP_BUILD_MON_STR( 'M', 'a', 'y' ) ?  5 :\
    CMP_BUILD_MON_STR( 'J', 'u', 'n' ) ?  6 :\
    CMP_BUILD_MON_STR( 'J', 'u', 'l' ) ?  7 :\
    CMP_BUILD_MON_STR( 'A', 'u', 'g' ) ?  8 :\
    CMP_BUILD_MON_STR( 'S', 'e', 'p' ) ?  9 :\
    CMP_BUILD_MON_STR( 'O', 'c', 't' ) ? 10 :\
    CMP_BUILD_MON_STR( 'N', 'o', 'v' ) ? 11 :\
    CMP_BUILD_MON_STR( 'D', 'e', 'c' ) ? 12 :\
    0\
)

// yyyymmdd 形式の日付文字列
const char BUILD_DATE[] =
{
    BUILD_DATE_CHR( 7 ),
    BUILD_DATE_CHR( 8 ),
    BUILD_DATE_CHR( 9 ),
    BUILD_DATE_CHR( 10 ),
    '0' + (BUILD_MON_NUM / 10),
    '0' + (BUILD_MON_NUM % 10),
    CMP_BUILD_DATE_CHR( 4, ' ' ) ? '0' : BUILD_DATE_CHR( 4 ),
    BUILD_DATE_CHR( 5 ),
    '\0'
};

// 使用済みマクロを削除
#undef BUILD_MON_INT
#undef CMP_BUILD_MON_STR
#undef CMP_BUILD_DATE_CHR
#undef BUILD_DATE_CHR

全ての処理を通った時点で定数 (グローバル変数) BUILD_DATE には "20200815" といった文字列が定義される。

テンプレートの値パラメーターを使って日付を静的定義する方法なども試してみたが、__DATE__[i] という式は定数とみなされないようで使うことができなかった。

他の方法

マクロでの解決はやや強引なので他の方法がないか模索してみたが、特に良い方法は見つからなかった。

実行ファイルの更新日時を使う

ファイル系関数を使って実行ファイルから更新日時を取得し、それをビルド日付として扱うという考え方。ファイルのタイムスタンプは FTP での転送など何らかの拍子に変化してしまうので正確性の面で難がある。

シェルで日付を生成する

date コマンド等で日付文字列を生成し、コンパイラのコマンドラインオプション経由で渡すという方法。環境によって日付を生成するコマンドが異なるので汎用性に欠ける。

ヘッダーファイルを動的生成する

スクリプト言語等を使って日付文字列定数を定義したヘッダーファイルを動的に生成するという方法。前述のシェルと同様に使用可能なスクリプト言語は環境によって異なる。また、ソースを Git で管理している場合は .gitignore で無視しておかないと生成の度に変更が検出されてしまう。

ソース内に手動で定義する

メンテナンスが面倒くさい。