

//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
//_/_/
//_/_/  This file is part of CMSDK version 2.1.1.260726 (26 July 2026)
//_/_/  
//_/_/  developed by
//_/_/  
//_/_/  Communicative Machines Ltd. 
//_/_/    http://cmlabs.com/
//_/_/  Copyright (c) 2026
//_/_/  
//_/_/  in collaboration with 
//_/_/  
//_/_/  Icelandic Institute for Intelligent Machines ses
//_/_/    Menntavegur 1
//_/_/    101 Reykjavik
//_/_/    ICELAND
//_/_/    http://www.iiim.is/
//_/_/
//_/_/  and is released under the following
//_/_/  
//_/_/  EXTENDED BSD License
//_/_/
//_/_/  Redistribution and use in source and binary forms, with or without
//_/_/  modification, is permitted provided that the following conditions 
//_/_/  are met:
//_/_/
//_/_/  - Redistributions of source code must retain the above copyright 
//_/_/    and collaboration notice, this list of conditions and the following 
//_/_/    disclaimer.
//_/_/
//_/_/  - Redistributions in binary form must reproduce the above copyright 
//_/_/    notice, this list of conditions and the following disclaimer in the 
//_/_/    documentation and/or other materials provided with the distribution.
//_/_/
//_/_/  - Neither the name of its copyright holders nor the names of its 
//_/_/    contributors may be used to endorse or promote products derived from 
//_/_/    this software without specific prior written permission.
//_/_/
//_/_/  - CADIA Clause: The license granted in and to the software under this 
//_/_/    agreement is a limited-use license. The software may not be used in 
//_/_/    furtherance of: (i) intentionally causing bodily injury or severe 
//_/_/    emotional harm to any person; (ii) invading the personal privacy or 
//_/_/    violating the human rights of any person; or (iii) committing or 
//_/_/    preparing for any act of war. 
//_/_/
//_/_/  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
//_/_/  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
//_/_/  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 
//_/_/  PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER 
//_/_/  OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
//_/_/  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
//_/_/  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
//_/_/  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
//_/_/  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
//_/_/  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
//_/_/  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//_/_/
//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
/*! @file PsyTime.cpp
	@brief Implementation of the CMSDK time functions (see PsyTime.h).

	Platform notes: the monotonic time source is QueryPerformanceCounter/
	QueryPerformanceFrequency on Windows (@c _WIN32) and clock_gettime(CLOCK_MONOTONIC)
	on POSIX (Linux/macOS); SyncToHardwareClock() anchors it to the wall clock
	(GetSystemTime / gettimeofday) by waiting for a tick edge, producing the Time
	Mapping Constant used by GetTimeNow().
*/
#ifdef _WIN32
	// To avoid complaints about sscanf
	#define _CRT_SECURE_NO_WARNINGS
#endif

#include "PsyTime.h"
#include "Utils.h"
#include "UnitTestFramework.h"

