Index: build/pch/PCH.h
===================================================================
--- build/pch/PCH.h	(revision 17650)
+++ build/pch/PCH.h	(working copy)
@@ -153,7 +153,6 @@
 #include <stdint.h>
 #include <stdlib.h>
 #include <string.h>
-#include <time.h>
 
 
 #endif // MPT_BUILD_ENABLE_PCH
Index: common/BuildSettings.h
===================================================================
--- common/BuildSettings.h	(revision 17650)
+++ common/BuildSettings.h	(working copy)
@@ -321,8 +321,11 @@
 
 #endif
 
+#define MPT_TIME_UTC_ON_DISK 0
+#define MPT_TIME_UTC_ON_DISK_VERSION MPT_V("1.31.00.13")
 
 
+
 // fixing stuff up
 
 #if defined(MPT_BUILD_ANALYZED) || defined(MPT_BUILD_CHECKED) 
Index: common/mptTime.cpp
===================================================================
--- common/mptTime.cpp	(revision 17650)
+++ common/mptTime.cpp	(working copy)
@@ -83,7 +83,7 @@
 
 #endif // MODPLUG_TRACKER
 
-#if MPT_CXX_BEFORE(20) || defined(MPT_LIBCXX_QUIRK_NO_CHRONO_DATE) || defined(MPT_TIME_CTIME)
+#if MPT_CXX_BEFORE(20) || defined(MPT_LIBCXX_QUIRK_NO_CHRONO_DATE)
 
 static int32 ToDaynum(int32 year, int32 month, int32 day)
 {
@@ -115,10 +115,6 @@
 	day = static_cast<int32>(dd);
 }
 
-#endif
-
-#if MPT_CXX_BEFORE(20) || defined(MPT_LIBCXX_QUIRK_NO_CHRONO_DATE)
-
 mpt::Date::Unix UnixFromUTC(UTC timeUtc)
 {
 	int32 daynum = ToDaynum(timeUtc.year, timeUtc.month, timeUtc.day);
@@ -140,12 +136,155 @@
 	result.day = day;
 	result.hours = static_cast<int32>(hours);
 	result.minutes = static_cast<int32>(minutes);
-	result.seconds = static_cast<int64>(seconds);
+	result.seconds = seconds;
 	return result;
 }
 
+#if defined(MODPLUG_TRACKER)
+
+mpt::Date::Unix UnixFromLocal(Local timeLocal)
+{
+#if defined(MPT_FALLBACK_TIMEZONE_WINDOWS_HISTORIC)
+	SYSTEMTIME sys_local{};
+	sys_local.wYear = static_cast<uint16>(timeLocal.year);
+	sys_local.wMonth = static_cast<uint16>(timeLocal.month);
+	sys_local.wDay = static_cast<uint16>(timeLocal.day);
+	sys_local.wHour = static_cast<uint16>(timeLocal.hours);
+	sys_local.wMinute = static_cast<uint16>(timeLocal.minutes);
+	sys_local.wSecond = static_cast<uint16>(timeLocal.seconds);
+	sys_local.wMilliseconds = 0;
+	DYNAMIC_TIME_ZONE_INFORMATION dtzi{};
+	if(GetDynamicTimeZoneInformation(&dtzi) == TIME_ZONE_ID_INVALID) // WinVista
+	{
+		return mpt::Date::Unix{};
+	}
+	SYSTEMTIME sys_utc{};
+	if(TzSpecificLocalTimeToSystemTimeEx(&dzti, &sys_local, &sys_utc) == FALSE) // Win7/Win8
+	{
+		return mpt::Date::Unix{};
+	}
+	FILETIME ft{};
+	if(SystemTimeToFileTime(&sys_utc, &ft) == FALSE) // Win 2000
+	{
+		return mpt::Date::Unix{};
+	}
+	ULARGE_INTEGER time_value{};
+	time_value.LowPart = ft.dwLowDateTime;
+	time_value.HighPart = ft.dwHighDateTime;
+	return mpt::Date::UnixFromSeconds(static_cast<int64>((time_value.QuadPart - 116444736000000000LL) / 10000000LL));
+#elif defined(MPT_FALLBACK_TIMEZONE_WINDOWS_CURRENT)
+	SYSTEMTIME sys_local{};
+	sys_local.wYear = static_cast<uint16>(timeLocal.year);
+	sys_local.wMonth = static_cast<uint16>(timeLocal.month);
+	sys_local.wDay = static_cast<uint16>(timeLocal.day);
+	sys_local.wHour = static_cast<uint16>(timeLocal.hours);
+	sys_local.wMinute = static_cast<uint16>(timeLocal.minutes);
+	sys_local.wSecond = static_cast<uint16>(timeLocal.seconds);
+	sys_local.wMilliseconds = 0;
+	SYSTEMTIME sys_utc{};
+	if(TzSpecificLocalTimeToSystemTime(NULL, &sys_local, &sys_utc) == FALSE) // WinXP
+	{
+		return mpt::Date::Unix{};
+	}
+	FILETIME ft{};
+	if(SystemTimeToFileTime(&sys_utc, &ft) == FALSE) // Win 2000
+	{
+		return mpt::Date::Unix{};
+	}
+	ULARGE_INTEGER time_value{};
+	time_value.LowPart = ft.dwLowDateTime;
+	time_value.HighPart = ft.dwHighDateTime;
+	return mpt::Date::UnixFromSeconds(static_cast<int64>((time_value.QuadPart - 116444736000000000LL) / 10000000LL));
+#elif defined(MPT_FALLBACK_TIMEZONE_C)
+	std::tm tmp{};
+	tmp.tm_year = timeLocal.year - 1900;
+	tmp.tm_mon = timeLocal.month - 1;
+	tmp.tm_mday = timeLocal.day;
+	tmp.tm_hour = timeLocal.hours;
+	tmp.tm_min = timeLocal.minutes;
+	tmp.tm_sec = static_cast<int>(timeLocal.seconds);
+	return mpt::Date::UnixFromSeconds(static_cast<int64>(std::mktime(&tmp)));
 #endif
+}
 
