// SafeC Standard Library — time implementation
#pragma once
#include <std/stddef.h>
#include <std/time.h>
namespace std {
extern long long time(long long* t);
// struct tm is passed as its leading int fields (see time_from_tm_).
extern size_t strftime(char* s, size_t max, const char* format, const int* tm);
extern long long mktime(int* tm);
// gmtime_r/localtime_r/strptime are POSIX, not in any C standard --
// Windows/MSVC's UCRT doesn't export them (same class of link failure as
// drand48/clock_gettime elsewhere in this file). A real Windows port needs
// SYSTEMTIME/FileTimeToSystemTime plus its own timezone-database story,
// not a one-line substitute the way time_wall_ns/time_sleep_ns below got —
// out of scope here. Stubbed honestly instead of silently wrong: zeroed
// output / a real failure return, not fabricated calendar fields.
// struct tm begins with the 9 int fields TimeFields mirrors; platform
// extras (tm_gmtoff, tm_zone) fit in the 14-int buffers used here.
static struct TimeFields time_from_tm_(const &int tm) {
struct TimeFields f;
unsafe {
f.sec = tm[0]; f.min = tm[1]; f.hour = tm[2]; f.mday = tm[3]; f.mon = tm[4];
f.year = tm[5]; f.wday = tm[6]; f.yday = tm[7]; f.isdst = tm[8];
}
return f;
}
static void time_to_tm_(const &TimeFields f, &int tm) {
unsafe {
tm[0] = f.sec; tm[1] = f.min; tm[2] = f.hour; tm[3] = f.mday; tm[4] = f.mon;
tm[5] = f.year; tm[6] = f.wday; tm[7] = f.yday; tm[8] = f.isdst;
int i = 9;
while (i < 14) { tm[i] = 0; i = i + 1; }
}
}
#ifdef _WIN32
inline struct TimeFields time_gmtime(long long t) {
int tm_buf[14];
int i = 0;
while (i < 14) { tm_buf[i] = 0; i = i + 1; }
return time_from_tm_(&tm_buf[0]);
}
inline struct TimeFields time_localtime(long long t) {
return time_gmtime(t);
}
inline int time_parse(const &char s, const &char fmt, &TimeFields tm) {
return 0; // not implemented on Windows -- see comment above
}
#else
extern int* gmtime_r(const long long* t, int* tm);
extern int* localtime_r(const long long* t, int* tm);
extern char* strptime(const char* s, const char* format, int* tm);
inline struct TimeFields time_gmtime(long long t) {
int tm_buf[14]; // 56 bytes: covers struct tm on all platforms
unsafe { gmtime_r((const long long*)&t, &tm_buf[0]); }
return time_from_tm_(&tm_buf[0]);
}
inline struct TimeFields time_localtime(long long t) {
int tm_buf[14];
unsafe { localtime_r((const long long*)&t, &tm_buf[0]); }
return time_from_tm_(&tm_buf[0]);
}
inline int time_parse(const &char s, const &char fmt, &TimeFields tm) {
int tm_buf[14];
int i = 0;
while (i < 14) { tm_buf[i] = 0; i = i + 1; }
tm_buf[8] = -1;
char* end;
unsafe { end = strptime(s, fmt, &tm_buf[0]); }
if (end == (char*)0) return 0;
*tm = time_from_tm_(&tm_buf[0]);
return 1;
}
#endif
// struct tm has 9 int fields (at minimum); we use a 56-byte buffer to be safe
inline long long time_now() {
unsafe { return time((long long*)0); }
}
inline unsigned long time_format([]char buf, const &char fmt, const &TimeFields tm) {
if (buf.len == 0UL) { return 0UL; }
int tm_buf[14];
time_to_tm_(tm, &tm_buf[0]);
unsafe { return strftime((char*)buf.ptr, buf.len, fmt, (const int*)&tm_buf[0]); }
}
inline long long time_mktime(const &TimeFields tm) {
int tm_buf[14];
time_to_tm_(tm, &tm_buf[0]);
unsafe { return mktime(&tm_buf[0]); }
}
inline double time_diff(long long end, long long start) {
return (double)(end - start);
}
// clockid_t values passed to clock_gettime() are NOT standardized by
// POSIX -- only the *names* are; each OS assigns its own integers, and
// they genuinely differ. CLOCK_REALTIME is 0 on both of the two families
// below (coincidence, not a guarantee), but CLOCK_MONOTONIC and
// CLOCK_PROCESS_CPUTIME_ID are not:
// Darwin (macOS/iOS): REALTIME=0 MONOTONIC=6 PROCESS_CPUTIME_ID=12
// Linux (glibc/musl): REALTIME=0 MONOTONIC=1 PROCESS_CPUTIME_ID=2
// This file used to hardcode the Darwin values unconditionally -- on
// Linux, id 6 isn't an error, which is what made this bug so easy to
// miss: it's CLOCK_MONOTONIC_COARSE, a real, valid clock that just
// trades precision for a cheap VDSO-only read (no syscall trap) by
// updating on the kernel's timer-tick cadence instead of continuously.
// Confirmed as a real, not theoretical, bug: benchmarking a CPU ML
// workload on WSL2 (see benchmarks.md) came back with train/inference
// times landing on suspiciously exact millisecond boundaries
// (100.000ms, 324.000ms to 3 decimal places) -- consistent with a clock
// that only ticks every several milliseconds on WSL2's virtualized timer,
// not with the workload's real, sub-tick-granular duration. id 12 is
// similarly wrong on Linux (some unrelated clock, not
// CLOCK_PROCESS_CPUTIME_ID). time_wall_ns() (CLOCK_REALTIME=0) was never
// affected either way.
#ifdef __APPLE__
#define SAFEC_CLOCK_MONOTONIC_ 6
#define SAFEC_CLOCK_CPUTIME_ 12
#else
#define SAFEC_CLOCK_MONOTONIC_ 1
#define SAFEC_CLOCK_CPUTIME_ 2
#endif
// GetSystemTimePreciseAsFileTime (Windows 8+) writes a FILETIME -- two
// packed 32-bit words that are just a 64-bit little-endian count of
// 100ns ticks since 1601-01-01 UTC when read as one i64 on this ABI, the
// same "read a multi-field Win32 struct through a same-size scalar
// pointer" trick already used elsewhere in this codebase (e.g.
// WSAPOLLFD). 116444736000000000 is the fixed offset (in 100ns ticks)
// between the Windows epoch (1601) and Unix epoch (1970) -- a standard,
// well-known constant for this exact conversion, not derived here.
#ifdef _WIN32
extern void GetSystemTimePreciseAsFileTime(long long* out);
inline long long time_wall_ns() {
unsafe {
long long filetime100ns = 0LL;
GetSystemTimePreciseAsFileTime(&filetime100ns);
return (filetime100ns - 116444736000000000LL) * 100LL;
}
}
#else
// struct timespec { time_t tv_sec; long tv_nsec; }: two 4-byte fields on a
// 32-bit C ABI, two 8-byte fields on a 64-bit one. (SafeC's own 'long' is 64
// bits everywhere, so it can't stand in for C's here.)
#if __SIZEOF_POINTER__ == 4
#define SAFEC_TS_FIELD_ int
#else
#define SAFEC_TS_FIELD_ long long
#endif
struct SafecTimespec_ {
SAFEC_TS_FIELD_ tv_sec;
SAFEC_TS_FIELD_ tv_nsec;
};
extern int clock_gettime(int clk, SafecTimespec_* ts);
inline long long time_wall_ns() {
unsafe {
struct SafecTimespec_ ts;
if (clock_gettime(0, &ts) != 0) return -1LL;
return (long long)ts.tv_sec * 1000000000LL + (long long)ts.tv_nsec;
}
}
#endif
// Windows has no clock_gettime() at all (confirmed: a program calling it
// fails to *link*, not just "wrong clock" the way id 6/12 were on Linux —
// the UCRT import libraries this toolchain links against don't export it).
// QueryPerformanceCounter/-Frequency are the real Win32 monotonic timer,
// available since Windows 2000, and are what every other Windows profiler
// (including .NET's own Stopwatch, used elsewhere on this same benchmarks
// page) is built on. Split into whole-seconds/remainder before scaling to
// ns to avoid overflowing a 64-bit intermediate on a long-running process
// (counter * 1e9 alone can exceed 2^63 well within realistic uptimes).
#ifdef _WIN32
extern int QueryPerformanceCounter(long long* out);
extern int QueryPerformanceFrequency(long long* out);
inline long long time_mono_ns() {
unsafe {
long long freq = 0LL;
long long counter = 0LL;
QueryPerformanceFrequency(&freq);
QueryPerformanceCounter(&counter);
if (freq <= 0LL) return -1LL;
long long whole = counter / freq;
long long rem = counter % freq;
return whole * 1000000000LL + (rem * 1000000000LL) / freq;
}
}
#else
inline long long time_mono_ns() {
unsafe {
struct SafecTimespec_ ts;
if (clock_gettime(SAFEC_CLOCK_MONOTONIC_, &ts) != 0) return -1LL;
return (long long)ts.tv_sec * 1000000000LL + (long long)ts.tv_nsec;
}
}
#endif
// GetProcessTimes' kernel+user FILETIME outputs (100ns ticks, duration
// not epoch-relative here) are the Win32 equivalent of
// CLOCK_PROCESS_CPUTIME_ID -- total CPU time this process has consumed.
#ifdef _WIN32
struct Win32Process_ { char opaque_; }; // a process HANDLE, opaque
extern Win32Process_* GetCurrentProcess();
extern int GetProcessTimes(Win32Process_* h, long long* creation, long long* exitT,
long long* kernel, long long* user);
inline long long time_cpu_ns() {
unsafe {
long long creation = 0LL; long long exitT = 0LL;
long long kernel = 0LL; long long user = 0LL;
Win32Process_* h = GetCurrentProcess();
if (GetProcessTimes(h, &creation, &exitT, &kernel, &user) == 0) return -1LL;
return (kernel + user) * 100LL;
}
}
#else
inline long long time_cpu_ns() {
unsafe {
struct SafecTimespec_ ts;
if (clock_gettime(SAFEC_CLOCK_CPUTIME_, &ts) != 0) return -1LL;
return (long long)ts.tv_sec * 1000000000LL + (long long)ts.tv_nsec;
}
}
#endif
inline long long time_elapsed_ns(long long start, long long end) {
return end - start;
}
inline double time_ns_to_ms(long long ns) {
return (double)ns / 1000000.0;
}
// Win32's Sleep() only takes whole milliseconds -- real precision loss
// versus nanosleep's nanosecond request, rounded up (not down) so a
// caller never sleeps for *less* than asked.
#ifdef _WIN32
extern void Sleep(unsigned int ms);
inline void time_sleep_ns(long long ns) {
unsafe {
long long ms = (ns + 999999LL) / 1000000LL;
if (ms < 0LL) { ms = 0LL; }
if (ms > 0xFFFFFFFELL) { ms = 0xFFFFFFFELL; } // INFINITE is 0xFFFFFFFF
Sleep((unsigned int)ms);
}
}
#else
extern int nanosleep(const SafecTimespec_* req, SafecTimespec_* rem);
inline void time_sleep_ns(long long ns) {
unsafe {
struct SafecTimespec_ ts;
ts.tv_sec = (SAFEC_TS_FIELD_)(ns / 1000000000LL);
ts.tv_nsec = (SAFEC_TS_FIELD_)(ns % 1000000000LL);
nanosleep(&ts, (SafecTimespec_*)0);
}
}
#endif
inline int time_is_leap(int year) {
return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
}
inline int time_days_in_month(int month, int year) {
int days[12];
days[0]=31; days[1]=28; days[2]=31; days[3]=30;
days[4]=31; days[5]=30; days[6]=31; days[7]=31;
days[8]=30; days[9]=31; days[10]=30; days[11]=31;
if (month == 1 && time_is_leap(year)) return 29;
if (month < 0 || month > 11) return -1;
return days[month];
}
} // namespace std