namespace cmlabs {

// Age = now - t, computed with an explicit branch because both operands are
// unsigned: (now - t) would underflow to a huge uint64 when t is in the
// future, so the future case is computed as -(t - now) instead. t == 0 is the
// "no timestamp" sentinel and yields 0 rather than a ~2 million year age.
int64 GetTimeAge(uint64 t) {
	if (!t)
		return 0;
	uint64 now = GetTimeNow();
	if (now >= t)
		return (int64)(now - t);
	else
		return (int64)((t - now) * -1);
}

int32 GetTimeAgeMS(uint64 t) {
	if (!t)
		return 0;
	uint64 now = GetTimeNow();
	if (now >= t)
		return (int32)((now - t) / 1000);
	else
		return (int32)((t - now) * -1)/1000;
}

// The µs-resolution "what time is it" call, safe from any thread.
//
// Algorithm (identical on all platforms):
//   1. Read the monotonic hardware counter and convert it to µs since the
//      counter's zero point ("lastWrapuSec").
//   2. Lazily (first call) or periodically (masters only, every
//      TMC_SYNC_INTERVAL of counter time) re-anchor the counter to the wall
//      clock via SyncToHardwareClock(), producing CurrentTMC = the absolute
//      PsyTime (µs since year 0) at which the counter read 0.
//   3. now = CurrentTMC + lastWrapuSec + NetTimeAdjust.
//
// Monotonic vs wallclock: between re-syncs the result is strictly monotonic
// because it follows the hardware counter, not the (NTP-steppable) wall
// clock; a master's re-sync may introduce a small step. TMC_SLAVE nodes never
// self-sync here — they receive TMC/NetTimeAdjust from the time master via
// SetCurrentTimeSyncData()/SetCurrentNetSyncDif(), keeping one shared
// timeline across machines.
//
// Platform sources: WINDOWS uses QueryPerformanceCounter/Frequency (the
// <0xFFFFFFFFFF branch keeps the tick→µs scaling in exact 64-bit integer
// math while the product still fits; larger counts fall back to double to
// avoid overflow, trading a little precision). LINUX/OSX use
// clock_gettime(CLOCK_MONOTONIC), which is already ns-resolution and cannot
// overflow in the µs conversion. Returns 0 only if the platform clock fails.
uint64 GetTimeNow() {
//	uint64 tmc = MemoryManager::GetTMC();
	//uint64 tmc = CurrentTMC;
	//uint64 lsa = NetTimeAdjust;
//	if (tmc == 0)
//		return 0;

	uint64 time = 0;
	#if defined	WINDOWS
		LARGE_INTEGER perfCount, perfFreq;
		if (QueryPerformanceCounter(&perfCount) != 0) {
			QueryPerformanceFrequency(&perfFreq);
			uint64 lastWrapuSec;
			// To avoid rounding errors...
		//	lastWrapuSec = perfCount.QuadPart * (1000000.0 / perfFreq.QuadPart);
		//	lastWrapuSec2 = (perfCount.QuadPart * 1000000) / perfFreq.QuadPart;
		//	if (lastWrapuSec != lastWrapuSec2) {
		//		lastWrapuSec = lastWrapuSec;
		//	}
			if (perfCount.QuadPart < 0xFFFFFFFFFFL)
				lastWrapuSec = (perfCount.QuadPart * 1000000) / perfFreq.QuadPart;
			else
				lastWrapuSec = (uint64)(perfCount.QuadPart * (1000000.0 / perfFreq.QuadPart));
				//lastWrapuSec = (perfCount.QuadPart / perfFreq.QuadPart) * 1000000;

			if (!CurrentTMC || ((CurrentTMCMode == TMC_MASTER) && (lastWrapuSec - LastTMCSync > TMC_SYNC_INTERVAL))) {
				CurrentTMC = SyncToHardwareClock();
				LastTMCSync = lastWrapuSec;
			}
			// Time now is usec at last wrap + usec since last wrap
			// We also need to add the Local Sync Adjustment, if syncing with other computers
			//uint64 now = tmc + lastWrapuSec + lsa;
			uint64 now = CurrentTMC + lastWrapuSec + NetTimeAdjust;

			//uint64 secs = lastWrapuSec / 1000000;
			//uint64 days = secs / (60*60*24);
			//uint64 years = days / 365;

			//uint64 nsecs = now / 1000000;
			//uint64 ndays = nsecs / (60*60*24);
			//uint64 nyears = ndays / 365;
			//int t = 0;
			return now;
		}
		else
			return 0;
		
	#elif defined LINUX || defined OSX
		struct timespec t;
		if (clock_gettime(CLOCK_MONOTONIC, &t) == 0) {
			uint64 lastWrapuSec = (uint64)(t.tv_sec) * 1000000 + (t.tv_nsec / 1000);

			if (!CurrentTMC || ((CurrentTMCMode == TMC_MASTER) && (lastWrapuSec - LastTMCSync > TMC_SYNC_INTERVAL))) {
				CurrentTMC = SyncToHardwareClock();
				LastTMCSync = lastWrapuSec;
			}

			uint64 now = CurrentTMC + lastWrapuSec + NetTimeAdjust;
			return now;
		}
		else
			return 0;
	#endif
	return 0;
}

bool SetCurrentNetSyncDif(int64 netTimeDif) {
	NetTimeAdjust += netTimeDif;
	return true;
}

bool SetCurrentTimeSyncData(uint64 tmc, int64 netTimeAdjust) {
	CurrentTMC = tmc;
	CurrentTMCMode = TMC_SLAVE;
	NetTimeAdjust = netTimeAdjust;
	return true;
}

bool GetCurrentTimeSyncData(uint64& tmc, int64& netTimeAdjust) {
	tmc = CurrentTMC;
	netTimeAdjust = NetTimeAdjust;
	return true;
}

// Calculate the current Time Mapping Constant: the absolute PsyTime (µs since
// year 0) at which the monotonic counter read 0, i.e. TMC = wallclock_now -
// monotonic_now (+ the year-0..1970 offset, since the wall clock is Unix-based).
//
// Accuracy trick: the wall clock is only readable at ~ms (ftime) or µs
// (gettimeofday) granularity, so simply subtracting two reads could be off by
// up to one tick. Instead we busy-wait for the wall clock to tick over and
// sample the monotonic counter at that exact edge, so both clocks are read at
// a known instant. On Linux/macOS the loop is bounded (~1 s, maxWait) because
// gettimeofday may have coarse or frozen resolution in some environments
// (and macOS ftime() is deprecated); on timeout we fall back to an unaligned
// read — slightly less accurate, never hung.
//
// Called lazily/periodically by GetTimeNow() on TMC_MASTER nodes; may block
// for up to one wall-clock tick, so call sparingly.
uint64 SyncToHardwareClock() {

	#if defined	WINDOWS
		// On Windows TMC contains the number of microseconds since year 0
		// when the PerfCount was last 0
		LARGE_INTEGER perfCount, perfFreq;
		if (QueryPerformanceCounter(&perfCount) != 0) {
			QueryPerformanceFrequency(&perfFreq);

			// We have performance counters here, good...
			// Now do a busy wait to find the perfCount exactly when the ftime clock ticks
			struct timeb timebuf1, timebuf2;
			ftime(&timebuf1);
			while (true) {
				ftime(&timebuf2);
				if (timebuf1.millitm != timebuf2.millitm) {
					QueryPerformanceCounter(&perfCount);
					break;
				}
			}
			// we now know the perfcount exactly when the clock in timebuf2 ticked
			uint64 lastWrapuSec;
			if (perfCount.QuadPart < 0xFFFFFFFFFFL)
				lastWrapuSec = (perfCount.QuadPart * 1000000) / perfFreq.QuadPart;
			else
				lastWrapuSec = (uint64)(perfCount.QuadPart * (1000000.0 / perfFreq.QuadPart));
				//lastWrapuSec = (perfCount.QuadPart / perfFreq.QuadPart) * 1000000;

			// usec since midnight (00:00:00), January 1, 1970, coordinated universal time (UTC)
			uint64 nowuSec = (((uint64)timebuf2.time) * 1000000) + ((uint64)timebuf2.millitm * 1000);
			// usec since 1970 - lastwrap + usec year 0-1970 = usec since year 0 at last wrap
			uint64 tmc = nowuSec - lastWrapuSec + USEC_YEAR_0_TO_1970;
			//uint64 usecs = lastWrapuSec;
			//uint64 secs = usecs / 1000000;
			//uint64 days = secs / (60*60*24);
			//uint64 years = days / 365;
			//int t = 0;
			return (CurrentTMC = tmc);
		}
		else
			return 0;
	#elif defined LINUX
		// On Linux TMC contains the number of microseconds since year 0
		// when CLOCK_MONOTONIC was last 0
		// struct timespec t;
		// if (clock_gettime(CLOCK_MONOTONIC, &t) == 0) {
		// 	struct timeb timebuf1, timebuf2;
		// 	ftime(&timebuf1);
		// 	while (true) {
		// 		ftime(&timebuf2);
		// 		if (timebuf1.millitm != timebuf2.millitm) {
		// 			clock_gettime(CLOCK_MONOTONIC, &t);
		// 			break;
		// 		}
		// 	}
		// 	uint64 lastWrapuSec = (uint64)(t.tv_sec) * 1000000 + (t.tv_nsec / 1000);

		// 	uint64 nowuSec = ((uint64)timebuf2.time) * 1000000 + (uint64)(timebuf2.millitm) * 1000;
		// 	uint64 tmc = nowuSec - lastWrapuSec + USEC_YEAR_0_TO_1970;
		// 	return (CurrentTMC = tmc);
		// }
		// else
		// 	return 0;
		struct timespec t;
		if (clock_gettime(CLOCK_MONOTONIC, &t) == 0) {
			struct timeval tv1, tv2;
			gettimeofday(&tv1, NULL);
			int waitCount = 0;
			const int maxWait = 1000000;  // ~1s safety limit
			while (waitCount < maxWait) {
				gettimeofday(&tv2, NULL);
				if (tv1.tv_sec != tv2.tv_sec || tv1.tv_usec != tv2.tv_usec) {
					clock_gettime(CLOCK_MONOTONIC, &t);
					break;
				}
				waitCount++;
			}
			if (waitCount >= maxWait)
				clock_gettime(CLOCK_MONOTONIC, &t);  // use current monotonic time
			uint64 lastWrapuSec = (uint64)(t.tv_sec) * 1000000 + (t.tv_nsec / 1000);
			uint64 nowuSec = (uint64)tv2.tv_sec * 1000000 + (uint64)tv2.tv_usec;
			uint64 tmc = nowuSec - lastWrapuSec + USEC_YEAR_0_TO_1970;
			return (CurrentTMC = tmc);
		}
		else
			return 0;
	#elif defined OSX
		// On macOS ftime() is deprecated and millitm may not change (infinite loop).
		// Use gettimeofday() for wall clock; wait for a tick with a safety timeout.
		struct timespec t;
		if (clock_gettime(CLOCK_MONOTONIC, &t) == 0) {
			struct timeval tv1, tv2;
			gettimeofday(&tv1, NULL);
			int waitCount = 0;
			const int maxWait = 1000000;  // ~1s safety limit
			while (waitCount < maxWait) {
				gettimeofday(&tv2, NULL);
				if (tv1.tv_sec != tv2.tv_sec || tv1.tv_usec != tv2.tv_usec) {
					clock_gettime(CLOCK_MONOTONIC, &t);
					break;
				}
				waitCount++;
			}
			if (waitCount >= maxWait)
				clock_gettime(CLOCK_MONOTONIC, &t);  // use current monotonic time
			uint64 lastWrapuSec = (uint64)(t.tv_sec) * 1000000 + (t.tv_nsec / 1000);
			uint64 nowuSec = (uint64)tv2.tv_sec * 1000000 + (uint64)tv2.tv_usec;
			uint64 tmc = nowuSec - lastWrapuSec + USEC_YEAR_0_TO_1970;
			return (CurrentTMC = tmc);
		}
		else
			return 0;
	#endif
}

// Estimate the next wrap-around of the underlying hardware counter (NOT of
// PsyTime itself — uint64 µs-since-year-0 lasts ~584,000 years). On Windows
// this is the µs until QueryPerformanceCounter reaches 2^64-1 (same
// integer/double scaling split as GetTimeNow()); on Linux/macOS
// CLOCK_MONOTONIC counts from boot in a 64-bit timespec and effectively never
// wraps, so "never" is reported as 0xFFFFFFFFFFFFFFFF.
uint64 EstNextTMCWrap() {
	#if defined	WINDOWS
		LARGE_INTEGER perfCount, perfFreq;
		if (QueryPerformanceCounter(&perfCount) != 0) {
			QueryPerformanceFrequency(&perfFreq);

			uint64 nextWrapTicks = 0xFFFFFFFFFFFFFFFFL - perfCount.QuadPart;
			uint64 nextWrapuSec;

			if (nextWrapTicks < 0xFFFFFFFFFFL)
				nextWrapuSec = (nextWrapTicks  * 1000000) / perfFreq.QuadPart;
			else
				nextWrapuSec = (uint64)(nextWrapTicks * (1000000.0 / perfFreq.QuadPart));
			return nextWrapuSec;
		}
		else
			return 0;
		
	#elif defined LINUX || defined OSX
		return 0xFFFFFFFFFFFFFFFFL;
	#endif
}

struct PsyDateAndTime GetDateAndTimeUTC(uint64 t) {
	return GetDateAndTime(t, false);
}

// Break a PsyTime timestamp into calendar fields. The conversion routes
// through the C library: whole seconds are converted to time_t (Unix epoch,
// hence the USEC_YEAR_0_TO_1970 subtraction — timestamps before 1970 are not
// representable and return a zeroed struct) and handed to the thread-safe
// localtime_s/gmtime_s (Windows) or localtime_r/gmtime_r (POSIX), so the OS
// timezone database supplies DST and zone name. Sub-second fields (msec/usec)
// are filled from the µs remainder, and tm's 0-based month/weekday/yearday
// are shifted to PsyDateAndTime's 1-based conventions.
struct PsyDateAndTime GetDateAndTime(uint64 t, bool local) {
	struct tm time;
	struct PsyDateAndTime tad;
	memset(&tad, 0, sizeof(struct PsyDateAndTime));
	if (t < USEC_YEAR_0_TO_1970)
		return tad;
	// First create the time_t from t
	uint64 usSince1970 = (t - USEC_YEAR_0_TO_1970);
	time_t timet = (time_t)(usSince1970 / 1000000);
	#if defined	WINDOWS
		if (local) {
			if (localtime_s(&time, &timet) != 0)
				return tad;
		}
		else {
			if (gmtime_s(&time, &timet) != 0)
				return tad;
		}

