From 45354a421c2a790413385174ef642a839a664cd6 Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@musicpd.org>
Date: Tue, 10 Aug 2021 14:07:15 +0200
Subject: [PATCH] time/FileTime: preserve the FILETIME resolution

Don't truncate the FILETIME to second resolution to pass it to
std::chrono::system_clock::from_time_t(); instead, calculate the
offset between the FILETIME epoch and the
std::system_clock::time_point epoch, and use that to initialize the
time_point directly.
---
 src/time/FileTime.hxx | 30 +++++++++++++++++++++++++-----
 1 file changed, 25 insertions(+), 5 deletions(-)

diff --git a/src/time/FileTime.hxx b/src/time/FileTime.hxx
index 4ac323331..6319d7749 100644
--- a/src/time/FileTime.hxx
+++ b/src/time/FileTime.hxx
@@ -71,17 +71,37 @@ FileTimeToChronoDuration(FILETIME ft) noexcept
 	return FileTimeDuration(ToInt64(ft));
 }
 
-constexpr time_t
-FileTimeToTimeT(FILETIME ft) noexcept
+/**
+ * Calculate a std::chrono::duration specifying the duration between
+ * the unix epoch and the given FILETIME.
+ */
+constexpr auto
+FileTimeToUnixEpochDuration(FILETIME ft) noexcept
 {
-	return (ToInt64(ft) - 116444736000000000) / 10000000;
+	/**
+	 * The number of days between the Windows FILETIME epoch
+	 * (1601-01-01T00:00) and the Unix epoch (1970-01-01T00:00).
+	 */
+	constexpr int_least64_t windows_unix_days = 134774;
+	constexpr int_least64_t windows_unix_hours = windows_unix_days * 24;
+
+	constexpr FileTimeDuration windows_unix_delta{std::chrono::hours{windows_unix_hours}};
+
+	return FileTimeToChronoDuration(ft) - windows_unix_delta;
 }
 
 inline std::chrono::system_clock::time_point
 FileTimeToChrono(FILETIME ft) noexcept
 {
-	// TODO: eliminate the time_t roundtrip, preserve sub-second resolution
-	return std::chrono::system_clock::from_time_t(FileTimeToTimeT(ft));
+	/* this is guaranteed to be 0 in C++20 */
+	const auto unix_epoch = std::chrono::system_clock::from_time_t(0);
+
+	const auto windows_duration = FileTimeToUnixEpochDuration(ft);
+	const auto sys_duration =
+		std::chrono::duration_cast<std::chrono::system_clock::duration>
+		(windows_duration);
+
+	return unix_epoch + sys_duration;
 }
 
 constexpr std::chrono::seconds