ファイル/フォルダーのタイムスタンプをWin32 APIにて取得する方法。
開発環境 | Visual Studio 2022 |
---|---|
言語 | C++ |
フレームワーク | Win32 API |
動作確認 | Windows 11 Windows 10 Windows 7 |
Win32 APIを使用する方法
#include <windows.h>
// FindFirstFileにフォルダーを指定する場合、末尾に「円マーク(\)」は不要
wchar_t fullPath[] = L"ファイルまたはフォルダーのパス";
// ファイル情報
WIN32_FIND_DATA findData{};
// ファイル時間
FILETIME fileTime{};
// タイムスタンプ
SYSTEMTIME creationTime{};
SYSTEMTIME lastWriteTime{};
SYSTEMTIME lastAccessTime{};
// ファイル情報取得
HANDLE hFile = FindFirstFile(fullPath, &findData);
if (hFile == INVALID_HANDLE_VALUE)
{
// 失敗(不正パス)
//
//
//
}
else
{
// 成功
// ファイル検索ハンドル閉じる
FindClose(hFile);
// FindFirstFileにてUTCタイムスタンプを取得(100ナノ秒単位)
// FileTimeToLocalFileTimeにてUTCからローカル時間に変換
// FileTimeToSystemTimeにて100ナノ秒単位を年月日時分秒ミリ秒に変換
// 作成日時
FileTimeToLocalFileTime(&findData.ftCreationTime, &fileTime);
FileTimeToSystemTime(&fileTime, &creationTime);
// 更新日時
FileTimeToLocalFileTime(&findData.ftLastWriteTime, &fileTime);
FileTimeToSystemTime(&fileTime, &lastWriteTime);
// アクセス日時
FileTimeToLocalFileTime(&findData.ftLastAccessTime, &fileTime);
FileTimeToSystemTime(&fileTime, &lastAccessTime);
}
Windowsのファイルシステムに記録されているタイムスタンプは、1601年1月1日0時を起点としている。 その時間は、NTFSはUTC、FATは保存時のローカル時間である。 FindFirstFileは、NTFSの場合そのままUTCを返し、FATの場合はタイムゾーン設定によりUTCに変換された値を返すので、ファイルシステムの違いを意識しなくてもよい。
標準C++を使用する方法
更新日時のみなら、標準C++でも取得できる。 <filesystem>を使用するので、プロジェクトのプロパティにて、[全般]-[C++言語標準]に「ISO C++17標準(/std:c++17)」以降を選択する。
ただし、Windowsでは実際のタイムスタンプより369年先の日時が取得される。 例えば2022年の場合、std::tm構造体のtm_yearには122が入るはずであるが、491が入る。 原因は、Visual Studioのstd::filesystem::last_write_timeはWin32 APIと同じ値を返すが、その値をstd::localtimeはUNIX時間として処理するからである (UNIX時間は1970年1月1日0時を起点とするため、1970-1601=369)。
標準C++規格が特定のファイルシステムの仕様を考慮しないのは言うまでもないが、では自分で「-369」する処理を入れてもよいのか。 もしかしたら、Visual Studioのstd::filesystem::last_write_time実装が変わる可能性もあるので、この場合はWin32 APIを使用したほうが無難であろう。
#include <filesystem>
#ifdef _WIN32
// error C4996の抑止
#pragma warning(disable:4996)
#endif
#ifdef _WIN32
std::filesystem::path p = L"ファイルまたはフォルダーのパス";
#else
std::filesystem::path p = u8"ファイルまたはディレクトリのパス";
#endif
// UTCタイムスタンプの取得
std::filesystem::file_time_type fileTime = std::filesystem::last_write_time(p);
// 秒に変換
std::chrono::seconds sec = std::chrono::duration_cast<std::chrono::seconds>(fileTime.time_since_epoch());
std::time_t t = sec.count();
// ローカル時間に変換
const std::tm *lastWriteTime = std::localtime(&t);
OS | 開発環境 | ファイルシステム | 結果 |
---|---|---|---|
Windows | Visual Studio | NTFS | 369年後の日時(※A) |
Ubuntu | g++ | ext4 | 204年前の日時(※A)(※B) |
FreeBSD | clang++ | UFS | 正しい日時 |
(※A) 年の補正前後のどちらか一方のみが「うるう年」の場合、日付によっては1日ずれる。
(※B) ext4にて、タイムスタンプを格納する領域を拡張したことと関係あるらしい。
サンプルソース
引数にフォルダーを指定すると、配下を再帰的に検索して全フォルダー/ファイルのタイムスタンプを出力する。 出力に<format>を使用するので、プロジェクトのプロパティにて、[全般]-[C++言語標準]に「ISO C++20標準(/std:c++20)」以降を選択する。
#include <windows.h>
#include <iostream>
#include <filesystem>
#include <format>
int wmain(int argc, wchar_t *argv[])
{
// 引数がフォルダー以外はエラー
if (argc < 2 || !std::filesystem::is_directory(argv[1]))
{
return 1;
}
// コマンドプロンプトに日本語出力
std::wcout.imbue(std::locale("Japanese"));
// ファイル検索ハンドル
HANDLE hFile;
// ファイル情報
WIN32_FIND_DATA findData{};
// ファイル時間
FILETIME fileTime{};
// タイムスタンプ
SYSTEMTIME creationTime{};
SYSTEMTIME lastWriteTime{};
SYSTEMTIME lastAccessTime{};
for (const std::filesystem::directory_entry &i : std::filesystem::recursive_directory_iterator(argv[1]))
{
if (i.is_regular_file() || i.is_directory())
{
// ファイル情報取得
hFile = FindFirstFile(i.path().c_str(), &findData);
FindClose(hFile);
// 作成日時
FileTimeToLocalFileTime(&findData.ftCreationTime, &fileTime);
FileTimeToSystemTime(&fileTime, &creationTime);
// 更新日時
FileTimeToLocalFileTime(&findData.ftLastWriteTime, &fileTime);
FileTimeToSystemTime(&fileTime, &lastWriteTime);
// アクセス日時
FileTimeToLocalFileTime(&findData.ftLastAccessTime, &fileTime);
FileTimeToSystemTime(&fileTime, &lastAccessTime);
// 出力
std::wcout << i.path() << std::endl;
std::wcout << std::format(L"作成 {:4d}/{:02d}/{:02d} {:02d}:{:02d}:{:02d}",
creationTime.wYear, creationTime.wMonth, creationTime.wDay,
creationTime.wHour, creationTime.wMinute, creationTime.wSecond) << std::endl;
std::wcout << std::format(L"更新 {:4d}/{:02d}/{:02d} {:02d}:{:02d}:{:02d}",
lastWriteTime.wYear, lastWriteTime.wMonth, lastWriteTime.wDay,
lastWriteTime.wHour, lastWriteTime.wMinute, lastWriteTime.wSecond) << std::endl;
std::wcout << std::format(L"アクセス {:4d}/{:02d}/{:02d} {:02d}:{:02d}:{:02d}",
lastAccessTime.wYear, lastAccessTime.wMonth, lastAccessTime.wDay,
lastAccessTime.wHour, lastAccessTime.wMinute, lastAccessTime.wSecond) << std::endl;
std::wcout << L"----" << std::endl;
}
}
}
リファレンス
FindFirstFileW function
https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-findfirstfilew
FileTimeToLocalFileTime function
https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-filetimetolocalfiletime
FileTimeToSystemTime function
https://learn.microsoft.com/en-us/windows/win32/api/timezoneapi/nf-timezoneapi-filetimetosystemtime
File Times
https://learn.microsoft.com/en-us/windows/win32/sysinfo/file-times