Index: common/mptTime.cpp
===================================================================
--- common/mptTime.cpp	(revision 16961)
+++ common/mptTime.cpp	(working copy)
@@ -13,7 +13,12 @@
 
 #include "mptStringBuffer.h"
 
-#include <time.h>
+#if MPT_CXX_AT_LEAST(20) && !defined(MPT_LIBCXX_QUIRK_NO_CHRONO_DATE)
+#include <chrono>
+#if 0
+#include <format>
+#endif
+#endif
 
 #if MPT_OS_WINDOWS
 #include <windows.h>
@@ -81,23 +86,8 @@
 
 #endif // MODPLUG_TRACKER
 
-Unix::Unix()
-	: Value(0)
-{
-	return;
-}
+#if MPT_CXX_BEFORE(20) || defined(MPT_LIBCXX_QUIRK_NO_CHRONO_DATE)
 
-Unix::Unix(int64 unixtime)
-	: Value(unixtime)
-{
-	return;
-}
-
-Unix::operator int64 () const
-{
-	return Value;
-}
-
 static int32 ToDaynum(int32 year, int32 month, int32 day)
 {
 	month = (month + 9) % 12;
@@ -128,77 +118,55 @@
 	day = static_cast<int32>(dd);
 }
 
-mpt::Date::Unix Unix::FromUTC(tm timeUtc)
+mpt::Date::Unix UnixFromUTC(UTC timeUtc)
 {
-	int32 daynum = ToDaynum(timeUtc.tm_year+1900, timeUtc.tm_mon+1, timeUtc.tm_mday);
-	int64 seconds = static_cast<int64>(daynum - ToDaynum(1970,1,1))*24*60*60 + timeUtc.tm_hour*60*60 + timeUtc.tm_min*60 + timeUtc.tm_sec;
-	return mpt::Date::Unix(seconds);
+	int32 daynum = ToDaynum(timeUtc.year, timeUtc.month, timeUtc.day);
+	int64 seconds = static_cast<int64>(daynum - ToDaynum(1970, 1, 1)) * 24 * 60 * 60 + timeUtc.hours * 60 * 60 + timeUtc.minutes * 60 + timeUtc.seconds;
+	return Unix{seconds};
 }
 
-tm Unix::AsUTC() const 
+mpt::Date::UTC UnixAsUTC(Unix tp)
 {
-	int64 tmp = Value;
+	int64 tmp = tp.value;
 	int64 seconds = tmp % 60; tmp /= 60;
 	int64 minutes = tmp % 60; tmp /= 60;
 	int64 hours   = tmp % 24; tmp /= 24;
 	int32 year = 0, month = 0, day = 0;
 	FromDaynum(static_cast<int32>(tmp) + ToDaynum(1970,1,1), year, month, day);
-	tm result = {};
-	result.tm_year = year - 1900;
-	result.tm_mon = month - 1;
-	result.tm_mday = day;
-	result.tm_hour = static_cast<int32>(hours);
-	result.tm_min = static_cast<int32>(minutes);
-	result.tm_sec = static_cast<int32>(seconds);
+	mpt::Date::UTC result = {};
+	result.year = year;
+	result.month = month;
+	result.day = day;
+	result.hours = static_cast<int32>(hours);
+	result.minutes = static_cast<int32>(minutes);
+	result.seconds = static_cast<int64>(seconds);
 	return result;
 }
 
-mpt::ustring ToShortenedISO8601(tm date)
+#endif
+
+mpt::ustring ToShortenedISO8601(mpt::Date::UTC date)
 {
-	// We assume date in UTC here.
-	// There are too many differences in supported format specifiers in strftime()
-	// and strftime does not support reduced precision ISO8601 at all.
-	// Just do the formatting ourselves.
 	mpt::ustring result;
 	mpt::ustring tz = U_("Z");
-	if(date.tm_year == 0)
+	if(date.year == 0)
 	{
 		return result;
 	}
-	result += mpt::ufmt::dec0<4>(date.tm_year + 1900);
-	if(date.tm_mon < 0 || date.tm_mon > 11)
+	result += mpt::ufmt::dec0<4>(date.year);
+	result += U_("-") + mpt::ufmt::dec0<2>(date.month);
+	result += U_("-") + mpt::ufmt::dec0<2>(date.day);
+	if(date.hours == 0 && date.minutes == 0 && date.seconds)
 	{
 		return result;
 	}
-	result += U_("-") + mpt::ufmt::dec0<2>(date.tm_mon + 1);
-	if(date.tm_mday < 1 || date.tm_mday > 31)
-	{
-		return result;
-	}
-	result += U_("-") + mpt::ufmt::dec0<2>(date.tm_mday);
-	if(date.tm_hour == 0 && date.tm_min == 0 && date.tm_sec == 0)
-	{
-		return result;
-	}
-	if(date.tm_hour < 0 || date.tm_hour > 23)
-	{
-		return result;
-	}
-	if(date.tm_min < 0 || date.tm_min > 59)
-	{
-		return result;
-	}
 	result += U_("T");
-	if(date.tm_isdst > 0)
+	result += mpt::ufmt::dec0<2>(date.hours) + U_(":") + mpt::ufmt::dec0<2>(date.minutes);
+	if(date.seconds == 0)
 	{
-		tz = U_("+01:00");
-	}
-	result += mpt::ufmt::dec0<2>(date.tm_hour) + U_(":") + mpt::ufmt::dec0<2>(date.tm_min);
-	if(date.tm_sec < 0 || date.tm_sec > 61)
-	{
 		return result + tz;
 	}
-	result += U_(":") + mpt::ufmt::dec0<2>(date.tm_sec);
+	result += U_(":") + mpt::ufmt::dec0<2>(date.seconds);
 	result += tz;
 	return result;
 }
Index: common/mptTime.h
===================================================================
--- common/mptTime.h	(revision 16961)
+++ common/mptTime.h	(working copy)
@@ -12,9 +12,14 @@
 
 #include "openmpt/all/BuildSettings.hpp"
 
+#if MPT_CXX_AT_LEAST(20) && !defined(MPT_LIBCXX_QUIRK_NO_CHRONO_DATE)
+#include <chrono>
+#endif
 #include <string>
 
