diff --git a/companion/src/simulation/wasmsimulatorinterface.cpp b/companion/src/simulation/wasmsimulatorinterface.cpp index 1698a6627..6f76aa824 100644 --- a/companion/src/simulation/wasmsimulatorinterface.cpp +++ b/companion/src/simulation/wasmsimulatorinterface.cpp @@ -15,6 +15,7 @@ #include "wasmsimulatorinterface.h" +#include #include #include #include @@ -559,8 +560,11 @@ void WasmSimulatorInterface::start(const char * filename, bool tests) } } - uint32_t argv[1] = {(uint32_t)tests}; - if (!wasm_runtime_call_wasm(m_execEnv, m_fnStart, 1, argv)) { + // WASI has no timezone support, so the module needs our UTC offset. + uint32_t argv[2] = { + (uint32_t)tests, + (uint32_t)(int32_t)QDateTime::currentDateTime().offsetFromUtc()}; + if (!wasm_runtime_call_wasm(m_execEnv, m_fnStart, 2, argv)) { qWarning() << "WASM simuStart failed:" << wasm_runtime_get_exception(m_moduleInst); return; diff --git a/radio/src/targets/simu/simufatfs.cpp b/radio/src/targets/simu/simufatfs.cpp index 3fcd5a3a2..0fabe00fb 100644 --- a/radio/src/targets/simu/simufatfs.cpp +++ b/radio/src/targets/simu/simufatfs.cpp @@ -208,14 +208,14 @@ FRESULT file_stat(const std::string& realPath, FILINFO* fno) if (!ec) { auto systime = ftime_to_systime(ftime); auto time_t_val = std::chrono::system_clock::to_time_t(systime); - struct tm* ltime = localtime(&time_t_val); + struct tm ltime; - if (ltime) { + if (simuLocalTime(time_t_val, <ime)) { // Convert to FatFs format - fno->fdate = ((ltime->tm_year - 80) << 9) | ((ltime->tm_mon + 1) << 5) | - ltime->tm_mday; + fno->fdate = ((ltime.tm_year - 80) << 9) | ((ltime.tm_mon + 1) << 5) | + ltime.tm_mday; fno->ftime = - (ltime->tm_hour << 11) | (ltime->tm_min << 5) | (ltime->tm_sec / 2); + (ltime.tm_hour << 11) | (ltime.tm_min << 5) | (ltime.tm_sec / 2); } else { fno->fdate = 0; fno->ftime = 0; @@ -572,7 +572,7 @@ FRESULT f_utime(const TCHAR* path, const FILINFO* fno) ltime.tm_sec = (fno->ftime & 0x1F) * 2; ltime.tm_isdst = -1; - time_t timestamp = mktime(<ime); + time_t timestamp = simuLocalTimeToEpoch(<ime); if (timestamp == -1) { return FR_INVALID_PARAMETER; } diff --git a/radio/src/targets/simu/simulib.cpp b/radio/src/targets/simu/simulib.cpp index 6b6c85319..3a7aa0bc7 100644 --- a/radio/src/targets/simu/simulib.cpp +++ b/radio/src/targets/simu/simulib.cpp @@ -142,11 +142,46 @@ void simuCreateDefaults() simuCreateDefaultSettings = true; } -void simuStart(bool tests) +#if defined(__wasm__) +static int32_t simuUtcOffset = 0; +#endif + +bool simuLocalTime(time_t t, struct tm* result) +{ + struct tm* tmp; +#if defined(__wasm__) + t += simuUtcOffset; + tmp = gmtime(&t); +#else + tmp = localtime(&t); +#endif + if (tmp == nullptr) return false; + *result = *tmp; + return true; +} + +time_t simuLocalTimeToEpoch(struct tm* tm) +{ +#if defined(__wasm__) + // wasi-libc's mktime() is timezone-less, i.e. equivalent to timegm() + time_t t = mktime(tm); + return t == (time_t)-1 ? t : t - simuUtcOffset; +#else + return mktime(tm); +#endif +} + +void simuStart(bool tests, int32_t utcOffset) { if (simu_running) return; +#if defined(__wasm__) + simuUtcOffset = utcOffset; +#else + (void)utcOffset; +#endif + #if !defined(COLORLCD) menuLevel = 0; #endif @@ -171,20 +206,19 @@ void simuStart(bool tests) #if defined(RTCLOCK) time_t rawtime; - struct tm * timeinfo; + struct tm timeinfo; time (&rawtime); - timeinfo = localtime (&rawtime); - if (timeinfo != nullptr) { + if (simuLocalTime(rawtime, &timeinfo)) { struct gtm gti; - gti.tm_sec = timeinfo->tm_sec; - gti.tm_min = timeinfo->tm_min; - gti.tm_hour = timeinfo->tm_hour; - gti.tm_mday = timeinfo->tm_mday; - gti.tm_mon = timeinfo->tm_mon; - gti.tm_year = timeinfo->tm_year; - gti.tm_wday = timeinfo->tm_wday; - gti.tm_yday = timeinfo->tm_yday; + gti.tm_sec = timeinfo.tm_sec; + gti.tm_min = timeinfo.tm_min; + gti.tm_hour = timeinfo.tm_hour; + gti.tm_mday = timeinfo.tm_mday; + gti.tm_mon = timeinfo.tm_mon; + gti.tm_year = timeinfo.tm_year; + gti.tm_wday = timeinfo.tm_wday; + gti.tm_yday = timeinfo.tm_yday; g_rtcTime = gmktime(>i); } else { g_rtcTime = rawtime; diff --git a/radio/src/targets/simu/simulib.h b/radio/src/targets/simu/simulib.h index b665dd0b5..905364150 100644 --- a/radio/src/targets/simu/simulib.h +++ b/radio/src/targets/simu/simulib.h @@ -24,6 +24,7 @@ #include #include #include +#include #include @@ -43,8 +44,10 @@ // Lifecycle: call simuInit() once, then simuFatfsSetPaths() + simuStart(). // Poll simuIsRunning() periodically. Call simuStop() to shut down. +// utcOffset: seconds east of UTC, DST applied; wasi-libc's localtime() always +// reports UTC. Ignored by native builds. void WASM_EXPORT(simuInit)(); -void WASM_EXPORT(simuStart)(bool tests = true); +void WASM_EXPORT(simuStart)(bool tests = true, int32_t utcOffset = 0); void WASM_EXPORT(simuStop)(); bool WASM_EXPORT(simuIsRunning)(); @@ -189,6 +192,11 @@ void WASM_IMPORT(simuAuxSerialSendBuffer)(uint8_t port_nr, const uint8_t* data, uint32_t len); // -- Internal (not exported) -- + +// Use instead of localtime()/mktime(): applies simuStart()'s utcOffset on WASM. +bool simuLocalTime(time_t t, struct tm* result); +time_t simuLocalTimeToEpoch(struct tm* tm); + void simuMain(); std::string simuFatfsGetCurrentPath(); std::string simuFatfsGetRealPath(const std::string& p); diff --git a/web/src/App.svelte b/web/src/App.svelte index fe24d4c24..4c424d018 100644 --- a/web/src/App.svelte +++ b/web/src/App.svelte @@ -448,7 +448,8 @@ ex.simuInit(); runner.setFatfsPaths('/', '/'); ex.simuCreateDefaults(); - ex.simuStart(1); + // WASI has no timezone support, so the module needs our UTC offset. + ex.simuStart(1, -new Date().getTimezoneOffset() * 60); if (canvas) { lcdRenderer = new LcdRenderer(canvas); diff --git a/web/src/lib/wasm-runner.ts b/web/src/lib/wasm-runner.ts index 9ff218f5e..5574c624c 100644 --- a/web/src/lib/wasm-runner.ts +++ b/web/src/lib/wasm-runner.ts @@ -45,7 +45,7 @@ export interface SimulatorExports { free: (ptr: number) => void; simuInit: () => void; - simuStart: (tests: number) => void; + simuStart: (tests: number, utcOffset: number) => void; simuStop: () => void; simuIsRunning: () => number; simuFatfsSetPaths: (sdPath: number, settingsPath: number) => void;