C++でプログラムを開発する際、ファイルの更新日時を取得して処理の判定を行いたい場面は多々あります。
例えば、アセットファイルの自動ロードシステムや、ログファイルの管理、あるいはバックアップツールなどが挙げられます。
かつてはOSごとに異なるAPI(WindowsのWin32 APIやPOSIXのstat関数など)を使い分ける必要がありましたが、現代のC++では「std::filesystem」ライブラリを使用することで、標準機能だけでプラットフォームに依存しないコードを記述可能になりました。
本記事では、C++17およびC++20以降におけるファイル更新日時の取得方法を、具体的なコード例と共に詳しく解説します。
std::filesystemとは
C++17から標準ライブラリに導入されたstd::filesystemは、ファイルシステム、パス、ディレクトリ、ファイル属性などを操作するための強力なツールセットです。
これを利用することで、WindowsやLinuxといった環境の差を意識せずに、ファイル操作を実現できるようになりました。

ファイル更新日時を取得する場合、このライブラリの中に含まれるstd::filesystem::last_write_timeという関数を使用します。
この関数は、指定されたパスにあるファイルやディレクトリの最終書き込み時刻を返します。
返される型はstd::filesystem::file_time_typeという特殊な型ですが、これを扱う方法を知ることで、日付や時刻の情報を自由自在に扱えるようになります。
モダンC++でのファイル操作のメリット
モダンなC++(C++17以降)でファイル操作を行う最大のメリットは、コードの可読性と保守性が飛躍的に向上する点にあります。
従来のC言語スタイルの手法では、構造体の初期化やエラーコードの判定が複雑になりがちでしたが、std::filesystemはオブジェクト指向的なインターフェースを提供しており、直感的な記述が可能です。
また、例外処理によるエラーハンドリングも標準でサポートされているため、堅牢なプログラムを構築しやすいという特徴があります。
基本的なファイル更新日時の取得方法
それでは、実際にファイルから更新日時を取得する基本的な手順を見ていきましょう。
最もシンプルな方法は、std::filesystem::pathオブジェクトを作成し、それをlast_write_time関数に渡すことです。

last_write_time関数の使い方
以下のサンプルプログラムは、指定したファイルの更新日時を取得し、その情報を出力する最小限のコードです。
#include <iostream>
#include <filesystem> // std::filesystemを使用するために必要
#include <chrono> // 時間操作のために必要
namespace fs = std::filesystem;
int main() {
// 取得対象のファイルパスを指定
fs::path filePath = "sample.txt";
// ファイルが存在するか確認
if (fs::exists(filePath)) {
try {
// ファイルの最終更新日時を取得
// 返り値は std::filesystem::file_time_type 型
auto ftime = fs::last_write_time(filePath);
std::cout << "更新日時の取得に成功しました。" << std::endl;
} catch (const fs::filesystem_error& e) {
// ファイルアクセス権限などのエラーハンドリング
std::cerr << "エラー: " << e.what() << std::endl;
}
} else {
std::cout << "指定されたファイルが存在しません。" << std::endl;
}
return 0;
}
このコードでは、まずfs::pathを使用してターゲットとなるファイルを定義しています。
次に、fs:existsを使用してファイルの存在を確認してから、fs::last_write_timeを呼び出しています。
なお、ファイルが開けない場合や権限がない場合には例外がスローされるため、try-catchブロックで囲むのが一般的です。
取得した日時を人間が読める形式に変換する
last_write_timeが返すfile_time_typeは、内部的にはシステムのクロックに基づいたカウント値であるため、そのままでは「2026年1月1日 12:00:00」といった形式で表示することができません。
これを解決するために、日時の変換処理が必要になります。
C++20以降の変換方法
C++20からは、std::chronoライブラリが大幅に強化され、file_clockからsystem_clockへの変換が非常に簡単になりました。
また、std::formatを用いることで、書式指定も容易に行えます。

