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 }