Preserve subsecond precision for last_write_time() and related filesystem metadata (#208)

This commit is contained in:
Steffen Schümann 2026-07-12 13:12:35 +02:00
parent 38f91bd470
commit 4bc18b0955
3 changed files with 37 additions and 18 deletions

View File

@ -667,6 +667,8 @@ to the expected behavior.
formatting now handles unknown error codes safely.
* Fix for [#207](https://github.com/gulrak/filesystem/issues/207),
`last_write_time()` now consistently follows symlinks.
* Fix for [#208](https://github.com/gulrak/filesystem/issues/208), filesystem
metadata now preserves subsecond modification times.
* Fix for [#185](https://github.com/gulrak/filesystem/issues/185),
`lexically_normal()` now preserves unresolved parent components in relative
paths.

View File

@ -890,7 +890,7 @@ private:
#ifndef GHC_OS_WINDOWS
uintmax_t _hard_link_count = static_cast<uintmax_t>(-1);
#endif
time_t _last_write_time = 0;
file_time_type _last_write_time = (file_time_type::min)();
};
// [fs.class.directory.iterator] Class directory_iterator
@ -2237,12 +2237,14 @@ GHC_INLINE path resolveSymlink(const path& p, std::error_code& ec)
}
#ifdef GHC_OS_WINDOWS
GHC_INLINE time_t timeFromFILETIME(const FILETIME& ft)
GHC_INLINE file_time_type timeFromFILETIME(const FILETIME& ft)
{
ULARGE_INTEGER ull;
ull.LowPart = ft.dwLowDateTime;
ull.HighPart = ft.dwHighDateTime;
return static_cast<time_t>(ull.QuadPart / 10000000ULL - 11644473600ULL);
const int64_t ticks = static_cast<int64_t>(ull.QuadPart) - 116444736000000000LL;
const auto duration = std::chrono::duration<int64_t, std::ratio<1, 10000000>>(ticks);
return file_time_type(std::chrono::duration_cast<file_time_type::duration>(duration));
}
GHC_INLINE void timeToFILETIME(time_t t, FILETIME& ft)
@ -2292,7 +2294,7 @@ GHC_INLINE bool is_symlink_from_INFO(const path &, const WIN32_FIND_DATAW* info,
}
template <typename INFO>
GHC_INLINE file_status status_from_INFO(const path& p, const INFO* info, std::error_code& ec, uintmax_t* sz = nullptr, time_t* lwt = nullptr)
GHC_INLINE file_status status_from_INFO(const path& p, const INFO* info, std::error_code& ec, uintmax_t* sz = nullptr, file_time_type* lwt = nullptr)
{
file_type ft = file_type::unknown;
if (is_symlink_from_INFO(p, info, ec)) {
@ -2331,7 +2333,7 @@ GHC_INLINE bool is_not_found_error(std::error_code& ec)
#endif
}
GHC_INLINE file_status symlink_status_ex(const path& p, std::error_code& ec, uintmax_t* sz = nullptr, uintmax_t* nhl = nullptr, time_t* lwt = nullptr) noexcept
GHC_INLINE file_status symlink_status_ex(const path& p, std::error_code& ec, uintmax_t* sz = nullptr, uintmax_t* nhl = nullptr, file_time_type* lwt = nullptr) noexcept
{
#ifdef GHC_OS_WINDOWS
file_status fs;
@ -2369,7 +2371,7 @@ GHC_INLINE file_status symlink_status_ex(const path& p, std::error_code& ec, uin
#endif
}
GHC_INLINE file_status status_ex(const path& p, std::error_code& ec, file_status* sls = nullptr, uintmax_t* sz = nullptr, uintmax_t* nhl = nullptr, time_t* lwt = nullptr, int recurse_count = 0) noexcept
GHC_INLINE file_status status_ex(const path& p, std::error_code& ec, file_status* sls = nullptr, uintmax_t* sz = nullptr, uintmax_t* nhl = nullptr, file_time_type* lwt = nullptr, int recurse_count = 0) noexcept
{
ec.clear();
#ifdef GHC_OS_WINDOWS
@ -2435,7 +2437,13 @@ GHC_INLINE file_status status_ex(const path& p, std::error_code& ec, file_status
*nhl = st.st_nlink;
}
if (lwt) {
*lwt = st.st_mtime;
#ifdef GHC_OS_APPLE
const auto subsecond = std::chrono::nanoseconds(st.st_mtimespec.tv_nsec);
#else
const auto subsecond = std::chrono::nanoseconds(st.st_mtim.tv_nsec);
#endif
const auto duration = std::chrono::seconds(st.st_mtime) + subsecond;
*lwt = file_time_type(std::chrono::duration_cast<file_time_type::duration>(duration));
}
return fs;
}
@ -4655,10 +4663,10 @@ GHC_INLINE file_time_type last_write_time(const path& p)
GHC_INLINE file_time_type last_write_time(const path& p, std::error_code& ec) noexcept
{
time_t result = 0;
file_time_type result = (file_time_type::min)();
ec.clear();
file_status fs = detail::status_ex(p, ec, nullptr, nullptr, nullptr, &result);
return ec ? (file_time_type::min)() : std::chrono::system_clock::from_time_t(result);
return ec ? (file_time_type::min)() : result;
}
#ifdef GHC_WITH_EXCEPTIONS
@ -5270,7 +5278,7 @@ GHC_INLINE directory_entry::directory_entry(const filesystem::path& p)
#ifndef GHC_OS_WINDOWS
, _hard_link_count(static_cast<uintmax_t>(-1))
#endif
, _last_write_time(0)
, _last_write_time((file_time_type::min)())
{
refresh();
}
@ -5282,7 +5290,7 @@ GHC_INLINE directory_entry::directory_entry(const filesystem::path& p, std::erro
#ifndef GHC_OS_WINDOWS
, _hard_link_count(static_cast<uintmax_t>(-1))
#endif
, _last_write_time(0)
, _last_write_time((file_time_type::min)())
{
refresh(ec);
}
@ -5530,8 +5538,8 @@ GHC_INLINE uintmax_t directory_entry::hard_link_count(std::error_code& ec) const
#ifdef GHC_WITH_EXCEPTIONS
GHC_INLINE file_time_type directory_entry::last_write_time() const
{
if (_last_write_time != 0) {
return std::chrono::system_clock::from_time_t(_last_write_time);
if (_last_write_time != (file_time_type::min)()) {
return _last_write_time;
}
return filesystem::last_write_time(path());
}
@ -5539,9 +5547,9 @@ GHC_INLINE file_time_type directory_entry::last_write_time() const
GHC_INLINE file_time_type directory_entry::last_write_time(std::error_code& ec) const noexcept
{
if (_last_write_time != 0) {
if (_last_write_time != (file_time_type::min)()) {
ec.clear();
return std::chrono::system_clock::from_time_t(_last_write_time);
return _last_write_time;
}
return filesystem::last_write_time(path(), ec);
}
@ -5716,7 +5724,7 @@ public:
}
else {
_dir_entry._file_size = static_cast<uintmax_t>(-1);
_dir_entry._last_write_time = 0;
_dir_entry._last_write_time = (file_time_type::min)();
}
}
}
@ -5805,7 +5813,7 @@ public:
}
_dir_entry._file_size = static_cast<uintmax_t>(-1);
_dir_entry._hard_link_count = static_cast<uintmax_t>(-1);
_dir_entry._last_write_time = 0;
_dir_entry._last_write_time = (file_time_type::min)();
}
path _base;
directory_options _options;

View File

@ -1820,8 +1820,11 @@ TEST_CASE("fs.op.copy_file - copy_file", "[filesystem][operations][fs.op.copy_fi
CHECK(fs::file_size("foo") == fs::file_size("bar"));
CHECK(fs::copy_file("foo", "bar2", ec));
CHECK(!ec);
std::this_thread::sleep_for(std::chrono::seconds(1));
generateFile("foo2", 200);
const auto copyTime = fs::file_time_type(std::chrono::seconds(1445401800));
fs::last_write_time("foo", copyTime);
fs::last_write_time("bar", copyTime + std::chrono::milliseconds(100));
fs::last_write_time("foo2", copyTime + std::chrono::milliseconds(200));
CHECK(fs::copy_file("foo2", "bar", fs::copy_options::update_existing));
CHECK(fs::file_size("bar") == 200);
CHECK(!fs::copy_file("foo", "bar", fs::copy_options::update_existing));
@ -2568,6 +2571,12 @@ TEST_CASE("fs.op.last_write_time - last_write_time", "[filesystem][operations][f
CHECK_NOTHROW(fs::last_write_time("foo", nt, ec));
CHECK(std::abs(std::chrono::duration_cast<std::chrono::seconds>(fs::last_write_time("foo") - nt).count()) < 1);
CHECK(!ec);
const auto preciseTime = nt + std::chrono::milliseconds(123);
CHECK_NOTHROW(fs::last_write_time("foo", preciseTime, ec));
REQUIRE(!ec);
CHECK(fs::last_write_time("foo") == preciseTime);
const fs::directory_entry preciseEntry("foo");
CHECK(preciseEntry.last_write_time() == preciseTime);
if (is_symlink_creation_supported()) {
#if !defined(GHC_OS_WINDOWS) && !defined(GHC_OS_WEB)
struct ::stat linkStatBefore;