1#include "../include/xtime.h"
9#if defined(__APPLE__) || defined(__unix__) || defined(__linux__)
15#include <mach/mach_time.h>
19static const int64_t NANOS_PER_SEC = 1000000000LL;
20static const int64_t NANOS_PER_MICRO = 1000LL;
21static const int64_t NANOS_PER_MILLI = 1000000LL;
24static const int16_t MAX_TZ_OFFSET = 1439;
29static inline bool is_leap_year(
int year) {
return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0); }
34static inline int days_in_month(
int year,
int month) {
35 static const int days[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
37 if (month < 0 || month > 11) {
41 if (month == 1 && is_leap_year(year)) {
50 return XTIME_ERR_INVALID_ARG;
52 *t = (
xtime_t){.seconds = 0, .nanoseconds = 0, .tz_offset = 0, .has_tz =
false};
60static int16_t get_local_tz_offset(time_t timestamp) {
61 struct tm utc_tm, local_tm;
64 if (gmtime_r(×tamp, &utc_tm) == NULL) {
67 if (localtime_r(×tamp, &local_tm) == NULL) {
73 int day_diff = local_tm.tm_mday - utc_tm.tm_mday;
74 int hour_diff = local_tm.tm_hour - utc_tm.tm_hour;
75 int min_diff = local_tm.tm_min - utc_tm.tm_min;
80 }
else if (day_diff < -1) {
84 int total_minutes = (day_diff * 24 * 60) + (hour_diff * 60) + min_diff;
87 if (abs(total_minutes) > MAX_TZ_OFFSET) {
91 return (int16_t)total_minutes;
96 return XTIME_ERR_INVALID_ARG;
99#if defined(_WIN32) || defined(_WIN64)
102 GetSystemTimePreciseAsFileTime(&ft);
106 uli.LowPart = ft.dwLowDateTime;
107 uli.HighPart = ft.dwHighDateTime;
111 const int64_t WINDOWS_TO_UNIX_EPOCH = 11644473600LL;
112 int64_t total_100ns = (int64_t)(uli.QuadPart);
114 t->seconds = (total_100ns / 10000000LL) - WINDOWS_TO_UNIX_EPOCH;
115 t->nanoseconds = (uint32_t)((total_100ns % 10000000LL) * 100);
117#elif defined(__APPLE__)
120 if (clock_gettime(CLOCK_REALTIME, &ts) != 0) {
123 if (gettimeofday(&tv, NULL) != 0) {
124 return XTIME_ERR_SYSTEM;
126 t->seconds = (int64_t)tv.tv_sec;
127 t->nanoseconds = (uint32_t)(tv.tv_usec * 1000);
129 t->seconds = (int64_t)ts.tv_sec;
130 t->nanoseconds = (uint32_t)ts.tv_nsec;
135 if (clock_gettime(CLOCK_REALTIME, &ts) != 0) {
136 return XTIME_ERR_SYSTEM;
139 t->seconds = (int64_t)ts.tv_sec;
140 t->nanoseconds = (uint32_t)ts.tv_nsec;
144 t->tz_offset = get_local_tz_offset((time_t)t->seconds);
152 if (err == XTIME_OK) {
163static bool parse_tz_offset(
const char* str, int16_t* offset) {
164 if (str == NULL || offset == NULL) {
169 while (*str ==
' ' || *str ==
'\t') {
178 }
else if (*str ==
'-') {
181 }
else if (*str ==
'Z' || *str ==
'z') {
189 if (!isdigit(str[0]) || !isdigit(str[1])) {
192 int hours = (str[0] -
'0') * 10 + (str[1] -
'0');
202 if (isdigit(str[0]) && isdigit(str[1])) {
203 minutes = (str[0] -
'0') * 10 + (str[1] -
'0');
207 if (hours > 23 || minutes > 59) {
211 int total_minutes = (hours * 60 + minutes) * sign;
212 if (abs(total_minutes) > MAX_TZ_OFFSET) {
216 *offset = (int16_t)total_minutes;
221 if (str == NULL || format == NULL || t == NULL) {
222 return XTIME_ERR_INVALID_ARG;
228 memset(&tm_result, 0,
sizeof(tm_result));
231 char* remaining = strptime(str, format, &tm_result);
232 if (remaining == NULL) {
233 return XTIME_ERR_PARSE_FAILED;
238 int year = tm_result.tm_year + 1900;
239 int month = tm_result.tm_mon;
240 int day = tm_result.tm_mday;
241 int hour = tm_result.tm_hour;
242 int minute = tm_result.tm_min;
243 int second = tm_result.tm_sec;
246 int max_day = days_in_month(year, month);
247 if (day < 1 || day > max_day) {
248 return XTIME_ERR_DATE_OUT_OF_RANGE;
255 for (
int y = 1970; y < year; y++) {
256 days += is_leap_year(y) ? 366 : 365;
260 for (
int m = 0; m < month; m++) {
261 days += days_in_month(year, m);
268 t->seconds = (days * 86400) + (hour * 3600) + (minute * 60) + second;
271 if (*remaining ==
'.') {
274 int64_t multiplier = 100000000;
278 while (isdigit(*remaining)) {
279 if (multiplier >= 1) {
280 nanos += (*remaining -
'0') * multiplier;
285 t->nanoseconds = nanos;
290 while (*remaining ==
' ') remaining++;
292 if (*remaining !=
'\0') {
293 int16_t tz_offset = 0;
294 if (parse_tz_offset(remaining, &tz_offset)) {
295 t->tz_offset = tz_offset;
304 if (t == NULL || format == NULL || buf == NULL || buflen == 0) {
305 return XTIME_ERR_INVALID_ARG;
309 if (strcmp(format,
"%s") == 0 || strcmp(format, XTIME_FMT_UNIX) == 0) {
310 int written = snprintf(buf, buflen,
"%lld", (
long long)t->seconds);
311 if (written < 0 || (
size_t)written >= buflen) {
312 return XTIME_ERR_BUFFER_TOO_SMALL;
318 time_t timestamp = (time_t)t->seconds;
319 if (t->has_tz && t->tz_offset != 0) {
321 timestamp += (time_t)(t->tz_offset * 60);
327 if (gmtime_r(×tamp, &tm_result) == NULL) {
328 return XTIME_ERR_INVALID_TIME;
332 size_t result = strftime(buf, buflen, format, &tm_result);
334 return XTIME_ERR_BUFFER_TOO_SMALL;
338 if (t->has_tz && strstr(format,
"%z") != NULL) {
339 size_t len = strlen(buf);
340 int hours = abs(t->tz_offset) / 60;
341 int minutes = abs(t->tz_offset) % 60;
342 char sign = t->tz_offset >= 0 ?
'+' :
'-';
344 int written = snprintf(buf + len, buflen - len,
"%c%02d:%02d", sign, hours, minutes);
345 if (written < 0 || len + (
size_t)written >= buflen) {
346 return XTIME_ERR_BUFFER_TOO_SMALL;
354 if (t == NULL || format == NULL || buf == NULL || buflen == 0) {
355 return XTIME_ERR_INVALID_ARG;
359 if (strcmp(format,
"%s") == 0 || strcmp(format, XTIME_FMT_UNIX) == 0) {
360 int written = snprintf(buf, buflen,
"%lld", (
long long)t->seconds);
361 if (written < 0 || (
size_t)written >= buflen) {
362 return XTIME_ERR_BUFFER_TOO_SMALL;
368 time_t timestamp = (time_t)t->seconds;
371 if (gmtime_r(×tamp, &tm_result) == NULL) {
372 return XTIME_ERR_INVALID_TIME;
376 size_t result = strftime(buf, buflen, format, &tm_result);
378 return XTIME_ERR_BUFFER_TOO_SMALL;
385 if (t == NULL || buf == NULL || buflen == 0) {
386 return XTIME_ERR_INVALID_ARG;
390 time_t timestamp = (time_t)t->seconds;
391 if (t->has_tz && t->tz_offset != 0) {
392 timestamp += (time_t)(t->tz_offset * 60);
396 if (gmtime_r(×tamp, &tm_val) == NULL) {
397 return XTIME_ERR_INVALID_TIME;
401 int written = snprintf(buf, buflen,
"%04d-%02d-%02dT%02d:%02d:%02d", tm_val.tm_year + 1900, tm_val.tm_mon + 1,
402 tm_val.tm_mday, tm_val.tm_hour, tm_val.tm_min, tm_val.tm_sec);
404 if (written < 0 || (
size_t)written >= buflen) {
405 return XTIME_ERR_BUFFER_TOO_SMALL;
407 size_t current_len = (size_t)written;
411 if (t->nanoseconds > 0) {
412 written = snprintf(buf + current_len, buflen - current_len,
".%09u", t->nanoseconds);
413 if (written < 0 || current_len + (
size_t)written >= buflen) {
414 return XTIME_ERR_BUFFER_TOO_SMALL;
416 current_len += (size_t)written;
420 if (!t->has_tz || t->tz_offset == 0) {
422 if (current_len + 1 >= buflen)
return XTIME_ERR_BUFFER_TOO_SMALL;
423 buf[current_len++] =
'Z';
424 buf[current_len] =
'\0';
427 int hrs = abs(t->tz_offset) / 60;
428 int mins = abs(t->tz_offset) % 60;
429 char sign = (t->tz_offset >= 0) ?
'+' :
'-';
431 written = snprintf(buf + current_len, buflen - current_len,
"%c%02d:%02d", sign, hrs, mins);
433 if (written < 0 || current_len + (
size_t)written >= buflen) {
434 return XTIME_ERR_BUFFER_TOO_SMALL;
450 return XTIME_ERR_INVALID_ARG;
453 t->seconds = timestamp;
463 return XTIME_ERR_INVALID_ARG;
466 t->seconds += seconds;
471 if (t1 == NULL || t2 == NULL) {
475 if (t1->seconds < t2->seconds) {
478 if (t1->seconds > t2->seconds) {
483 if (t1->nanoseconds < t2->nanoseconds) {
486 if (t1->nanoseconds > t2->nanoseconds) {
494 if (t1 == NULL || t2 == NULL || diff == NULL) {
495 return XTIME_ERR_INVALID_ARG;
498 int64_t sec_diff = t1->seconds - t2->seconds;
499 int64_t nano_diff = (int64_t)t1->nanoseconds - (int64_t)t2->nanoseconds;
501 *diff = (double)sec_diff + ((
double)nano_diff / (double)NANOS_PER_SEC);
510 case XTIME_ERR_INVALID_ARG:
511 return "Invalid argument";
512 case XTIME_ERR_PARSE_FAILED:
513 return "Failed to parse time string";
514 case XTIME_ERR_DATE_OUT_OF_RANGE:
515 return "Date of out of range for month";
516 case XTIME_ERR_BUFFER_TOO_SMALL:
517 return "Output buffer too small";
518 case XTIME_ERR_INVALID_TIME:
519 return "Invalid time value";
520 case XTIME_ERR_SYSTEM:
521 return "System error";
523 return "Unknown error";
531 return XTIME_ERR_INVALID_ARG;
534 int64_t total_nanos = (int64_t)t->nanoseconds + nanos;
537 if (total_nanos >= NANOS_PER_SEC) {
538 int64_t extra_secs = total_nanos / NANOS_PER_SEC;
539 t->seconds += extra_secs;
540 t->nanoseconds = (uint32_t)(total_nanos % NANOS_PER_SEC);
541 }
else if (total_nanos < 0) {
542 int64_t borrow_secs = (-total_nanos + NANOS_PER_SEC - 1) / NANOS_PER_SEC;
543 t->seconds -= borrow_secs;
544 t->nanoseconds = (uint32_t)(total_nanos + (borrow_secs * NANOS_PER_SEC));
546 t->nanoseconds = (uint32_t)total_nanos;
554 return XTIME_ERR_INVALID_ARG;
562 return XTIME_ERR_INVALID_ARG;
570 return XTIME_ERR_INVALID_ARG;
578 return XTIME_ERR_INVALID_ARG;
586 return XTIME_ERR_INVALID_ARG;
594 return XTIME_ERR_INVALID_ARG;
598 int64_t time_of_day_seconds = t->seconds % 86400;
599 if (time_of_day_seconds < 0) {
600 time_of_day_seconds += 86400;
604 time_t timestamp = (time_t)t->seconds;
607 if (gmtime_r(×tamp, &tm_val) == NULL) {
608 return XTIME_ERR_INVALID_TIME;
612 int total_months = tm_val.tm_mon + months;
613 int year_adjust = total_months / 12;
614 int new_month = total_months % 12;
622 int new_year = tm_val.tm_year + 1900 + year_adjust;
625 int max_day = days_in_month(new_year, new_month);
626 int new_day = (tm_val.tm_mday > max_day) ? max_day : tm_val.tm_mday;
633 for (
int y = 1970; y < new_year; y++) {
634 days += is_leap_year(y) ? 366 : 365;
638 for (
int m = 0; m < new_month; m++) {
639 days += days_in_month(new_year, m);
646 t->seconds = (days * 86400) + time_of_day_seconds;
653 return XTIME_ERR_INVALID_ARG;
657 int64_t time_of_day_seconds = t->seconds % 86400;
658 if (time_of_day_seconds < 0) {
659 time_of_day_seconds += 86400;
663 time_t timestamp = (time_t)t->seconds;
666 if (gmtime_r(×tamp, &tm_val) == NULL) {
667 return XTIME_ERR_INVALID_TIME;
670 int new_year = tm_val.tm_year + 1900 + years;
671 int new_month = tm_val.tm_mon;
672 int new_day = tm_val.tm_mday;
675 if (new_month == 1 && new_day == 29) {
676 if (!is_leap_year(new_year)) {
685 for (
int y = 1970; y < new_year; y++) {
686 days += is_leap_year(y) ? 366 : 365;
690 for (
int m = 0; m < new_month; m++) {
691 days += days_in_month(new_year, m);
698 t->seconds = (days * 86400) + time_of_day_seconds;
704 if (t1 == NULL || t2 == NULL || nanos == NULL) {
705 return XTIME_ERR_INVALID_ARG;
708 int64_t sec_diff = t1->seconds - t2->seconds;
709 int64_t nano_diff = (int64_t)t1->nanoseconds - (int64_t)t2->nanoseconds;
711 *nanos = (sec_diff * NANOS_PER_SEC) + nano_diff;
717 if (t1 == NULL || t2 == NULL || micros == NULL) {
718 return XTIME_ERR_INVALID_ARG;
723 if (err != XTIME_OK) {
727 *micros = nanos / NANOS_PER_MICRO;
733 if (t1 == NULL || t2 == NULL || millis == NULL) {
734 return XTIME_ERR_INVALID_ARG;
739 if (err != XTIME_OK) {
743 *millis = nanos / NANOS_PER_MILLI;
749 if (t1 == NULL || t2 == NULL || seconds == NULL) {
750 return XTIME_ERR_INVALID_ARG;
753 *seconds = t1->seconds - t2->seconds;
759 if (t1 == NULL || t2 == NULL || minutes == NULL) {
760 return XTIME_ERR_INVALID_ARG;
763 *minutes = (t1->seconds - t2->seconds) / 60;
769 if (t1 == NULL || t2 == NULL || hours == NULL) {
770 return XTIME_ERR_INVALID_ARG;
773 *hours = (t1->seconds - t2->seconds) / 3600;
779 if (t1 == NULL || t2 == NULL || days == NULL) {
780 return XTIME_ERR_INVALID_ARG;
783 *days = (t1->seconds - t2->seconds) / 86400;
793 time_t timestamp = (time_t)t->seconds;
796 if (gmtime_r(×tamp, &tm_val) == NULL) {
800 return is_leap_year(tm_val.tm_year + 1900);
805 return XTIME_ERR_INVALID_ARG;
815 return XTIME_ERR_INVALID_ARG;
820 t->seconds = (t->seconds / 60) * 60;
828 return XTIME_ERR_INVALID_ARG;
833 t->seconds = (t->seconds / 3600) * 3600;
841 return XTIME_ERR_INVALID_ARG;
846 t->seconds = (t->seconds / 86400) * 86400;
853 if (t == NULL || result == NULL) {
854 return XTIME_ERR_INVALID_ARG;
861 if (err != XTIME_OK) {
866 time_t timestamp = (time_t)result->seconds;
869 if (gmtime_r(×tamp, &tm_val) == NULL) {
870 return XTIME_ERR_INVALID_TIME;
874 int days_to_monday = (tm_val.tm_wday == 0) ? 6 : (tm_val.tm_wday - 1);
881 if (t == NULL || result == NULL) {
882 return XTIME_ERR_INVALID_ARG;
887 time_t timestamp = (time_t)result->seconds;
890 if (gmtime_r(×tamp, &tm_val) == NULL) {
891 return XTIME_ERR_INVALID_TIME;
895 int days_to_subtract = tm_val.tm_mday - 1;
899 if (err != XTIME_OK) {
908 if (t == NULL || result == NULL) {
909 return XTIME_ERR_INVALID_ARG;
914 time_t timestamp = (time_t)result->seconds;
917 if (gmtime_r(×tamp, &tm_val) == NULL) {
918 return XTIME_ERR_INVALID_TIME;
922 int days_to_subtract = tm_val.tm_yday;
926 if (err != XTIME_OK) {
935 if (t == NULL || result == NULL) {
936 return XTIME_ERR_INVALID_ARG;
943 if (err != XTIME_OK) {
948 result->seconds += 86399;
949 result->nanoseconds = 999999999;
955 if (t == NULL || result == NULL) {
956 return XTIME_ERR_INVALID_ARG;
961 time_t timestamp = (time_t)result->seconds;
964 if (gmtime_r(×tamp, &tm_val) == NULL) {
965 return XTIME_ERR_INVALID_TIME;
969 int last_day = days_in_month(tm_val.tm_year + 1900, tm_val.tm_mon);
972 int days_to_add = last_day - tm_val.tm_mday;
976 if (err != XTIME_OK) {
982 if (err != XTIME_OK) {
987 result->seconds += 86399;
988 result->nanoseconds = 999999999;
994 if (t == NULL || result == NULL) {
995 return XTIME_ERR_INVALID_ARG;
1000 time_t timestamp = (time_t)result->seconds;
1003 if (gmtime_r(×tamp, &tm_val) == NULL) {
1004 return XTIME_ERR_INVALID_TIME;
1008 int year = tm_val.tm_year + 1900;
1009 int total_days_in_year = is_leap_year(year) ? 366 : 365;
1012 int days_to_add = (total_days_in_year - 1) - tm_val.tm_yday;
1016 if (err != XTIME_OK) {
1022 if (err != XTIME_OK) {
1027 result->seconds += 86399;
1028 result->nanoseconds = 999999999;
xtime_error_t xtime_diff_minutes(const xtime_t *t1, const xtime_t *t2, int64_t *minutes)
xtime_error_t xtime_truncate_to_minute(xtime_t *t)
xtime_error_t xtime_from_unix(int64_t timestamp, xtime_t *t)
xtime_error_t xtime_diff(const xtime_t *t1, const xtime_t *t2, double *diff)
xtime_error_t xtime_format_utc(const xtime_t *t, const char *format, char *buf, size_t buflen)
bool xtime_is_leap_year(const xtime_t *t)
xtime_error_t xtime_add_minutes(xtime_t *t, int64_t minutes)
xtime_error_t xtime_end_of_day(const xtime_t *t, xtime_t *result)
xtime_error_t xtime_diff_seconds(const xtime_t *t1, const xtime_t *t2, int64_t *seconds)
xtime_error_t xtime_utc_now(xtime_t *t)
xtime_error_t xtime_parse(const char *str, const char *format, xtime_t *t)
xtime_error_t xtime_add_years(xtime_t *t, int years)
int xtime_compare(const xtime_t *t1, const xtime_t *t2)
xtime_error_t xtime_add_months(xtime_t *t, int months)
const char * xtime_strerror(xtime_error_t err)
xtime_error_t xtime_truncate_to_hour(xtime_t *t)
xtime_error_t xtime_add_milliseconds(xtime_t *t, int64_t millis)
xtime_error_t xtime_end_of_month(const xtime_t *t, xtime_t *result)
xtime_error_t xtime_to_json(const xtime_t *t, char *buf, size_t buflen)
xtime_error_t xtime_add_nanoseconds(xtime_t *t, int64_t nanos)
xtime_error_t xtime_format(const xtime_t *t, const char *format, char *buf, size_t buflen)
xtime_error_t xtime_diff_nanos(const xtime_t *t1, const xtime_t *t2, int64_t *nanos)
xtime_error_t xtime_start_of_month(const xtime_t *t, xtime_t *result)
xtime_error_t xtime_truncate_to_day(xtime_t *t)
xtime_error_t xtime_now(xtime_t *t)
xtime_error_t xtime_diff_days(const xtime_t *t1, const xtime_t *t2, int64_t *days)
xtime_error_t xtime_diff_micros(const xtime_t *t1, const xtime_t *t2, int64_t *micros)
xtime_error_t xtime_init(xtime_t *t)
xtime_error_t xtime_truncate_to_second(xtime_t *t)
xtime_error_t xtime_diff_hours(const xtime_t *t1, const xtime_t *t2, int64_t *hours)
xtime_error_t xtime_add_seconds(xtime_t *t, int64_t seconds)
xtime_error_t xtime_add_days(xtime_t *t, int64_t days)
xtime_error_t xtime_start_of_week(const xtime_t *t, xtime_t *result)
int64_t xtime_to_unix(const xtime_t *t)
xtime_error_t xtime_add_microseconds(xtime_t *t, int64_t micros)
xtime_error_t xtime_diff_millis(const xtime_t *t1, const xtime_t *t2, int64_t *millis)
xtime_error_t xtime_end_of_year(const xtime_t *t, xtime_t *result)
xtime_error_t xtime_add_hours(xtime_t *t, int64_t hours)
xtime_error_t xtime_start_of_year(const xtime_t *t, xtime_t *result)