		_get_dstbias((long*)&tad.dstsec); // tad.dstsec = _dstbias*(-1);
		_get_timezone((long*)&tad.tzsec); // tad.tzsec = _timezone;
		size_t l;
		_get_tzname(&l, tad.zonename, 64, 1);
		// utils::strcpyavail(tad.tzname, _tzname[0], 64, true);
	#elif defined LINUX || defined OSX
		if (local)
			localtime_r(&timet, &time);
		else
			gmtime_r(&timet, &time);
		tad.dstsec = daylight*SECS_PER_HOUR;
		tad.tzsec = timezone;
		utils::strcpyavail(tad.zonename, tzname[0], 64, true);
	#endif

	tad.usec = usSince1970 % 1000000;
	tad.msec = tad.usec / 1000;
	tad.sec = time.tm_sec;
	tad.min = time.tm_min;
	tad.hour = time.tm_hour;
	tad.day = time.tm_mday;
	tad.mon = time.tm_mon + 1;
	tad.year = time.tm_year + 1900;
	tad.wday = time.tm_wday;
	tad.yday = time.tm_yday + 1;
	tad.dst = (time.tm_isdst > 0);
	return tad;
}

// Inverse of GetDateAndTime() via mktime(): interprets the fields as LOCAL
// time (tm_isdst forced to 0), converts to time_t seconds and re-adds the
// year-0 offset. Sub-second fields (msec/usec) are ignored, so the result has
// whole-second resolution; returns 0 when mktime() cannot represent the date.
uint64 GetTimeFromPsyDateAndTime(struct PsyDateAndTime &tad) {
	struct tm time;
	time.tm_sec = tad.sec;
	time.tm_min = tad.min;
	time.tm_hour = tad.hour;
	time.tm_mday = tad.day;
	time.tm_mon = tad.mon - 1;
	time.tm_year = tad.year - 1900;
	time.tm_isdst = 0;

	time_t t = mktime(&time);
	if (t == (time_t)-1)
		return 0;
	
	return (((uint64)t) * 1000000) + USEC_YEAR_0_TO_1970;
}

struct PsyDateAndTime GetTimeDifference(uint64 t2, uint64 t1) {
	return GetTimeDifference(t2-t1);
}

uint32 GetTimeOffsetGMT() {
	// ##################
	return 0;
}

// Express a signed µs difference as duration fields. Unlike GetDateAndTime()
// this does NOT use the calendar: it divides by the fixed constants
// SECS_PER_YEAR (365 days) and SECS_PER_MONTH (average Gregorian month), so
// year/mon/day are approximations suitable for display ("1 year 2 months"),
// not exact calendar arithmetic. yday carries the total whole-day count and
// the negative flag preserves the sign (all field values are magnitudes).
struct PsyDateAndTime GetTimeDifference(int64 dif) {
	struct PsyDateAndTime tad;
	memset(&tad, 0, sizeof(struct PsyDateAndTime));
	int64 t = dif;
	if (dif < 0) {
		t = -1*dif;
		tad.negative = true;
	}
	tad.usec = t % 1000000;
	tad.msec = tad.usec / 1000;
	uint64 left = t / 1000000;
	tad.yday = (uint16) (left / SECS_PER_DAY);
	tad.year = (uint16) (left / SECS_PER_YEAR);
	left = left % SECS_PER_YEAR;
	tad.mon = (uint16) (left / SECS_PER_MONTH);
	left = left % SECS_PER_MONTH;
	tad.day = (uint16) (left / SECS_PER_DAY);
	left = left % SECS_PER_DAY;
	tad.hour = (uint16) (left / SECS_PER_HOUR);
	left = left % SECS_PER_HOUR;
	tad.min = (uint16) (left / SECS_PER_MIN);
	tad.sec = left % SECS_PER_MIN;
	return tad;
}

// Parse a textual date/time by trying a fixed list of sscanf patterns in
// order: RFC 822/1123, RFC 850, asctime(), then the PrintTimeSortable*
// output forms (with µs, ms, s and legacy 2-digit-year variants). Named
// month/day fields are resolved via the PsyMonths table afterwards; sortable
// forms are always interpreted as GMT. Returns 0 when no pattern matches.
uint64 GetTimeFromString(const char* str) {
	char dayname[64];
	char monthname[64];
	char tzone[64];
	int r;
	char* ch;
	uint32 day, hour, min, sec, year, mon = 0, usec = 0, msec = 0;
	//Sun, 06 Nov 1994 08:49:37 GMT  ; RFC 822, updated by RFC 1123
	if ( (r = sscanf(str, "%63s %u %63s %u %u:%u:%u %63s",
			dayname, &day, monthname, &year, &hour, &min, &sec, tzone)) == 8) {
	}
	//Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036
	else if ( (r = sscanf(str, "%63s %u-%63s-%u %u:%u:%u %63s",
			dayname, &day, monthname, &year, &hour, &min, &sec, tzone)) == 8) {
	}
	//Sun Nov  6 08:49:37 1994       ; ANSI C's asctime() format
	else if ( (r = sscanf(str, "%63s %63s %u %u:%u:%u %u",
			dayname, monthname, &day, &hour, &min, &sec, &year)) == 7) {
		utils::strcpyavail(tzone, "GMT", 64, true);
	}
	// Sortable format microsecs
	else if ( (r = sscanf(str, "%04u%02u%02u-%02u%02u%02u.%03u.%03u",
			&year, &mon, &day, &hour, &min, &sec, &msec, &usec)) == 8) {
		utils::strcpyavail(tzone, "GMT", 64, true);
	}
	// Sortable format millisecs
	else if ( (r = sscanf(str, "%04u%02u%02u-%02u%02u%02u.%03u",
			&year, &mon, &day, &hour, &min, &sec, &msec)) == 7) {
		utils::strcpyavail(tzone, "GMT", 64, true);
	}
	// Sortable format secs
	else if ( (r = sscanf(str, "%04u%02u%02u-%02u%02u%02u",
			&year, &mon, &day, &hour, &min, &sec)) == 6) {
		utils::strcpyavail(tzone, "GMT", 64, true);
	}
	// Sortable format
	else if ( (r = sscanf(str, "%02u%02u%02u%02u%02u%02u",
			&year, &mon, &day, &hour, &min, &sec)) == 6) {
		utils::strcpyavail(tzone, "GMT", 64, true);
	}
	else
		return 0;

	if ( (ch = strchr(dayname, ',')) != NULL)
		*ch = 0;

	if ( stricmp(tzone, "GMT") != 0 )
		return 0;

	if (year < 100)
		year += (year < 80) ? 2000 : 1900;

	struct PsyDateAndTime tad;
	memset(&tad, 0, sizeof(struct PsyDateAndTime));

	tad.usec = usec + (1000*msec);
	tad.msec = msec;
	tad.year = year;
	tad.day = day;
	tad.hour = hour;
	tad.min = min;
	tad.sec = sec;
	tad.mon = 0;

	if (mon)
		tad.mon = mon;
	else {
		for (uint16 n=0; n<12; n++) {
			if ( (stricmp(monthname, PsyMonths[n]) == 0) || (stricmp(monthname, PsyMonthsFull[n]) == 0) ) {
				tad.mon = n;
				break;
			}
		}
	}
	if (tad.mon == 0)
		return 0;

	return GetTimeFromPsyDateAndTime(tad);
}

uint32 GetHTTPTime(uint64 time, char* buffer, uint32 size) {
	if (!buffer || (size < 64))
		return 0;
	struct PsyDateAndTime ts = GetDateAndTime(time, false);
	snprintf(buffer, size, "%s, %u %s %u %02u:%02u:%02u GMT",
		PsyDays[ts.wday], ts.day, PsyMonths[ts.mon], ts.year, ts.hour, ts.min, ts.sec);
	return (uint32)strlen(buffer);
}

char* PrintTime(uint64 t, bool local, bool us, bool ms) {
	char* str = new char[1024];
	struct PsyDateAndTime time = GetDateAndTime(t, local);
	if (us)
		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);
	else if (ms)
		snprintf(str, 1024, "%02u/%02u/%04u %02u:%02u:%02u.%03u", time.day, time.mon, time.year, time.hour, time.min, time.sec, time.msec);
	else
		snprintf(str, 1024, "%02u/%02u/%04u %02u:%02u:%02u", time.day, time.mon, time.year, time.hour, time.min, time.sec);
	return str;
}