+#if MPT_CXX_BEFORE(20) || defined(MPT_LIBCXX_QUIRK_NO_CHRONO_DATE)
 #include <time.h>
+#endif
 
 
 OPENMPT_NAMESPACE_BEGIN
@@ -44,22 +49,124 @@
 
 #endif // MODPLUG_TRACKER
 
-class Unix
+struct UTC
 {
+	int          year    = 0;
+	unsigned int month   = 0;
+	unsigned int day     = 0;
+	int32        hours   = 0;
+	int32        minutes = 0;
+	int64        seconds = 0;
+	friend bool operator==(const UTC& lhs, const UTC& rhs)
+	{
+		return true
+			&& lhs.year == rhs.year
+			&& lhs.month == rhs.month
+			&& lhs.day == rhs.day
+			&& lhs.hours == rhs.hours
+			&& lhs.minutes == rhs.minutes
+			&& lhs.seconds == rhs.seconds
+			;
+	}
+	friend bool operator!=(const UTC& lhs, const UTC& rhs)
+	{
+		return false
+			|| lhs.year != rhs.year
+			|| lhs.month != rhs.month
+			|| lhs.day != rhs.day
+			|| lhs.hours != rhs.hours
+			|| lhs.minutes != rhs.minutes
+			|| lhs.seconds != rhs.seconds
+			;
+	}
+};
+
+#if MPT_CXX_AT_LEAST(20) && !defined(MPT_LIBCXX_QUIRK_NO_CHRONO_DATE)
+
+using Unix = std::chrono::system_clock::time_point;
+
+inline Unix UnixNow()
+{
+	return std::chrono::system_clock::now();
+}
+
+inline int64 UnixAsSeconds(Unix tp)
+{
+	return std::chrono::duration_cast<std::chrono::seconds>(tp.time_since_epoch()).count();
+}
+
+inline Unix UnixFromSeconds(int64 seconds)
+{
+	return std::chrono::system_clock::time_point{std::chrono::seconds{seconds}};
+}
+
+inline mpt::Date::Unix UnixFromUTC(UTC utc)
+{
+	std::chrono::year_month_day ymd =
+		std::chrono::year{utc.year} /
+		std::chrono::month{utc.month} /
+		std::chrono::day{utc.day};
+	std::chrono::hh_mm_ss<std::chrono::seconds> hms{
+		std::chrono::hours{utc.hours} +
+		std::chrono::minutes{utc.minutes} +
+		std::chrono::seconds{utc.seconds}};
+	return std::chrono::system_clock::time_point{static_cast<std::chrono::sys_days>(ymd)} + hms.to_duration();
+}
+
+inline mpt::Date::UTC UnixAsUTC(Unix tp)
+{
+	std::chrono::sys_days dp = std::chrono::floor<std::chrono::days>(tp);
+	std::chrono::year_month_day ymd{dp};
+	std::chrono::hh_mm_ss hms{tp - dp};
+	mpt::Date::UTC result;
+	result.year = static_cast<int>(ymd.year());
+	result.month = static_cast<unsigned int>(ymd.month());
+	result.day = static_cast<unsigned int>(ymd.day());
+	result.hours = hms.hours().count();
+	result.minutes = hms.minutes().count();
+	result.seconds = hms.seconds().count();
+	return result;
+}
+
+#else
+
 // int64 counts 1s since 1970-01-01T00:00Z
-private:
-	int64 Value;
-public:
-	Unix();
-	explicit Unix(int64 unixtime);
-	operator int64 () const;
-public:
-	static mpt::Date::Unix FromUTC(tm timeUtc);
-	tm AsUTC() const;
+struct Unix
+{
+	int64 value{};
+	friend bool operator==(const Unix &a, const Unix &b)
+	{
+		return a.value == b.value;
+	}
+	friend bool operator!=(const Unix &a, const Unix &b)
+	{
+		return a.value != b.value;
+	}
 };
 
-mpt::ustring ToShortenedISO8601(tm date); // i.e. 2015-01-15T18:32:01Z
+inline Unix UnixNow()
+{
+	return Unix{static_cast<int64>(time(nullptr))};
+}
 
+inline int64 UnixAsSeconds(Unix tp)
+{
+	return tp.value;
+}
+
+inline Unix UnixFromSeconds(int64 seconds)
+{
+	return Unix{seconds};
+}
+
+mpt::Date::Unix UnixFromUTC(UTC timeUtc);
+
+mpt::Date::UTC UnixAsUTC(Unix tp);
+
+#endif
+
+mpt::ustring ToShortenedISO8601(UTC date); // i.e. 2015-01-15T18:32:01Z
+
 } // namespace Date
 } // namespace mpt
 
Index: common/versionNumber.h
===================================================================
--- common/versionNumber.h	(revision 16961)
+++ common/versionNumber.h	(working copy)
@@ -18,6 +18,6 @@
 #define VER_MAJORMAJOR  1
 #define VER_MAJOR      31
 #define VER_MINOR      00
-#define VER_MINORMINOR 09
+#define VER_MINORMINOR 10
 
 OPENMPT_NAMESPACE_END