+mpt::Date::Local UnixAsLocal(Unix tp)
+{
+#if defined(MPT_FALLBACK_TIMEZONE_WINDOWS_HISTORIC)
+	ULARGE_INTEGER time_value{};
+	time_value.QuadPart = static_cast<int64>(mpt::Date::UnixAsSeconds(tp)) * 10000000LL + 116444736000000000LL;
+	FILETIME ft{};
+	ft.dwLowDateTime = time_value.LowPart;
+	ft.dwHighDateTime = time_value.HighPart;
+	SYSTEMTIME sys_utc{};
+	if(FileTimeToSystemTime(&ft, &sys_utc) == FALSE) // WinXP
+	{
+		return mpt::Date::Local{};
+	}
+	DYNAMIC_TIME_ZONE_INFORMATION dtzi{};
+	if(GetDynamicTimeZoneInformation(&dtzi) == TIME_ZONE_ID_INVALID) // WinVista
+	{
+		return mpt::Date::Local{};
+	}
+	SYSTEMTIME sys_local{};
+	if(SystemTimeToTzSpecificLocalTimeEx(&dzti, &sys_utc, &sys_local) == FALSE) // Win7/Win8
+	{
+		return mpt::Date::Local{};
+	}
+	mpt::Date::Local result{};
+	result.year = sys_local.wYear;
+	result.month = sys_local.wMonth;
+	result.day = sys_local.wDay;
+	result.hours = sys_local.wHour;
+	result.minutes = sys_local.wMinute;
+	result.seconds = sys_local.wSecond;
+	return result;
+#elif defined(MPT_FALLBACK_TIMEZONE_WINDOWS_CURRENT)
+	ULARGE_INTEGER time_value{};
+	time_value.QuadPart = static_cast<int64>(mpt::Date::UnixAsSeconds(tp)) * 10000000LL + 116444736000000000LL;
+	FILETIME ft{};
+	ft.dwLowDateTime = time_value.LowPart;
+	ft.dwHighDateTime = time_value.HighPart;
+	SYSTEMTIME sys_utc{};
+	if(FileTimeToSystemTime(&ft, &sys_utc) == FALSE) // WinXP
+	{
+		return mpt::Date::Local{};
+	}
+	SYSTEMTIME sys_local{};
+	if(SystemTimeToTzSpecificLocalTime(NULL, &sys_utc, &sys_local) == FALSE) // Win2000
+	{
+		return mpt::Date::Local{};
+	}
+	mpt::Date::Local result{};
+	result.year = sys_local.wYear;
+	result.month = sys_local.wMonth;
+	result.day = sys_local.wDay;
+	result.hours = sys_local.wHour;
+	result.minutes = sys_local.wMinute;
+	result.seconds = sys_local.wSecond;
+	return result;
+#elif defined(MPT_FALLBACK_TIMEZONE_C)
+	std::time_t time_tp = static_cast<std::time_t>(mpt::Date::UnixAsSeconds(tp));
+	std::tm *tmp = std::localtime(&time_tp);
+	if(!tmp)
+	{
+		return mpt::Date::Local{};
+	}
+	std::tm local = *tmp;
+	mpt::Date::Local result{};
+	result.year = local.tm_year + 1900;
+	result.month = local.tm_mon + 1;
+	result.day = local.tm_mday;
+	result.hours = local.tm_hour;
+	result.minutes = local.tm_min;
+	result.seconds = local.tm_sec;
+	return result;
+#endif
+}
+
+#endif // MODPLUG_TRACKER
+
+#endif
+
 template <LogicalTimezone TZ>
 static mpt::ustring ToShortenedISO8601Impl(mpt::Date::Gregorian<TZ> date)
 {
@@ -193,85 +332,13 @@
 	return ToShortenedISO8601Impl(date);
 }
 
