diff --git a/inc/time.h b/inc/time.h index e04460a..308a681 100644 --- a/inc/time.h +++ b/inc/time.h @@ -1,15 +1,28 @@ #pragma once -#include +// 7.29.1 p.2: Macros + +#define __STDC_VERSION_TIME_H__ 202311L #if !defined(NULL) #define NULL ((void *)0) #endif -#define TIME_UTC 1 - #define CLOCKS_PER_SEC ((clock_t)1000000) +#define TIME_UTC 1 +#define TIME_MONOTONIC 2 +#define TIME_ACTIVE 3 +#define TIME_THREAD_ACTIVE 4 + +// 7.29.1 p.4: Types + +#if defined(_WIN32) + typedef unsigned long long size_t; +#else + typedef unsigned long size_t; +#endif + typedef unsigned long long clock_t; typedef unsigned long long time_t; @@ -36,28 +49,30 @@ typedef unsigned long long time_t; }; #endif -clock_t clock (void); -time_t time (time_t *timer); -time_t mktime (struct tm *timeptr); -double difftime (time_t time1, time_t time0); -int timespec_get(struct timespec *ts, int base); -char *asctime (const struct tm *timeptr); -char *ctime (const time_t *timer); -struct tm *gmtime (const time_t *timer); -struct tm *localtime (const time_t *timer); +// Basic timer functions +clock_t clock(void); +time_t time(time_t *timer); +double difftime(time_t time1, time_t time0); -size_t strftime( - char *restrict s, - size_t maxsize, - const char *restrict format, - const struct tm *restrict timeptr -); +// Human-readable time functions +time_t mktime(struct tm *timeptr); +struct tm *gmtime(const time_t *timer); +struct tm *localtime(const time_t *timer); + +// Timespec functions +int timespec_get(struct timespec *ts, int base); +int timespec_getres(struct timespec *ts, int base); + +// Time formatting functions +char *asctime(const struct tm *timeptr); +char *ctime(const time_t *timer); +size_t strftime(char *restrict str, size_t sz, const char *restrict fmt, const struct tm *restrict time); #ifdef __STDC_WANT_LIB_EXT1__ errno_t asctime_s(char *s, rsize_t maxsize, const struct tm *timeptr); -errno_t ctime_s (char *s, rsize_t maxsize, const time_t *timer); -struct tm *gmtime_s (const time_t * restrict timer, struct tm * restrict result); +errno_t ctime_s(char *s, rsize_t maxsize, const time_t *timer); +struct tm *gmtime_s(const time_t * restrict timer, struct tm * restrict result); struct tm *localtime_s(const time_t * restrict timer, struct tm * restrict result); #endif diff --git a/src/os_win/time.c b/src/os_win/time.c index 022ec01..2ea5903 100644 --- a/src/os_win/time.c +++ b/src/os_win/time.c @@ -1,17 +1,18 @@ -#define TIME_TICKS_PER_SEC 1000 -#define FILE_TICKS_PER_SEC 10000000 -#define FILE_TICKS_UNTIL_UNIX_EPOCH 116444736000000000ULL +#define NS_PER_SEC 1000000000 +#define NS_PER_MS 1000000 -#define NANOS_PER_SEC 1000000000 -#define NANOS_PER_FILE_TICK (NANOS_PER_SEC / FILE_TICKS_PER_SEC) -#define NANOS_PER_TIME_TICK (NANOS_PER_SEC / TIME_TICKS_PER_SEC) +#define TIME_TICKS_PER_SEC 1000000000ULL +#define NS_BEFORE_UNIX_EPOCH 11644473600000000000ULL +typedef struct tm tm_t; // Store the time since we started running the process static uint64_t timer_freq; static uint64_t timer_start; +// Initialize timers + static void _setup_timer(void) { LARGE_INTEGER freq, start; QueryPerformanceFrequency(&freq); @@ -20,19 +21,63 @@ static void _setup_timer(void) { timer_freq = freq.QuadPart; } -static ULONGLONG win32_get_unix_nanos() { - FILETIME ft; - GetSystemTimeAsFileTime(&ft); +// Timestamp utilitity functions + +static ULONGLONG _time_filetime_to_ns(FILETIME ft) { ULARGE_INTEGER ft64 = { .LowPart = ft.dwLowDateTime, .HighPart = ft.dwHighDateTime, }; - ULONGLONG fticks = ft64.QuadPart; - ULONGLONG unix_fticks = fticks - FILE_TICKS_UNTIL_UNIX_EPOCH; - ULONGLONG unix_nanos = unix_fticks * NANOS_PER_FILE_TICK; - return unix_nanos; + ULONGLONG fticks = ft64.QuadPart; + return fticks * 100; } +static ULONGLONG _time_utc_ns() { + FILETIME ft; + GetSystemTimeAsFileTime(&ft); + ULONGLONG ns = _time_filetime_to_ns(ft); + ULONGLONG unix_ns = ns - NS_BEFORE_UNIX_EPOCH; + return unix_ns; +} + +static ULONGLONG _time_mono_ns() { + ULONGLONG time_ms = GetTickCount64(); + ULONGLONG time_ns = time_ms * NS_PER_MS; + return time_ns; +} + +static ULONGLONG _time_process_ns() { + FILETIME _1; + FILETIME _2; + FILETIME ktime; + FILETIME utime; + HANDLE current_process = GetCurrentProcess(); + if(GetProcessTimes(current_process, &_1, &_2, &ktime, &utime) == 0) { + abort(); + } + ULONGLONG kernel_time = _time_filetime_to_ns(ktime); + ULONGLONG user_time = _time_filetime_to_ns(utime); + ULONGLONG cpu_time = kernel_time + user_time; + return cpu_time; +} + +static ULONGLONG _time_thread_ns() { + FILETIME _1; + FILETIME _2; + FILETIME ktime; + FILETIME utime; + HANDLE current_thread = GetCurrentThread(); + if(GetThreadTimes(current_thread, &_1, &_2, &ktime, &utime) == 0) { + abort(); + } + ULONGLONG kernel_time = _time_filetime_to_ns(ktime); + ULONGLONG user_time = _time_filetime_to_ns(utime); + ULONGLONG cpu_time = kernel_time + user_time; + return cpu_time; +} + +// Timer functions + clock_t clock(void) { LARGE_INTEGER curr; if (!QueryPerformanceCounter(&curr)) { @@ -42,14 +87,14 @@ clock_t clock(void) { return -1; } clock_t ticks = curr.QuadPart - timer_start; - // Basically millis / timer_freq * CLOCKS_PER_SEC but more precise + // Same as (ticks / timer_freq * CLOCKS_PER_SEC) but more precise return ticks / timer_freq * CLOCKS_PER_SEC + ticks % timer_freq * CLOCKS_PER_SEC / timer_freq; } time_t time(time_t *timer) { - ULONGLONG unix_nanos = win32_get_unix_nanos(); - time_t timer_ticks = unix_nanos / NANOS_PER_TIME_TICK; + ULONGLONG unix_nanos = _time_mono_ns(); + time_t timer_ticks = unix_nanos; if(timer != NULL) { *timer = timer_ticks; } @@ -63,9 +108,290 @@ double difftime(time_t time1, time_t time0) { } int timespec_get(struct timespec *ts, int base) { - if (base != TIME_UTC) return 0; - ULONGLONG unix_nanos = win32_get_unix_nanos(); - ts->tv_sec = unix_nanos / NANOS_PER_SEC; - ts->tv_nsec = unix_nanos; + ULONGLONG current_ns; + switch(base) { + case TIME_UTC: current_ns = _time_utc_ns(); break; + case TIME_MONOTONIC: current_ns = _time_mono_ns(); break; + case TIME_ACTIVE: current_ns = _time_process_ns(); break; + case TIME_THREAD_ACTIVE: current_ns = _time_thread_ns(); break; + default: return 0; + } + ts->tv_sec = current_ns / NS_PER_SEC; + ts->tv_nsec = current_ns; return base; } + +int timespec_getres(struct timespec *ts, int base) { + switch(base) { + case TIME_UTC: return NS_PER_SEC; + case TIME_MONOTONIC: return NS_PER_SEC; + case TIME_ACTIVE: return NS_PER_SEC; + case TIME_THREAD_ACTIVE: return NS_PER_SEC; + default: return 0; + } +} + +// Human-readable time + +const int _days_in_month[12] = { + 31,28, 31,30,31, 30,31,31, 30,31,30, 31 +}; + +const int _month_days_since_year_start[12] = { + 0, + 31, + 31+28, + 31+28+31, + 31+28+31+30, + 31+28+31+30+31, + 31+28+31+30+31+30, + 31+28+31+30+31+30+31, + 31+28+31+30+31+30+31+31, + 31+28+31+30+31+30+31+31+30, + 31+28+31+30+31+30+31+31+30+31, + 31+28+31+30+31+30+31+31+30+31+30, +}; + +static bool _is_leap_year(int year) { + if(year % 400 == 0) { + return true; + } + if(year % 100 == 0) { + return false; + } + if(year % 4 == 0) { + return true; + } + return false; +} + +static int _leap_years_before(int year) { + year--; + return (year / 4) - (year / 100) + (year / 400); +} + +static int _leap_years_between(int start, int end) { + return _leap_years_before(end) - _leap_years_before(start + 1); +} + +static int _whole_days_since_year_start(int year, int mon, int day) { + int days = _month_days_since_year_start[mon] + day; + if(mon > 1 || (mon == 1 && day > 27)) { + if(_is_leap_year(year)) { + days += 1; + } + } + return days; +} + +static void _tm_adjust(tm_t *time) { + long long year = 1900 + time->tm_year; + long long mon = time->tm_mon; + long long day = time->tm_mday - 1; + long long hour = time->tm_hour; + long long min = time->tm_min; + long long sec = time->tm_sec; + // Adjust + if(sec < 0) { + min -= (-sec+59)/60; + sec = (sec%60 + 60) % 60; + } + else if(sec >= 60) { + min += sec / 60; + sec %= 60; + } + if(min < 0) { + hour -= (-min+59)/60; + min = (min%60 + 60) % 60; + } + else if(min >= 60) { + hour += min / 60; + min %= 60; + } + if(hour < 0) { + day -= (-hour+23)/24; + hour = (hour%24 + 24) % 24; + } + else if(hour >= 24) { + day += hour / 24; + hour %= 24; + } + if(mon < 0) { + year -= (-mon+11)/12; + mon = (mon%12+12)%12; + } + else if(mon >= 12) { + year += mon / 12; + mon %= 12; + } + if(day < 0) { + for(;;) { + int mon_prev = mon - 1; + int year_prev = year; + if(mon_prev == -1) { + mon_prev = 11; + year_prev -= 1; + } + int days_in_prev_month = _days_in_month[mon_prev]; + if(mon_prev == 1 && _is_leap_year(year_prev)) { + days_in_prev_month += 1; + } + if(-day > days_in_prev_month) { + day += days_in_prev_month; + mon -= 1; + if(mon == -1) { + mon = 11; + year -= 1; + } + } + else { + break; // Can't subtract any whole months, exit the loop + } + } + if(day < 0) { + mon -= 1; + day = _days_in_month[mon] + day; + if(mon == -1) { + mon = 11; + year -= 1; + } + } + } + else { + for(;;) { + int days_in_month = _days_in_month[mon]; + if(mon == 1 && _is_leap_year(year)) { + days_in_month += 1; + } + if(day < days_in_month) { + break; + } + day -= days_in_month; + mon += 1; + } + } + // Refill the struct with normalized time + time->tm_year = year - 1900; + time->tm_mon = mon; + time->tm_mday = day + 1; + time->tm_hour = hour; + time->tm_min = min; + time->tm_sec = sec; +} + +time_t mktime(tm_t *time) { + // Get the human-readable time from the structure + _tm_adjust(time); + long long year = 1900 + time->tm_year; + long long mon = time->tm_mon; + long long day = time->tm_mday - 1; + long long hour = time->tm_hour; + long long min = time->tm_min; + long long sec = time->tm_sec; + if(year < 1970) { + return (time_t)(-1); + } + // Calculate unix timestamp + time_t timestamp; + timestamp = (year - 1970) * 365 + _leap_years_between(1970, year); + time_t days_since_year = _whole_days_since_year_start(year, mon, day); + timestamp = timestamp + days_since_year; + time_t days_since_epoch = timestamp; + timestamp = timestamp * 24 + hour; + timestamp = timestamp * 60 + min; + timestamp = timestamp * 60 + sec; + timestamp = timestamp * NS_PER_SEC; + // Refill the week day and year day + time->tm_wday = (days_since_epoch % 7 + 4) % 7; // 1970-01-01 is thursday (wday=4) + time->tm_yday = days_since_year; + time->tm_isdst = -1; + return timestamp; +} + +// Breaking-down of the time + +tm_t *gmtime(const time_t *timer) { + tm_t *time = malloc(sizeof(tm_t)); + time_t timestamp = *timer; + timestamp /= NS_PER_SEC; + time->tm_sec = timestamp % 60; + timestamp /= 60; + time->tm_min = timestamp % 60; + timestamp /= 60; + time->tm_hour = timestamp % 24; + timestamp /= 24; + int year = 1970; + int days = timestamp; + // Start subtracting 400,100,4-year-stretches + const int DAYS_IN_400_YEARS = 365 * 400 + 97; + const int DAYS_IN_100_YEARS = 365 * 100 + 24; + const int DAYS_IN_4_YEARS = 365 * 4 + 1; + year += (days / DAYS_IN_400_YEARS) * 400; + days %= DAYS_IN_400_YEARS; + year += (days / DAYS_IN_100_YEARS) * 100; + days %= DAYS_IN_100_YEARS; + year += (days / DAYS_IN_4_YEARS) * 4; + days %= DAYS_IN_4_YEARS; + // Save + int days_since_epoch = days; + // Count remaining up-to 3 years in a loop + for(;;) { + int this_year_days = 365 + _is_leap_year(year); + if(days < this_year_days) { + break; + } + year += 1; + days -= this_year_days; + } + // Save + int days_since_year = days; + // Find the current month + int month = 0; + for(;;) { + int days_in_month = _days_in_month[month] + _is_leap_year(year); + if(days < days_in_month) { + break; + } + days -= days_in_month; + month += 1; + } + time->tm_year = year - 1900; + time->tm_mon = month; + time->tm_mday = days + 1; + time->tm_wday = (days_since_epoch % 7 + 4) % 7; // 1970-01-01 is thursday (wday=4) + time->tm_yday = days_since_year; + time->tm_isdst = -1; + return time; +} + +static bool _offset_utc_time_to_local(tm_t *time) { + TIME_ZONE_INFORMATION tz; + if(GetTimeZoneInformation(&tz) == TIME_ZONE_ID_INVALID) { + return false; + } + int bias = tz.Bias; + time->tm_min -= bias; + _tm_adjust(time); + return true; +} + +tm_t *localtime(const time_t *timer) { + tm_t *utc_time = gmtime(timer); + if(!_offset_utc_time_to_local(utc_time)) { + return NULL; + } + return utc_time; +} + +// String formatting + +char *asctime(const tm_t *timeptr) { + return NULL; +} +char *ctime(const time_t *timer) { + return NULL; +} + +size_t strftime(char *restrict s, size_t size, const char *restrict fmt, const tm_t *restrict time) { + return 0; +} diff --git a/test/test_time.c b/test/test_time.c index e759891..4c95f80 100644 --- a/test/test_time.c +++ b/test/test_time.c @@ -1,10 +1,26 @@ - -#include #include +#include +static const char *const wday[] = { + "Sunday", "Monday", "Tuesday", "Wednesday", + "Thursday", "Friday", "Saturday", "-unknown-" +}; int main() { - time_t t1 = time(NULL); - time_t t2 = time(NULL); - double s = difftime(t2, t1); + // July 4, 2001: Wednesday + struct tm time_str; + time_str.tm_year = 2001 - 1900; + time_str.tm_mon = 7 - 1; + time_str.tm_mday = 4; + time_str.tm_hour = 0; + time_str.tm_min = 0; + time_str.tm_sec = 1; + time_str.tm_isdst = -1; + time_t timestamp; + if((timestamp = mktime(&time_str)) == (time_t)(-1)) { + time_str.tm_wday = 7; + } + char const *day_of_week = wday[time_str.tm_wday]; + struct tm *gmt = gmtime(×tamp); + struct tm *local = localtime(×tamp); return 0; -} +} \ No newline at end of file