Index: mptrack/dlg_misc.cpp
===================================================================
--- mptrack/dlg_misc.cpp	(revision 16961)
+++ mptrack/dlg_misc.cpp	(working copy)
@@ -1311,18 +1311,8 @@
 	for(const auto &entry : editHistory)
 	{
 		totalTime += entry.openTime;
-
 		// Date
-		CString sDate;
-		if(entry.HasValidDate())
-		{
-			TCHAR szDate[32];
-			_tcsftime(szDate, std::size(szDate), _T("%d %b %Y, %H:%M:%S"), &entry.loadDate);
-			sDate = szDate;
-		} else
-		{
-			sDate = _T("<unknown date>");
-		}
+		CString sDate = entry.HasValidDate() ? CTime(mpt::Date::UnixAsSeconds(mpt::Date::UnixFromUTC(entry.loadDate))).Format(_T("%d %b %Y, %H:%M:%S")) : CString(_T("<unknown date>"));
 		// Time + stuff
 		uint32 duration = mpt::saturate_round<uint32>(entry.openTime / HISTORY_TIMER_PRECISION);
 		s += MPT_CFORMAT("Loaded {}, open for {}h {}m {}s\r\n")(
Index: mptrack/MainFrm.cpp
===================================================================
--- mptrack/MainFrm.cpp	(revision 16961)
+++ mptrack/MainFrm.cpp	(working copy)
@@ -2784,8 +2784,10 @@
 	const bool isAutoUpdate = CUpdateCheck::IsAutoUpdateFromMessage(wparam, lparam);
 	const UpdateCheckResult &result = CUpdateCheck::MessageAsResult(wparam, lparam);
 	CUpdateCheck::AcknowledgeSuccess(result);
-	if(result.CheckTime != time_t{})
-		TrackerSettings::Instance().UpdateLastUpdateCheck = mpt::Date::Unix(result.CheckTime);
+	if(result.CheckTime != mpt::Date::Unix{})
+	{
+		TrackerSettings::Instance().UpdateLastUpdateCheck = result.CheckTime;
+	}
 	if(!isAutoUpdate)
 	{
 		if(m_UpdateOptionsDialog)
Index: mptrack/Moddoc.cpp
===================================================================
--- mptrack/Moddoc.cpp	(revision 16961)
+++ mptrack/Moddoc.cpp	(working copy)
@@ -133,7 +133,7 @@
 	, m_InstrumentUndo(*this)
 {
 	// Set the creation date of this file (or the load time if we're loading an existing file)
-	time(&m_creationTime);
+	m_creationTime = mpt::Date::UnixNow();
 
 	ReinitRecordState();
 
Index: mptrack/Moddoc.h
===================================================================
--- mptrack/Moddoc.h	(revision 16961)
+++ mptrack/Moddoc.h	(working copy)
@@ -14,10 +14,10 @@
 
 #include "Sndfile.h"
 #include "../common/misc_util.h"
+#include "../common/mptTime.h"
 #include "Undo.h"
 #include "Notification.h"
 #include "UpdateHints.h"
-#include <time.h>
 
 OPENMPT_NAMESPACE_BEGIN
 
@@ -130,7 +130,7 @@
 	CSampleUndo m_SampleUndo;
 	CInstrumentUndo m_InstrumentUndo;
 	SplitKeyboardSettings m_SplitKeyboardSettings;	// this is maybe not the best place to keep them, but it should do the job
-	time_t m_creationTime;
+	mpt::Date::Unix m_creationTime;
 
 	std::atomic<bool> m_modifiedAutosave = false; // Modified since last autosave?
 
@@ -221,7 +221,7 @@
 	CInstrumentUndo &GetInstrumentUndo() { return m_InstrumentUndo; }
 	SplitKeyboardSettings &GetSplitKeyboardSettings() { return m_SplitKeyboardSettings; }
 
-	time_t GetCreationTime() const { return m_creationTime; }
+	mpt::Date::Unix GetCreationTime() const { return m_creationTime; }
 
 // operations
 public:
Index: mptrack/TrackerSettings.cpp
===================================================================
--- mptrack/TrackerSettings.cpp	(revision 16961)
+++ mptrack/TrackerSettings.cpp	(working copy)
@@ -326,7 +326,7 @@
 	// Update
 	, UpdateEnabled(conf, U_("Update"), U_("Enabled"), true)
 	, UpdateInstallAutomatically(conf, U_("Update"), U_("InstallAutomatically"), false)
-	, UpdateLastUpdateCheck(conf, U_("Update"), U_("LastUpdateCheck"), mpt::Date::Unix(time_t()))
+	, UpdateLastUpdateCheck(conf, U_("Update"), U_("LastUpdateCheck"), mpt::Date::Unix{})
 	, UpdateUpdateCheckPeriod_DEPRECATED(conf, U_("Update"), U_("UpdateCheckPeriod"), 7)
 	, UpdateIntervalDays(conf, U_("Update"), U_("UpdateCheckIntervalDays"), 7)
 	, UpdateChannel(conf, U_("Update"), U_("Channel"), UpdateChannelRelease)
@@ -822,6 +822,10 @@
 		conf.Forget(UpdateUpdateURL_DEPRECATED.GetPath());
 		conf.Forget(UpdateSendGUID_DEPRECATED.GetPath());
 	}
+	if(storedVersion < MPT_V("1.31.00.10"))
+	{
+		UpdateLastUpdateCheck = mpt::Date::Unix{};
+	}
 #endif // MPT_ENABLE_UPDATE
 
 	if(storedVersion < MPT_V("1.29.00.39"))
Index: mptrack/TrackerSettings.h
===================================================================
--- mptrack/TrackerSettings.h	(revision 16961)
+++ mptrack/TrackerSettings.h	(working copy)
@@ -471,32 +471,12 @@
 
 template<> inline SettingValue ToSettingValue(const mpt::Date::Unix &val)
 {
-	time_t t = val;
-	const tm* lastUpdate = gmtime(&t);
-	CString outDate;
-	if(lastUpdate)
-	{
-		outDate.Format(_T("%04d-%02d-%02d %02d:%02d"), lastUpdate->tm_year + 1900, lastUpdate->tm_mon + 1, lastUpdate->tm_mday, lastUpdate->tm_hour, lastUpdate->tm_min);
-	}
-	return SettingValue(mpt::ToUnicode(outDate), "UTC");
+	return SettingValue(mpt::ufmt::val(mpt::Date::UnixAsSeconds(val)), "UnixTime");
 }
 template<> inline mpt::Date::Unix FromSettingValue(const SettingValue &val)
 {
-	MPT_ASSERT(val.GetTypeTag() == "UTC");
-	std::string s = mpt::ToCharset(mpt::Charset::Locale, val.as<mpt::ustring>());
-	tm lastUpdate;
-	MemsetZero(lastUpdate);
-	if(sscanf(s.c_str(), "%04d-%02d-%02d %02d:%02d", &lastUpdate.tm_year, &lastUpdate.tm_mon, &lastUpdate.tm_mday, &lastUpdate.tm_hour, &lastUpdate.tm_min) == 5)
-	{
-		lastUpdate.tm_year -= 1900;
-		lastUpdate.tm_mon--;
-	}
-	time_t outTime = mpt::Date::Unix::FromUTC(lastUpdate);
-	if(outTime < 0)
-	{
-		outTime = 0;
-	}
-	return mpt::Date::Unix(outTime);
+	MPT_ASSERT(val.GetTypeTag() == "UnixTime");
+	return mpt::Date::UnixFromSeconds(mpt::ConvertStringTo<int64>(val.as<mpt::ustring>()));
 }
 
 struct FontSetting
Index: mptrack/UpdateCheck.cpp
===================================================================
--- mptrack/UpdateCheck.cpp	(revision 16961)
+++ mptrack/UpdateCheck.cpp	(working copy)
@@ -453,11 +453,11 @@
 			return;
 		}
 		// Do we actually need to run the update check right now?
-		const time_t now = time(nullptr);
-		const time_t lastCheck = TrackerSettings::Instance().UpdateLastUpdateCheck.Get();
+		const mpt::Date::Unix now = mpt::Date::UnixNow();
+		const mpt::Date::Unix lastCheck = TrackerSettings::Instance().UpdateLastUpdateCheck.Get();
 		// Check update interval. Note that we always check for updates when the system time had gone backwards (i.e. when the last update check supposedly happened in the future).
-		const double secsSinceLastCheck = difftime(now, lastCheck);
-		if(secsSinceLastCheck > 0.0 && secsSinceLastCheck < updateCheckPeriod * 86400.0)
+		const int64 secsSinceLastCheck = mpt::Date::UnixAsSeconds(now) - mpt::Date::UnixAsSeconds(lastCheck);
+		if(secsSinceLastCheck > 0 && secsSinceLastCheck < updateCheckPeriod * 86400)
 		{
 			loadPersisted = true;
 		}
@@ -475,7 +475,7 @@
 				);
 			if(Reporting::Confirm(msg, _T("OpenMPT Update")) == cnfNo)
 			{
-				TrackerSettings::Instance().UpdateLastUpdateCheck = mpt::Date::Unix(now);
+				TrackerSettings::Instance().UpdateLastUpdateCheck = now;
 				return;
 			}
 		}
