【C++/Win32】ファイル/フォルダーのタイムスタンプを取得する


更新

 ファイル/フォルダーのタイムスタンプを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開発環境ファイルシステム結果
WindowsVisual StudioNTFS369年後の日時(※A)
Ubuntug++ext4204年前の日時(※A)(※B)
FreeBSDclang++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