View Issue Details
ID | Project | Category | View Status | Date Submitted | Last Update |
---|---|---|---|---|---|
0001572 | OpenMPT | General | public | 2022-02-23 14:38 | 2022-07-09 13:42 |
Reporter | manx | Assigned To | manx | ||
Priority | normal | Severity | minor | Reproducibility | have not tried |
Status | resolved | Resolution | fixed | ||
Product Version | OpenMPT 1.31.00.* (old testing) | ||||
Target Version | OpenMPT 1.31.01.00 / libopenmpt 0.7.0 (upgrade first) | Fixed in Version | OpenMPT 1.31.01.00 / libopenmpt 0.7.0 (upgrade first) | ||
Summary | 0001572: use more std::chrono and less ctime | ||||
Description | Getting rid of C time.h simplifies date calculations and solves all year2038 problems, amongst other things. | ||||
Tags | No tags attached. | ||||
Has the bug occurred in previous versions? | |||||
Tested code revision (in case you know it) | |||||
chrono-v8.patch (34,303 bytes)
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 |
|
Except for EditHistory, this has been partially merged as r17377, r17379, r17380, r17383. |
|
Todo:
chrono-v12.patch (22,283 bytes)
Index: common/mptTime.cpp =================================================================== --- common/mptTime.cpp (revision 17409) +++ 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); @@ -144,6 +140,40 @@ 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 = timeLocal.seconds; + return static_cast<int64>(std::mktime(&tmp)); +} + +mpt::Date::Local UnixAsLocal(Local tp) +{ + std::tm *tmp = std::localtime(static_cast<std::time_t>(tp)); + if(!tmp) + { + return mpt::Date::Local{}; + } + std::tm local = *tmp; + mpt::Date::Local result{}; + result.year = local.tm_year + 1900; + result.mon = local.tm_month + 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 <LogicalTimezone TZ> @@ -193,85 +223,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 17409) +++ common/mptTime.h (working copy) @@ -21,13 +21,7 @@ #include <time.h> #endif -#define MPT_TIME_CTIME -#if defined(MPT_TIME_CTIME) -#include <time.h> -#endif - - OPENMPT_NAMESPACE_BEGIN @@ -99,37 +93,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 +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<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 +172,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 +224,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 +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(Local 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 17409) +++ 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 17409) +++ 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<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 17409) +++ 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 17409) +++ 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 17409) +++ 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/OPLExport.cpp =================================================================== --- mptrack/OPLExport.cpp (revision 17409) +++ 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 17409) +++ 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<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 17409) +++ 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 17409) +++ 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<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); } @@ -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<mpt::Date::LogicalTimezone::Local>(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 17409) +++ 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; |
|
chrono-v13.patch (26,451 bytes)
Index: common/mptTime.cpp =================================================================== --- common/mptTime.cpp (revision 17411) +++ 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); @@ -144,6 +140,40 @@ 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 = timeLocal.seconds; + return static_cast<int64>(std::mktime(&tmp)); +} + +mpt::Date::Local UnixAsLocal(Local tp) +{ + std::tm *tmp = std::localtime(static_cast<std::time_t>(tp)); + if(!tmp) + { + return mpt::Date::Local{}; + } + std::tm local = *tmp; + mpt::Date::Local result{}; + result.year = local.tm_year + 1900; + result.mon = local.tm_month + 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 <LogicalTimezone TZ> @@ -193,85 +223,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 17411) +++ common/mptTime.h (working copy) @@ -21,13 +21,7 @@ #include <time.h> #endif -#define MPT_TIME_CTIME -#if defined(MPT_TIME_CTIME) -#include <time.h> -#endif - - OPENMPT_NAMESPACE_BEGIN @@ -99,37 +93,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 +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<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 +172,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 +224,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 +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(Local 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 17411) +++ 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 17411) +++ 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<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 17411) +++ 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 17411) +++ 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 17411) +++ 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 17411) +++ 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 <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 17411) +++ 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 17411) +++ 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 17411) +++ 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<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 17411) +++ 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 17411) +++ 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<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); } @@ -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<mpt::Date::LogicalTimezone::Local>(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 17411) +++ 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 17411) +++ src/mpt/base/detect_quirks.hpp (working copy) @@ -215,14 +215,15 @@ #elif MPT_LIBCXX_LLVM_BEFORE(7000) #define MPT_LIBCXX_QUIRK_NO_CHRONO_DATE #endif -#if MPT_LIBCXX_MS && (MPT_MSVC_BEFORE(2022, 2) || !MPT_COMPILER_MSVC) +#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_CHRONO_TZ_MEMLEAK +#endif +#if MPT_LIBCXX_GNU #define MPT_LIBCXX_QUIRK_NO_CHRONO_DATE_PARSE -#elif MPT_LIBCXX_GNU -#define MPT_LIBCXX_QUIRK_NO_CHRONO_DATE_PARSE #endif #endif Index: test/test.cpp =================================================================== --- test/test.cpp (revision 17411) +++ test/test.cpp (working copy) @@ -2071,12 +2071,25 @@ // 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); + 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); + 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 |
|
chrono-v14.patch (27,445 bytes)
Index: build/pch/PCH.h =================================================================== --- build/pch/PCH.h (revision 17419) +++ build/pch/PCH.h (working copy) @@ -151,7 +151,6 @@ #include <stdint.h> #include <stdlib.h> #include <string.h> -#include <time.h> #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<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,10 +136,45 @@ 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) +{ + 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))); +} + +mpt::Date::Local UnixAsLocal(Unix tp) +{ + 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 // MODPLUG_TRACKER + #endif template <LogicalTimezone TZ> @@ -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<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 17419) +++ common/mptTime.h (working copy) @@ -18,16 +18,10 @@ #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> -#endif - - OPENMPT_NAMESPACE_BEGIN @@ -99,37 +93,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 +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<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 +172,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 +224,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 +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<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 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("<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 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 <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 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 <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 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<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 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<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); } @@ -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<mpt::Date::LogicalTimezone::Local>(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 // <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 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<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_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<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, 21); +#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 |
|
chrono-v15.patch (32,627 bytes)
Index: build/pch/PCH.h =================================================================== --- build/pch/PCH.h (revision 17427) +++ build/pch/PCH.h (working copy) @@ -151,7 +151,6 @@ #include <stdint.h> #include <stdlib.h> #include <string.h> -#include <time.h> #endif // MPT_BUILD_ENABLE_PCH Index: common/mptTime.cpp =================================================================== --- common/mptTime.cpp (revision 17427) +++ 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 17427) +++ 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: common/versionNumber.h =================================================================== --- common/versionNumber.h (revision 17427) +++ 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 17427) +++ 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<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 17427) +++ 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 17427) +++ 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 17427) +++ 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 17427) +++ mptrack/Mptrack.cpp (working copy) @@ -631,6 +631,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 17427) +++ 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 17427) +++ 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 17427) +++ 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<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 17427) +++ 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 17427) +++ 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<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); } @@ -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<mpt::Date::LogicalTimezone::Local>(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 17427) +++ 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 17427) +++ 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 // <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 17427) +++ test/test.cpp (working copy) @@ -2096,12 +2096,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 |
|
chrono-v16.patch (33,551 bytes)
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 |
|
Date Modified | Username | Field | Change |
---|---|---|---|
2022-02-23 14:38 | manx | New Issue | |
2022-02-23 14:38 | manx | Status | new => assigned |
2022-02-23 14:38 | manx | Assigned To | => manx |
2022-02-23 14:39 | manx | Relationship added | related to 0001555 |
2022-02-23 14:39 | manx | Note Added: 0005113 | |
2022-02-23 14:39 | manx | File Added: chrono-v8.patch | |
2022-05-28 05:20 | manx | Note Added: 0005193 | |
2022-06-04 16:34 | manx | Note Added: 0005195 | |
2022-06-04 16:34 | manx | File Added: chrono-v12.patch | |
2022-06-04 18:25 | manx | Note Added: 0005196 | |
2022-06-04 18:25 | manx | File Added: chrono-v13.patch | |
2022-06-06 08:01 | manx | Note Added: 0005197 | |
2022-06-06 08:01 | manx | File Added: chrono-v14.patch | |
2022-06-07 08:03 | manx | Note Added: 0005198 | |
2022-06-07 08:03 | manx | File Added: chrono-v15.patch | |
2022-06-07 08:20 | manx | Note Edited: 0005198 | |
2022-07-09 11:56 | manx | Note Added: 0005250 | |
2022-07-09 11:56 | manx | File Added: chrono-v16.patch | |
2022-07-09 13:32 | manx | Note Added: 0005251 | |
2022-07-09 13:42 | manx | Relationship added | child of 0001614 |
2022-07-09 13:42 | manx | Status | assigned => resolved |
2022-07-09 13:42 | manx | Resolution | open => fixed |
2022-07-09 13:42 | manx | Fixed in Version | => OpenMPT 1.31.01.00 / libopenmpt 0.7.0 (upgrade first) |