-#if defined(MPT_TIME_CTIME)
-
-mpt::Date::Unix UnixFromUTCtm(tm timeUtc)
+#ifdef MODPLUG_TRACKER
+mpt::ustring ToShortenedISO8601(Local date)
 {
-	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::UnixFromSeconds(seconds);
+	return ToShortenedISO8601Impl(date);
 }
+#endif // MODPLUG_TRACKER
 
-tm UnixAsUTCtm(mpt::Date::Unix unixtime)
-{
-	int64 tmp = mpt::Date::UnixAsSeconds(unixtime);
-	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);
-	return result;
-}
-
-mpt::ustring ToShortenedISO8601(tm 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)
-	{
-		return result;
-	}
-	result += mpt::ufmt::dec0<4>(date.tm_year + 1900);
-	if(date.tm_mon < 0 || date.tm_mon > 11)
-	{
-		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)
-	{
-		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 += tz;
-	return result;
-}
-
-#endif
-
 } // namespace Date
 } // namespace mpt
 
Index: common/mptTime.h
===================================================================
--- common/mptTime.h	(revision 17650)
+++ common/mptTime.h	(working copy)
@@ -18,16 +18,25 @@
 #include <string>
 
 #if MPT_CXX_BEFORE(20) || defined(MPT_LIBCXX_QUIRK_NO_CHRONO_DATE)
-#include <time.h>
+#include <ctime>
 #endif
 
-#define MPT_TIME_CTIME
-
-#if defined(MPT_TIME_CTIME)
-#include <time.h>
+#if MPT_CXX_BEFORE(20) || defined(MPT_LIBCXX_QUIRK_NO_CHRONO_DATE)
+#if MPT_OS_WINDOWS && defined(_WIN32_WINNT)
+#if (_WIN32_WINNT >= 0x0602) // Win8
+#define MPT_FALLBACK_TIMEZONE_WINDOWS_HISTORIC
+#elif (_WIN32_WINNT >= 0x0501) // WinXP
+#define MPT_FALLBACK_TIMEZONE_WINDOWS_CURRENT
+#else
+#define MPT_FALLBACK_TIMEZONE_C
 #endif
+#else
+#define MPT_FALLBACK_TIMEZONE_C
+#endif
+#endif
 
 
+
 OPENMPT_NAMESPACE_BEGIN
 
 
@@ -99,37 +108,38 @@
 
 using AnyGregorian = Gregorian<LogicalTimezone::Unspecified>;
 
-#if defined(MPT_TIME_CTIME)
-inline tm AsTm(AnyGregorian val)
+using UTC = Gregorian<LogicalTimezone::UTC>;
+
+#if defined(MODPLUG_TRACKER)
+using Local = Gregorian<LogicalTimezone::Local>;
+#endif // MODPLUG_TRACKER
+
+template <LogicalTimezone TZ>
+inline Gregorian<TZ> interpret_as_timezone(AnyGregorian gregorian)
 {
-	tm result{};
-	result.tm_year = val.year - 1900;
-	result.tm_mon = val.month - 1;
-	result.tm_mday = val.day;
-	result.tm_hour = val.hours;
-	result.tm_min = val.minutes;
-	result.tm_sec = static_cast<int>(val.seconds);
+	Gregorian<TZ> result;
+	result.year = gregorian.year;
+	result.month = gregorian.month;
+	result.day = gregorian.day;
+	result.hours = gregorian.hours;
+	result.minutes = gregorian.minutes;
+	result.seconds = gregorian.seconds;
 	return result;
 }
-inline AnyGregorian AsGregorian(tm val)
+
+template <LogicalTimezone TZ>
+inline Gregorian<LogicalTimezone::Unspecified> forget_timezone(Gregorian<TZ> gregorian)
 {
-	AnyGregorian result{};
-	result.year = val.tm_year + 1900;
-	result.month = val.tm_mon + 1;
-	result.day = val.tm_mday;
-	result.hours = val.tm_hour;
-	result.minutes = val.tm_min;
-	result.seconds = val.tm_sec;
+	Gregorian<LogicalTimezone::Unspecified> result;
+	result.year = gregorian.year;
+	result.month = gregorian.month;
+	result.day = gregorian.day;
+	result.hours = gregorian.hours;
+	result.minutes = gregorian.minutes;
+	result.seconds = gregorian.seconds;
 	return result;
 }
-#endif
 
-using UTC = Gregorian<LogicalTimezone::UTC>;
-
-#if defined(MODPLUG_TRACKER)
-using Local = Gregorian<LogicalTimezone::Local>;
-#endif // MODPLUG_TRACKER
-
 #if MPT_CXX_AT_LEAST(20) && !defined(MPT_LIBCXX_QUIRK_NO_CHRONO_DATE)
 
 using Unix = std::chrono::system_clock::time_point;
@@ -151,15 +161,15 @@
 
 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();
+	return std::chrono::system_clock::time_point{
+		std::chrono::sys_days {
+			std::chrono::year{ utc.year } /
+			std::chrono::month{ utc.month } /
+			std::chrono::day{ utc.day }
+		} +
+		std::chrono::hours{ utc.hours } +
+		std::chrono::minutes{ utc.minutes } +
+		std::chrono::seconds{ utc.seconds }};
 }
 
 inline mpt::Date::UTC UnixAsUTC(Unix tp)
