Allow set and get last modified times on files.

PiperOrigin-RevId: 791407269
This commit is contained in:
Francis Tsui
2025-08-05 16:08:30 -07:00
committed by Copybara-Service
parent 09e11ee122
commit 38827f2b4b
11 changed files with 144 additions and 6 deletions
@@ -16,6 +16,7 @@
#include <fileapi.h>
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <limits>
@@ -24,6 +25,9 @@
#include "absl/memory/memory.h"
#include "absl/strings/string_view.h"
#include "absl/time/civil_time.h"
#include "absl/time/clock.h"
#include "absl/time/time.h"
#include "internal/platform/byte_array.h"
#include "internal/platform/exception.h"
#include "internal/platform/implementation/windows/string_utils.h"
@@ -31,6 +35,57 @@
namespace nearby::windows {
namespace {
static const absl::Time kFiletimeEpoch =
absl::FromCivil(absl::CivilYear(1601), absl::UTCTimeZone());
constexpr absl::Duration kFiletimeUnit = absl::Nanoseconds(100);
const int64_t kFiletimeUnitsPerSecond = absl::Seconds(1) / kFiletimeUnit;
static const absl::Time kMinDosFileTime =
absl::FromCivil(absl::CivilYear(1980), absl::UTCTimeZone());
static const absl::Time kMaxDosFileTime =
absl::FromCivil(absl::CivilYear(2108), absl::UTCTimeZone()) - kFiletimeUnit;
absl::Duration DurationFromFiletime(const FILETIME& file_time) {
LONGLONG quadDateTime =
(static_cast<LONGLONG>(file_time.dwHighDateTime) << 32) |
static_cast<LONGLONG>(file_time.dwLowDateTime);
const absl::Duration subsecond =
kFiletimeUnit * (quadDateTime % kFiletimeUnitsPerSecond);
const absl::Duration seconds =
absl::Seconds(quadDateTime / kFiletimeUnitsPerSecond);
return seconds + subsecond;
}
absl::Time TimeFromFiletime(const FILETIME& file_time) {
return kFiletimeEpoch + DurationFromFiletime(file_time);
}
bool DurationToFiletime(const absl::Duration duration, FILETIME* file_time) {
absl::Duration remainder;
const int64_t filetime_value =
absl::IDivDuration(duration, kFiletimeUnit, &remainder);
if (remainder <= -kFiletimeUnit || remainder >= kFiletimeUnit) {
return false;
}
file_time->dwLowDateTime = static_cast<DWORD>(filetime_value);
file_time->dwHighDateTime = static_cast<DWORD>(filetime_value >> 32);
return true;
}
void TimeToFiletime(absl::Time time, FILETIME* file_time) {
// Clamp to the range that FileTimeToDosDateTime supports. Explorer's date
// display and built-in zip feature can only handle this range.
time = std::clamp(time, kMinDosFileTime, kMaxDosFileTime);
if (!DurationToFiletime(time - kFiletimeEpoch, file_time)) {
DurationToFiletime(kMaxDosFileTime - kFiletimeEpoch, file_time);
}
}
} // namespace
// InputFile
std::unique_ptr<IOFile> IOFile::CreateInputFile(absl::string_view file_path,
size_t size) {
@@ -128,4 +183,34 @@ Exception IOFile::Write(const ByteArray& data) {
return {Exception::kSuccess};
}
absl::Time IOFile::GetLastModifiedTime() const {
if (file_ == INVALID_HANDLE_VALUE) {
LOG(ERROR) << "Failed to get file modified time for: " << path_;
return absl::Now();
}
FILETIME last_write_time;
if (::GetFileTime(file_, /*lpCreationTime=*/nullptr,
/*lpLastAccessTime=*/nullptr, &last_write_time) == 0) {
LOG(ERROR) << "Failed to get file last write time: " << path_
<< " with error: " << ::GetLastError();
return absl::Now();
}
return TimeFromFiletime(last_write_time);
}
void IOFile::SetLastModifiedTime(absl::Time last_modified_time) {
if (file_ == INVALID_HANDLE_VALUE) {
LOG(ERROR) << "Failed to set file modified time for: " << path_;
return;
}
FILETIME last_write_time;
TimeToFiletime(last_modified_time, &last_write_time);
// Set both creation time and last write time.
if (::SetFileTime(file_, /*lpCreationTime=*/&last_write_time,
/*lpLastAccessTime=*/nullptr, &last_write_time) == 0) {
LOG(ERROR) << "Failed to set file last write time: " << path_
<< " with error: " << ::GetLastError();
}
}
} // namespace nearby::windows
@@ -23,6 +23,7 @@
#include <string>
#include "absl/strings/string_view.h"
#include "absl/time/time.h"
#include "internal/platform/byte_array.h"
#include "internal/platform/exception.h"
#include "internal/platform/implementation/input_file.h"
@@ -49,6 +50,8 @@ class IOFile final : public api::InputFile, public api::OutputFile {
Exception Close() override;
Exception Write(const ByteArray& data) override;
absl::Time GetLastModifiedTime() const override;
void SetLastModifiedTime(absl::Time last_modified_time) override;
private:
explicit IOFile(absl::string_view file_path, size_t size);
@@ -23,6 +23,7 @@
#include "gtest/gtest.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "absl/time/time.h"
#include "internal/platform/byte_array.h"
#include "internal/platform/exception.h"
#include "internal/platform/logging.h"
@@ -187,5 +188,21 @@ TEST(IOFileTest, OutputFileWrite) {
::DeleteFileA(temp_file.data());
}
TEST(IOFileTest, GetSetModifiedTime) {
std::string temp_file = GetTempFileName("GetSetModifiedTime");
std::unique_ptr<IOFile> output_file = IOFile::CreateOutputFile(temp_file);
ASSERT_NE(output_file, nullptr);
output_file->SetLastModifiedTime(absl::FromUnixSeconds(1234567890));
output_file->Close();
std::unique_ptr<IOFile> input_file =
IOFile::CreateInputFile(temp_file, /*size=*/0);
EXPECT_EQ(input_file->GetLastModifiedTime(),
absl::FromUnixSeconds(1234567890));
::DeleteFileA(temp_file.data());
}
} // namespace
} // namespace nearby::windows