@@ -650,7 +650,7 @@
 				{
 					std::vector<std::byte> data = GetFileReader(f).ReadRawDataAsByteVector();
 					nlohmann::json::parse(mpt::buffer_cast<std::string>(data)).get<Update::versions>();
-					result.CheckTime = time_t{};
+					result.CheckTime = mpt::Date::Unix{};
 					result.json = data;
 					loaded = true;
 				}
@@ -781,7 +781,7 @@
 
 	// Now, evaluate the downloaded data.
 	UpdateCheckResult result;
-	result.CheckTime = time(nullptr);
+	result.CheckTime = mpt::Date::UnixNow();
 	try
 	{
 		nlohmann::json::parse(mpt::buffer_cast<std::string>(resultHTTP.Data)).get<Update::versions>();
@@ -1315,7 +1315,7 @@
 {
 	if(!result.IsFromCache())
 	{
-		TrackerSettings::Instance().UpdateLastUpdateCheck = mpt::Date::Unix(result.CheckTime);
+		TrackerSettings::Instance().UpdateLastUpdateCheck = result.CheckTime;
 	}
 }
 
@@ -1612,14 +1612,10 @@
 	if(changedPath == TrackerSettings::Instance().UpdateLastUpdateCheck.GetPath())
 	{
 		CString updateText;
-		const time_t t = TrackerSettings::Instance().UpdateLastUpdateCheck.Get();
-		if(t > 0)
+		const mpt::Date::Unix t = TrackerSettings::Instance().UpdateLastUpdateCheck.Get();
+		if(t != mpt::Date::Unix{})
 		{
-			const tm* const lastUpdate = localtime(&t);
-			if(lastUpdate != nullptr)
-			{
-				updateText.Format(_T("The last successful update check was run on %04d-%02d-%02d, %02d:%02d."), lastUpdate->tm_year + 1900, lastUpdate->tm_mon + 1, lastUpdate->tm_mday, lastUpdate->tm_hour, lastUpdate->tm_min);
-			}
+			updateText += CTime(mpt::Date::UnixAsSeconds(t)).Format(_T("The last successful update check was run on %F, %R."));
 		}
 		updateText += _T("\r\n");
 		SetDlgItemText(IDC_LASTUPDATE, updateText);
Index: mptrack/UpdateCheck.h
===================================================================
--- mptrack/UpdateCheck.h	(revision 16961)
+++ mptrack/UpdateCheck.h	(working copy)
@@ -14,7 +14,7 @@
 
 #include "mpt/uuid/uuid.hpp"
 
-#include <time.h>
+#include "../common/mptTime.h"
 
 #include <atomic>
 
@@ -40,9 +40,12 @@
 
 struct UpdateCheckResult
 {
-	time_t CheckTime = time_t{};
+	mpt::Date::Unix CheckTime = mpt::Date::Unix{};
 	std::vector<std::byte> json;
-	bool IsFromCache() const noexcept { return CheckTime == time_t{}; }
+	bool IsFromCache() const noexcept
+	{
+		return CheckTime == mpt::Date::Unix{};
+	}
 };
 
 class CUpdateCheck
Index: soundlib/ITTools.cpp
===================================================================
--- soundlib/ITTools.cpp	(revision 16961)
+++ soundlib/ITTools.cpp	(working copy)
@@ -637,15 +637,15 @@
 void ITHistoryStruct::ConvertToMPT(FileHistory &mptHistory) const
 {
 	// Decode FAT date and time
-	MemsetZero(mptHistory.loadDate);
+	mptHistory.loadDate = mpt::Date::UTC{};
 	if(fatdate != 0 || fattime != 0)
 	{
-		mptHistory.loadDate.tm_year = ((fatdate >> 9) & 0x7F) + 80;
-		mptHistory.loadDate.tm_mon = Clamp((fatdate >> 5) & 0x0F, 1, 12) - 1;
-		mptHistory.loadDate.tm_mday = Clamp(fatdate & 0x1F, 1, 31);
-		mptHistory.loadDate.tm_hour = Clamp((fattime >> 11) & 0x1F, 0, 23);
-		mptHistory.loadDate.tm_min = Clamp((fattime >> 5) & 0x3F, 0, 59);
-		mptHistory.loadDate.tm_sec = Clamp((fattime & 0x1F) * 2, 0, 59);
+		mptHistory.loadDate.year = ((fatdate >> 9) & 0x7F) + 1980;
+		mptHistory.loadDate.month = Clamp((fatdate >> 5) & 0x0F, 1, 12);
+		mptHistory.loadDate.day = Clamp(fatdate & 0x1F, 1, 31);
+		mptHistory.loadDate.hours = Clamp((fattime >> 11) & 0x1F, 0, 23);
+		mptHistory.loadDate.minutes = Clamp((fattime >> 5) & 0x3F, 0, 59);
+		mptHistory.loadDate.seconds = Clamp((fattime & 0x1F) * 2, 0, 59);
 	}
 	mptHistory.openTime = static_cast<uint32>(runtime * (HISTORY_TIMER_PRECISION / 18.2));
 }
@@ -657,8 +657,8 @@
 	// Create FAT file dates
 	if(mptHistory.HasValidDate())
 	{
-		fatdate = static_cast<uint16>(mptHistory.loadDate.tm_mday | ((mptHistory.loadDate.tm_mon + 1) << 5) | ((mptHistory.loadDate.tm_year - 80) << 9));
-		fattime = static_cast<uint16>((mptHistory.loadDate.tm_sec / 2) | (mptHistory.loadDate.tm_min << 5) | (mptHistory.loadDate.tm_hour << 11));
+		fatdate = static_cast<uint16>(mptHistory.loadDate.day | (mptHistory.loadDate.month << 5) | ((mptHistory.loadDate.year - 1980) << 9));
+		fattime = static_cast<uint16>((mptHistory.loadDate.seconds / 2) | (mptHistory.loadDate.minutes << 5) | (mptHistory.loadDate.hours << 11));
 	} else
 	{
 		fatdate = 0;
Index: soundlib/Load_dmf.cpp
===================================================================
--- soundlib/Load_dmf.cpp	(revision 16961)
+++ soundlib/Load_dmf.cpp	(working copy)
@@ -911,9 +911,9 @@
 	m_songArtist = mpt::ToUnicode(mpt::Charset::CP437, mpt::String::ReadBuf(mpt::String::spacePadded, fileHeader.composer));
 
 	FileHistory mptHistory;
-	mptHistory.loadDate.tm_mday = Clamp(fileHeader.creationDay, uint8(1), uint8(31));
-	mptHistory.loadDate.tm_mon = Clamp(fileHeader.creationMonth, uint8(1), uint8(12)) - 1;
-	mptHistory.loadDate.tm_year = fileHeader.creationYear;
+	mptHistory.loadDate.day = Clamp(fileHeader.creationDay, uint8(1), uint8(31));
+	mptHistory.loadDate.month = Clamp(fileHeader.creationMonth, uint8(1), uint8(12));
+	mptHistory.loadDate.year = 1900 + fileHeader.creationYear;
 	m_FileHistory.clear();
 	m_FileHistory.push_back(mptHistory);
 
Index: soundlib/Load_gt2.cpp
===================================================================
--- soundlib/Load_gt2.cpp	(revision 16961)
+++ soundlib/Load_gt2.cpp	(working copy)
@@ -1214,9 +1214,9 @@
 	m_playBehaviour.set(kApplyOffsetWithoutNote);
 
 	FileHistory mptHistory;
-	mptHistory.loadDate.tm_mday = Clamp<uint8, uint8>(fileHeader.day, 1, 31);
-	mptHistory.loadDate.tm_mon = Clamp<uint8, uint8>(fileHeader.month, 1, 12) - 1;
-	mptHistory.loadDate.tm_year = fileHeader.year - 1900;
+	mptHistory.loadDate.day = Clamp<uint8, uint8>(fileHeader.day, 1, 31);
+	mptHistory.loadDate.month = Clamp<uint8, uint8>(fileHeader.month, 1, 12);
+	mptHistory.loadDate.year = fileHeader.year;
 	m_FileHistory.push_back(mptHistory);
 
 	m_nChannels = 32;
Index: soundlib/Load_it.cpp
===================================================================
--- soundlib/Load_it.cpp	(revision 16961)
+++ soundlib/Load_it.cpp	(working copy)
@@ -1352,17 +1352,9 @@
 		} else if(pModDoc != nullptr)
 		{
 			// Current ("new") timestamp
-			const time_t creationTime = pModDoc->GetCreationTime();
-
-			MemsetZero(mptHistory.loadDate);
-			//localtime_s(&loadDate, &creationTime);
-			const tm* const p = localtime(&creationTime);
-			if (p != nullptr)
-				mptHistory.loadDate = *p;
-			else
-				sndFile.AddToLog(LogError, U_("Unable to retrieve current time."));
-
-			mptHistory.openTime = (uint32)(difftime(time(nullptr), creationTime) * HISTORY_TIMER_PRECISION);
+			const mpt::Date::Unix creationTime = pModDoc->GetCreationTime();
+			mptHistory.loadDate = mpt::Date::UnixAsUTC(creationTime);
+			mptHistory.openTime = static_cast<uint32>(mpt::round((mpt::Date::UnixAsSeconds(mpt::Date::UnixNow()) - mpt::Date::UnixAsSeconds(creationTime)) * HISTORY_TIMER_PRECISION));
 #endif // MODPLUG_TRACKER
 		}
 
Index: soundlib/Load_mod.cpp
===================================================================
--- soundlib/Load_mod.cpp	(revision 16961)
+++ soundlib/Load_mod.cpp	(working copy)
@@ -2180,12 +2180,12 @@
 		   && mpt::is_in_range(info.dateMinute, 0, 59) && mpt::is_in_range(info.dateSecond, 0, 59))
 		{
 			FileHistory mptHistory;
-			mptHistory.loadDate.tm_year = info.dateYear;
-			mptHistory.loadDate.tm_mon = info.dateMonth - 1;
-			mptHistory.loadDate.tm_mday = info.dateDay;
-			mptHistory.loadDate.tm_hour = info.dateHour;
-			mptHistory.loadDate.tm_min = info.dateMinute;
-			mptHistory.loadDate.tm_sec = info.dateSecond;
+			mptHistory.loadDate.year = info.dateYear + 1900;
+			mptHistory.loadDate.month = info.dateMonth;
+			mptHistory.loadDate.day = info.dateDay;
+			mptHistory.loadDate.hours = info.dateHour;
+			mptHistory.loadDate.minutes = info.dateMinute;
+			mptHistory.loadDate.seconds = info.dateSecond;
 			m_FileHistory.push_back(mptHistory);
 		}
 	}