@@ -177,6 +187,40 @@
 	return result;
 }
 
+#if defined(MODPLUG_TRACKER)
+
+inline mpt::Date::Unix UnixFromLocal(Local local)
+{
+	std::chrono::time_point<std::chrono::local_t, std::chrono::seconds> local_tp = 
+		std::chrono::local_days {
+			std::chrono::year{ local.year } /
+			std::chrono::month{ local.month } /
+			std::chrono::day{ local.day }
+		} +
+		std::chrono::hours{ local.hours } +
+		std::chrono::minutes{ local.minutes } +
+		std::chrono::seconds{ local.seconds };
+	return std::chrono::zoned_time{std::chrono::current_zone(), local_tp}.get_sys_time();
+}
+
+inline mpt::Date::Local UnixAsLocal(Unix tp)
+{
+	std::chrono::zoned_time local_tp{ std::chrono::current_zone(), tp };
+	std::chrono::local_days dp = std::chrono::floor<std::chrono::days>(local_tp.get_local_time());
+	std::chrono::year_month_day ymd{dp};
+	std::chrono::hh_mm_ss hms{local_tp.get_local_time() - dp};
+	mpt::Date::Local 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;
+}
+
+#endif // MODPLUG_TRACKER
+
 #else
 
 // int64 counts 1s since 1970-01-01T00:00Z
@@ -195,7 +239,7 @@
 
 inline Unix UnixNow()
 {
-	return Unix{static_cast<int64>(time(nullptr))};
+	return Unix{static_cast<int64>(std::time(nullptr))};
 }
 
 inline int64 UnixAsSeconds(Unix tp)
@@ -212,21 +256,23 @@
 
 mpt::Date::UTC UnixAsUTC(Unix tp);
 
-#endif
+#if defined(MODPLUG_TRACKER)
 
-mpt::ustring ToShortenedISO8601(AnyGregorian date); // i.e. 2015-01-15T18:32:01
+mpt::Date::Unix UnixFromLocal(Local timeLocal);
 
-mpt::ustring ToShortenedISO8601(UTC date); // i.e. 2015-01-15T18:32:01Z
+mpt::Date::Local UnixAsLocal(Unix tp);
 
-#if defined(MPT_TIME_CTIME)
+#endif // MODPLUG_TRACKER
 
-mpt::Date::Unix UnixFromUTCtm(tm timeUtc);
+#endif
 
-tm UnixAsUTCtm(mpt::Date::Unix unixtime);
+mpt::ustring ToShortenedISO8601(AnyGregorian date); // i.e. 2015-01-15T18:32:01
 
-mpt::ustring ToShortenedISO8601(tm date); // i.e. 2015-01-15T18:32:01Z
+mpt::ustring ToShortenedISO8601(UTC date); // i.e. 2015-01-15T18:32:01Z
 
-#endif
+#ifdef MODPLUG_TRACKER
+mpt::ustring ToShortenedISO8601(Local date); // i.e. 2015-01-15T18:32:01
+#endif // MODPLUG_TRACKER
 
 } // namespace Date
 } // namespace mpt
Index: libopenmpt/libopenmpt_impl.cpp
===================================================================
--- libopenmpt/libopenmpt_impl.cpp	(revision 17650)
+++ libopenmpt/libopenmpt_impl.cpp	(working copy)
@@ -1229,7 +1229,7 @@
 		if ( m_sndFile->GetFileHistory().empty() || !m_sndFile->GetFileHistory().back().HasValidDate() ) {
 			return std::string();
 		}
