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