char* PrintTimeOnly(uint64 t, bool local, bool us, bool ms) {
	char* str = new char[1024];
	struct PsyDateAndTime time = GetDateAndTime(t, local);
	if (us)
		snprintf(str, 1024, "%02u:%02u:%02u.%03u.%03u", time.hour, time.min, time.sec, time.msec, time.usec % 1000);
	else if (ms)
		snprintf(str, 1024, "%02u:%02u:%02u.%03u", time.hour, time.min, time.sec, time.msec);
	else
		snprintf(str, 1024, "%02u:%02u:%02u", time.hour, time.min, time.sec);
	return str;
}

char* PrintDate(uint64 t, bool local) {
	char* str = new char[1024];
	struct PsyDateAndTime time = GetDateAndTime(t, local);
	snprintf(str, 1024, "%02u/%02u/%04u", time.day, time.mon, time.year);
	return str;
}

char* PrintTimeSortable(uint64 t, bool local) {
	char* str = new char[1024];
	struct PsyDateAndTime time = GetDateAndTime(t, local);
	snprintf(str, 1024, "%04u%02u%02u-%02u%02u%02u", time.year, time.mon, time.day, time.hour, time.min, time.sec);
	return str;
}

char* PrintTimeSortableMillisec(uint64 t, bool local) {
	char* str = new char[1024];
	struct PsyDateAndTime time = GetDateAndTime(t, local);
	snprintf(str, 1024, "%04u%02u%02u-%02u%02u%02u.%03u", time.year, time.mon, time.day, time.hour, time.min, time.sec, time.msec);
	return str;
}

