1#include "../include/xtime.h"
2#include "../include/macros.h"
10#if defined(__APPLE__) || defined(__unix__) || defined(__linux__)
16#include <mach/mach_time.h>
20static const int64_t NANOS_PER_SEC = 1000000000LL;
21static const int64_t NANOS_PER_MICRO = 1000LL;
22static const int64_t NANOS_PER_MILLI = 1000000LL;
25static const int16_t MAX_TZ_OFFSET = 1439;
30static inline bool is_leap_year(
int year) {
return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0); }
35static inline int days_in_month(
int year,
int month) {
36 static const int days[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
38 if (month < 0 || month > 11) {
42 if (month == 1 && is_leap_year(year)) {
51 return XTIME_ERR_INVALID_ARG;
53 *t = (
xtime_t){.seconds = 0, .nanoseconds = 0, .tz_offset = 0, .has_tz =
false};
61static int16_t get_local_tz_offset(time_t timestamp) {
62 struct tm utc_tm, local_tm;
65 if (gmtime_r(×tamp, &utc_tm) == NULL) {
68 if (localtime_r(×tamp, &local_tm) == NULL) {
74 int day_diff = local_tm.tm_mday - utc_tm.tm_mday;
75 int hour_diff = local_tm.tm_hour - utc_tm.tm_hour;
76 int min_diff = local_tm.tm_min - utc_tm.tm_min;
81 }
else if (day_diff < -1) {
85 int total_minutes = (day_diff * 24 * 60) + (hour_diff * 60) + min_diff;
88 if (abs(total_minutes) > MAX_TZ_OFFSET) {
92 return (int16_t)total_minutes;
97 return XTIME_ERR_INVALID_ARG;
100#if defined(_WIN32) || defined(_WIN64)
103 GetSystemTimePreciseAsFileTime(&ft);
107 uli.LowPart = ft.dwLowDateTime;
108 uli.HighPart = ft.dwHighDateTime;
112 const int64_t WINDOWS_TO_UNIX_EPOCH = 11644473600LL;
113 int64_t total_100ns = (int64_t)(uli.QuadPart);
115 t->seconds = (total_100ns / 10000000LL) - WINDOWS_TO_UNIX_EPOCH;
116 t->nanoseconds = (uint32_t)((total_100ns % 10000000LL) * 100);
118#elif defined(__APPLE__)
121 if (clock_gettime(CLOCK_REALTIME, &ts) != 0) {
124 if (gettimeofday(&tv, NULL) != 0) {
125 return XTIME_ERR_SYSTEM;
127 t->seconds = (int64_t)tv.tv_sec;
128 t->nanoseconds = (uint32_t)(tv.tv_usec * 1000);
130 t->seconds = (int64_t)ts.tv_sec;
131 t->nanoseconds = (uint32_t)ts.tv_nsec;
136 if (clock_gettime(CLOCK_REALTIME, &ts) != 0) {
137 return XTIME_ERR_SYSTEM;
140 t->seconds = (int64_t)ts.tv_sec;
141 t->nanoseconds = (uint32_t)ts.tv_nsec;
145 t->tz_offset = get_local_tz_offset((time_t)t->seconds);
153 if (err == XTIME_OK) {
164static bool parse_tz_offset(
const char* str, int16_t* offset) {
165 if (str == NULL || offset == NULL) {
170 while (*str ==
' ' || *str ==
'\t') {
179 }
else if (*str ==
'-') {
182 }
else if (*str ==
'Z' || *str ==
'z') {
190 if (!isdigit(str[0]) || !isdigit(str[1])) {
193 int hours = (str[0] -
'0') * 10 + (str[1] -
'0');
203 if (isdigit(str[0]) && isdigit(str[1])) {
204 minutes = (str[0] -
'0') * 10 + (str[1] -
'0');
208 if (hours > 23 || minutes > 59) {
212 int total_minutes = (hours * 60 + minutes) * sign;
213 if (abs(total_minutes) > MAX_TZ_OFFSET) {
217 *offset = (int16_t)total_minutes;
222 if (str == NULL || format == NULL || t == NULL) {
223 return XTIME_ERR_INVALID_ARG;
229 memset(&tm_result, 0,
sizeof(tm_result));
232 char* remaining = strptime(str, format, &tm_result);
233 if (remaining == NULL) {
234 return XTIME_ERR_PARSE_FAILED;
239 int year = tm_result.tm_year + 1900;
240 int month = tm_result.tm_mon;
241 int day = tm_result.tm_mday;
242 int hour = tm_result.tm_hour;
243 int minute = tm_result.tm_min;
244 int second = tm_result.tm_sec;
247 int max_day = days_in_month(year, month);
248 if (day < 1 || day > max_day) {
249 return XTIME_ERR_DATE_OUT_OF_RANGE;
256 for (
int y = 1970; y < year; y++) {
257 days += is_leap_year(y) ? 366 : 365;
261 for (
int m = 0; m < month; m++) {
262 days += days_in_month(year, m);
269 t->seconds = (days * 86400) + (hour * 3600) + (minute * 60) + second;
272 if (*remaining ==
'.') {
275 int64_t multiplier = 100000000;
279 while (isdigit(*remaining)) {
280 if (multiplier >= 1) {
281 nanos += (*remaining -
'0') * multiplier;
286 t->nanoseconds = nanos;
291 while (*remaining ==
' ') remaining++;
293 if (*remaining !=
'\0') {
294 int16_t tz_offset = 0;
295 if (parse_tz_offset(remaining, &tz_offset)) {
296 t->tz_offset = tz_offset;
305 if (t == NULL || format == NULL || buf == NULL || buflen == 0) {
306 return XTIME_ERR_INVALID_ARG;
310 if (strcmp(format,
"%s") == 0 || strcmp(format, XTIME_FMT_UNIX) == 0) {
311 int written = snprintf(buf, buflen,
"%lld", (
long long)t->seconds);
312 if (written < 0 || (
size_t)written >= buflen) {
313 return XTIME_ERR_BUFFER_TOO_SMALL;
319 time_t timestamp = (time_t)t->seconds;
320 if (t->has_tz && t->tz_offset != 0) {
322 timestamp += (time_t)(t->tz_offset * 60);
328 if (gmtime_r(×tamp, &tm_result) == NULL) {
329 return XTIME_ERR_INVALID_TIME;
333 size_t result = strftime(buf, buflen, format, &tm_result);
335 return XTIME_ERR_BUFFER_TOO_SMALL;
339 if (t->has_tz && strstr(format,
"%z") != NULL) {
340 size_t len = strlen(buf);
341 int hours = abs(t->tz_offset) / 60;
342 int minutes = abs(t->tz_offset) % 60;
343 char sign = t->tz_offset >= 0 ?
'+' :
'-';
345 int written = snprintf(buf + len, buflen - len,
"%c%02d:%02d", sign, hours, minutes);
346 if (written < 0 || len + (
size_t)written >= buflen) {
347 return XTIME_ERR_BUFFER_TOO_SMALL;
355 if (t == NULL || format == NULL || buf == NULL || buflen == 0) {
356 return XTIME_ERR_INVALID_ARG;
360 if (strcmp(format,
"%s") == 0 || strcmp(format, XTIME_FMT_UNIX) == 0) {
361 int written = snprintf(buf, buflen,
"%lld", (
long long)t->seconds);
362 if (written < 0 || (
size_t)written >= buflen) {
363 return XTIME_ERR_BUFFER_TOO_SMALL;
369 time_t timestamp = (time_t)t->seconds;
372 if (gmtime_r(×tamp, &tm_result) == NULL) {
373 return XTIME_ERR_INVALID_TIME;
377 size_t result = strftime(buf, buflen, format, &tm_result);
379 return XTIME_ERR_BUFFER_TOO_SMALL;
386 if (t == NULL || buf == NULL || buflen == 0) {
387 return XTIME_ERR_INVALID_ARG;
391 time_t timestamp = (time_t)t->seconds;
392 if (t->has_tz && t->tz_offset != 0) {
393 timestamp += (time_t)(t->tz_offset * 60);
397 if (gmtime_r(×tamp, &tm_val) == NULL) {
398 return XTIME_ERR_INVALID_TIME;
402 int written = snprintf(buf, buflen,
"%04d-%02d-%02dT%02d:%02d:%02d", tm_val.tm_year + 1900, tm_val.tm_mon + 1,
403 tm_val.tm_mday, tm_val.tm_hour, tm_val.tm_min, tm_val.tm_sec);
405 if (written < 0 || (
size_t)written >= buflen) {
406 return XTIME_ERR_BUFFER_TOO_SMALL;
408 size_t current_len = (size_t)written;
412 if (t->nanoseconds > 0) {
413 written = snprintf(buf + current_len, buflen - current_len,
".%09u", t->nanoseconds);
414 if (written < 0 || current_len + (
size_t)written >= buflen) {
415 return XTIME_ERR_BUFFER_TOO_SMALL;
417 current_len += (size_t)written;
421 if (!t->has_tz || t->tz_offset == 0) {
423 if (current_len + 1 >= buflen)
return XTIME_ERR_BUFFER_TOO_SMALL;
424 buf[current_len++] =
'Z';
425 buf[current_len] =
'\0';
428 int hrs = abs(t->tz_offset) / 60;
429 int mins = abs(t->tz_offset) % 60;
430 char sign = (t->tz_offset >= 0) ?
'+' :
'-';
432 written = snprintf(buf + current_len, buflen - current_len,
"%c%02d:%02d", sign, hrs, mins);
434 if (written < 0 || current_len + (
size_t)written >= buflen) {
435 return XTIME_ERR_BUFFER_TOO_SMALL;
451 return XTIME_ERR_INVALID_ARG;
454 t->seconds = timestamp;
464 return XTIME_ERR_INVALID_ARG;
467 t->seconds += seconds;
472 if (t1 == NULL || t2 == NULL) {
476 if (t1->seconds < t2->seconds) {
479 if (t1->seconds > t2->seconds) {
484 if (t1->nanoseconds < t2->nanoseconds) {
487 if (t1->nanoseconds > t2->nanoseconds) {
495 if (t1 == NULL || t2 == NULL || diff == NULL) {
496 return XTIME_ERR_INVALID_ARG;
499 int64_t sec_diff = t1->seconds - t2->seconds;
500 int64_t nano_diff = (int64_t)t1->nanoseconds - (int64_t)t2->nanoseconds;
502 *diff = (double)sec_diff + ((
double)nano_diff / (double)NANOS_PER_SEC);
511 case XTIME_ERR_INVALID_ARG:
512 return "Invalid argument";
513 case XTIME_ERR_PARSE_FAILED:
514 return "Failed to parse time string";
515 case XTIME_ERR_DATE_OUT_OF_RANGE:
516 return "Date of out of range for month";
517 case XTIME_ERR_BUFFER_TOO_SMALL:
518 return "Output buffer too small";
519 case XTIME_ERR_INVALID_TIME:
520 return "Invalid time value";
521 case XTIME_ERR_SYSTEM:
522 return "System error";
524 return "Unknown error";
532 return XTIME_ERR_INVALID_ARG;
535 int64_t total_nanos = (int64_t)t->nanoseconds + nanos;
538 if (total_nanos >= NANOS_PER_SEC) {
539 int64_t extra_secs = total_nanos / NANOS_PER_SEC;
540 t->seconds += extra_secs;
541 t->nanoseconds = (uint32_t)(total_nanos % NANOS_PER_SEC);
542 }
else if (total_nanos < 0) {
543 int64_t borrow_secs = (-total_nanos + NANOS_PER_SEC - 1) / NANOS_PER_SEC;
544 t->seconds -= borrow_secs;
545 t->nanoseconds = (uint32_t)(total_nanos + (borrow_secs * NANOS_PER_SEC));
547 t->nanoseconds = (uint32_t)total_nanos;
555 return XTIME_ERR_INVALID_ARG;
563 return XTIME_ERR_INVALID_ARG;
571 return XTIME_ERR_INVALID_ARG;
579 return XTIME_ERR_INVALID_ARG;
587 return XTIME_ERR_INVALID_ARG;
595 return XTIME_ERR_INVALID_ARG;
599 int64_t time_of_day_seconds = t->seconds % 86400;
600 if (time_of_day_seconds < 0) {
601 time_of_day_seconds += 86400;
605 time_t timestamp = (time_t)t->seconds;
608 if (gmtime_r(×tamp, &tm_val) == NULL) {
609 return XTIME_ERR_INVALID_TIME;
613 int total_months = tm_val.tm_mon + months;
614 int year_adjust = total_months / 12;
615 int new_month = total_months % 12;
623 int new_year = tm_val.tm_year + 1900 + year_adjust;
626 int max_day = days_in_month(new_year, new_month);
627 int new_day = (tm_val.tm_mday > max_day) ? max_day : tm_val.tm_mday;
634 for (
int y = 1970; y < new_year; y++) {
635 days += is_leap_year(y) ? 366 : 365;
639 for (
int m = 0; m < new_month; m++) {
640 days += days_in_month(new_year, m);
647 t->seconds = (days * 86400) + time_of_day_seconds;
654 return XTIME_ERR_INVALID_ARG;
658 int64_t time_of_day_seconds = t->seconds % 86400;
659 if (time_of_day_seconds < 0) {
660 time_of_day_seconds += 86400;
664 time_t timestamp = (time_t)t->seconds;
667 if (gmtime_r(×tamp, &tm_val) == NULL) {
668 return XTIME_ERR_INVALID_TIME;
671 int new_year = tm_val.tm_year + 1900 + years;
672 int new_month = tm_val.tm_mon;
673 int new_day = tm_val.tm_mday;
676 if (new_month == 1 && new_day == 29) {
677 if (!is_leap_year(new_year)) {
686 for (
int y = 1970; y < new_year; y++) {
687 days += is_leap_year(y) ? 366 : 365;
691 for (
int m = 0; m < new_month; m++) {
692 days += days_in_month(new_year, m);
699 t->seconds = (days * 86400) + time_of_day_seconds;
705 if (t1 == NULL || t2 == NULL || nanos == NULL) {
706 return XTIME_ERR_INVALID_ARG;
709 int64_t sec_diff = t1->seconds - t2->seconds;
710 int64_t nano_diff = (int64_t)t1->nanoseconds - (int64_t)t2->nanoseconds;
712 *nanos = (sec_diff * NANOS_PER_SEC) + nano_diff;
718 if (t1 == NULL || t2 == NULL || micros == NULL) {
719 return XTIME_ERR_INVALID_ARG;
724 if (err != XTIME_OK) {
728 *micros = nanos / NANOS_PER_MICRO;
734 if (t1 == NULL || t2 == NULL || millis == NULL) {
735 return XTIME_ERR_INVALID_ARG;
740 if (err != XTIME_OK) {
744 *millis = nanos / NANOS_PER_MILLI;
750 if (t1 == NULL || t2 == NULL || seconds == NULL) {
751 return XTIME_ERR_INVALID_ARG;
754 *seconds = t1->seconds - t2->seconds;
760 if (t1 == NULL || t2 == NULL || minutes == NULL) {
761 return XTIME_ERR_INVALID_ARG;
764 *minutes = (t1->seconds - t2->seconds) / 60;
770 if (t1 == NULL || t2 == NULL || hours == NULL) {
771 return XTIME_ERR_INVALID_ARG;
774 *hours = (t1->seconds - t2->seconds) / 3600;
780 if (t1 == NULL || t2 == NULL || days == NULL) {
781 return XTIME_ERR_INVALID_ARG;
784 *days = (t1->seconds - t2->seconds) / 86400;
794 time_t timestamp = (time_t)t->seconds;
797 if (gmtime_r(×tamp, &tm_val) == NULL) {
801 return is_leap_year(tm_val.tm_year + 1900);
806 return XTIME_ERR_INVALID_ARG;
816 return XTIME_ERR_INVALID_ARG;
821 t->seconds = (t->seconds / 60) * 60;
829 return XTIME_ERR_INVALID_ARG;
834 t->seconds = (t->seconds / 3600) * 3600;
842 return XTIME_ERR_INVALID_ARG;
847 t->seconds = (t->seconds / 86400) * 86400;
854 if (t == NULL || result == NULL) {
855 return XTIME_ERR_INVALID_ARG;
862 if (err != XTIME_OK) {
867 time_t timestamp = (time_t)result->seconds;
870 if (gmtime_r(×tamp, &tm_val) == NULL) {
871 return XTIME_ERR_INVALID_TIME;
875 int days_to_monday = (tm_val.tm_wday == 0) ? 6 : (tm_val.tm_wday - 1);
882 if (t == NULL || result == NULL) {
883 return XTIME_ERR_INVALID_ARG;
888 time_t timestamp = (time_t)result->seconds;
891 if (gmtime_r(×tamp, &tm_val) == NULL) {
892 return XTIME_ERR_INVALID_TIME;
896 int days_to_subtract = tm_val.tm_mday - 1;
900 if (err != XTIME_OK) {
909 if (t == NULL || result == NULL) {
910 return XTIME_ERR_INVALID_ARG;
915 time_t timestamp = (time_t)result->seconds;
918 if (gmtime_r(×tamp, &tm_val) == NULL) {
919 return XTIME_ERR_INVALID_TIME;
923 int days_to_subtract = tm_val.tm_yday;
927 if (err != XTIME_OK) {
936 if (t == NULL || result == NULL) {
937 return XTIME_ERR_INVALID_ARG;
944 if (err != XTIME_OK) {
949 result->seconds += 86399;
950 result->nanoseconds = 999999999;
956 if (t == NULL || result == NULL) {
957 return XTIME_ERR_INVALID_ARG;
962 time_t timestamp = (time_t)result->seconds;
965 if (gmtime_r(×tamp, &tm_val) == NULL) {
966 return XTIME_ERR_INVALID_TIME;
970 int last_day = days_in_month(tm_val.tm_year + 1900, tm_val.tm_mon);
973 int days_to_add = last_day - tm_val.tm_mday;
977 if (err != XTIME_OK) {
983 if (err != XTIME_OK) {
988 result->seconds += 86399;
989 result->nanoseconds = 999999999;
995 if (t == NULL || result == NULL) {
996 return XTIME_ERR_INVALID_ARG;
1001 time_t timestamp = (time_t)result->seconds;
1004 if (gmtime_r(×tamp, &tm_val) == NULL) {
1005 return XTIME_ERR_INVALID_TIME;
1009 int year = tm_val.tm_year + 1900;
1010 int total_days_in_year = is_leap_year(year) ? 366 : 365;
1013 int days_to_add = (total_days_in_year - 1) - tm_val.tm_yday;
1017 if (err != XTIME_OK) {
1023 if (err != XTIME_OK) {
1028 result->seconds += 86399;
1029 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)