Index: soundlib/Sndfile.cpp
===================================================================
--- soundlib/Sndfile.cpp	(revision 16961)
+++ soundlib/Sndfile.cpp	(working copy)
@@ -56,15 +56,15 @@
 
 mpt::ustring FileHistory::AsISO8601() const
 {
-	tm date = loadDate;
+	mpt::Date::UTC date = loadDate;
 	if(openTime > 0)
 	{
 		// Calculate the date when editing finished.
 		double openSeconds = static_cast<double>(openTime) / HISTORY_TIMER_PRECISION;
-		tm tmpLoadDate = loadDate;
-		int64 loadDateSinceEpoch = mpt::Date::Unix::FromUTC(tmpLoadDate);
+		mpt::Date::UTC tmpLoadDate = loadDate;
+		int64 loadDateSinceEpoch = mpt::Date::UnixAsSeconds(mpt::Date::UnixFromUTC(tmpLoadDate));
 		int64 saveDateSinceEpoch = loadDateSinceEpoch + mpt::saturate_round<int64>(openSeconds);
-		date = mpt::Date::Unix(saveDateSinceEpoch).AsUTC();
+		date = mpt::Date::UnixAsUTC(mpt::Date::UnixFromSeconds(saveDateSinceEpoch));
 	}
 	return mpt::Date::ToShortenedISO8601(date);
 }