#include <iostream>
#include <filesystem>
#include <chrono>
#include <format> // C++20のフォーマットライブラリ
namespace fs = std::filesystem;
int main() {
fs::path filePath = "example.txt";
if (fs::exists(filePath)) {
// 更新日時の取得
auto ftime = fs::last_write_time(filePath);
// C++20: file_clockをsystem_clockに変換する
// これにより、通常のシステム時刻として扱えるようになる
auto s_time = std::chrono::clock_cast<std::chrono::system_clock>(ftime);
// C++20: std::formatを使って読みやすい文字列に変換
// %Y-%m-%d %H:%M:%S などのフォーマット指定が可能
std::string formattedTime = std::format("{:%Y-%m-%d %H:%M:%S}", s_time);
std::cout << "ファイルの更新日時: " << formattedTime << std::endl;
}
return 0;
}
C++20以降では、clock_castを使用するのが標準的なアプローチです。
これにより、OS固有の時刻表現に悩まされることなく、安全に時刻変換を行うことができます。
ファイルの更新日時: 2026-01-14 09:45:23
C++17での変換方法
C++17を使用している環境では、clock_castがまだ存在しません。
そのため、一度time_t型を経由して変換を行う必要があります。
この方法は少し煩雑ですが、多くの環境で動作する互換性があります。
#include <iostream>
#include <filesystem>
#include <chrono>
#include <ctime>
#include <iomanip>
namespace fs = std::filesystem;
int main() {
fs::path filePath = "old_style.txt";
if (fs::exists(filePath)) {
auto ftime = fs::last_write_time(filePath);
// file_time_type から time_t への変換 (環境依存の側面がある)
// file_clockの起点とsystem_clockの起点の差を考慮する必要がある場合がある
auto sctp = std::chrono::time_point_cast<std::chrono::system_clock::duration>(
ftime - fs::file_time_type::clock::now() + std::chrono::system_clock::now()
);
std::time_t tt = std::chrono::system_clock::to_time_t(sctp);
// ローカル時刻に変換して表示
std::tm* lt = std::localtime(&tt);
std::cout << "更新日時: " << std::put_time(lt, "%Y/%m/%d %H:%M:%S") << std::endl;
}
}
この方法では、file_time_typeとsystem_clockの差分を計算して補正を行っています。
C++17の実装によっては、直接変換をサポートする関数がないため、このような計算が必要になることがあります。
実践的な応用:ファイルの更新チェック
ファイル更新日時の取得は、単に表示するだけでなく、「ファイルが書き換えられたかどうか」を判定するロジックによく使われます。
例えば、設定ファイルが更新されたときだけ再読み込みを行うプログラムが考えられます。

ファイル更新検知のサンプル
次のコードは、ループ内でファイルの更新日時を常に監視し、変更があった場合に通知する仕組みを簡略化したものです。
#include <iostream>
#include <filesystem>
#include <thread>
#include <chrono>
namespace fs = std::filesystem;
int main() {
fs::path targetFile = "config.json";
if (!fs::exists(targetFile)) {
std::cerr << "監視対象ファイルが見つかりません。" << std::endl;
return 1;
}
// 初回の更新日時を記録
auto lastRecordedTime = fs::last_write_time(targetFile);
std::cout << "監視を開始します..." << std::endl;
for (int i = 0; i < 5; ++i) { // 5回ほどチェック
// 2秒待機
std::this_thread::sleep_for(std::chrono::seconds(2));
// 現在の更新日時を取得
auto currentWriteTime = fs::last_write_time(targetFile);
// 前回記録した時間と比較
if (currentWriteTime != lastRecordedTime) {
std::cout << "ファイルが更新されました! 再読み込みを実行します。" << std::endl;
// 更新日時を更新して次に備える
lastRecordedTime = currentWriteTime;
} else {
std::cout << "変更なし..." << std::endl;
}
}
return 0;
}
このプログラムでは、last_write_timeが返す値を直接比較演算子!=で比較しています。
file_time_typeは比較演算子が定義されているため、わざわざ文字列や他の時刻型に変換しなくても、更新の有無を確認できるのが大きなメリットです。
エラーハンドリングと注意点
ファイル操作には、常にエラーの可能性があります。
ファイルが存在しない、他のプロセスが排他的にロックしている、アクセス権限がないといった状況に対処しなければなりません。
| 発生しうる問題 | 原因と対策 |
|---|---|
filesystem_error 例外 | パスが無効な場合やアクセス権限不足。try-catchで捕捉する。 |
| 精度の違い | OSやファイルシステム(NTFS, ext4など)により、日時の精度(ナノ秒単位か秒単位か)が異なる。 |
| タイムゾーンの考慮 | last_write_timeは通常UTCベース。表示の際はローカル時刻への変換が必要。 |
std::error_code を使った例外なしの取得
例外を投げたくないパフォーマンス重視のコードでは、std::error_codeを引数に渡すオーバーロードを使用します。
std::error_code ec;
auto ftime = fs::last_write_time(filePath, ec);
if (ec) {
// 例外は発生せず、ecにエラー内容が格納される
std::cerr << "エラーコード: " << ec.value() << " メッセージ: " << ec.message() << std::endl;
}
このように、第2引数にstd::error_codeを渡すことで、プログラムの制御フローを例外に頼らず管理することができます。
これは、ループ内で頻繁にファイルチェックを行う場合などに有効なテクニックです。

まとめ
C++17から導入されたstd::filesystemにより、ファイル更新日時の取得は非常にシンプルかつ強力なものとなりました。
以前の複雑なOS固有APIを使わずに、last_write_time関数ひとつでプラットフォームを問わない実装が可能です。
特にC++20からはclock_castやstd::formatの登場により、取得した時刻を人間が読みやすい形式に変換する手間も大幅に削減されました。
ファイルを監視するシステムや、アセット管理が必要なゲーム開発、ツール開発において、この機能は欠かせない存在です。
ぜひ本記事で紹介したコードを参考に、モダンなC++による効率的なファイル操作を自身のプロジェクトに取り入れてみてください。
最新のC++規格を積極的に活用することで、より安全でメンテナンス性の高いコードを実現できるはずです。
