solidc
Robust collection of general-purpose cross-platform C libraries and data structures designed for rapid and safe development in C
Loading...
Searching...
No Matches
xtime.c
1#include "../include/xtime.h"
2#include "../include/macros.h"
3
4#include <ctype.h> // for isdigit
5#include <errno.h> // for errno
6#include <stdio.h> // for snprintf
7#include <stdlib.h> // for strtol, abs
8#include <string.h> // for strncpy, memset, strlen
9
10#if defined(__APPLE__) || defined(__unix__) || defined(__linux__)
11#include <sys/time.h> // for gettimeofday
12#endif
13
14// Platform-specific includes for high-resolution time
15#if defined(__APPLE__)
16#include <mach/mach_time.h>
17#endif
18
20static const int64_t NANOS_PER_SEC = 1000000000LL;
21static const int64_t NANOS_PER_MICRO = 1000LL;
22static const int64_t NANOS_PER_MILLI = 1000000LL;
23
25static const int16_t MAX_TZ_OFFSET = 1439; // ±23:59
26
30static inline bool is_leap_year(int year) { return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0); }
31
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};
37
38 if (month < 0 || month > 11) {
39 return 0;
40 }
41
42 if (month == 1 && is_leap_year(year)) { // February in leap year
43 return 29;
44 }
45
46 return days[month];
47}
48
50 if (t == NULL) {
51 return XTIME_ERR_INVALID_ARG;
52 }
53 *t = (xtime_t){.seconds = 0, .nanoseconds = 0, .tz_offset = 0, .has_tz = false};
54 return XTIME_OK;
55}
56
61static int16_t get_local_tz_offset(time_t timestamp) {
62 struct tm utc_tm, local_tm;
63
64 // Get both UTC and local time representations
65 if (gmtime_r(&timestamp, &utc_tm) == NULL) {
66 return 0;
67 }
68 if (localtime_r(&timestamp, &local_tm) == NULL) {
69 return 0;
70 }
71
72 // Calculate difference in minutes
73 // Account for day boundary crossings
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;
77
78 // Adjust for day boundaries
79 if (day_diff > 1) {
80 day_diff = -1; // Wrapped backwards
81 } else if (day_diff < -1) {
82 day_diff = 1; // Wrapped forwards
83 }
84
85 int total_minutes = (day_diff * 24 * 60) + (hour_diff * 60) + min_diff;
86
87 // Sanity check
88 if (abs(total_minutes) > MAX_TZ_OFFSET) {
89 return 0;
90 }
91
92 return (int16_t)total_minutes;
93}
94
96 if (t == NULL) {
97 return XTIME_ERR_INVALID_ARG;
98 }
99
100#if defined(_WIN32) || defined(_WIN64)
101 // Windows: Use GetSystemTimePreciseAsFileTime for high precision
102 FILETIME ft;
103 GetSystemTimePreciseAsFileTime(&ft);
104
105 // Convert FILETIME (100-nanosecond intervals since 1601-01-01) to Unix time
106 ULARGE_INTEGER uli;
107 uli.LowPart = ft.dwLowDateTime;
108 uli.HighPart = ft.dwHighDateTime;
109
110 // Windows epoch is 1601-01-01, Unix epoch is 1970-01-01
111 // Difference: 116444736000000000 * 100ns = 11644473600 seconds
112 const int64_t WINDOWS_TO_UNIX_EPOCH = 11644473600LL;
113 int64_t total_100ns = (int64_t)(uli.QuadPart);
114
115 t->seconds = (total_100ns / 10000000LL) - WINDOWS_TO_UNIX_EPOCH;
116 t->nanoseconds = (uint32_t)((total_100ns % 10000000LL) * 100);
117
118#elif defined(__APPLE__)
119 // macOS: Use clock_gettime with CLOCK_REALTIME (available since macOS 10.12)
120 struct timespec ts;
121 if (clock_gettime(CLOCK_REALTIME, &ts) != 0) {
122 // Fallback to gettimeofday if clock_gettime fails
123 struct timeval tv;
124 if (gettimeofday(&tv, NULL) != 0) {
125 return XTIME_ERR_SYSTEM;
126 }
127 t->seconds = (int64_t)tv.tv_sec;
128 t->nanoseconds = (uint32_t)(tv.tv_usec * 1000);
129 } else {
130 t->seconds = (int64_t)ts.tv_sec;
131 t->nanoseconds = (uint32_t)ts.tv_nsec;
132 }
133#else
134 // Linux/POSIX: Use clock_gettime with CLOCK_REALTIME
135 struct timespec ts;
136 if (clock_gettime(CLOCK_REALTIME, &ts) != 0) {
137 return XTIME_ERR_SYSTEM;
138 }
139
140 t->seconds = (int64_t)ts.tv_sec;
141 t->nanoseconds = (uint32_t)ts.tv_nsec;
142#endif
143
144 // Capture local timezone offset
145 t->tz_offset = get_local_tz_offset((time_t)t->seconds);
146 t->has_tz = true;
147
148 return XTIME_OK;
149}
150
152 xtime_error_t err = xtime_now(t);
153 if (err == XTIME_OK) {
154 t->tz_offset = 0;
155 t->has_tz = false;
156 }
157 return err;
158}
159
164static bool parse_tz_offset(const char* str, int16_t* offset) {
165 if (str == NULL || offset == NULL) {
166 return false;
167 }
168
169 // Skip whitespace
170 while (*str == ' ' || *str == '\t') {
171 str++;
172 }
173
174 // Check for sign
175 int sign = 1;
176 if (*str == '+') {
177 sign = 1;
178 str++;
179 } else if (*str == '-') {
180 sign = -1;
181 str++;
182 } else if (*str == 'Z' || *str == 'z') {
183 *offset = 0;
184 return true;
185 } else {
186 return false;
187 }
188
189 // Parse hours
190 if (!isdigit(str[0]) || !isdigit(str[1])) {
191 return false;
192 }
193 int hours = (str[0] - '0') * 10 + (str[1] - '0');
194 str += 2;
195
196 // Check for colon separator (optional)
197 if (*str == ':') {
198 str++;
199 }
200
201 // Parse minutes
202 int minutes = 0;
203 if (isdigit(str[0]) && isdigit(str[1])) {
204 minutes = (str[0] - '0') * 10 + (str[1] - '0');
205 }
206
207 // Validate range
208 if (hours > 23 || minutes > 59) {
209 return false;
210 }
211
212 int total_minutes = (hours * 60 + minutes) * sign;
213 if (abs(total_minutes) > MAX_TZ_OFFSET) {
214 return false;
215 }
216
217 *offset = (int16_t)total_minutes;
218 return true;
219}
220
221xtime_error_t xtime_parse(const char* str, const char* format, xtime_t* t) {
222 if (str == NULL || format == NULL || t == NULL) {
223 return XTIME_ERR_INVALID_ARG;
224 }
225
226 xtime_init(t);
227
228 struct tm tm_result;
229 memset(&tm_result, 0, sizeof(tm_result));
230
231 // 1. Parse the main date/time parts
232 char* remaining = strptime(str, format, &tm_result);
233 if (remaining == NULL) {
234 return XTIME_ERR_PARSE_FAILED;
235 }
236
237 // 2. Convert struct tm to Unix timestamp (UTC-based)
238 // Manual calculation to avoid mktime/timegm portability issues
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;
245
246 // Validate that the day is within valid range for the given month
247 int max_day = days_in_month(year, month);
248 if (day < 1 || day > max_day) {
249 return XTIME_ERR_DATE_OUT_OF_RANGE;
250 }
251
252 // Calculate days since Unix epoch (1970-01-01)
253 int64_t days = 0;
254
255 // Add years
256 for (int y = 1970; y < year; y++) {
257 days += is_leap_year(y) ? 366 : 365;
258 }
259
260 // Add months
261 for (int m = 0; m < month; m++) {
262 days += days_in_month(year, m);
263 }
264
265 // Add days (tm_mday is 1-based)
266 days += day - 1;
267
268 // Convert to seconds and add time components
269 t->seconds = (days * 86400) + (hour * 3600) + (minute * 60) + second;
270
271 // 3. Handle Fractional Seconds (The ".123" part from JS/JSON)
272 if (*remaining == '.') {
273 remaining++; // Skip the dot
274
275 int64_t multiplier = 100000000; // Start at 100ms
276 uint32_t nanos = 0;
277
278 // Read up to 9 digits (nanosecond precision)
279 while (isdigit(*remaining)) {
280 if (multiplier >= 1) {
281 nanos += (*remaining - '0') * multiplier;
282 multiplier /= 10;
283 }
284 remaining++;
285 }
286 t->nanoseconds = nanos;
287 }
288
289 // 4. Handle Timezone
290 // Skip any leftover whitespace
291 while (*remaining == ' ') remaining++;
292
293 if (*remaining != '\0') {
294 int16_t tz_offset = 0;
295 if (parse_tz_offset(remaining, &tz_offset)) {
296 t->tz_offset = tz_offset;
297 t->has_tz = true;
298 }
299 }
300
301 return XTIME_OK;
302}
303
304xtime_error_t xtime_format(const xtime_t* t, const char* format, char* buf, size_t buflen) {
305 if (t == NULL || format == NULL || buf == NULL || buflen == 0) {
306 return XTIME_ERR_INVALID_ARG;
307 }
308
309 // Handle special format: Unix timestamp
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;
314 }
315 return XTIME_OK;
316 }
317
318 // Adjust timestamp for timezone if present
319 time_t timestamp = (time_t)t->seconds;
320 if (t->has_tz && t->tz_offset != 0) {
321 // Add timezone offset to display time in local timezone
322 timestamp += (time_t)(t->tz_offset * 60);
323 }
324
325 struct tm tm_result;
326
327 // Use gmtime_r since we've already adjusted for timezone
328 if (gmtime_r(&timestamp, &tm_result) == NULL) {
329 return XTIME_ERR_INVALID_TIME;
330 }
331
332 // Format using strftime
333 size_t result = strftime(buf, buflen, format, &tm_result);
334 if (result == 0) {
335 return XTIME_ERR_BUFFER_TOO_SMALL;
336 }
337
338 // Append timezone if format contains %z and we have timezone info
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 ? '+' : '-';
344
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;
348 }
349 }
350
351 return XTIME_OK;
352}
353
354xtime_error_t xtime_format_utc(const xtime_t* t, const char* format, char* buf, size_t buflen) {
355 if (t == NULL || format == NULL || buf == NULL || buflen == 0) {
356 return XTIME_ERR_INVALID_ARG;
357 }
358
359 // Handle special format: Unix timestamp
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;
364 }
365 return XTIME_OK;
366 }
367
368 // Convert to struct tm in UTC
369 time_t timestamp = (time_t)t->seconds;
370 struct tm tm_result;
371
372 if (gmtime_r(&timestamp, &tm_result) == NULL) {
373 return XTIME_ERR_INVALID_TIME;
374 }
375
376 // Format using strftime
377 size_t result = strftime(buf, buflen, format, &tm_result);
378 if (result == 0) {
379 return XTIME_ERR_BUFFER_TOO_SMALL;
380 }
381
382 return XTIME_OK;
383}
384
385xtime_error_t xtime_to_json(const xtime_t* t, char* buf, size_t buflen) {
386 if (t == NULL || buf == NULL || buflen == 0) {
387 return XTIME_ERR_INVALID_ARG;
388 }
389
390 // Adjust seconds for timezone if present for the breakdown
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);
394 }
395
396 struct tm tm_val;
397 if (gmtime_r(&timestamp, &tm_val) == NULL) {
398 return XTIME_ERR_INVALID_TIME;
399 }
400
401 // 1. Format Date and Time: YYYY-MM-DDTHH:MM:SS
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);
404
405 if (written < 0 || (size_t)written >= buflen) {
406 return XTIME_ERR_BUFFER_TOO_SMALL;
407 }
408 size_t current_len = (size_t)written;
409
410 // 2. Append Nanoseconds: .nnnnnnnnn
411 // RFC 3339 allows fractional seconds. We print full precision.
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;
416 }
417 current_len += (size_t)written;
418 }
419
420 // 3. Append Timezone: Z or ±HH:MM
421 if (!t->has_tz || t->tz_offset == 0) {
422 // UTC
423 if (current_len + 1 >= buflen) return XTIME_ERR_BUFFER_TOO_SMALL;
424 buf[current_len++] = 'Z';
425 buf[current_len] = '\0';
426 } else {
427 // Offset
428 int hrs = abs(t->tz_offset) / 60;
429 int mins = abs(t->tz_offset) % 60;
430 char sign = (t->tz_offset >= 0) ? '+' : '-';
431
432 written = snprintf(buf + current_len, buflen - current_len, "%c%02d:%02d", sign, hrs, mins);
433
434 if (written < 0 || current_len + (size_t)written >= buflen) {
435 return XTIME_ERR_BUFFER_TOO_SMALL;
436 }
437 }
438
439 return XTIME_OK;
440}
441
442int64_t xtime_to_unix(const xtime_t* t) {
443 if (t == NULL) {
444 return -1;
445 }
446 return t->seconds;
447}
448
449xtime_error_t xtime_from_unix(int64_t timestamp, xtime_t* t) {
450 if (t == NULL) {
451 return XTIME_ERR_INVALID_ARG;
452 }
453
454 t->seconds = timestamp;
455 t->nanoseconds = 0;
456 t->tz_offset = 0;
457 t->has_tz = false;
458
459 return XTIME_OK;
460}
461
463 if (t == NULL) {
464 return XTIME_ERR_INVALID_ARG;
465 }
466
467 t->seconds += seconds;
468 return XTIME_OK;
469}
470
471int xtime_compare(const xtime_t* t1, const xtime_t* t2) {
472 if (t1 == NULL || t2 == NULL) {
473 return -2;
474 }
475
476 if (t1->seconds < t2->seconds) {
477 return -1;
478 }
479 if (t1->seconds > t2->seconds) {
480 return 1;
481 }
482
483 // Seconds are equal, compare nanoseconds
484 if (t1->nanoseconds < t2->nanoseconds) {
485 return -1;
486 }
487 if (t1->nanoseconds > t2->nanoseconds) {
488 return 1;
489 }
490
491 return 0;
492}
493
494xtime_error_t xtime_diff(const xtime_t* t1, const xtime_t* t2, double* diff) {
495 if (t1 == NULL || t2 == NULL || diff == NULL) {
496 return XTIME_ERR_INVALID_ARG;
497 }
498
499 int64_t sec_diff = t1->seconds - t2->seconds;
500 int64_t nano_diff = (int64_t)t1->nanoseconds - (int64_t)t2->nanoseconds;
501
502 *diff = (double)sec_diff + ((double)nano_diff / (double)NANOS_PER_SEC);
503
504 return XTIME_OK;
505}
506
508 switch (err) {
509 case XTIME_OK:
510 return "Success";
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";
523 default:
524 return "Unknown error";
525 }
526}
527
528// =============== Helpers ===========================
529
531 if (t == NULL) {
532 return XTIME_ERR_INVALID_ARG;
533 }
534
535 int64_t total_nanos = (int64_t)t->nanoseconds + nanos;
536
537 // Handle overflow/underflow into seconds
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));
546 } else {
547 t->nanoseconds = (uint32_t)total_nanos;
548 }
549
550 return XTIME_OK;
551}
552
554 if (t == NULL) {
555 return XTIME_ERR_INVALID_ARG;
556 }
557
558 return xtime_add_nanoseconds(t, micros * NANOS_PER_MICRO);
559}
560
562 if (t == NULL) {
563 return XTIME_ERR_INVALID_ARG;
564 }
565
566 return xtime_add_nanoseconds(t, millis * NANOS_PER_MILLI);
567}
568
570 if (t == NULL) {
571 return XTIME_ERR_INVALID_ARG;
572 }
573
574 return xtime_add_seconds(t, minutes * 60);
575}
576
578 if (t == NULL) {
579 return XTIME_ERR_INVALID_ARG;
580 }
581
582 return xtime_add_seconds(t, hours * 3600);
583}
584
586 if (t == NULL) {
587 return XTIME_ERR_INVALID_ARG;
588 }
589
590 return xtime_add_seconds(t, days * 86400);
591}
592
594 if (t == NULL) {
595 return XTIME_ERR_INVALID_ARG;
596 }
597
598 // Store the time-of-day portion
599 int64_t time_of_day_seconds = t->seconds % 86400;
600 if (time_of_day_seconds < 0) {
601 time_of_day_seconds += 86400;
602 }
603
604 // Get the date components
605 time_t timestamp = (time_t)t->seconds;
606 struct tm tm_val;
607
608 if (gmtime_r(&timestamp, &tm_val) == NULL) {
609 return XTIME_ERR_INVALID_TIME;
610 }
611
612 // Add months
613 int total_months = tm_val.tm_mon + months;
614 int year_adjust = total_months / 12;
615 int new_month = total_months % 12;
616
617 // Handle negative months
618 if (new_month < 0) {
619 new_month += 12;
620 year_adjust -= 1;
621 }
622
623 int new_year = tm_val.tm_year + 1900 + year_adjust;
624
625 // Adjust day if it exceeds the new month's length
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;
628
629 // Calculate days since Unix epoch (1970-01-01)
630 // This is more reliable than using mktime/timegm
631 int64_t days = 0;
632
633 // Add years
634 for (int y = 1970; y < new_year; y++) {
635 days += is_leap_year(y) ? 366 : 365;
636 }
637
638 // Add months
639 for (int m = 0; m < new_month; m++) {
640 days += days_in_month(new_year, m);
641 }
642
643 // Add days
644 days += new_day - 1; // -1 because day 1 is the first day
645
646 // Reconstruct the timestamp
647 t->seconds = (days * 86400) + time_of_day_seconds;
648
649 return XTIME_OK;
650}
651
653 if (t == NULL) {
654 return XTIME_ERR_INVALID_ARG;
655 }
656
657 // Store the time-of-day portion
658 int64_t time_of_day_seconds = t->seconds % 86400;
659 if (time_of_day_seconds < 0) {
660 time_of_day_seconds += 86400;
661 }
662
663 // Get the date components
664 time_t timestamp = (time_t)t->seconds;
665 struct tm tm_val;
666
667 if (gmtime_r(&timestamp, &tm_val) == NULL) {
668 return XTIME_ERR_INVALID_TIME;
669 }
670
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;
674
675 // Handle Feb 29 in non-leap years
676 if (new_month == 1 && new_day == 29) {
677 if (!is_leap_year(new_year)) {
678 new_day = 28;
679 }
680 }
681
682 // Calculate days since Unix epoch (1970-01-01)
683 int64_t days = 0;
684
685 // Add years
686 for (int y = 1970; y < new_year; y++) {
687 days += is_leap_year(y) ? 366 : 365;
688 }
689
690 // Add months
691 for (int m = 0; m < new_month; m++) {
692 days += days_in_month(new_year, m);
693 }
694
695 // Add days
696 days += new_day - 1; // -1 because day 1 is the first day
697
698 // Reconstruct the timestamp
699 t->seconds = (days * 86400) + time_of_day_seconds;
700
701 return XTIME_OK;
702}
703
704xtime_error_t xtime_diff_nanos(const xtime_t* t1, const xtime_t* t2, int64_t* nanos) {
705 if (t1 == NULL || t2 == NULL || nanos == NULL) {
706 return XTIME_ERR_INVALID_ARG;
707 }
708
709 int64_t sec_diff = t1->seconds - t2->seconds;
710 int64_t nano_diff = (int64_t)t1->nanoseconds - (int64_t)t2->nanoseconds;
711
712 *nanos = (sec_diff * NANOS_PER_SEC) + nano_diff;
713
714 return XTIME_OK;
715}
716
717xtime_error_t xtime_diff_micros(const xtime_t* t1, const xtime_t* t2, int64_t* micros) {
718 if (t1 == NULL || t2 == NULL || micros == NULL) {
719 return XTIME_ERR_INVALID_ARG;
720 }
721
722 int64_t nanos;
723 xtime_error_t err = xtime_diff_nanos(t1, t2, &nanos);
724 if (err != XTIME_OK) {
725 return err;
726 }
727
728 *micros = nanos / NANOS_PER_MICRO;
729
730 return XTIME_OK;
731}
732
733xtime_error_t xtime_diff_millis(const xtime_t* t1, const xtime_t* t2, int64_t* millis) {
734 if (t1 == NULL || t2 == NULL || millis == NULL) {
735 return XTIME_ERR_INVALID_ARG;
736 }
737
738 int64_t nanos;
739 xtime_error_t err = xtime_diff_nanos(t1, t2, &nanos);
740 if (err != XTIME_OK) {
741 return err;
742 }
743
744 *millis = nanos / NANOS_PER_MILLI;
745
746 return XTIME_OK;
747}
748
749xtime_error_t xtime_diff_seconds(const xtime_t* t1, const xtime_t* t2, int64_t* seconds) {
750 if (t1 == NULL || t2 == NULL || seconds == NULL) {
751 return XTIME_ERR_INVALID_ARG;
752 }
753
754 *seconds = t1->seconds - t2->seconds;
755
756 return XTIME_OK;
757}
758
759xtime_error_t xtime_diff_minutes(const xtime_t* t1, const xtime_t* t2, int64_t* minutes) {
760 if (t1 == NULL || t2 == NULL || minutes == NULL) {
761 return XTIME_ERR_INVALID_ARG;
762 }
763
764 *minutes = (t1->seconds - t2->seconds) / 60;
765
766 return XTIME_OK;
767}
768
769xtime_error_t xtime_diff_hours(const xtime_t* t1, const xtime_t* t2, int64_t* hours) {
770 if (t1 == NULL || t2 == NULL || hours == NULL) {
771 return XTIME_ERR_INVALID_ARG;
772 }
773
774 *hours = (t1->seconds - t2->seconds) / 3600;
775
776 return XTIME_OK;
777}
778
779xtime_error_t xtime_diff_days(const xtime_t* t1, const xtime_t* t2, int64_t* days) {
780 if (t1 == NULL || t2 == NULL || days == NULL) {
781 return XTIME_ERR_INVALID_ARG;
782 }
783
784 *days = (t1->seconds - t2->seconds) / 86400;
785
786 return XTIME_OK;
787}
788
790 if (t == NULL) {
791 return false;
792 }
793
794 time_t timestamp = (time_t)t->seconds;
795 struct tm tm_val;
796
797 if (gmtime_r(&timestamp, &tm_val) == NULL) {
798 return false;
799 }
800
801 return is_leap_year(tm_val.tm_year + 1900);
802}
803
805 if (t == NULL) {
806 return XTIME_ERR_INVALID_ARG;
807 }
808
809 t->nanoseconds = 0;
810
811 return XTIME_OK;
812}
813
815 if (t == NULL) {
816 return XTIME_ERR_INVALID_ARG;
817 }
818
819 // Truncate to the start of the current minute
820 // Remove seconds and nanoseconds
821 t->seconds = (t->seconds / 60) * 60;
822 t->nanoseconds = 0;
823
824 return XTIME_OK;
825}
826
828 if (t == NULL) {
829 return XTIME_ERR_INVALID_ARG;
830 }
831
832 // Truncate to the start of the current hour
833 // Remove minutes, seconds, and nanoseconds
834 t->seconds = (t->seconds / 3600) * 3600;
835 t->nanoseconds = 0;
836
837 return XTIME_OK;
838}
839
841 if (t == NULL) {
842 return XTIME_ERR_INVALID_ARG;
843 }
844
845 // Truncate to the start of the current day (00:00:00 UTC)
846 // Remove hours, minutes, seconds, and nanoseconds
847 t->seconds = (t->seconds / 86400) * 86400;
848 t->nanoseconds = 0;
849
850 return XTIME_OK;
851}
852
854 if (t == NULL || result == NULL) {
855 return XTIME_ERR_INVALID_ARG;
856 }
857
858 *result = *t;
859
860 // First truncate to start of day
862 if (err != XTIME_OK) {
863 return err;
864 }
865
866 // Get the day of week
867 time_t timestamp = (time_t)result->seconds;
868 struct tm tm_val;
869
870 if (gmtime_r(&timestamp, &tm_val) == NULL) {
871 return XTIME_ERR_INVALID_TIME;
872 }
873
874 // Calculate days to subtract to get to Monday (tm_wday: 0=Sun, 1=Mon, ...)
875 int days_to_monday = (tm_val.tm_wday == 0) ? 6 : (tm_val.tm_wday - 1);
876
877 // Subtract days to get to Monday
878 return xtime_add_days(result, -days_to_monday);
879}
880
882 if (t == NULL || result == NULL) {
883 return XTIME_ERR_INVALID_ARG;
884 }
885
886 *result = *t;
887
888 time_t timestamp = (time_t)result->seconds;
889 struct tm tm_val;
890
891 if (gmtime_r(&timestamp, &tm_val) == NULL) {
892 return XTIME_ERR_INVALID_TIME;
893 }
894
895 // Calculate how many days to subtract to get to day 1
896 int days_to_subtract = tm_val.tm_mday - 1;
897
898 // First truncate to start of current day
900 if (err != XTIME_OK) {
901 return err;
902 }
903
904 // Then subtract days to get to the 1st
905 return xtime_add_days(result, -days_to_subtract);
906}
907
909 if (t == NULL || result == NULL) {
910 return XTIME_ERR_INVALID_ARG;
911 }
912
913 *result = *t;
914
915 time_t timestamp = (time_t)result->seconds;
916 struct tm tm_val;
917
918 if (gmtime_r(&timestamp, &tm_val) == NULL) {
919 return XTIME_ERR_INVALID_TIME;
920 }
921
922 // Calculate days since January 1st (tm_yday is 0-based)
923 int days_to_subtract = tm_val.tm_yday;
924
925 // First truncate to start of current day
927 if (err != XTIME_OK) {
928 return err;
929 }
930
931 // Then subtract days to get to Jan 1
932 return xtime_add_days(result, -days_to_subtract);
933}
934
936 if (t == NULL || result == NULL) {
937 return XTIME_ERR_INVALID_ARG;
938 }
939
940 *result = *t;
941
942 // Truncate to start of day
944 if (err != XTIME_OK) {
945 return err;
946 }
947
948 // Add 86399 seconds (23:59:59) and 999999999 nanoseconds
949 result->seconds += 86399;
950 result->nanoseconds = 999999999;
951
952 return XTIME_OK;
953}
954
956 if (t == NULL || result == NULL) {
957 return XTIME_ERR_INVALID_ARG;
958 }
959
960 *result = *t;
961
962 time_t timestamp = (time_t)result->seconds;
963 struct tm tm_val;
964
965 if (gmtime_r(&timestamp, &tm_val) == NULL) {
966 return XTIME_ERR_INVALID_TIME;
967 }
968
969 // Get last day of month
970 int last_day = days_in_month(tm_val.tm_year + 1900, tm_val.tm_mon);
971
972 // Calculate days to add to get to last day
973 int days_to_add = last_day - tm_val.tm_mday;
974
975 // Truncate to start of current day
977 if (err != XTIME_OK) {
978 return err;
979 }
980
981 // Add days to get to last day
982 err = xtime_add_days(result, days_to_add);
983 if (err != XTIME_OK) {
984 return err;
985 }
986
987 // Set to end of day (23:59:59.999999999)
988 result->seconds += 86399;
989 result->nanoseconds = 999999999;
990
991 return XTIME_OK;
992}
993
995 if (t == NULL || result == NULL) {
996 return XTIME_ERR_INVALID_ARG;
997 }
998
999 *result = *t;
1000
1001 time_t timestamp = (time_t)result->seconds;
1002 struct tm tm_val;
1003
1004 if (gmtime_r(&timestamp, &tm_val) == NULL) {
1005 return XTIME_ERR_INVALID_TIME;
1006 }
1007
1008 // Calculate if leap year
1009 int year = tm_val.tm_year + 1900;
1010 int total_days_in_year = is_leap_year(year) ? 366 : 365;
1011
1012 // Calculate days to add to get to Dec 31
1013 int days_to_add = (total_days_in_year - 1) - tm_val.tm_yday;
1014
1015 // Truncate to start of current day
1017 if (err != XTIME_OK) {
1018 return err;
1019 }
1020
1021 // Add days to get to Dec 31
1022 err = xtime_add_days(result, days_to_add);
1023 if (err != XTIME_OK) {
1024 return err;
1025 }
1026
1027 // Set to end of day (23:59:59.999999999)
1028 result->seconds += 86399;
1029 result->nanoseconds = 999999999;
1030
1031 return XTIME_OK;
1032}
xtime_error_t xtime_diff_minutes(const xtime_t *t1, const xtime_t *t2, int64_t *minutes)
Definition xtime.c:759
xtime_error_t xtime_truncate_to_minute(xtime_t *t)
Definition xtime.c:814
xtime_error_t xtime_from_unix(int64_t timestamp, xtime_t *t)
Definition xtime.c:449
xtime_error_t xtime_diff(const xtime_t *t1, const xtime_t *t2, double *diff)
Definition xtime.c:494
xtime_error_t xtime_format_utc(const xtime_t *t, const char *format, char *buf, size_t buflen)
Definition xtime.c:354
bool xtime_is_leap_year(const xtime_t *t)
Definition xtime.c:789
xtime_error_t xtime_add_minutes(xtime_t *t, int64_t minutes)
Definition xtime.c:569
xtime_error_t xtime_end_of_day(const xtime_t *t, xtime_t *result)
Definition xtime.c:935
xtime_error_t xtime_diff_seconds(const xtime_t *t1, const xtime_t *t2, int64_t *seconds)
Definition xtime.c:749
xtime_error_t xtime_utc_now(xtime_t *t)
Definition xtime.c:151
xtime_error_t xtime_parse(const char *str, const char *format, xtime_t *t)
Definition xtime.c:221
xtime_error_t xtime_add_years(xtime_t *t, int years)
Definition xtime.c:652
int xtime_compare(const xtime_t *t1, const xtime_t *t2)
Definition xtime.c:471
xtime_error_t xtime_add_months(xtime_t *t, int months)
Definition xtime.c:593
const char * xtime_strerror(xtime_error_t err)
Definition xtime.c:507
xtime_error_t xtime_truncate_to_hour(xtime_t *t)
Definition xtime.c:827
xtime_error_t xtime_add_milliseconds(xtime_t *t, int64_t millis)
Definition xtime.c:561
xtime_error_t xtime_end_of_month(const xtime_t *t, xtime_t *result)
Definition xtime.c:955
xtime_error_t
Definition xtime.h:37
xtime_error_t xtime_to_json(const xtime_t *t, char *buf, size_t buflen)
Definition xtime.c:385
xtime_error_t xtime_add_nanoseconds(xtime_t *t, int64_t nanos)
Definition xtime.c:530
xtime_error_t xtime_format(const xtime_t *t, const char *format, char *buf, size_t buflen)
Definition xtime.c:304
xtime_error_t xtime_diff_nanos(const xtime_t *t1, const xtime_t *t2, int64_t *nanos)
Definition xtime.c:704
xtime_error_t xtime_start_of_month(const xtime_t *t, xtime_t *result)
Definition xtime.c:881
xtime_error_t xtime_truncate_to_day(xtime_t *t)
Definition xtime.c:840
xtime_error_t xtime_now(xtime_t *t)
Definition xtime.c:95
xtime_error_t xtime_diff_days(const xtime_t *t1, const xtime_t *t2, int64_t *days)
Definition xtime.c:779
xtime_error_t xtime_diff_micros(const xtime_t *t1, const xtime_t *t2, int64_t *micros)
Definition xtime.c:717
xtime_error_t xtime_init(xtime_t *t)
Definition xtime.c:49
xtime_error_t xtime_truncate_to_second(xtime_t *t)
Definition xtime.c:804
xtime_error_t xtime_diff_hours(const xtime_t *t1, const xtime_t *t2, int64_t *hours)
Definition xtime.c:769
xtime_error_t xtime_add_seconds(xtime_t *t, int64_t seconds)
Definition xtime.c:462
xtime_error_t xtime_add_days(xtime_t *t, int64_t days)
Definition xtime.c:585
xtime_error_t xtime_start_of_week(const xtime_t *t, xtime_t *result)
Definition xtime.c:853
int64_t xtime_to_unix(const xtime_t *t)
Definition xtime.c:442
xtime_error_t xtime_add_microseconds(xtime_t *t, int64_t micros)
Definition xtime.c:553
xtime_error_t xtime_diff_millis(const xtime_t *t1, const xtime_t *t2, int64_t *millis)
Definition xtime.c:733
xtime_error_t xtime_end_of_year(const xtime_t *t, xtime_t *result)
Definition xtime.c:994
xtime_error_t xtime_add_hours(xtime_t *t, int64_t hours)
Definition xtime.c:577
xtime_error_t xtime_start_of_year(const xtime_t *t, xtime_t *result)
Definition xtime.c:908