Mercurial > foo_out_sdl
comparison 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 |
comparison
equal
deleted
inserted
replaced
| 0:e9bb126753e7 | 1:20d02a178406 |
|---|---|
| 1 #include "pfc-lite.h" | |
| 2 | |
| 3 #include "filetimetools.h" | |
| 4 | |
| 5 #include "timers.h" | |
| 6 | |
| 7 #include <math.h> | |
| 8 | |
| 9 namespace { | |
| 10 class exception_time_error {}; | |
| 11 } | |
| 12 | |
| 13 using namespace pfc; | |
| 14 | |
| 15 #ifndef _WIN32 | |
| 16 namespace { | |
| 17 typedef uint16_t WORD; | |
| 18 | |
| 19 typedef struct _SYSTEMTIME { | |
| 20 WORD wYear; | |
| 21 WORD wMonth; | |
| 22 WORD wDayOfWeek; | |
| 23 WORD wDay; | |
| 24 WORD wHour; | |
| 25 WORD wMinute; | |
| 26 WORD wSecond; | |
| 27 WORD wMilliseconds; | |
| 28 } SYSTEMTIME, * PSYSTEMTIME, * LPSYSTEMTIME; | |
| 29 | |
| 30 } | |
| 31 static void SystemTimeToNix(const SYSTEMTIME& st, struct tm& Time) { | |
| 32 memset(&Time, 0, sizeof(Time)); | |
| 33 Time.tm_sec = st.wSecond; | |
| 34 Time.tm_min = st.wMinute; | |
| 35 Time.tm_hour = st.wHour; | |
| 36 Time.tm_mday = st.wDay; | |
| 37 Time.tm_mon = st.wMonth - 1; | |
| 38 Time.tm_year = st.wYear - 1900; | |
| 39 } | |
| 40 | |
| 41 static t_filetimestamp ExportSystemTime(const SYSTEMTIME& st) { | |
| 42 struct tm Time; | |
| 43 SystemTimeToNix(st, Time); | |
| 44 return pfc::fileTimeUtoW(mktime(&Time)); | |
| 45 } | |
| 46 | |
| 47 static t_filetimestamp ExportSystemTimeLocal(const SYSTEMTIME& st) { | |
| 48 struct tm Time, Local; | |
| 49 SystemTimeToNix(st, Time); | |
| 50 time_t t = mktime(&Time); | |
| 51 localtime_r(&t, &Local); | |
| 52 return pfc::fileTimeUtoW(mktime(&Local)); | |
| 53 } | |
| 54 static void SystemTimeFromNix(SYSTEMTIME& st, struct tm const& Time, t_filetimestamp origTS) { | |
| 55 memset(&st, 0, sizeof(st)); | |
| 56 st.wSecond = Time.tm_sec; | |
| 57 st.wMinute = Time.tm_min; | |
| 58 st.wHour = Time.tm_hour; | |
| 59 st.wDay = Time.tm_mday; | |
| 60 st.wDayOfWeek = Time.tm_wday; | |
| 61 st.wMonth = Time.tm_mon + 1; | |
| 62 st.wYear = Time.tm_year + 1900; | |
| 63 st.wMilliseconds = (origTS % filetimestamp_1second_increment) / (filetimestamp_1second_increment/1000); | |
| 64 } | |
| 65 | |
| 66 static bool MakeSystemTime(SYSTEMTIME& st, t_filetimestamp ts) { | |
| 67 time_t t = (time_t)pfc::fileTimeWtoU(ts); | |
| 68 struct tm Time; | |
| 69 if (gmtime_r(&t, &Time) == NULL) return false; | |
| 70 SystemTimeFromNix(st, Time, ts); | |
| 71 return true; | |
| 72 } | |
| 73 | |
| 74 static bool MakeSystemTimeLocal(SYSTEMTIME& st, t_filetimestamp ts) { | |
| 75 time_t t = (time_t)pfc::fileTimeWtoU(ts); | |
| 76 struct tm Time; | |
| 77 if (localtime_r(&t, &Time) == NULL) return false; | |
| 78 SystemTimeFromNix(st, Time, ts); | |
| 79 return true; | |
| 80 } | |
| 81 | |
| 82 #else | |
| 83 static t_filetimestamp ExportSystemTime(const SYSTEMTIME& st) { | |
| 84 t_filetimestamp base; | |
| 85 if (!SystemTimeToFileTime(&st, (FILETIME*)&base)) throw exception_time_error(); | |
| 86 return base; | |
| 87 } | |
| 88 static t_filetimestamp ExportSystemTimeLocal(const SYSTEMTIME& st) { | |
| 89 #ifdef FOOBAR2000_DESKTOP_WINDOWS | |
| 90 t_filetimestamp base, out; | |
| 91 if (!SystemTimeToFileTime(&st, (FILETIME*)&base)) throw exception_time_error(); | |
| 92 if (!LocalFileTimeToFileTime((const FILETIME*)&base, (FILETIME*)&out)) throw exception_time_error(); | |
| 93 return out; | |
| 94 #else | |
| 95 SYSTEMTIME UTC; | |
| 96 if (!TzSpecificLocalTimeToSystemTime(NULL, &st, &UTC)) throw exception_time_error(); | |
| 97 return ExportSystemTime(UTC); | |
| 98 #endif | |
| 99 } | |
| 100 static bool MakeSystemTime(SYSTEMTIME& st, t_filetimestamp ts) { | |
| 101 if (ts == filetimestamp_invalid) return false; | |
| 102 return !!FileTimeToSystemTime((const FILETIME*)&ts, &st); | |
| 103 | |
| 104 } | |
| 105 static bool MakeSystemTimeLocal(SYSTEMTIME& st, t_filetimestamp ts) { | |
| 106 if (ts == filetimestamp_invalid) return false; | |
| 107 #ifdef FOOBAR2000_DESKTOP_WINDOWS | |
| 108 FILETIME ft; | |
| 109 if (FileTimeToLocalFileTime((FILETIME*)&ts, &ft)) { | |
| 110 if (FileTimeToSystemTime(&ft, &st)) { | |
| 111 return true; | |
| 112 } | |
| 113 } | |
| 114 return false; | |
| 115 #else | |
| 116 SYSTEMTIME UTC; | |
| 117 if (FileTimeToSystemTime((FILETIME*)&ts, &UTC)) { | |
| 118 if (SystemTimeToTzSpecificLocalTime(NULL, &UTC, &st)) return true; | |
| 119 } | |
| 120 return false; | |
| 121 #endif | |
| 122 } | |
| 123 #endif // _WIN32 | |
| 124 | |
| 125 static bool is_spacing(char c) { return c == ' ' || c == 10 || c == 13 || c == '\t'; } | |
| 126 | |
| 127 static unsigned ParseDateElem(const char* ptr, t_size len) { | |
| 128 unsigned ret = 0; | |
| 129 for (t_size walk = 0; walk < len; ++walk) { | |
| 130 const char c = ptr[walk]; | |
| 131 if (c < '0' || c > '9') throw exception_time_error(); | |
| 132 ret = ret * 10 + (unsigned)(c - '0'); | |
| 133 } | |
| 134 return ret; | |
| 135 } | |
| 136 | |
| 137 static bool st_sanity(SYSTEMTIME const& st) { | |
| 138 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; | |
| 139 } | |
| 140 static t_filetimestamp filetimestamp_from_string_internal(const char* date, bool local) { | |
| 141 // Accepted format | |
| 142 // YYYY-MM-DD HH:MM:SS | |
| 143 try { | |
| 144 SYSTEMTIME st = {}; | |
| 145 st.wDay = 1; st.wMonth = 1; | |
| 146 | |
| 147 unsigned walk = 0; | |
| 148 auto worker = [&](unsigned n) { | |
| 149 auto ret = ParseDateElem(date + walk, n); | |
| 150 walk += n; | |
| 151 if (ret > UINT16_MAX) throw exception_time_error(); | |
| 152 return (WORD)ret;; | |
| 153 }; | |
| 154 | |
| 155 auto skip = [&](char c) { | |
| 156 if (date[walk] == c) ++walk; | |
| 157 }; | |
| 158 | |
| 159 auto skipSpacing = [&] { | |
| 160 while (is_spacing(date[walk])) ++walk; | |
| 161 }; | |
| 162 skipSpacing(); | |
| 163 st.wYear = worker(4); | |
| 164 skip('-'); | |
| 165 st.wMonth = worker(2); | |
| 166 skip('-'); | |
| 167 st.wDay = worker(2); | |
| 168 skipSpacing(); | |
| 169 st.wHour = worker(2); | |
| 170 skip(':'); | |
| 171 st.wMinute = worker(2); | |
| 172 skip(':'); | |
| 173 st.wSecond = worker(2); | |
| 174 if (date[walk] == '.') { | |
| 175 double v = pfc::string_to_float(date + walk); | |
| 176 st.wMilliseconds = (WORD)floor(v * 1000.f); // don't ever round up, don't want to handle ms of 1000 | |
| 177 } | |
| 178 | |
| 179 if (!st_sanity(st)) throw exception_time_error(); | |
| 180 | |
| 181 if (local) { | |
| 182 return ExportSystemTimeLocal(st); | |
| 183 } else { | |
| 184 return ExportSystemTime(st); | |
| 185 } | |
| 186 } catch (exception_time_error const &) { | |
| 187 return filetimestamp_invalid; | |
| 188 } | |
| 189 } | |
| 190 | |
| 191 namespace pfc { | |
| 192 t_filetimestamp filetimestamp_from_string(const char* date) { | |
| 193 return filetimestamp_from_string_internal(date, true); | |
| 194 } | |
| 195 | |
| 196 t_filetimestamp filetimestamp_from_string_utc(const char* date) { | |
| 197 return filetimestamp_from_string_internal(date, false); | |
| 198 } | |
| 199 | |
| 200 static constexpr char g_invalidMsg[] = "<invalid timestamp>"; | |
| 201 | |
| 202 pfc::string_formatter format_filetimestamp(t_filetimestamp p_timestamp) { | |
| 203 try { | |
| 204 SYSTEMTIME st; | |
| 205 if (MakeSystemTimeLocal(st, p_timestamp)) { | |
| 206 pfc::string_formatter buffer; | |
| 207 buffer | |
| 208 << pfc::format_uint(st.wYear, 4) << "-" << pfc::format_uint(st.wMonth, 2) << "-" << pfc::format_uint(st.wDay, 2) << " " | |
| 209 << pfc::format_uint(st.wHour, 2) << ":" << pfc::format_uint(st.wMinute, 2) << ":" << pfc::format_uint(st.wSecond, 2); | |
| 210 return buffer; | |
| 211 } | |
| 212 } catch (...) {} | |
| 213 return g_invalidMsg; | |
| 214 } | |
| 215 | |
| 216 pfc::string_formatter format_filetimestamp_ms(t_filetimestamp p_timestamp) { | |
| 217 try { | |
| 218 SYSTEMTIME st; | |
| 219 if (MakeSystemTimeLocal(st, p_timestamp)) { | |
| 220 pfc::string_formatter buffer; | |
| 221 buffer | |
| 222 << pfc::format_uint(st.wYear, 4) << "-" << pfc::format_uint(st.wMonth, 2) << "-" << pfc::format_uint(st.wDay, 2) << " " | |
| 223 << pfc::format_uint(st.wHour, 2) << ":" << pfc::format_uint(st.wMinute, 2) << ":" << pfc::format_uint(st.wSecond, 2) << "." << pfc::format_uint(st.wMilliseconds, 3); | |
| 224 return buffer; | |
| 225 } | |
| 226 } catch (...) {} | |
| 227 return g_invalidMsg; | |
| 228 } | |
| 229 | |
| 230 pfc::string_formatter format_filetimestamp_utc(t_filetimestamp p_timestamp) { | |
| 231 try { | |
| 232 SYSTEMTIME st; | |
| 233 if (MakeSystemTime(st, p_timestamp)) { | |
| 234 pfc::string_formatter buffer; | |
| 235 buffer | |
| 236 << pfc::format_uint(st.wYear, 4) << "-" << pfc::format_uint(st.wMonth, 2) << "-" << pfc::format_uint(st.wDay, 2) << " " | |
| 237 << pfc::format_uint(st.wHour, 2) << ":" << pfc::format_uint(st.wMinute, 2) << ":" << pfc::format_uint(st.wSecond, 2); | |
| 238 return buffer; | |
| 239 } | |
| 240 } catch (...) {} | |
| 241 return g_invalidMsg; | |
| 242 } | |
| 243 | |
| 244 } // namespace foobar2000_io | |
| 245 | |
| 246 namespace { | |
| 247 struct dateISO_t { | |
| 248 unsigned Y, M, D; | |
| 249 unsigned h, m, s; | |
| 250 double sfrac; | |
| 251 int tzdelta; | |
| 252 }; | |
| 253 | |
| 254 dateISO_t read_ISO_8601(const char* dateISO) { | |
| 255 dateISO_t ret = {}; | |
| 256 // 2022-01-26T13:44:51.200000Z | |
| 257 // 2010-02-19T14:54:23.031+08:00 | |
| 258 // 2022-01-27T11:01:49+00:00 | |
| 259 // 2022-01-27T11:01:49Z | |
| 260 // 20220127T110149Z | |
| 261 | |
| 262 unsigned walk = 0; | |
| 263 auto worker = [&](unsigned n) { | |
| 264 auto ret = ParseDateElem(dateISO + walk, n); | |
| 265 walk += n; | |
| 266 return ret; | |
| 267 }; | |
| 268 auto skip = [&](char c) { | |
| 269 if (dateISO[walk] == c) ++walk; | |
| 270 }; | |
| 271 auto expect = [&](char c) { | |
| 272 if (dateISO[walk] != c) throw exception_time_error(); | |
| 273 ++walk; | |
| 274 }; | |
| 275 ret.Y = worker(4); | |
| 276 skip('-'); | |
| 277 ret.M = worker(2); | |
| 278 skip('-'); | |
| 279 ret.D = worker(2); | |
| 280 expect('T'); | |
| 281 ret.h = worker(2); | |
| 282 skip(':'); | |
| 283 ret.m = worker(2); | |
| 284 skip(':'); | |
| 285 ret.s = worker(2); | |
| 286 if (dateISO[walk] == '.') { | |
| 287 unsigned base = walk; | |
| 288 ++walk; | |
| 289 while (pfc::char_is_numeric(dateISO[walk])) ++walk; | |
| 290 ret.sfrac = pfc::string_to_float(dateISO + base, walk - base); | |
| 291 } | |
| 292 if (dateISO[walk] == '+' || dateISO[walk] == '-') { | |
| 293 bool neg = dateISO[walk] == '-'; | |
| 294 ++walk; | |
| 295 unsigned tz_h = worker(2); | |
| 296 if (tz_h >= 24) throw exception_time_error(); | |
| 297 skip(':'); | |
| 298 unsigned tz_m = worker(2); | |
| 299 if (tz_m >= 60) throw exception_time_error(); | |
| 300 tz_m += tz_h * 60; | |
| 301 ret.tzdelta = neg ? (int)tz_m : -(int)tz_m; // reversed! it's a timezone offset, have to *add* it if timezone has a minus | |
| 302 } | |
| 303 return ret; | |
| 304 } | |
| 305 } | |
| 306 | |
| 307 t_filetimestamp pfc::filetimestamp_from_string_ISO_8601(const char* dateISO) { | |
| 308 try { | |
| 309 auto elems = read_ISO_8601(dateISO); | |
| 310 | |
| 311 SYSTEMTIME st = {}; | |
| 312 st.wDay = 1; st.wMonth = 1; | |
| 313 st.wYear = (WORD)elems.Y; | |
| 314 st.wMonth = (WORD)elems.M; | |
| 315 st.wDay = (WORD)elems.D; | |
| 316 st.wHour = (WORD)elems.h; | |
| 317 st.wMinute = (WORD)elems.m; | |
| 318 st.wSecond = (WORD)elems.s; | |
| 319 st.wMilliseconds = (WORD)floor(elems.sfrac * 1000.f); | |
| 320 | |
| 321 if (!st_sanity(st)) throw exception_time_error(); | |
| 322 | |
| 323 auto ret = ExportSystemTime(st); | |
| 324 | |
| 325 ret += filetimestamp_1second_increment * elems.tzdelta * 60; | |
| 326 | |
| 327 return ret; | |
| 328 } catch (...) { | |
| 329 return filetimestamp_invalid; | |
| 330 } | |
| 331 } |