-		return mpt::transcode<std::string>( mpt::common_encoding::utf8, m_sndFile->GetFileHistory().back().AsISO8601() );
+		return mpt::transcode<std::string>( mpt::common_encoding::utf8, m_sndFile->GetFileHistory().back().AsISO8601( m_sndFile->GetTimezoneInternal() ) );
 	} else if ( key == std::string("message") ) {
 		std::string retval = m_sndFile->m_songMessage.GetFormatted( OpenMPT::SongMessage::leLF );
 		if ( retval.empty() ) {
Index: mptrack/dlg_misc.cpp
===================================================================
--- mptrack/dlg_misc.cpp	(revision 17650)
+++ mptrack/dlg_misc.cpp	(working copy)
@@ -1311,18 +1311,15 @@
 	for(const auto &entry : editHistory)
 	{
 		totalTime += entry.openTime;
-
 		// Date
-		CString sDate;
+		CString sDate = CString(_T("<unknown date>"));
 		if(entry.HasValidDate())
 		{
-			TCHAR szDate[32];
-			const tm loadDate = mpt::Date::AsTm(entry.loadDate);
-			_tcsftime(szDate, std::size(szDate), _T("%d %b %Y, %H:%M:%S"), &loadDate);
-			sDate = szDate;
-		} else
-		{
-			sDate = _T("<unknown date>");
+			const mpt::Date::Unix unixdate = ((m_modDoc.GetSoundFile().GetTimezoneInternal() == mpt::Date::LogicalTimezone::Local) || (m_modDoc.GetSoundFile().GetTimezoneInternal() == mpt::Date::LogicalTimezone::Unspecified))
+				? mpt::Date::UnixFromLocal(mpt::Date::interpret_as_timezone<mpt::Date::LogicalTimezone::Local>(entry.loadDate))
+				: mpt::Date::UnixFromUTC(mpt::Date::interpret_as_timezone<mpt::Date::LogicalTimezone::UTC>(entry.loadDate));
+				;
+			sDate = CTime(mpt::Date::UnixAsSeconds(unixdate)).Format(_T("%d %b %Y, %H:%M:%S"));
 		}
 		// Time + stuff
 		uint32 duration = mpt::saturate_round<uint32>(entry.openTime / HISTORY_TIMER_PRECISION);
Index: mptrack/Moddoc.cpp
===================================================================
--- mptrack/Moddoc.cpp	(revision 17650)
+++ mptrack/Moddoc.cpp	(working copy)
@@ -138,7 +138,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 17650)
+++ 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/Mptrack.cpp
===================================================================
--- mptrack/Mptrack.cpp	(revision 17650)
+++ mptrack/Mptrack.cpp	(working copy)
@@ -634,6 +634,24 @@
 }
 
 
+CTrackApp::~CTrackApp()
+{
+#if !defined(MPT_LIBCXX_QUIRK_NO_CHRONO_DATE) && defined(MPT_LIBCXX_QUIRK_CHRONO_TZ_MEMLEAK)
+	// Work-around memleak (see <https://github.com/microsoft/STL/issues/2504#issuecomment-1068008937>)
+	try
+	{
+		std::chrono::get_tzdb_list().~tzdb_list();
+	} catch(const std::exception &)
+	{
+		// nothing
+	} catch(...)
+	{
+		// nothing
+	}
+#endif
+}
+
+
 class OpenMPTDataRecoveryHandler
 	: public CDataRecoveryHandler
 {
Index: mptrack/Mptrack.h
===================================================================
--- mptrack/Mptrack.h	(revision 17650)
+++ mptrack/Mptrack.h	(working copy)
@@ -166,6 +166,7 @@
 
 public:
 	CTrackApp();
+	~CTrackApp();
 
 	CDataRecoveryHandler *GetDataRecoveryHandler() override;
 	void AddToRecentFileList(LPCTSTR lpszPathName) override;
Index: mptrack/OPLExport.cpp
===================================================================
--- mptrack/OPLExport.cpp	(revision 17650)
+++ mptrack/OPLExport.cpp	(working copy)
@@ -398,7 +398,7 @@
 		SetDlgItemText(IDC_EDIT2, mpt::ToWin(m_sndFile.GetCharsetFile(), m_sndFile.GetTitle()).c_str());
 		SetDlgItemText(IDC_EDIT3, mpt::ToWin(m_sndFile.m_songArtist).c_str());
 		if(!m_sndFile.GetFileHistory().empty())
-			SetDlgItemText(IDC_EDIT4, mpt::ToWin(mpt::String::Replace(m_sndFile.GetFileHistory().back().AsISO8601().substr(0, 10), U_("-"), U_("/"))).c_str());
+			SetDlgItemText(IDC_EDIT4, mpt::ToWin(mpt::String::Replace(m_sndFile.GetFileHistory().back().AsISO8601(m_sndFile.GetTimezoneInternal()).substr(0, 10), U_("-"), U_("/"))).c_str());
 		SetDlgItemText(IDC_EDIT5, mpt::ToWin(m_sndFile.GetCharsetFile(), m_sndFile.m_songMessage.GetFormatted(SongMessage::leCRLF)).c_str());
 
 		m_locked = false;
Index: soundlib/Load_it.cpp
===================================================================
--- soundlib/Load_it.cpp	(revision 17650)
+++ soundlib/Load_it.cpp	(working copy)
@@ -1278,6 +1278,19 @@
 	m_modFormat.type = (GetType() == MOD_TYPE_MPT) ? U_("mptm") : U_("it");
 	m_modFormat.madeWithTracker = std::move(madeWithTracker);
 	m_modFormat.charset = m_dwLastSavedWithVersion ? mpt::Charset::Windows1252 : mpt::Charset::CP437;
+#if MPT_TIME_UTC_ON_DISK
+#ifdef MODPLUG_TRACKER
+	m_modFormat.timezone = (m_dwLastSavedWithVersion && (m_dwLastSavedWithVersion >= MPT_TIME_UTC_ON_DISK_VERSION)) ? mpt::Date::LogicalTimezone::UTC : mpt::Date::LogicalTimezone::Local;
+#else
+	m_modFormat.timezone = (m_dwLastSavedWithVersion && (m_dwLastSavedWithVersion >= MPT_TIME_UTC_ON_DISK_VERSION)) ? mpt::Date::LogicalTimezone::UTC : mpt::Date::LogicalTimezone::Unspecified;
+#endif
+#else
+#ifdef MODPLUG_TRACKER
+	m_modFormat.timezone = mpt::Date::LogicalTimezone::Local;
+#else
+	m_modFormat.timezone = mpt::Date::LogicalTimezone::Unspecified;
+#endif
+#endif
 
 	return true;
 }
@@ -1359,15 +1372,19 @@
 		} else if(pModDoc != nullptr)
 		{
 			// Current ("new") timestamp
-			const time_t creationTime = pModDoc->GetCreationTime();
-			mptHistory.loadDate = mpt::Date::AnyGregorian{};
-			//localtime_s(&loadDate, &creationTime);
-			const tm* const p = localtime(&creationTime);
-			if (p != nullptr)
-				mptHistory.loadDate = mpt::Date::AsGregorian(*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();
+			if(sndFile.GetTimezoneInternal() == mpt::Date::LogicalTimezone::UTC)
+			{
+				mptHistory.loadDate = mpt::Date::forget_timezone(mpt::Date::UnixAsUTC(creationTime));
+			} else if(sndFile.GetTimezoneInternal() == mpt::Date::LogicalTimezone::Local)
+			{
+				mptHistory.loadDate = mpt::Date::forget_timezone(mpt::Date::UnixAsLocal(creationTime));
+			} else
+			{
+				// assume UTC
+				mptHistory.loadDate = mpt::Date::forget_timezone(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 17650)
+++ soundlib/Load_mod.cpp	(working copy)
@@ -2182,6 +2182,11 @@
 		if(mpt::is_in_range(info.dateMonth, 1, 12) && mpt::is_in_range(info.dateDay, 1, 31) && mpt::is_in_range(info.dateHour, 0, 23)
 		   && mpt::is_in_range(info.dateMinute, 0, 59) && mpt::is_in_range(info.dateSecond, 0, 59))
 		{
+#ifdef MODPLUG_TRACKER
+			m_modFormat.timezone = mpt::Date::LogicalTimezone::Local;
+#else
+			m_modFormat.timezone = mpt::Date::LogicalTimezone::Unspecified;
+#endif
 			FileHistory mptHistory;
 			mptHistory.loadDate.year = info.dateYear + 1900;
 			mptHistory.loadDate.month = info.dateMonth;
Index: soundlib/Sndfile.cpp
===================================================================
--- soundlib/Sndfile.cpp	(revision 17650)
+++ soundlib/Sndfile.cpp	(working copy)
@@ -57,19 +57,47 @@
 }
 
 
-mpt::ustring FileHistory::AsISO8601() const
+mpt::ustring FileHistory::AsISO8601(mpt::Date::LogicalTimezone internalTimezone) const
 {
-	tm date = mpt::Date::AsTm(loadDate);
 	if(openTime > 0)
 	{
 		// Calculate the date when editing finished.
 		double openSeconds = static_cast<double>(openTime) / HISTORY_TIMER_PRECISION;
-		tm tmpLoadDate = mpt::Date::AsTm(loadDate);
-		int64 loadDateSinceEpoch = mpt::Date::UnixAsSeconds(mpt::Date::UnixFromUTCtm(tmpLoadDate));
-		int64 saveDateSinceEpoch = loadDateSinceEpoch + mpt::saturate_round<int64>(openSeconds);
-		date = mpt::Date::UnixAsUTCtm(mpt::Date::UnixFromSeconds(saveDateSinceEpoch));
+		mpt::Date::AnyGregorian tmpLoadDate = loadDate;
+		if (internalTimezone == mpt::Date::LogicalTimezone::UTC)
+		{
+			int64 loadDateSinceEpoch = mpt::Date::UnixAsSeconds(mpt::Date::UnixFromUTC(mpt::Date::interpret_as_timezone<mpt::Date::LogicalTimezone::UTC>(tmpLoadDate)));
+			int64 saveDateSinceEpoch = loadDateSinceEpoch + mpt::saturate_round<int64>(openSeconds);
+			return mpt::Date::ToShortenedISO8601(mpt::Date::UnixAsUTC(mpt::Date::UnixFromSeconds(saveDateSinceEpoch)));
+#ifdef MODPLUG_TRACKER
+		} else if(internalTimezone == mpt::Date::LogicalTimezone::Local)
+		{
+			int64 loadDateSinceEpoch = mpt::Date::UnixAsSeconds(mpt::Date::UnixFromLocal(mpt::Date::interpret_as_timezone<mpt::Date::LogicalTimezone::Local>(tmpLoadDate)));
+			int64 saveDateSinceEpoch = loadDateSinceEpoch + mpt::saturate_round<int64>(openSeconds);
+			return mpt::Date::ToShortenedISO8601(mpt::Date::UnixAsLocal(mpt::Date::UnixFromSeconds(saveDateSinceEpoch)));
+#endif // MODPLUG_TRACKER
+		} else
+		{
+			// assume UTC for unspecified timezone when calculating
+			int64 loadDateSinceEpoch = mpt::Date::UnixAsSeconds(mpt::Date::UnixFromUTC(mpt::Date::interpret_as_timezone<mpt::Date::LogicalTimezone::UTC>(tmpLoadDate)));
+			int64 saveDateSinceEpoch = loadDateSinceEpoch + mpt::saturate_round<int64>(openSeconds);
+			return mpt::Date::ToShortenedISO8601(mpt::Date::forget_timezone(mpt::Date::UnixAsUTC(mpt::Date::UnixFromSeconds(saveDateSinceEpoch))));
+		}
+	} else
+	{
+		if(internalTimezone == mpt::Date::LogicalTimezone::UTC)
+		{
+			return mpt::Date::ToShortenedISO8601(mpt::Date::interpret_as_timezone<mpt::Date::LogicalTimezone::UTC>(loadDate));
+#ifdef MODPLUG_TRACKER
+		} else if(internalTimezone == mpt::Date::LogicalTimezone::Local)
+		{
+			return mpt::Date::ToShortenedISO8601(mpt::Date::interpret_as_timezone<mpt::Date::LogicalTimezone::Local>(loadDate));
+#endif // MODPLUG_TRACKER
+		} else
+		{
+			return mpt::Date::ToShortenedISO8601(loadDate);
+		}
 	}
-	return mpt::Date::ToShortenedISO8601(date);
 }
 
 
@@ -486,8 +514,50 @@
 		InitializeGlobals();
 		m_visitedRows.Initialize(true);
 		m_dwCreatedWithVersion = Version::Current();
+#if MPT_TIME_UTC_ON_DISK
+#ifdef MODPLUG_TRACKER
+		if(GetType() & MOD_TYPE_IT)
+		{
+			m_modFormat.timezone = mpt::Date::LogicalTimezone::UTC;
+		} else
+		{
+			m_modFormat.timezone = mpt::Date::LogicalTimezone::Local;
+		}
+#else // !MODPLUG_TRACKER
+		if (GetType() & MOD_TYPE_IT)
+		{
+			m_modFormat.timezone = mpt::Date::LogicalTimezone::UTC;
+		} else
+		{
+			m_modFormat.timezone = mpt::Date::LogicalTimezone::Unspecified;
+		}
+#endif // MODPLUG_TRACKER
+#else
+#ifdef MODPLUG_TRACKER
+		m_modFormat.timezone = mpt::Date::LogicalTimezone::Local;
+#else // !MODPLUG_TRACKER
+		m_modFormat.timezone = mpt::Date::LogicalTimezone::Unspecified;
+#endif // MODPLUG_TRACKER
+#endif
 	}
 
+#if MPT_TIME_UTC_ON_DISK
+#ifdef MODPLUG_TRACKER
+	// convert timestamps to UTC
+	if(m_modFormat.timezone == mpt::Date::LogicalTimezone::Local)
+	{
+		for(auto & fileHistoryEntry : m_FileHistory)
+		{
+			if(fileHistoryEntry.HasValidDate())
+			{
+				fileHistoryEntry.loadDate = mpt::Date::forget_timezone(mpt::Date::UnixAsUTC(mpt::Date::UnixFromLocal(mpt::Date::interpret_as_timezone<mpt::Date::LogicalTimezone::Local>(fileHistoryEntry.loadDate))));
+			}
+		}
+		m_modFormat.timezone = mpt::Date::LogicalTimezone::UTC;
+	}
+#endif // MODPLUG_TRACKER
+#endif
+
 	// Adjust channels
 	const auto muteFlag = GetChannelMuteFlag();
 	for(CHANNELINDEX chn = 0; chn < MAX_BASECHANNELS; chn++)
Index: soundlib/Sndfile.h
===================================================================
--- soundlib/Sndfile.h	(revision 17650)
+++ soundlib/Sndfile.h	(working copy)
@@ -248,7 +248,7 @@
 	// 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;
+	mpt::ustring AsISO8601(mpt::Date::LogicalTimezone internalTimezone) const;
 	// Returns true if the date component is valid. Some formats only store edit time, not edit date.
 	bool HasValidDate() const
 	{
@@ -286,6 +286,7 @@
 	mpt::ustring originalFormatName; // "FastTracker 2" in the case of converted formats like MO3 or GDM
 	mpt::ustring originalType;       // "xm" in the case of converted formats like MO3 or GDM
 	mpt::Charset charset = mpt::Charset::UTF8;
+	mpt::Date::LogicalTimezone timezone = mpt::Date::LogicalTimezone::Unspecified;
 };
 
 
@@ -753,6 +754,10 @@
 			return GetCharsetFile();
 		#endif // MODPLUG_TRACKER
 	}
+	mpt::Date::LogicalTimezone GetTimezoneInternal() const
+	{
+		return m_modFormat.timezone;
+	}
 
 	ModMessageHeuristicOrder GetMessageHeuristic() const;
 
Index: src/mpt/base/detect_quirks.hpp
===================================================================
--- src/mpt/base/detect_quirks.hpp	(revision 17650)
+++ src/mpt/base/detect_quirks.hpp	(working copy)
@@ -244,13 +244,15 @@
 #define MPT_LIBCXX_QUIRK_NO_CHRONO_DATE
 #endif
 #if MPT_LIBCXX_MS && (MPT_MSVC_BEFORE(2022, 2) || !MPT_COMPILER_MSVC)
+#elif MPT_LIBCXX_GNU
+#define MPT_LIBCXX_QUIRK_NO_CHRONO_DATE_PARSE
+#endif
+#if MPT_LIBCXX_MS && (MPT_MSVC_BEFORE(2022, 3) || !MPT_COMPILER_MSVC)
 // 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
+#define MPT_LIBCXX_QUIRK_CHRONO_TZ_MEMLEAK
 #endif
 #endif
 
Index: test/test.cpp
===================================================================
--- test/test.cpp	(revision 17650)
+++ test/test.cpp	(working copy)
@@ -2741,12 +2741,37 @@
 	// Edit history
 	VERIFY_EQUAL_NONCONT(sndFile.GetFileHistory().size() > 15, true);
 	const FileHistory &fh = sndFile.GetFileHistory().front();
-	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);
+#ifdef MODPLUG_TRACKER
+	if(sndFile.GetTimezoneInternal() == mpt::Date::LogicalTimezone::UTC)
+	{
+		VERIFY_EQUAL_NONCONT(mpt::Date::forget_timezone(mpt::Date::UnixAsLocal(mpt::Date::UnixFromUTC(mpt::Date::interpret_as_timezone<mpt::Date::LogicalTimezone::UTC>(fh.loadDate)))).year, 2011);
+		VERIFY_EQUAL_NONCONT(mpt::Date::forget_timezone(mpt::Date::UnixAsLocal(mpt::Date::UnixFromUTC(mpt::Date::interpret_as_timezone<mpt::Date::LogicalTimezone::UTC>(fh.loadDate)))).month, 6);
+		VERIFY_EQUAL_NONCONT(mpt::Date::forget_timezone(mpt::Date::UnixAsLocal(mpt::Date::UnixFromUTC(mpt::Date::interpret_as_timezone<mpt::Date::LogicalTimezone::UTC>(fh.loadDate)))).day, 14);
+#if MPT_CXX_AT_LEAST(20) && !defined(MPT_LIBCXX_QUIRK_NO_CHRONO_DATE)
+		VERIFY_EQUAL_NONCONT(mpt::Date::forget_timezone(mpt::Date::UnixAsLocal(mpt::Date::UnixFromUTC(mpt::Date::interpret_as_timezone<mpt::Date::LogicalTimezone::UTC>(fh.loadDate)))).hours, 21);
+#else
+#if defined(MPT_FALLBACK_TIMEZONE_WINDOWS_HISTORIC)
+		VERIFY_EQUAL_NONCONT(mpt::Date::forget_timezone(mpt::Date::UnixAsLocal(mpt::Date::UnixFromUTC(mpt::Date::interpret_as_timezone<mpt::Date::LogicalTimezone::UTC>(fh.loadDate)))).hours, 21);
+#elif defined(MPT_FALLBACK_TIMEZONE_WINDOWS_CURRENT)
+		VERIFY_EQUAL_NONCONT(mpt::Date::forget_timezone(mpt::Date::UnixAsLocal(mpt::Date::UnixFromUTC(mpt::Date::interpret_as_timezone<mpt::Date::LogicalTimezone::UTC>(fh.loadDate)))).hours, 21);
+#elif defined(MPT_FALLBACK_TIMEZONE_C)
+		VERIFY_EQUAL_NONCONT(mpt::Date::forget_timezone(mpt::Date::UnixAsLocal(mpt::Date::UnixFromUTC(mpt::Date::interpret_as_timezone<mpt::Date::LogicalTimezone::UTC>(fh.loadDate)))).hours, 22);
+#else
+		VERIFY_EQUAL_NONCONT(mpt::Date::forget_timezone(mpt::Date::UnixAsLocal(mpt::Date::UnixFromUTC(mpt::Date::interpret_as_timezone<mpt::Date::LogicalTimezone::UTC>(fh.loadDate)))).hours, 22);
+#endif
+#endif
+		VERIFY_EQUAL_NONCONT(mpt::Date::forget_timezone(mpt::Date::UnixAsLocal(mpt::Date::UnixFromUTC(mpt::Date::interpret_as_timezone<mpt::Date::LogicalTimezone::UTC>(fh.loadDate)))).minutes, 8);
+		VERIFY_EQUAL_NONCONT(mpt::Date::forget_timezone(mpt::Date::UnixAsLocal(mpt::Date::UnixFromUTC(mpt::Date::interpret_as_timezone<mpt::Date::LogicalTimezone::UTC>(fh.loadDate)))).seconds, 32);
+	} else
+#endif // MODPLUG_TRACKER
+	{
+		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