char* PrintTimeSortableMicrosec(uint64 t, bool local) {
	char* str = new char[1024];
	struct PsyDateAndTime time = GetDateAndTime(t, local);
	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);
	return str;
}

char* PrintDateSortable(uint64 t, bool local) {
	char* str = new char[1024];
	struct PsyDateAndTime time = GetDateAndTime(t, local);
	snprintf(str, 1024, "%04u%02u%02u", time.year, time.mon, time.day);
	return str;
}

char* PrintDateSortableDelimiter(uint64 t, const char* del, bool local) {
	char* str = new char[1024];
	struct PsyDateAndTime time = GetDateAndTime(t, local);
	snprintf(str, 1024, "%04u%s%02u%s%02u", time.year, del, time.mon, del, time.day);
	return str;
}

// Scale-adaptive duration formatter. Picks the unit from the magnitude —
// "250us" / "1.500ms" / "2.750s" / "2m 3s" / "01:02:03" / "3 days 01:02:03" /
// "1 year(s) ..." — with the us/ms flags controlling how much sub-second
// detail is kept at each scale. Above one minute the split into fields is
// delegated to GetTimeDifference(). Note: t is uint64, so the "-" sign branch
// is effectively dead — negative durations must be handled by the caller
// (e.g. via GetTimeAge()) before formatting. Returns a new char[1024] the
// caller must delete []; prefer PrintTimeDifString().
char* PrintTimeDif(uint64 t, bool us, bool ms) {
	char* str = new char[1024];
	char* sign = new char[2];
	char* subsecs = NULL;
	utils::strcpyavail(sign, (t<0) ? "-" : "", 2, true);
	if (t == 0) {
		if (us)
			snprintf(str, 1024, "0us");
		else if (ms)
			snprintf(str, 1024, "0ms");
		else
			snprintf(str, 1024, "0s");
	}
	else {
		// uint64 absT = abs64(t);
		uint64 absT = t;
		if (absT < 1000) {
			if (us)
				snprintf(str, 1024, "%s%uus", sign, (uint32)absT);
			else if (ms)
				snprintf(str, 1024, "%s%ums", sign, (uint32)round((double)absT / 1000.0));
			else
				snprintf(str, 1024, "%s%us", sign, (uint32)round((double)absT / 1000000.0));
		}
		else if (absT < 1000000) {
			if (us)
				snprintf(str, 1024, "%s%.3fms", sign, absT / 1000.0);
			else if (ms)
				snprintf(str, 1024, "%s%ums", sign, (uint32)round(absT / 1000.0));
			else
				snprintf(str, 1024, "%s%us", sign, (uint32)round((double)absT/1000000.0));
		}
		else if (absT < 60000000) {
			if (us)
				snprintf(str, 1024, "%s%.6fs", sign, absT / 1000000.0);
			else if (ms)
				snprintf(str, 1024, "%s%.3fs", sign, absT / 1000000.0);
			else
				snprintf(str, 1024, "%s%us", sign, (uint32)round((double)absT / 1000000.0));
		}
		else {
			subsecs = new char[20];
			uint64 absSec = absT/1000000;
			uint32 roundMS;
			struct PsyDateAndTime difTime = GetTimeDifference(t);

			if (us && difTime.usec)
				snprintf(subsecs, 20, "%s%06uus", (absSec < SECS_PER_HOUR) ? " " : ".", difTime.usec);
			else if (ms && (roundMS = (uint32)round(difTime.usec / 1000.0)))
				snprintf(subsecs, 20, "%s%03ums", (absSec < SECS_PER_HOUR) ? " " : ".", roundMS);
			else
				subsecs[0] = 0;

			if (absSec < SECS_PER_HOUR)
				snprintf(str, 1024, "%s%um %us%s", sign, difTime.min, difTime.sec, subsecs);
			else if (absSec < SECS_PER_DAY)
				snprintf(str, 1024, "%s%02u:%02u:%02u%s", sign, difTime.hour, difTime.min, difTime.sec, subsecs);
			else if (difTime.yday == 1)
				snprintf(str, 1024, "%s%u day %02u:%02u:%02u%s", sign, difTime.yday, difTime.hour, difTime.min, difTime.sec, subsecs);
			else if (difTime.yday < 365)
				snprintf(str, 1024, "%s%u days %02u:%02u:%02u%s", sign, difTime.yday, difTime.hour, difTime.min, difTime.sec, subsecs);
			else
				snprintf(str, 1024, "%s%u year(s) %u month(s) %u day(s) %02u:%02u:%02u%s",
					sign, difTime.year, difTime.mon, difTime.day, difTime.hour, difTime.min, difTime.sec, subsecs);
			delete[] subsecs;
		}
	}
	delete[] sign;
	return str;
}