Index: soundlib/Sndfile.h
===================================================================
--- soundlib/Sndfile.h	(revision 16961)
+++ soundlib/Sndfile.h	(working copy)
@@ -243,13 +243,16 @@
 struct FileHistory
 {
 	// Date when the file was loaded in the the tracker or created.
-	tm loadDate = {};
+	mpt::Date::UTC loadDate = {};
 	// Time the file was open in the editor, in 1/18.2th seconds (frequency of a standard DOS timer, to keep compatibility with Impulse Tracker easy).
 	uint32 openTime = 0;
 	// Return the date as a (possibly truncated if not enough precision is available) ISO 8601 formatted date.
 	mpt::ustring AsISO8601() const;
 	// Returns true if the date component is valid. Some formats only store edit time, not edit date.
-	bool HasValidDate() const { return loadDate.tm_mday != 0; }
+	bool HasValidDate() const
+	{
+		return loadDate != mpt::Date::UTC{};
+	}
 };
 
 
Index: src/mpt/base/detect_quirks.hpp
===================================================================
--- src/mpt/base/detect_quirks.hpp	(revision 16961)
+++ src/mpt/base/detect_quirks.hpp	(working copy)
@@ -145,4 +145,34 @@
 
 
 
+#if MPT_CXX_AT_LEAST(20)
+#if MPT_LIBCXX_MS && MPT_OS_WINDOWS
+#if defined(NTDDI_VERSION)
+#if (NTDDI_VERSION < 0x0A000007) // < Windows 10 1903
+// std::chrono timezones require Windows 10 1903 with VS2022 as of 2022-01-22.
+// See <https://github.com/microsoft/STL/issues/1911>
+// and <https://github.com/microsoft/STL/issues/2163>.
+#define MPT_LIBCXX_QUIRK_NO_CHRONO_DATE
+#endif
+#else
+#define MPT_LIBCXX_QUIRK_NO_CHRONO_DATE
+#endif
+#endif
+#if MPT_LIBCXX_GNU_BEFORE(11)
+#define MPT_LIBCXX_QUIRK_NO_CHRONO_DATE
+#elif MPT_LIBCXX_LLVM_BEFORE(7000)
+#define MPT_LIBCXX_QUIRK_NO_CHRONO_DATE
+#endif
+#if MPT_LIBCXX_MS
+// Causes massive memory leaks.
+// See <https://developercommunity.visualstudio.com/t/stdchronoget-tzdb-list-memory-leak/1644641>
+// / <https://github.com/microsoft/STL/issues/2504>.
+#define MPT_LIBCXX_QUIRK_NO_CHRONO_DATE_PARSE
+#elif MPT_LIBCXX_GNU
+#define MPT_LIBCXX_QUIRK_NO_CHRONO_DATE_PARSE
+#endif
+#endif
+
+
+
 #endif // MPT_BASE_DETECT_QUIRKS_HPP
Index: test/test.cpp
===================================================================
--- test/test.cpp	(revision 16961)
+++ test/test.cpp	(working copy)
@@ -777,48 +777,15 @@
 }
 
 
