|
1
|
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 }
|