std::string PrintTimeNowString(bool local, bool us, bool ms) {
	return PrintTimeString(GetTimeNow(), local, us, ms);
}

std::string PrintTimeString(uint64 t, bool local, bool us, bool ms) {
	char* str = PrintTime(t, local, us, ms);
	if (!str)
		return "";
	std::string res = str;
	delete [] str;
	return res;
}

std::string PrintTimeOnlyString(uint64 t, bool local, bool us, bool ms) {
	char* str = PrintTimeOnly(t, local, us, ms);
	if (!str)
		return "";
	std::string res = str;
	delete[] str;
	return res;
}

std::string PrintDateString(uint64 t, bool local) {
	char* str = PrintDate(t, local);
	if (!str)
		return "";
	std::string res = str;
	delete [] str;
	return res;
}

std::string PrintDateStringSortable(uint64 t, bool local) {
	char* str = PrintDateSortable(t, local);
	if (!str)
		return "";
	std::string res = str;
	delete [] str;
	return res;
}

std::string PrintDateStringSortableDelimiter(uint64 t, const char* del, bool local) {
	char* str = PrintDateSortableDelimiter(t, del, local);
	if (!str)
		return "";
	std::string res = str;
	delete [] str;
	return res;
}


std::string PrintTimeDifString(uint64 t, bool us, bool ms) {
	char* str = PrintTimeDif(t, us, ms);
	if (!str)
		return "";
	std::string res = str;
	delete [] str;
	return res;
}

