Index: build/pch/PCH.h =================================================================== --- build/pch/PCH.h (revision 17419) +++ build/pch/PCH.h (working copy) @@ -151,7 +151,6 @@ #include #include #include -#include #endif // MPT_BUILD_ENABLE_PCH Index: common/mptTime.cpp =================================================================== --- common/mptTime.cpp (revision 17419) +++ 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(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,10 +136,45 @@ result.day = day; result.hours = static_cast(hours); result.minutes = static_cast(minutes); - result.seconds = static_cast(seconds); + result.seconds = seconds; return result; } +#if defined(MODPLUG_TRACKER) + +mpt::Date::Unix UnixFromLocal(Local timeLocal) +{ + 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(timeLocal.seconds); + return mpt::Date::UnixFromSeconds(static_cast(std::mktime(&tmp))); +} + +mpt::Date::Local UnixAsLocal(Unix tp) +{ + std::time_t time_tp = static_cast(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 // MODPLUG_TRACKER + #endif template @@ -193,85 +224,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(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(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(hours); - result.tm_min = static_cast(minutes); - result.tm_sec = static_cast(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 17419) +++ common/mptTime.h (working copy) @@ -18,16 +18,10 @@ #include #if MPT_CXX_BEFORE(20) || defined(MPT_LIBCXX_QUIRK_NO_CHRONO_DATE) -#include +#include #endif -#define MPT_TIME_CTIME -#if defined(MPT_TIME_CTIME) -#include -#endif - - OPENMPT_NAMESPACE_BEGIN @@ -99,37 +93,38 @@ using AnyGregorian = Gregorian; -#if defined(MPT_TIME_CTIME) -inline tm AsTm(AnyGregorian val) +using UTC = Gregorian; + +#if defined(MODPLUG_TRACKER) +using Local = Gregorian; +#endif // MODPLUG_TRACKER + +template +inline Gregorian 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(val.seconds); + Gregorian 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 +inline Gregorian forget_timezone(Gregorian 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 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; - -#if defined(MODPLUG_TRACKER) -using Local = Gregorian; -#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 +146,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 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(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 +172,40 @@ return result; } +#if defined(MODPLUG_TRACKER) + +inline mpt::Date::Unix UnixFromLocal(Local local) +{ + std::chrono::time_point 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(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(ymd.year()); + result.month = static_cast(ymd.month()); + result.day = static_cast(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 +224,7 @@ inline Unix UnixNow() { - return Unix{static_cast(time(nullptr))}; + return Unix{static_cast(std::time(nullptr))}; } inline int64 UnixAsSeconds(Unix tp) @@ -212,21 +241,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: common/versionNumber.h =================================================================== --- common/versionNumber.h (revision 17419) +++ common/versionNumber.h (working copy) @@ -18,6 +18,6 @@ #define VER_MAJORMAJOR 1 #define VER_MAJOR 31 #define VER_MINOR 00 -#define VER_MINORMINOR 12 +#define VER_MINORMINOR 13 OPENMPT_NAMESPACE_END Index: libopenmpt/libopenmpt_impl.cpp =================================================================== --- libopenmpt/libopenmpt_impl.cpp (revision 17419) +++ libopenmpt/libopenmpt_impl.cpp (working copy) @@ -1241,7 +1241,7 @@ if ( m_sndFile->GetFileHistory().empty() || !m_sndFile->GetFileHistory().back().HasValidDate() ) { return std::string(); } - return mpt::transcode( mpt::common_encoding::utf8, m_sndFile->GetFileHistory().back().AsISO8601() ); + return mpt::transcode( 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 17419) +++ 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("")); 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(""); + 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(entry.loadDate)) + : mpt::Date::UnixFromUTC(mpt::Date::interpret_as_timezone(entry.loadDate)); + ; + sDate = CTime(mpt::Date::UnixAsSeconds(unixdate)).Format(_T("%d %b %Y, %H:%M:%S")); } // Time + stuff uint32 duration = mpt::saturate_round(entry.openTime / HISTORY_TIMER_PRECISION); Index: mptrack/Moddoc.cpp =================================================================== --- mptrack/Moddoc.cpp (revision 17419) +++ 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 17419) +++ 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 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 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 17419) +++ mptrack/Mptrack.cpp (working copy) @@ -613,6 +613,24 @@ } +CTrackApp::~CTrackApp() +{ +#if !defined(MPT_LIBCXX_QUIRK_NO_CHRONO_DATE) && defined(MPT_LIBCXX_QUIRK_CHRONO_TZ_MEMLEAK) + // Work-around memleak (see ) + 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 17419) +++ 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 17419) +++ mptrack/OPLExport.cpp (working copy) @@ -397,7 +397,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 17419) +++ soundlib/Load_it.cpp (working copy) @@ -1278,6 +1278,11 @@ 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; +#ifdef MODPLUG_TRACKER + m_modFormat.timezone = (m_dwLastSavedWithVersion && (m_dwLastSavedWithVersion >= MPT_V("1.31.00.13"))) ? mpt::Date::LogicalTimezone::UTC : mpt::Date::LogicalTimezone::Local; +#else + m_modFormat.timezone = (m_dwLastSavedWithVersion && (m_dwLastSavedWithVersion >= MPT_V("1.31.00.13"))) ? mpt::Date::LogicalTimezone::UTC : mpt::Date::LogicalTimezone::Unspecified; +#endif return true; } @@ -1359,15 +1364,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(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 17419) +++ soundlib/Load_mod.cpp (working copy) @@ -2179,6 +2179,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 17419) +++ soundlib/Sndfile.cpp (working copy) @@ -54,19 +54,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(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(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(tmpLoadDate))); + int64 saveDateSinceEpoch = loadDateSinceEpoch + mpt::saturate_round(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(tmpLoadDate))); + int64 saveDateSinceEpoch = loadDateSinceEpoch + mpt::saturate_round(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(tmpLoadDate))); + int64 saveDateSinceEpoch = loadDateSinceEpoch + mpt::saturate_round(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(loadDate)); +#ifdef MODPLUG_TRACKER + } else if(internalTimezone == mpt::Date::LogicalTimezone::Local) + { + return mpt::Date::ToShortenedISO8601(mpt::Date::interpret_as_timezone(loadDate)); +#endif // MODPLUG_TRACKER + } else + { + return mpt::Date::ToShortenedISO8601(loadDate); + } } - return mpt::Date::ToShortenedISO8601(date); } @@ -483,8 +511,24 @@ InitializeGlobals(); m_visitedRows.Initialize(true); m_dwCreatedWithVersion = Version::Current(); + m_modFormat.timezone = mpt::Date::LogicalTimezone::UTC; } +#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(fileHistoryEntry.loadDate)))); + } + } + m_modFormat.timezone = mpt::Date::LogicalTimezone::UTC; + } +#endif // MODPLUG_TRACKER + // Adjust channels const auto muteFlag = GetChannelMuteFlag(); for(CHANNELINDEX chn = 0; chn < MAX_BASECHANNELS; chn++) Index: soundlib/Sndfile.h =================================================================== --- soundlib/Sndfile.h (revision 17419) +++ soundlib/Sndfile.h (working copy) @@ -247,7 +247,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 { @@ -285,6 +285,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; }; @@ -752,6 +753,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 17419) +++ src/mpt/base/detect_quirks.hpp (working copy) @@ -235,13 +235,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 // // / . -#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 17419) +++ test/test.cpp (working copy) @@ -2096,12 +2096,29 @@ // 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(fh.loadDate)))).year, 2011); + VERIFY_EQUAL_NONCONT(mpt::Date::forget_timezone(mpt::Date::UnixAsLocal(mpt::Date::UnixFromUTC(mpt::Date::interpret_as_timezone(fh.loadDate)))).month, 6); + VERIFY_EQUAL_NONCONT(mpt::Date::forget_timezone(mpt::Date::UnixAsLocal(mpt::Date::UnixFromUTC(mpt::Date::interpret_as_timezone(fh.loadDate)))).day, 14); +#if MPT_CXX_BEFORE(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(fh.loadDate)))).hours, 22); +#else + VERIFY_EQUAL_NONCONT(mpt::Date::forget_timezone(mpt::Date::UnixAsLocal(mpt::Date::UnixFromUTC(mpt::Date::interpret_as_timezone(fh.loadDate)))).hours, 21); +#endif + VERIFY_EQUAL_NONCONT(mpt::Date::forget_timezone(mpt::Date::UnixAsLocal(mpt::Date::UnixFromUTC(mpt::Date::interpret_as_timezone(fh.loadDate)))).minutes, 8); + VERIFY_EQUAL_NONCONT(mpt::Date::forget_timezone(mpt::Date::UnixAsLocal(mpt::Date::UnixFromUTC(mpt::Date::interpret_as_timezone(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