CMSDK 2.0.1
Cross-platform C++ base library and SDK for the Psyclone AIOS platform
Loading...
Searching...
No Matches
PsyTime.cpp
Go to the documentation of this file.
1
10#ifdef _WIN32
11 // To avoid complaints about sscanf
12 #define _CRT_SECURE_NO_WARNINGS
13#endif
14
15#include "PsyTime.h"
16#include "Utils.h"
17#include "UnitTestFramework.h"
18
19namespace cmlabs {
20
21// Age = now - t, computed with an explicit branch because both operands are
22// unsigned: (now - t) would underflow to a huge uint64 when t is in the
23// future, so the future case is computed as -(t - now) instead. t == 0 is the
24// "no timestamp" sentinel and yields 0 rather than a ~2 million year age.
25int64 GetTimeAge(uint64 t) {
26 if (!t)
27 return 0;
28 uint64 now = GetTimeNow();
29 if (now >= t)
30 return (int64)(now - t);
31 else
32 return (int64)((t - now) * -1);
33}
34
35int32 GetTimeAgeMS(uint64 t) {
36 if (!t)
37 return 0;
38 uint64 now = GetTimeNow();
39 if (now >= t)
40 return (int32)((now - t) / 1000);
41 else
42 return (int32)((t - now) * -1)/1000;
43}
44
45// The µs-resolution "what time is it" call, safe from any thread.
46//
47// Algorithm (identical on all platforms):
48// 1. Read the monotonic hardware counter and convert it to µs since the
49// counter's zero point ("lastWrapuSec").
50// 2. Lazily (first call) or periodically (masters only, every
51// TMC_SYNC_INTERVAL of counter time) re-anchor the counter to the wall
52// clock via SyncToHardwareClock(), producing CurrentTMC = the absolute
53// PsyTime (µs since year 0) at which the counter read 0.
54// 3. now = CurrentTMC + lastWrapuSec + NetTimeAdjust.
55//
56// Monotonic vs wallclock: between re-syncs the result is strictly monotonic
57// because it follows the hardware counter, not the (NTP-steppable) wall
58// clock; a master's re-sync may introduce a small step. TMC_SLAVE nodes never
59// self-sync here — they receive TMC/NetTimeAdjust from the time master via
60// SetCurrentTimeSyncData()/SetCurrentNetSyncDif(), keeping one shared
61// timeline across machines.
62//
63// Platform sources: WINDOWS uses QueryPerformanceCounter/Frequency (the
64// <0xFFFFFFFFFF branch keeps the tick→µs scaling in exact 64-bit integer
65// math while the product still fits; larger counts fall back to double to
66// avoid overflow, trading a little precision). LINUX/OSX use
67// clock_gettime(CLOCK_MONOTONIC), which is already ns-resolution and cannot
68// overflow in the µs conversion. Returns 0 only if the platform clock fails.
69uint64 GetTimeNow() {
70// uint64 tmc = MemoryManager::GetTMC();
71 //uint64 tmc = CurrentTMC;
72 //uint64 lsa = NetTimeAdjust;
73// if (tmc == 0)
74// return 0;
75
76 uint64 time = 0;
77 #if defined WINDOWS
78 LARGE_INTEGER perfCount, perfFreq;
79 if (QueryPerformanceCounter(&perfCount) != 0) {
80 QueryPerformanceFrequency(&perfFreq);
81 uint64 lastWrapuSec;
82 // To avoid rounding errors...
83 // lastWrapuSec = perfCount.QuadPart * (1000000.0 / perfFreq.QuadPart);
84 // lastWrapuSec2 = (perfCount.QuadPart * 1000000) / perfFreq.QuadPart;
85 // if (lastWrapuSec != lastWrapuSec2) {
86 // lastWrapuSec = lastWrapuSec;
87 // }
88 if (perfCount.QuadPart < 0xFFFFFFFFFFL)
89 lastWrapuSec = (perfCount.QuadPart * 1000000) / perfFreq.QuadPart;
90 else
91 lastWrapuSec = (uint64)(perfCount.QuadPart * (1000000.0 / perfFreq.QuadPart));
92 //lastWrapuSec = (perfCount.QuadPart / perfFreq.QuadPart) * 1000000;
93
94 if (!CurrentTMC || ((CurrentTMCMode == TMC_MASTER) && (lastWrapuSec - LastTMCSync > TMC_SYNC_INTERVAL))) {
96 LastTMCSync = lastWrapuSec;
97 }
98 // Time now is usec at last wrap + usec since last wrap
99 // We also need to add the Local Sync Adjustment, if syncing with other computers
100 //uint64 now = tmc + lastWrapuSec + lsa;
101 uint64 now = CurrentTMC + lastWrapuSec + NetTimeAdjust;
102
103 //uint64 secs = lastWrapuSec / 1000000;
104 //uint64 days = secs / (60*60*24);
105 //uint64 years = days / 365;
106
107 //uint64 nsecs = now / 1000000;
108 //uint64 ndays = nsecs / (60*60*24);
109 //uint64 nyears = ndays / 365;
110 //int t = 0;
111 return now;
112 }
113 else
114 return 0;
115
116 #elif defined LINUX || defined OSX
117 struct timespec t;
118 if (clock_gettime(CLOCK_MONOTONIC, &t) == 0) {
119 uint64 lastWrapuSec = (uint64)(t.tv_sec) * 1000000 + (t.tv_nsec / 1000);
120
121 if (!CurrentTMC || ((CurrentTMCMode == TMC_MASTER) && (lastWrapuSec - LastTMCSync > TMC_SYNC_INTERVAL))) {
123 LastTMCSync = lastWrapuSec;
124 }
125
126 uint64 now = CurrentTMC + lastWrapuSec + NetTimeAdjust;
127 return now;
128 }
129 else
130 return 0;
131 #endif
132 return 0;
133}
134
135bool SetCurrentNetSyncDif(int64 netTimeDif) {
136 NetTimeAdjust += netTimeDif;
137 return true;
138}
139
140bool SetCurrentTimeSyncData(uint64 tmc, int64 netTimeAdjust) {
141 CurrentTMC = tmc;
143 NetTimeAdjust = netTimeAdjust;
144 return true;
145}
146
147bool GetCurrentTimeSyncData(uint64& tmc, int64& netTimeAdjust) {
148 tmc = CurrentTMC;
149 netTimeAdjust = NetTimeAdjust;
150 return true;
151}
152
153// Calculate the current Time Mapping Constant: the absolute PsyTime (µs since
154// year 0) at which the monotonic counter read 0, i.e. TMC = wallclock_now -
155// monotonic_now (+ the year-0..1970 offset, since the wall clock is Unix-based).
156//
157// Accuracy trick: the wall clock is only readable at ~ms (ftime) or µs
158// (gettimeofday) granularity, so simply subtracting two reads could be off by
159// up to one tick. Instead we busy-wait for the wall clock to tick over and
160// sample the monotonic counter at that exact edge, so both clocks are read at
161// a known instant. On Linux/macOS the loop is bounded (~1 s, maxWait) because
162// gettimeofday may have coarse or frozen resolution in some environments
163// (and macOS ftime() is deprecated); on timeout we fall back to an unaligned
164// read — slightly less accurate, never hung.
165//
166// Called lazily/periodically by GetTimeNow() on TMC_MASTER nodes; may block
167// for up to one wall-clock tick, so call sparingly.
169
170 #if defined WINDOWS
171 // On Windows TMC contains the number of microseconds since year 0
172 // when the PerfCount was last 0
173 LARGE_INTEGER perfCount, perfFreq;
174 if (QueryPerformanceCounter(&perfCount) != 0) {
175 QueryPerformanceFrequency(&perfFreq);
176
177 // We have performance counters here, good...
178 // Now do a busy wait to find the perfCount exactly when the ftime clock ticks
179 struct timeb timebuf1, timebuf2;
180 ftime(&timebuf1);
181 while (true) {
182 ftime(&timebuf2);
183 if (timebuf1.millitm != timebuf2.millitm) {
184 QueryPerformanceCounter(&perfCount);
185 break;
186 }
187 }
188 // we now know the perfcount exactly when the clock in timebuf2 ticked
189 uint64 lastWrapuSec;
190 if (perfCount.QuadPart < 0xFFFFFFFFFFL)
191 lastWrapuSec = (perfCount.QuadPart * 1000000) / perfFreq.QuadPart;
192 else
193 lastWrapuSec = (uint64)(perfCount.QuadPart * (1000000.0 / perfFreq.QuadPart));
194 //lastWrapuSec = (perfCount.QuadPart / perfFreq.QuadPart) * 1000000;
195
196 // usec since midnight (00:00:00), January 1, 1970, coordinated universal time (UTC)
197 uint64 nowuSec = (((uint64)timebuf2.time) * 1000000) + ((uint64)timebuf2.millitm * 1000);
198 // usec since 1970 - lastwrap + usec year 0-1970 = usec since year 0 at last wrap
199 uint64 tmc = nowuSec - lastWrapuSec + USEC_YEAR_0_TO_1970;
200 //uint64 usecs = lastWrapuSec;
201 //uint64 secs = usecs / 1000000;
202 //uint64 days = secs / (60*60*24);
203 //uint64 years = days / 365;
204 //int t = 0;
205 return (CurrentTMC = tmc);
206 }
207 else
208 return 0;
209 #elif defined LINUX
210 // On Linux TMC contains the number of microseconds since year 0
211 // when CLOCK_MONOTONIC was last 0
212 // struct timespec t;
213 // if (clock_gettime(CLOCK_MONOTONIC, &t) == 0) {
214 // struct timeb timebuf1, timebuf2;
215 // ftime(&timebuf1);
216 // while (true) {
217 // ftime(&timebuf2);
218 // if (timebuf1.millitm != timebuf2.millitm) {
219 // clock_gettime(CLOCK_MONOTONIC, &t);
220 // break;
221 // }
222 // }
223 // uint64 lastWrapuSec = (uint64)(t.tv_sec) * 1000000 + (t.tv_nsec / 1000);
224
225 // uint64 nowuSec = ((uint64)timebuf2.time) * 1000000 + (uint64)(timebuf2.millitm) * 1000;
226 // uint64 tmc = nowuSec - lastWrapuSec + USEC_YEAR_0_TO_1970;
227 // return (CurrentTMC = tmc);
228 // }
229 // else
230 // return 0;
231 struct timespec t;
232 if (clock_gettime(CLOCK_MONOTONIC, &t) == 0) {
233 struct timeval tv1, tv2;
234 gettimeofday(&tv1, NULL);
235 int waitCount = 0;
236 const int maxWait = 1000000; // ~1s safety limit
237 while (waitCount < maxWait) {
238 gettimeofday(&tv2, NULL);
239 if (tv1.tv_sec != tv2.tv_sec || tv1.tv_usec != tv2.tv_usec) {
240 clock_gettime(CLOCK_MONOTONIC, &t);
241 break;
242 }
243 waitCount++;
244 }
245 if (waitCount >= maxWait)
246 clock_gettime(CLOCK_MONOTONIC, &t); // use current monotonic time
247 uint64 lastWrapuSec = (uint64)(t.tv_sec) * 1000000 + (t.tv_nsec / 1000);
248 uint64 nowuSec = (uint64)tv2.tv_sec * 1000000 + (uint64)tv2.tv_usec;
249 uint64 tmc = nowuSec - lastWrapuSec + USEC_YEAR_0_TO_1970;
250 return (CurrentTMC = tmc);
251 }
252 else
253 return 0;
254 #elif defined OSX
255 // On macOS ftime() is deprecated and millitm may not change (infinite loop).
256 // Use gettimeofday() for wall clock; wait for a tick with a safety timeout.
257 struct timespec t;
258 if (clock_gettime(CLOCK_MONOTONIC, &t) == 0) {
259 struct timeval tv1, tv2;
260 gettimeofday(&tv1, NULL);
261 int waitCount = 0;
262 const int maxWait = 1000000; // ~1s safety limit
263 while (waitCount < maxWait) {
264 gettimeofday(&tv2, NULL);
265 if (tv1.tv_sec != tv2.tv_sec || tv1.tv_usec != tv2.tv_usec) {
266 clock_gettime(CLOCK_MONOTONIC, &t);
267 break;
268 }
269 waitCount++;
270 }
271 if (waitCount >= maxWait)
272 clock_gettime(CLOCK_MONOTONIC, &t); // use current monotonic time
273 uint64 lastWrapuSec = (uint64)(t.tv_sec) * 1000000 + (t.tv_nsec / 1000);
274 uint64 nowuSec = (uint64)tv2.tv_sec * 1000000 + (uint64)tv2.tv_usec;
275 uint64 tmc = nowuSec - lastWrapuSec + USEC_YEAR_0_TO_1970;
276 return (CurrentTMC = tmc);
277 }
278 else
279 return 0;
280 #endif
281}
282
283// Estimate the next wrap-around of the underlying hardware counter (NOT of
284// PsyTime itself — uint64 µs-since-year-0 lasts ~584,000 years). On Windows
285// this is the µs until QueryPerformanceCounter reaches 2^64-1 (same
286// integer/double scaling split as GetTimeNow()); on Linux/macOS
287// CLOCK_MONOTONIC counts from boot in a 64-bit timespec and effectively never
288// wraps, so "never" is reported as 0xFFFFFFFFFFFFFFFF.
290 #if defined WINDOWS
291 LARGE_INTEGER perfCount, perfFreq;
292 if (QueryPerformanceCounter(&perfCount) != 0) {
293 QueryPerformanceFrequency(&perfFreq);
294
295 uint64 nextWrapTicks = 0xFFFFFFFFFFFFFFFFL - perfCount.QuadPart;
296 uint64 nextWrapuSec;
297
298 if (nextWrapTicks < 0xFFFFFFFFFFL)
299 nextWrapuSec = (nextWrapTicks * 1000000) / perfFreq.QuadPart;
300 else
301 nextWrapuSec = (uint64)(nextWrapTicks * (1000000.0 / perfFreq.QuadPart));
302 return nextWrapuSec;
303 }
304 else
305 return 0;
306
307 #elif defined LINUX || defined OSX
308 return 0xFFFFFFFFFFFFFFFFL;
309 #endif
310}
311
313 return GetDateAndTime(t, false);
314}
315
316// Break a PsyTime timestamp into calendar fields. The conversion routes
317// through the C library: whole seconds are converted to time_t (Unix epoch,
318// hence the USEC_YEAR_0_TO_1970 subtraction — timestamps before 1970 are not
319// representable and return a zeroed struct) and handed to the thread-safe
320// localtime_s/gmtime_s (Windows) or localtime_r/gmtime_r (POSIX), so the OS
321// timezone database supplies DST and zone name. Sub-second fields (msec/usec)
322// are filled from the µs remainder, and tm's 0-based month/weekday/yearday
323// are shifted to PsyDateAndTime's 1-based conventions.
324struct PsyDateAndTime GetDateAndTime(uint64 t, bool local) {
325 struct tm time;
326 struct PsyDateAndTime tad;
327 memset(&tad, 0, sizeof(struct PsyDateAndTime));
328 if (t < USEC_YEAR_0_TO_1970)
329 return tad;
330 // First create the time_t from t
331 uint64 usSince1970 = (t - USEC_YEAR_0_TO_1970);
332 time_t timet = (time_t)(usSince1970 / 1000000);
333 #if defined WINDOWS
334 if (local) {
335 if (localtime_s(&time, &timet) != 0)
336 return tad;
337 }
338 else {
339 if (gmtime_s(&time, &timet) != 0)
340 return tad;
341 }
342
343 _get_dstbias((long*)&tad.dstsec); // tad.dstsec = _dstbias*(-1);
344 _get_timezone((long*)&tad.tzsec); // tad.tzsec = _timezone;
345 size_t l;
346 _get_tzname(&l, tad.zonename, 64, 1);
347 // utils::strcpyavail(tad.tzname, _tzname[0], 64, true);
348 #elif defined LINUX || defined OSX
349 if (local)
350 localtime_r(&timet, &time);
351 else
352 gmtime_r(&timet, &time);
353 tad.dstsec = daylight*SECS_PER_HOUR;
354 tad.tzsec = timezone;
355 utils::strcpyavail(tad.zonename, tzname[0], 64, true);
356 #endif
357
358 tad.usec = usSince1970 % 1000000;
359 tad.msec = tad.usec / 1000;
360 tad.sec = time.tm_sec;
361 tad.min = time.tm_min;
362 tad.hour = time.tm_hour;
363 tad.day = time.tm_mday;
364 tad.mon = time.tm_mon + 1;
365 tad.year = time.tm_year + 1900;
366 tad.wday = time.tm_wday;
367 tad.yday = time.tm_yday + 1;
368 tad.dst = (time.tm_isdst > 0);
369 return tad;
370}
371
372// Inverse of GetDateAndTime() via mktime(): interprets the fields as LOCAL
373// time (tm_isdst forced to 0), converts to time_t seconds and re-adds the
374// year-0 offset. Sub-second fields (msec/usec) are ignored, so the result has
375// whole-second resolution; returns 0 when mktime() cannot represent the date.
377 struct tm time;
378 time.tm_sec = tad.sec;
379 time.tm_min = tad.min;
380 time.tm_hour = tad.hour;
381 time.tm_mday = tad.day;
382 time.tm_mon = tad.mon - 1;
383 time.tm_year = tad.year - 1900;
384 time.tm_isdst = 0;
385
386 time_t t = mktime(&time);
387 if (t == (time_t)-1)
388 return 0;
389
390 return (((uint64)t) * 1000000) + USEC_YEAR_0_TO_1970;
391}
392
393struct PsyDateAndTime GetTimeDifference(uint64 t2, uint64 t1) {
394 return GetTimeDifference(t2-t1);
395}
396
398 // ##################
399 return 0;
400}
401
402// Express a signed µs difference as duration fields. Unlike GetDateAndTime()
403// this does NOT use the calendar: it divides by the fixed constants
404// SECS_PER_YEAR (365 days) and SECS_PER_MONTH (average Gregorian month), so
405// year/mon/day are approximations suitable for display ("1 year 2 months"),
406// not exact calendar arithmetic. yday carries the total whole-day count and
407// the negative flag preserves the sign (all field values are magnitudes).
409 struct PsyDateAndTime tad;
410 memset(&tad, 0, sizeof(struct PsyDateAndTime));
411 int64 t = dif;
412 if (dif < 0) {
413 t = -1*dif;
414 tad.negative = true;
415 }
416 tad.usec = t % 1000000;
417 tad.msec = tad.usec / 1000;
418 uint64 left = t / 1000000;
419 tad.yday = (uint16) (left / SECS_PER_DAY);
420 tad.year = (uint16) (left / SECS_PER_YEAR);
421 left = left % SECS_PER_YEAR;
422 tad.mon = (uint16) (left / SECS_PER_MONTH);
423 left = left % SECS_PER_MONTH;
424 tad.day = (uint16) (left / SECS_PER_DAY);
425 left = left % SECS_PER_DAY;
426 tad.hour = (uint16) (left / SECS_PER_HOUR);
427 left = left % SECS_PER_HOUR;
428 tad.min = (uint16) (left / SECS_PER_MIN);
429 tad.sec = left % SECS_PER_MIN;
430 return tad;
431}
432
433// Parse a textual date/time by trying a fixed list of sscanf patterns in
434// order: RFC 822/1123, RFC 850, asctime(), then the PrintTimeSortable*
435// output forms (with µs, ms, s and legacy 2-digit-year variants). Named
436// month/day fields are resolved via the PsyMonths table afterwards; sortable
437// forms are always interpreted as GMT. Returns 0 when no pattern matches.
438uint64 GetTimeFromString(const char* str) {
439 char dayname[64];
440 char monthname[64];
441 char tzone[64];
442 int r;
443 char* ch;
444 uint32 day, hour, min, sec, year, mon = 0, usec = 0, msec = 0;
445 //Sun, 06 Nov 1994 08:49:37 GMT ; RFC 822, updated by RFC 1123
446 if ( (r = sscanf(str, "%63s %u %63s %u %u:%u:%u %63s",
447 dayname, &day, monthname, &year, &hour, &min, &sec, tzone)) == 8) {
448 }
449 //Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036
450 else if ( (r = sscanf(str, "%63s %u-%63s-%u %u:%u:%u %63s",
451 dayname, &day, monthname, &year, &hour, &min, &sec, tzone)) == 8) {
452 }
453 //Sun Nov 6 08:49:37 1994 ; ANSI C's asctime() format
454 else if ( (r = sscanf(str, "%63s %63s %u %u:%u:%u %u",
455 dayname, monthname, &day, &hour, &min, &sec, &year)) == 7) {
456 utils::strcpyavail(tzone, "GMT", 64, true);
457 }
458 // Sortable format microsecs
459 else if ( (r = sscanf(str, "%04u%02u%02u-%02u%02u%02u.%03u.%03u",
460 &year, &mon, &day, &hour, &min, &sec, &msec, &usec)) == 8) {
461 utils::strcpyavail(tzone, "GMT", 64, true);
462 }
463 // Sortable format millisecs
464 else if ( (r = sscanf(str, "%04u%02u%02u-%02u%02u%02u.%03u",
465 &year, &mon, &day, &hour, &min, &sec, &msec)) == 7) {
466 utils::strcpyavail(tzone, "GMT", 64, true);
467 }
468 // Sortable format secs
469 else if ( (r = sscanf(str, "%04u%02u%02u-%02u%02u%02u",
470 &year, &mon, &day, &hour, &min, &sec)) == 6) {
471 utils::strcpyavail(tzone, "GMT", 64, true);
472 }
473 // Sortable format
474 else if ( (r = sscanf(str, "%02u%02u%02u%02u%02u%02u",
475 &year, &mon, &day, &hour, &min, &sec)) == 6) {
476 utils::strcpyavail(tzone, "GMT", 64, true);
477 }
478 else
479 return 0;
480
481 if ( (ch = strchr(dayname, ',')) != NULL)
482 *ch = 0;
483
484 if ( stricmp(tzone, "GMT") != 0 )
485 return 0;
486
487 if (year < 100)
488 year += (year < 80) ? 2000 : 1900;
489
490 struct PsyDateAndTime tad;
491 memset(&tad, 0, sizeof(struct PsyDateAndTime));
492
493 tad.usec = usec + (1000*msec);
494 tad.msec = msec;
495 tad.year = year;
496 tad.day = day;
497 tad.hour = hour;
498 tad.min = min;
499 tad.sec = sec;
500 tad.mon = 0;
501
502 if (mon)
503 tad.mon = mon;
504 else {
505 for (uint16 n=0; n<12; n++) {
506 if ( (stricmp(monthname, PsyMonths[n]) == 0) || (stricmp(monthname, PsyMonthsFull[n]) == 0) ) {
507 tad.mon = n;
508 break;
509 }
510 }
511 }
512 if (tad.mon == 0)
513 return 0;
514
515 return GetTimeFromPsyDateAndTime(tad);
516}
517
518uint32 GetHTTPTime(uint64 time, char* buffer, uint32 size) {
519 if (!buffer || (size < 64))
520 return 0;
521 struct PsyDateAndTime ts = GetDateAndTime(time, false);
522 snprintf(buffer, size, "%s, %u %s %u %02u:%02u:%02u GMT",
523 PsyDays[ts.wday], ts.day, PsyMonths[ts.mon], ts.year, ts.hour, ts.min, ts.sec);
524 return (uint32)strlen(buffer);
525}
526
527char* PrintTime(uint64 t, bool local, bool us, bool ms) {
528 char* str = new char[1024];
529 struct PsyDateAndTime time = GetDateAndTime(t, local);
530 if (us)
531 snprintf(str, 1024, "%02u/%02u/%04u %02u:%02u:%02u.%03u.%03u", time.day, time.mon, time.year, time.hour, time.min, time.sec, time.msec, time.usec % 1000);
532 else if (ms)
533 snprintf(str, 1024, "%02u/%02u/%04u %02u:%02u:%02u.%03u", time.day, time.mon, time.year, time.hour, time.min, time.sec, time.msec);
534 else
535 snprintf(str, 1024, "%02u/%02u/%04u %02u:%02u:%02u", time.day, time.mon, time.year, time.hour, time.min, time.sec);
536 return str;
537}
538
539char* PrintTimeOnly(uint64 t, bool local, bool us, bool ms) {
540 char* str = new char[1024];
541 struct PsyDateAndTime time = GetDateAndTime(t, local);
542 if (us)
543 snprintf(str, 1024, "%02u:%02u:%02u.%03u.%03u", time.hour, time.min, time.sec, time.msec, time.usec % 1000);
544 else if (ms)
545 snprintf(str, 1024, "%02u:%02u:%02u.%03u", time.hour, time.min, time.sec, time.msec);
546 else
547 snprintf(str, 1024, "%02u:%02u:%02u", time.hour, time.min, time.sec);
548 return str;
549}
550
551char* PrintDate(uint64 t, bool local) {
552 char* str = new char[1024];
553 struct PsyDateAndTime time = GetDateAndTime(t, local);
554 snprintf(str, 1024, "%02u/%02u/%04u", time.day, time.mon, time.year);
555 return str;
556}
557
558char* PrintTimeSortable(uint64 t, bool local) {
559 char* str = new char[1024];
560 struct PsyDateAndTime time = GetDateAndTime(t, local);
561 snprintf(str, 1024, "%04u%02u%02u-%02u%02u%02u", time.year, time.mon, time.day, time.hour, time.min, time.sec);
562 return str;
563}
564
565char* PrintTimeSortableMillisec(uint64 t, bool local) {
566 char* str = new char[1024];
567 struct PsyDateAndTime time = GetDateAndTime(t, local);
568 snprintf(str, 1024, "%04u%02u%02u-%02u%02u%02u.%03u", time.year, time.mon, time.day, time.hour, time.min, time.sec, time.msec);
569 return str;
570}
571
572char* PrintTimeSortableMicrosec(uint64 t, bool local) {
573 char* str = new char[1024];
574 struct PsyDateAndTime time = GetDateAndTime(t, local);
575 snprintf(str, 1024, "%04u%02u%02u-%02u%02u%02u.%03u.%03u", time.year, time.mon, time.day, time.hour, time.min, time.sec, time.msec, time.usec%1000);
576 return str;
577}
578
579char* PrintDateSortable(uint64 t, bool local) {
580 char* str = new char[1024];
581 struct PsyDateAndTime time = GetDateAndTime(t, local);
582 snprintf(str, 1024, "%04u%02u%02u", time.year, time.mon, time.day);
583 return str;
584}
585
586char* PrintDateSortableDelimiter(uint64 t, const char* del, bool local) {
587 char* str = new char[1024];
588 struct PsyDateAndTime time = GetDateAndTime(t, local);
589 snprintf(str, 1024, "%04u%s%02u%s%02u", time.year, del, time.mon, del, time.day);
590 return str;
591}
592
593// Scale-adaptive duration formatter. Picks the unit from the magnitude —
594// "250us" / "1.500ms" / "2.750s" / "2m 3s" / "01:02:03" / "3 days 01:02:03" /
595// "1 year(s) ..." — with the us/ms flags controlling how much sub-second
596// detail is kept at each scale. Above one minute the split into fields is
597// delegated to GetTimeDifference(). Note: t is uint64, so the "-" sign branch
598// is effectively dead — negative durations must be handled by the caller
599// (e.g. via GetTimeAge()) before formatting. Returns a new char[1024] the
600// caller must delete []; prefer PrintTimeDifString().
601char* PrintTimeDif(uint64 t, bool us, bool ms) {
602 char* str = new char[1024];
603 char* sign = new char[2];
604 char* subsecs = NULL;
605 utils::strcpyavail(sign, (t<0) ? "-" : "", 2, true);
606 if (t == 0) {
607 if (us)
608 snprintf(str, 1024, "0us");
609 else if (ms)
610 snprintf(str, 1024, "0ms");
611 else
612 snprintf(str, 1024, "0s");
613 }
614 else {
615 // uint64 absT = abs64(t);
616 uint64 absT = t;
617 if (absT < 1000) {
618 if (us)
619 snprintf(str, 1024, "%s%uus", sign, (uint32)absT);
620 else if (ms)
621 snprintf(str, 1024, "%s%ums", sign, (uint32)round((double)absT / 1000.0));
622 else
623 snprintf(str, 1024, "%s%us", sign, (uint32)round((double)absT / 1000000.0));
624 }
625 else if (absT < 1000000) {
626 if (us)
627 snprintf(str, 1024, "%s%.3fms", sign, absT / 1000.0);
628 else if (ms)
629 snprintf(str, 1024, "%s%ums", sign, (uint32)round(absT / 1000.0));
630 else
631 snprintf(str, 1024, "%s%us", sign, (uint32)round((double)absT/1000000.0));
632 }
633 else if (absT < 60000000) {
634 if (us)
635 snprintf(str, 1024, "%s%.6fs", sign, absT / 1000000.0);
636 else if (ms)
637 snprintf(str, 1024, "%s%.3fs", sign, absT / 1000000.0);
638 else
639 snprintf(str, 1024, "%s%us", sign, (uint32)round((double)absT / 1000000.0));
640 }
641 else {
642 subsecs = new char[20];
643 uint64 absSec = absT/1000000;
644 uint32 roundMS;
645 struct PsyDateAndTime difTime = GetTimeDifference(t);
646
647 if (us && difTime.usec)
648 snprintf(subsecs, 20, "%s%06uus", (absSec < SECS_PER_HOUR) ? " " : ".", difTime.usec);
649 else if (ms && (roundMS = (uint32)round(difTime.usec / 1000.0)))
650 snprintf(subsecs, 20, "%s%03ums", (absSec < SECS_PER_HOUR) ? " " : ".", roundMS);
651 else
652 subsecs[0] = 0;
653
654 if (absSec < SECS_PER_HOUR)
655 snprintf(str, 1024, "%s%um %us%s", sign, difTime.min, difTime.sec, subsecs);
656 else if (absSec < SECS_PER_DAY)
657 snprintf(str, 1024, "%s%02u:%02u:%02u%s", sign, difTime.hour, difTime.min, difTime.sec, subsecs);
658 else if (difTime.yday == 1)
659 snprintf(str, 1024, "%s%u day %02u:%02u:%02u%s", sign, difTime.yday, difTime.hour, difTime.min, difTime.sec, subsecs);
660 else if (difTime.yday < 365)
661 snprintf(str, 1024, "%s%u days %02u:%02u:%02u%s", sign, difTime.yday, difTime.hour, difTime.min, difTime.sec, subsecs);
662 else
663 snprintf(str, 1024, "%s%u year(s) %u month(s) %u day(s) %02u:%02u:%02u%s",
664 sign, difTime.year, difTime.mon, difTime.day, difTime.hour, difTime.min, difTime.sec, subsecs);
665 delete[] subsecs;
666 }
667 }
668 delete[] sign;
669 return str;
670}
671
672std::string PrintTimeNowString(bool local, bool us, bool ms) {
673 return PrintTimeString(GetTimeNow(), local, us, ms);
674}
675
676std::string PrintTimeString(uint64 t, bool local, bool us, bool ms) {
677 char* str = PrintTime(t, local, us, ms);
678 if (!str)
679 return "";
680 std::string res = str;
681 delete [] str;
682 return res;
683}
684
685std::string PrintTimeOnlyString(uint64 t, bool local, bool us, bool ms) {
686 char* str = PrintTimeOnly(t, local, us, ms);
687 if (!str)
688 return "";
689 std::string res = str;
690 delete[] str;
691 return res;
692}
693
694std::string PrintDateString(uint64 t, bool local) {
695 char* str = PrintDate(t, local);
696 if (!str)
697 return "";
698 std::string res = str;
699 delete [] str;
700 return res;
701}
702
703std::string PrintDateStringSortable(uint64 t, bool local) {
704 char* str = PrintDateSortable(t, local);
705 if (!str)
706 return "";
707 std::string res = str;
708 delete [] str;
709 return res;
710}
711
712std::string PrintDateStringSortableDelimiter(uint64 t, const char* del, bool local) {
713 char* str = PrintDateSortableDelimiter(t, del, local);
714 if (!str)
715 return "";
716 std::string res = str;
717 delete [] str;
718 return res;
719}
720
721
722std::string PrintTimeDifString(uint64 t, bool us, bool ms) {
723 char* str = PrintTimeDif(t, us, ms);
724 if (!str)
725 return "";
726 std::string res = str;
727 delete [] str;
728 return res;
729}
730
731std::string PrintTimeSortableString(uint64 t, bool local) {
732 char* str = PrintTimeSortable(t, local);
733 if (!str)
734 return "";
735 std::string res = str;
736 delete [] str;
737 return res;
738}
739
740std::string PrintTimeSortableMillisecString(uint64 t, bool local) {
741 char* str = PrintTimeSortableMillisec(t, local);
742 if (!str)
743 return "";
744 std::string res = str;
745 delete [] str;
746 return res;
747}
748
749std::string PrintTimeSortableMicrosecString(uint64 t, bool local) {
750 char* str = PrintTimeSortableMicrosec(t, local);
751 if (!str)
752 return "";
753 std::string res = str;
754 delete [] str;
755 return res;
756}
757
758uint64 FTime2PsyTime(uint64 t) {
759 return (t * 1000000) + USEC_YEAR_0_TO_1970;
760}
761
762
763
765
766 int64 dif;
767
768 // 1. Basic now / date breakdown
769 unittest::progress(10, "time now and date breakdown");
770
771 uint64 t0 = GetTimeNow();
772 utils::Sleep(10);
773 uint64 t1 = GetTimeNow();
774
775 if (!t0 || !t1) { unittest::fail("PsyTime test: GetTimeNow returned 0\n"); return false; }
776 if (t1 <= t0) { unittest::fail("PsyTime test: time did not advance across Sleep\n"); return false; }
777
778 struct PsyDateAndTime d0 = GetDateAndTime(t0);
779 struct PsyDateAndTime d1 = GetDateAndTime(t1);
780 struct PsyDateAndTime difTime = GetTimeDifference(t1, t0);
781
782 unittest::detail("Time0: %02u:%02u:%02u.%06u (%d + %d)", d0.hour, d0.min, d0.sec, d0.usec, d0.tzsec, d0.dstsec);
783 unittest::detail("Time1: %02u:%02u:%02u.%06u (%d + %d)", d1.hour, d1.min, d1.sec, d1.usec, d1.tzsec, d1.dstsec);
784 if (difTime.negative)
785 unittest::detail("Dif: -%02u:%02u:%02u.%06u", difTime.hour, difTime.min, difTime.sec, difTime.usec);
786 else
787 unittest::detail("Dif: +%02u:%02u:%02u.%06u", difTime.hour, difTime.min, difTime.sec, difTime.usec);
788
789 // 2. Repeated sleep timing accuracy
790 unittest::progress(30, "repeated sleep timing");
791
792 int iterations = 100;
793 uint64 loopStart = GetTimeNow();
794 for (int n=0; n<iterations; n++) {
795 t0 = GetTimeNow();
796 utils::Sleep(10);
797 t1 = GetTimeNow();
798 dif = t1 - t0;
799 if ( (dif < 8000) || (dif > 30000) ) {
800 difTime = GetTimeDifference(t1, t0);
801 if (difTime.negative)
802 unittest::detail("Dif [%d]: -%02u:%02u:%02u.%06u (%llu - %llu = %lld)", n, difTime.hour, difTime.min, difTime.sec, difTime.usec, t1, t0, dif);
803 else
804 unittest::detail("Dif [%d]: +%02u:%02u:%02u.%06u (%llu - %llu = %lld)", n, difTime.hour, difTime.min, difTime.sec, difTime.usec, t1, t0, dif);
805 unittest::fail("PsyTime test: sleep timing out of range at iteration %d (dif=%lld us)\n", n, dif);
806 return false;
807 }
808 unittest::progress(30 + (n * 60) / iterations, "repeated sleep timing");
809 }
810 double totalUs = (double)(GetTimeNow() - loopStart);
811 unittest::metric("avg_sleep10_latency", totalUs / iterations, "us", false);
812
813 unittest::progress(100, "done");
814 return true;
815}
816
817
820 "Time now, date breakdown, and sleep timing accuracy", "core");
821}
822
823
824} // namespace cmlabs
CMSDK time: µs-resolution 64-bit timestamps and the Time Mapping Constant (TMC).
Small, dependency-free unit test harness used by all CMSDK object tests.
Cross-platform utility toolbox for CMSDK: threading, synchronization, shared memory,...
#define stricmp
Definition Utils.h:132
static UnitTestRunner & instance()
Access the singleton (created on first use).
void registerTest(const char *name, UnitTestFunc func, const char *description="", const char *category="", bool inDefaultRun=true)
Register a test with the runner.
static uint64 CurrentTMC
Current Time Mapping Constant (µs offset from monotonic clock to absolute PsyTime).
Definition PsyTime.h:223
std::string PrintTimeString(uint64 t, bool local=true, bool us=true, bool ms=true)
Definition PsyTime.cpp:676
#define TMC_SYNC_INTERVAL
Re-sync interval in µs (1 minute).
Definition PsyTime.h:219
#define SECS_PER_HOUR
Seconds per hour.
Definition PsyTime.h:91
char * PrintDateSortable(uint64 t, bool local=true)
Date only, sortable (e.g.
Definition PsyTime.cpp:579
std::string PrintDateStringSortableDelimiter(uint64 t, const char *del, bool local=true)
Definition PsyTime.cpp:712
char * PrintTime(uint64 t, bool local=true, bool us=true, bool ms=true)
Full date + time-of-day.
Definition PsyTime.cpp:527
std::string PrintTimeSortableMillisecString(uint64 t, bool local=true)
Sortable date+time with milliseconds.
Definition PsyTime.cpp:740
char * PrintTimeSortableMicrosec(uint64 t, bool local)
Sortable date+time with microseconds.
Definition PsyTime.cpp:572
uint64 GetTimeFromPsyDateAndTime(struct PsyDateAndTime &tad)
Convert a broken-down PsyDateAndTime back into a µs timestamp.
Definition PsyTime.cpp:376
uint64 FTime2PsyTime(uint64 t)
Convert an ftime-style value (ms since Unix epoch) to a PsyTime µs timestamp.
Definition PsyTime.cpp:758
#define USEC_YEAR_0_TO_1970
Microseconds between year 0000 and the Unix epoch (1970-01-01).
Definition PsyTime.h:84
static uint8 CurrentTMCMode
Current TMC role: TMC_MASTER or TMC_SLAVE.
Definition PsyTime.h:225
char * PrintTimeDif(uint64 t, bool us=true, bool ms=true)
Format a µs duration with scale-adaptive units: "250us", "1.500ms", "2.750s", "2m 3s",...
Definition PsyTime.cpp:601
uint64 GetTimeNow()
Return the current absolute time (µs since year 0) according to the TMC.
Definition PsyTime.cpp:69
#define SECS_PER_MIN
Seconds per minute.
Definition PsyTime.h:92
std::string PrintDateString(uint64 t, bool local=true)
Definition PsyTime.cpp:694
uint32 GetTimeOffsetGMT()
Definition PsyTime.cpp:397
static char PsyMonthsFull[][10]
Full month names indexed 1..12; index 0 is "None".
Definition PsyTime.h:140
char * PrintTimeSortableMillisec(uint64 t, bool local)
Sortable date+time with milliseconds.
Definition PsyTime.cpp:565
static char PsyDays[][5]
Abbreviated weekday names indexed 1..7 (Mon..Sun); index 0 is "None".
Definition PsyTime.h:134
char * PrintTimeOnly(uint64 t, bool local=true, bool us=true, bool ms=true)
Time-of-day only.
Definition PsyTime.cpp:539
static int64 NetTimeAdjust
Signed network time adjustment (µs) applied when slaved to a remote master.
Definition PsyTime.h:229
#define SECS_PER_MONTH
Average seconds per Gregorian month (365.2425/12 days).
Definition PsyTime.h:88
bool SetCurrentTimeSyncData(uint64 tmc, int64 netTimeAdjust)
Install externally computed sync data (e.g.
Definition PsyTime.cpp:140
bool PsyTime_UnitTest()
Run the PsyTime unit tests.
Definition PsyTime.cpp:764
uint32 GetHTTPTime(uint64 time, char *buffer, uint32 size)
Format a timestamp as an HTTP-date (RFC 7231) string, e.g.
Definition PsyTime.cpp:518
std::string PrintTimeSortableString(uint64 t, bool local=true)
Sortable date+time, second resolution.
Definition PsyTime.cpp:731
uint64 SyncToHardwareClock()
Recompute the Time Mapping Constant by anchoring the monotonic clock to the wall clock.
Definition PsyTime.cpp:168
std::string PrintTimeNowString(bool local=true, bool us=true, bool ms=true)
Format GetTimeNow().
Definition PsyTime.cpp:672
std::string PrintTimeDifString(uint64 t, bool us=true, bool ms=true)
Definition PsyTime.cpp:722
char * PrintDateSortableDelimiter(uint64 t, const char *del, bool local=true)
Sortable date with custom field delimiter.
Definition PsyTime.cpp:586
bool SetCurrentNetSyncDif(int64 netTimeDif)
Set the network time difference for a slave node so its GetTimeNow() matches the master.
Definition PsyTime.cpp:135
uint64 GetTimeFromString(const char *str)
Parse a textual date/time into a PsyTime timestamp.
Definition PsyTime.cpp:438
std::string PrintTimeOnlyString(uint64 t, bool local=true, bool us=true, bool ms=true)
Definition PsyTime.cpp:685
#define TMC_SLAVE
This node slaves its clock to a remote master.
Definition PsyTime.h:218
int32 GetTimeAgeMS(uint64 t)
Age of a timestamp relative to now, in milliseconds.
Definition PsyTime.cpp:35
#define TMC_MASTER
This node is the time master; others sync to it.
Definition PsyTime.h:217
struct PsyDateAndTime GetTimeDifference(uint64 t1, uint64 t2)
Express the difference t1 - t2 as calendar fields (negative flag set when t2 > t1).
Definition PsyTime.cpp:393
int64 GetTimeAge(uint64 t)
Age of a timestamp relative to now.
Definition PsyTime.cpp:25
static char PsyMonths[][5]
Abbreviated month names indexed 1..12 (Jan..Dec); index 0 is "None".
Definition PsyTime.h:138
struct PsyDateAndTime GetDateAndTimeUTC(uint64 t)
Break a timestamp into calendar fields in UTC.
Definition PsyTime.cpp:312
static uint64 LastTMCSync
PsyTime timestamp of the last hardware-clock sync.
Definition PsyTime.h:227
std::string PrintTimeSortableMicrosecString(uint64 t, bool local=true)
Sortable date+time with microseconds.
Definition PsyTime.cpp:749
struct PsyDateAndTime GetDateAndTime(uint64 t, bool local=true)
Break a timestamp into calendar fields.
Definition PsyTime.cpp:324
#define SECS_PER_DAY
Seconds per day.
Definition PsyTime.h:90
#define SECS_PER_YEAR
Seconds in a (non-leap) 365-day year.
Definition PsyTime.h:86
std::string PrintDateStringSortable(uint64 t, bool local=true)
Definition PsyTime.cpp:703
char * PrintDate(uint64 t, bool local=true)
Date only, human-readable.
Definition PsyTime.cpp:551
bool GetCurrentTimeSyncData(uint64 &tmc, int64 &netTimeAdjust)
Read the current sync data.
Definition PsyTime.cpp:147
char * PrintTimeSortable(uint64 t, bool local)
Sortable date+time, second resolution.
Definition PsyTime.cpp:558
uint64 EstNextTMCWrap()
Estimate when the underlying hardware counter will next wrap around.
Definition PsyTime.cpp:289
bool Sleep(uint32 ms)
Suspend the calling thread.
Definition Utils.cpp:2802
uint32 strcpyavail(char *dst, const char *src, uint32 maxlen, bool copyAvailable)
Bounded strcpy that always NUL-terminates.
Definition Utils.cpp:6056
void fail(const char *fmt,...)
Set an explanatory reason shown on the FAIL line.
void metric(const char *name, double value, const char *unit="", bool higherIsBetter=true)
Record a performance metric.
void detail(const char *fmt,...)
Verbose-only indented diagnostic line (shown only when verbose=1).
void progress(int percent, const char *action)
Report progress with a short description of the current action.
void Register_PsyTime_Tests()
Definition PsyTime.cpp:818
A PsyTime timestamp or time difference broken down into calendar fields.
Definition PsyTime.h:150