std::string PrintTimeSortableString(uint64 t, bool local) {
	char* str = PrintTimeSortable(t, local);
	if (!str)
		return "";
	std::string res = str;
	delete [] str;
	return res;
}

std::string PrintTimeSortableMillisecString(uint64 t, bool local) {
	char* str = PrintTimeSortableMillisec(t, local);
	if (!str)
		return "";
	std::string res = str;
	delete [] str;
	return res;
}

std::string PrintTimeSortableMicrosecString(uint64 t, bool local) {
	char* str = PrintTimeSortableMicrosec(t, local);
	if (!str)
		return "";
	std::string res = str;
	delete [] str;
	return res;
}

uint64 FTime2PsyTime(uint64 t) {
	return (t * 1000000) + USEC_YEAR_0_TO_1970;
}



bool PsyTime_UnitTest() {

	int64 dif;

	// 1. Basic now / date breakdown
	unittest::progress(10, "time now and date breakdown");

	uint64 t0 = GetTimeNow();
	utils::Sleep(10);
	uint64 t1 = GetTimeNow();

	if (!t0 || !t1) { unittest::fail("PsyTime test: GetTimeNow returned 0\n"); return false; }
	if (t1 <= t0) { unittest::fail("PsyTime test: time did not advance across Sleep\n"); return false; }

	struct PsyDateAndTime d0 = GetDateAndTime(t0);
	struct PsyDateAndTime d1 = GetDateAndTime(t1);
	struct PsyDateAndTime difTime = GetTimeDifference(t1, t0);

	unittest::detail("Time0: %02u:%02u:%02u.%06u (%d + %d)", d0.hour, d0.min, d0.sec, d0.usec, d0.tzsec, d0.dstsec);
	unittest::detail("Time1: %02u:%02u:%02u.%06u (%d + %d)", d1.hour, d1.min, d1.sec, d1.usec, d1.tzsec, d1.dstsec);
	if (difTime.negative)
		unittest::detail("Dif:   -%02u:%02u:%02u.%06u", difTime.hour, difTime.min, difTime.sec, difTime.usec);
	else
		unittest::detail("Dif:   +%02u:%02u:%02u.%06u", difTime.hour, difTime.min, difTime.sec, difTime.usec);

	// 2. Repeated sleep timing accuracy
	unittest::progress(30, "repeated sleep timing");

	int iterations = 100;
	uint64 loopStart = GetTimeNow();
	for (int n=0; n<iterations; n++) {
		t0 = GetTimeNow();
		utils::Sleep(10);
		t1 = GetTimeNow();
		dif = t1 - t0;
		if ( (dif < 8000) || (dif > 30000) ) {
			difTime = GetTimeDifference(t1, t0);
			if (difTime.negative)
				unittest::detail("Dif [%d]:   -%02u:%02u:%02u.%06u (%llu - %llu = %lld)", n, difTime.hour, difTime.min, difTime.sec, difTime.usec, t1, t0, dif);
			else
				unittest::detail("Dif [%d]:   +%02u:%02u:%02u.%06u (%llu - %llu = %lld)", n, difTime.hour, difTime.min, difTime.sec, difTime.usec, t1, t0, dif);
			unittest::fail("PsyTime test: sleep timing out of range at iteration %d (dif=%lld us)\n", n, dif);
			return false;
		}
		unittest::progress(30 + (n * 60) / iterations, "repeated sleep timing");
	}
	double totalUs = (double)(GetTimeNow() - loopStart);
	unittest::metric("avg_sleep10_latency", totalUs / iterations, "us", false);

	unittest::progress(100, "done");
	return true;
}


void Register_PsyTime_Tests() {
	UnitTestRunner::instance().registerTest("psytime", PsyTime_UnitTest,
		"Time now, date breakdown, and sleep timing accuracy", "core");
}


} // namespace cmlabs
