Mercurial > foo_out_sdl
diff foosdk/sdk/pfc/filetimetools.cpp @ 1:20d02a178406 default tip
*: check in everything else
yay
| author | Paper <paper@tflc.us> |
|---|---|
| date | Mon, 05 Jan 2026 02:15:46 -0500 |
| parents | |
| children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/foosdk/sdk/pfc/filetimetools.cpp Mon Jan 05 02:15:46 2026 -0500 @@ -0,0 +1,331 @@ +#include "pfc-lite.h" + +#include "filetimetools.h" + +#include "timers.h" + +#include <math.h> + +namespace { + class exception_time_error {}; +} + +using namespace pfc; + +#ifndef _WIN32 +namespace { + typedef uint16_t WORD; + + typedef struct _SYSTEMTIME { + WORD wYear; + WORD wMonth; + WORD wDayOfWeek; + WORD wDay; + WORD wHour; + WORD wMinute; + WORD wSecond; + WORD wMilliseconds; + } SYSTEMTIME, * PSYSTEMTIME, * LPSYSTEMTIME; + +} +static void SystemTimeToNix(const SYSTEMTIME& st, struct tm& Time) { + memset(&Time, 0, sizeof(Time)); + Time.tm_sec = st.wSecond; + Time.tm_min = st.wMinute; + Time.tm_hour = st.wHour; + Time.tm_mday = st.wDay; + Time.tm_mon = st.wMonth - 1; + Time.tm_year = st.wYear - 1900; +} + +static t_filetimestamp ExportSystemTime(const SYSTEMTIME& st) { + struct tm Time; + SystemTimeToNix(st, Time); + return pfc::fileTimeUtoW(mktime(&Time)); +} + +static t_filetimestamp ExportSystemTimeLocal(const SYSTEMTIME& st) { + struct tm Time, Local; + SystemTimeToNix(st, Time); + time_t t = mktime(&Time); + localtime_r(&t, &Local); + return pfc::fileTimeUtoW(mktime(&Local)); +} +static void SystemTimeFromNix(SYSTEMTIME& st, struct tm const& Time, t_filetimestamp origTS) { + memset(&st, 0, sizeof(st)); + st.wSecond = Time.tm_sec; + st.wMinute = Time.tm_min; + st.wHour = Time.tm_hour; + st.wDay = Time.tm_mday; + st.wDayOfWeek = Time.tm_wday; + st.wMonth = Time.tm_mon + 1; + st.wYear = Time.tm_year + 1900; + st.wMilliseconds = (origTS % filetimestamp_1second_increment) / (filetimestamp_1second_increment/1000); +} + +static bool MakeSystemTime(SYSTEMTIME& st, t_filetimestamp ts) { + time_t t = (time_t)pfc::fileTimeWtoU(ts); + struct tm Time; + if (gmtime_r(&t, &Time) == NULL) return false; + SystemTimeFromNix(st, Time, ts); + return true; +} + +static bool MakeSystemTimeLocal(SYSTEMTIME& st, t_filetimestamp ts) { + time_t t = (time_t)pfc::fileTimeWtoU(ts); + struct tm Time; + if (localtime_r(&t, &Time) == NULL) return false; + SystemTimeFromNix(st, Time, ts); + return true; +} + +#else +static t_filetimestamp ExportSystemTime(const SYSTEMTIME& st) { + t_filetimestamp base; + if (!SystemTimeToFileTime(&st, (FILETIME*)&base)) throw exception_time_error(); + return base; +} +static t_filetimestamp ExportSystemTimeLocal(const SYSTEMTIME& st) { +#ifdef FOOBAR2000_DESKTOP_WINDOWS + t_filetimestamp base, out; + if (!SystemTimeToFileTime(&st, (FILETIME*)&base)) throw exception_time_error(); + if (!LocalFileTimeToFileTime((const FILETIME*)&base, (FILETIME*)&out)) throw exception_time_error(); + return out; +#else + SYSTEMTIME UTC; + if (!TzSpecificLocalTimeToSystemTime(NULL, &st, &UTC)) throw exception_time_error(); + return ExportSystemTime(UTC); +#endif +} +static bool MakeSystemTime(SYSTEMTIME& st, t_filetimestamp ts) { + if (ts == filetimestamp_invalid) return false; + return !!FileTimeToSystemTime((const FILETIME*)&ts, &st); + +} +static bool MakeSystemTimeLocal(SYSTEMTIME& st, t_filetimestamp ts) { + if (ts == filetimestamp_invalid) return false; +#ifdef FOOBAR2000_DESKTOP_WINDOWS + FILETIME ft; + if (FileTimeToLocalFileTime((FILETIME*)&ts, &ft)) { + if (FileTimeToSystemTime(&ft, &st)) { + return true; + } + } + return false; +#else + SYSTEMTIME UTC; + if (FileTimeToSystemTime((FILETIME*)&ts, &UTC)) { + if (SystemTimeToTzSpecificLocalTime(NULL, &UTC, &st)) return true; + } + return false; +#endif +} +#endif // _WIN32 + +static bool is_spacing(char c) { return c == ' ' || c == 10 || c == 13 || c == '\t'; } + +static unsigned ParseDateElem(const char* ptr, t_size len) { + unsigned ret = 0; + for (t_size walk = 0; walk < len; ++walk) { + const char c = ptr[walk]; + if (c < '0' || c > '9') throw exception_time_error(); + ret = ret * 10 + (unsigned)(c - '0'); + } + return ret; +} + +static bool st_sanity(SYSTEMTIME const& st) { + return st.wYear >= 1601 && st.wMonth >= 1 && st.wMonth <= 12 && st.wDay >= 1 && st.wDay <= 31 && st.wHour < 24 && st.wMinute < 60 && st.wSecond < 60 && st.wMilliseconds < 1000; +} +static t_filetimestamp filetimestamp_from_string_internal(const char* date, bool local) { + // Accepted format + // YYYY-MM-DD HH:MM:SS + try { + SYSTEMTIME st = {}; + st.wDay = 1; st.wMonth = 1; + + unsigned walk = 0; + auto worker = [&](unsigned n) { + auto ret = ParseDateElem(date + walk, n); + walk += n; + if (ret > UINT16_MAX) throw exception_time_error(); + return (WORD)ret;; + }; + + auto skip = [&](char c) { + if (date[walk] == c) ++walk; + }; + + auto skipSpacing = [&] { + while (is_spacing(date[walk])) ++walk; + }; + skipSpacing(); + st.wYear = worker(4); + skip('-'); + st.wMonth = worker(2); + skip('-'); + st.wDay = worker(2); + skipSpacing(); + st.wHour = worker(2); + skip(':'); + st.wMinute = worker(2); + skip(':'); + st.wSecond = worker(2); + if (date[walk] == '.') { + double v = pfc::string_to_float(date + walk); + st.wMilliseconds = (WORD)floor(v * 1000.f); // don't ever round up, don't want to handle ms of 1000 + } + + if (!st_sanity(st)) throw exception_time_error(); + + if (local) { + return ExportSystemTimeLocal(st); + } else { + return ExportSystemTime(st); + } + } catch (exception_time_error const &) { + return filetimestamp_invalid; + } +} + +namespace pfc { + t_filetimestamp filetimestamp_from_string(const char* date) { + return filetimestamp_from_string_internal(date, true); + } + + t_filetimestamp filetimestamp_from_string_utc(const char* date) { + return filetimestamp_from_string_internal(date, false); + } + + static constexpr char g_invalidMsg[] = "<invalid timestamp>"; + + pfc::string_formatter format_filetimestamp(t_filetimestamp p_timestamp) { + try { + SYSTEMTIME st; + if (MakeSystemTimeLocal(st, p_timestamp)) { + pfc::string_formatter buffer; + buffer + << pfc::format_uint(st.wYear, 4) << "-" << pfc::format_uint(st.wMonth, 2) << "-" << pfc::format_uint(st.wDay, 2) << " " + << pfc::format_uint(st.wHour, 2) << ":" << pfc::format_uint(st.wMinute, 2) << ":" << pfc::format_uint(st.wSecond, 2); + return buffer; + } + } catch (...) {} + return g_invalidMsg; + } + + pfc::string_formatter format_filetimestamp_ms(t_filetimestamp p_timestamp) { + try { + SYSTEMTIME st; + if (MakeSystemTimeLocal(st, p_timestamp)) { + pfc::string_formatter buffer; + buffer + << pfc::format_uint(st.wYear, 4) << "-" << pfc::format_uint(st.wMonth, 2) << "-" << pfc::format_uint(st.wDay, 2) << " " + << pfc::format_uint(st.wHour, 2) << ":" << pfc::format_uint(st.wMinute, 2) << ":" << pfc::format_uint(st.wSecond, 2) << "." << pfc::format_uint(st.wMilliseconds, 3); + return buffer; + } + } catch (...) {} + return g_invalidMsg; + } + + pfc::string_formatter format_filetimestamp_utc(t_filetimestamp p_timestamp) { + try { + SYSTEMTIME st; + if (MakeSystemTime(st, p_timestamp)) { + pfc::string_formatter buffer; + buffer + << pfc::format_uint(st.wYear, 4) << "-" << pfc::format_uint(st.wMonth, 2) << "-" << pfc::format_uint(st.wDay, 2) << " " + << pfc::format_uint(st.wHour, 2) << ":" << pfc::format_uint(st.wMinute, 2) << ":" << pfc::format_uint(st.wSecond, 2); + return buffer; + } + } catch (...) {} + return g_invalidMsg; + } + +} // namespace foobar2000_io + +namespace { + struct dateISO_t { + unsigned Y, M, D; + unsigned h, m, s; + double sfrac; + int tzdelta; + }; + + dateISO_t read_ISO_8601(const char* dateISO) { + dateISO_t ret = {}; + // 2022-01-26T13:44:51.200000Z + // 2010-02-19T14:54:23.031+08:00 + // 2022-01-27T11:01:49+00:00 + // 2022-01-27T11:01:49Z + // 20220127T110149Z + + unsigned walk = 0; + auto worker = [&](unsigned n) { + auto ret = ParseDateElem(dateISO + walk, n); + walk += n; + return ret; + }; + auto skip = [&](char c) { + if (dateISO[walk] == c) ++walk; + }; + auto expect = [&](char c) { + if (dateISO[walk] != c) throw exception_time_error(); + ++walk; + }; + ret.Y = worker(4); + skip('-'); + ret.M = worker(2); + skip('-'); + ret.D = worker(2); + expect('T'); + ret.h = worker(2); + skip(':'); + ret.m = worker(2); + skip(':'); + ret.s = worker(2); + if (dateISO[walk] == '.') { + unsigned base = walk; + ++walk; + while (pfc::char_is_numeric(dateISO[walk])) ++walk; + ret.sfrac = pfc::string_to_float(dateISO + base, walk - base); + } + if (dateISO[walk] == '+' || dateISO[walk] == '-') { + bool neg = dateISO[walk] == '-'; + ++walk; + unsigned tz_h = worker(2); + if (tz_h >= 24) throw exception_time_error(); + skip(':'); + unsigned tz_m = worker(2); + if (tz_m >= 60) throw exception_time_error(); + tz_m += tz_h * 60; + ret.tzdelta = neg ? (int)tz_m : -(int)tz_m; // reversed! it's a timezone offset, have to *add* it if timezone has a minus + } + return ret; + } +} + +t_filetimestamp pfc::filetimestamp_from_string_ISO_8601(const char* dateISO) { + try { + auto elems = read_ISO_8601(dateISO); + + SYSTEMTIME st = {}; + st.wDay = 1; st.wMonth = 1; + st.wYear = (WORD)elems.Y; + st.wMonth = (WORD)elems.M; + st.wDay = (WORD)elems.D; + st.wHour = (WORD)elems.h; + st.wMinute = (WORD)elems.m; + st.wSecond = (WORD)elems.s; + st.wMilliseconds = (WORD)floor(elems.sfrac * 1000.f); + + if (!st_sanity(st)) throw exception_time_error(); + + auto ret = ExportSystemTime(st); + + ret += filetimestamp_1second_increment * elems.tzdelta * 60; + + return ret; + } catch (...) { + return filetimestamp_invalid; + } +}