-namespace {
-
-struct Gregorian {
-	int Y,M,D,h,m,s;
-	static Gregorian FromTM(tm t) {
-		Gregorian g;
-		g.Y = t.tm_year + 1900;
-		g.M = t.tm_mon + 1;
-		g.D = t.tm_mday;
-		g.h = t.tm_hour;
-		g.m = t.tm_min;
-		g.s = t.tm_sec;
-		return g;
-	}
-	static tm ToTM(Gregorian g) {
-		tm t;
-		MemsetZero(t);
-		t.tm_year = g.Y - 1900;
-		t.tm_mon = g.M - 1;
-		t.tm_mday = g.D;
-		t.tm_hour = g.h;
-		t.tm_min = g.m;
-		t.tm_sec = g.s;
-		return t;
-	}
-};
-
-inline bool operator ==(Gregorian a, Gregorian b) {
-	return a.Y == b.Y && a.M == b.M && a.D == b.D && a.h == b.h && a.m == b.m && a.s == b.s;
+static int64 TestDate1(int s, int m, int h, unsigned int D, unsigned int M, int Y) {
+	return mpt::Date::UnixAsSeconds(mpt::Date::UnixFromUTC(mpt::Date::UTC{Y,M,D,h,m,s}));
 }
 
+static mpt::Date::UTC TestDate2(int s, int m, int h, unsigned int D, unsigned int M, int Y) {
+	return mpt::Date::UTC{Y,M,D,h,m,s};
 }
 
-static int64 TestDate1(int s, int m, int h, int D, int M, int Y) {
-	return mpt::Date::Unix::FromUTC(Gregorian::ToTM(Gregorian{Y,M,D,h,m,s}));
-}
 
-static Gregorian TestDate2(int s, int m, int h, int D, int M, int Y) {
-	return Gregorian{Y,M,D,h,m,s};
-}
-
-
 static MPT_NOINLINE void TestMisc1()
 {
 
@@ -1086,30 +1053,30 @@
 	VERIFY_EQUAL(    1413064016, TestDate1( 56, 46, 21, 11, 10, 2014 ));
 	VERIFY_EQUAL(    1413064100, TestDate1( 20, 48, 21, 11, 10, 2014 ));
 
-	VERIFY_EQUAL(Gregorian::FromTM(mpt::Date::Unix(             0).AsUTC()), TestDate2(  0,  0,  0,  1,  1, 1970 ));
-	VERIFY_EQUAL(Gregorian::FromTM(mpt::Date::Unix(          3600).AsUTC()), TestDate2(  0,  0,  1,  1,  1, 1970 ));
-	VERIFY_EQUAL(Gregorian::FromTM(mpt::Date::Unix(         86400).AsUTC()), TestDate2(  0,  0,  0,  2,  1, 1970 ));
-	VERIFY_EQUAL(Gregorian::FromTM(mpt::Date::Unix(      31536000).AsUTC()), TestDate2(  0,  0,  0,  1,  1, 1971 ));
-	VERIFY_EQUAL(Gregorian::FromTM(mpt::Date::Unix(     100000000).AsUTC()), TestDate2( 40, 46,  9,  3,  3, 1973 ));
-	VERIFY_EQUAL(Gregorian::FromTM(mpt::Date::Unix(     951782400).AsUTC()), TestDate2(  0,  0,  0, 29,  2, 2000 ));
-	VERIFY_EQUAL(Gregorian::FromTM(mpt::Date::Unix(    1000000000).AsUTC()), TestDate2( 40, 46,  1,  9,  9, 2001 ));
-	VERIFY_EQUAL(Gregorian::FromTM(mpt::Date::Unix(    1044057600).AsUTC()), TestDate2(  0,  0,  0,  1,  2, 2003 ));
-	VERIFY_EQUAL(Gregorian::FromTM(mpt::Date::Unix(    1044144000).AsUTC()), TestDate2(  0,  0,  0,  2,  2, 2003 ));
-	VERIFY_EQUAL(Gregorian::FromTM(mpt::Date::Unix(    1046476800).AsUTC()), TestDate2(  0,  0,  0,  1,  3, 2003 ));
-	VERIFY_EQUAL(Gregorian::FromTM(mpt::Date::Unix(    1064966400).AsUTC()), TestDate2(  0,  0,  0,  1, 10, 2003 ));
-	VERIFY_EQUAL(Gregorian::FromTM(mpt::Date::Unix(    1077926399).AsUTC()), TestDate2( 59, 59, 23, 27,  2, 2004 ));
-	VERIFY_EQUAL(Gregorian::FromTM(mpt::Date::Unix(    1077926400).AsUTC()), TestDate2(  0,  0,  0, 28,  2, 2004 ));
-	VERIFY_EQUAL(Gregorian::FromTM(mpt::Date::Unix(    1077926410).AsUTC()), TestDate2( 10,  0,  0, 28,  2, 2004 ));
-	VERIFY_EQUAL(Gregorian::FromTM(mpt::Date::Unix(    1078012799).AsUTC()), TestDate2( 59, 59, 23, 28,  2, 2004 ));
-	VERIFY_EQUAL(Gregorian::FromTM(mpt::Date::Unix(    1078012800).AsUTC()), TestDate2(  0,  0,  0, 29,  2, 2004 ));
-	VERIFY_EQUAL(Gregorian::FromTM(mpt::Date::Unix(    1078012820).AsUTC()), TestDate2( 20,  0,  0, 29,  2, 2004 ));
-	VERIFY_EQUAL(Gregorian::FromTM(mpt::Date::Unix(    1078099199).AsUTC()), TestDate2( 59, 59, 23, 29,  2, 2004 ));
-	VERIFY_EQUAL(Gregorian::FromTM(mpt::Date::Unix(    1078099200).AsUTC()), TestDate2(  0,  0,  0,  1,  3, 2004 ));
-	VERIFY_EQUAL(Gregorian::FromTM(mpt::Date::Unix(    1078099230).AsUTC()), TestDate2( 30,  0,  0,  1,  3, 2004 ));
-	VERIFY_EQUAL(Gregorian::FromTM(mpt::Date::Unix(    1078185599).AsUTC()), TestDate2( 59, 59, 23,  1,  3, 2004 ));
-	VERIFY_EQUAL(Gregorian::FromTM(mpt::Date::Unix(    1096588800).AsUTC()), TestDate2(  0,  0,  0,  1, 10, 2004 ));
-	VERIFY_EQUAL(Gregorian::FromTM(mpt::Date::Unix(    1413064016).AsUTC()), TestDate2( 56, 46, 21, 11, 10, 2014 ));
-	VERIFY_EQUAL(Gregorian::FromTM(mpt::Date::Unix(    1413064100).AsUTC()), TestDate2( 20, 48, 21, 11, 10, 2014 ));
+	VERIFY_EQUAL(mpt::Date::UnixAsUTC(mpt::Date::UnixFromSeconds(             0)), TestDate2(  0,  0,  0,  1,  1, 1970 ));
+	VERIFY_EQUAL(mpt::Date::UnixAsUTC(mpt::Date::UnixFromSeconds(          3600)), TestDate2(  0,  0,  1,  1,  1, 1970 ));
+	VERIFY_EQUAL(mpt::Date::UnixAsUTC(mpt::Date::UnixFromSeconds(         86400)), TestDate2(  0,  0,  0,  2,  1, 1970 ));
+	VERIFY_EQUAL(mpt::Date::UnixAsUTC(mpt::Date::UnixFromSeconds(      31536000)), TestDate2(  0,  0,  0,  1,  1, 1971 ));
+	VERIFY_EQUAL(mpt::Date::UnixAsUTC(mpt::Date::UnixFromSeconds(     100000000)), TestDate2( 40, 46,  9,  3,  3, 1973 ));
+	VERIFY_EQUAL(mpt::Date::UnixAsUTC(mpt::Date::UnixFromSeconds(     951782400)), TestDate2(  0,  0,  0, 29,  2, 2000 ));
+	VERIFY_EQUAL(mpt::Date::UnixAsUTC(mpt::Date::UnixFromSeconds(    1000000000)), TestDate2( 40, 46,  1,  9,  9, 2001 ));
+	VERIFY_EQUAL(mpt::Date::UnixAsUTC(mpt::Date::UnixFromSeconds(    1044057600)), TestDate2(  0,  0,  0,  1,  2, 2003 ));
+	VERIFY_EQUAL(mpt::Date::UnixAsUTC(mpt::Date::UnixFromSeconds(    1044144000)), TestDate2(  0,  0,  0,  2,  2, 2003 ));
+	VERIFY_EQUAL(mpt::Date::UnixAsUTC(mpt::Date::UnixFromSeconds(    1046476800)), TestDate2(  0,  0,  0,  1,  3, 2003 ));
+	VERIFY_EQUAL(mpt::Date::UnixAsUTC(mpt::Date::UnixFromSeconds(    1064966400)), TestDate2(  0,  0,  0,  1, 10, 2003 ));
+	VERIFY_EQUAL(mpt::Date::UnixAsUTC(mpt::Date::UnixFromSeconds(    1077926399)), TestDate2( 59, 59, 23, 27,  2, 2004 ));
+	VERIFY_EQUAL(mpt::Date::UnixAsUTC(mpt::Date::UnixFromSeconds(    1077926400)), TestDate2(  0,  0,  0, 28,  2, 2004 ));
+	VERIFY_EQUAL(mpt::Date::UnixAsUTC(mpt::Date::UnixFromSeconds(    1077926410)), TestDate2( 10,  0,  0, 28,  2, 2004 ));
+	VERIFY_EQUAL(mpt::Date::UnixAsUTC(mpt::Date::UnixFromSeconds(    1078012799)), TestDate2( 59, 59, 23, 28,  2, 2004 ));
+	VERIFY_EQUAL(mpt::Date::UnixAsUTC(mpt::Date::UnixFromSeconds(    1078012800)), TestDate2(  0,  0,  0, 29,  2, 2004 ));
+	VERIFY_EQUAL(mpt::Date::UnixAsUTC(mpt::Date::UnixFromSeconds(    1078012820)), TestDate2( 20,  0,  0, 29,  2, 2004 ));
+	VERIFY_EQUAL(mpt::Date::UnixAsUTC(mpt::Date::UnixFromSeconds(    1078099199)), TestDate2( 59, 59, 23, 29,  2, 2004 ));
+	VERIFY_EQUAL(mpt::Date::UnixAsUTC(mpt::Date::UnixFromSeconds(    1078099200)), TestDate2(  0,  0,  0,  1,  3, 2004 ));
+	VERIFY_EQUAL(mpt::Date::UnixAsUTC(mpt::Date::UnixFromSeconds(    1078099230)), TestDate2( 30,  0,  0,  1,  3, 2004 ));
+	VERIFY_EQUAL(mpt::Date::UnixAsUTC(mpt::Date::UnixFromSeconds(    1078185599)), TestDate2( 59, 59, 23,  1,  3, 2004 ));
+	VERIFY_EQUAL(mpt::Date::UnixAsUTC(mpt::Date::UnixFromSeconds(    1096588800)), TestDate2(  0,  0,  0,  1, 10, 2004 ));
+	VERIFY_EQUAL(mpt::Date::UnixAsUTC(mpt::Date::UnixFromSeconds(    1413064016)), TestDate2( 56, 46, 21, 11, 10, 2014 ));
+	VERIFY_EQUAL(mpt::Date::UnixAsUTC(mpt::Date::UnixFromSeconds(    1413064100)), TestDate2( 20, 48, 21, 11, 10, 2014 ));
 
 
 #ifdef MODPLUG_TRACKER
@@ -2069,12 +2036,12 @@
 	// Edit history
 	VERIFY_EQUAL_NONCONT(sndFile.GetFileHistory().size() > 15, true);
 	const FileHistory &fh = sndFile.GetFileHistory().front();
-	VERIFY_EQUAL_NONCONT(fh.loadDate.tm_year, 111);
-	VERIFY_EQUAL_NONCONT(fh.loadDate.tm_mon, 5);
-	VERIFY_EQUAL_NONCONT(fh.loadDate.tm_mday, 14);
-	VERIFY_EQUAL_NONCONT(fh.loadDate.tm_hour, 21);
-	VERIFY_EQUAL_NONCONT(fh.loadDate.tm_min, 8);
-	VERIFY_EQUAL_NONCONT(fh.loadDate.tm_sec, 32);
+	VERIFY_EQUAL_NONCONT(fh.loadDate.year, 2011);
+	VERIFY_EQUAL_NONCONT(fh.loadDate.month, 6);
+	VERIFY_EQUAL_NONCONT(fh.loadDate.day, 14);
+	VERIFY_EQUAL_NONCONT(fh.loadDate.hours, 21);
+	VERIFY_EQUAL_NONCONT(fh.loadDate.minutes, 8);
+	VERIFY_EQUAL_NONCONT(fh.loadDate.seconds, 32);
 	VERIFY_EQUAL_NONCONT((uint32)((double)fh.openTime / HISTORY_TIMER_PRECISION), 31);
 
 	// Macros
