pacemaker 3.0.1-16e74fc4da
Scalable High-Availability cluster resource manager
Loading...
Searching...
No Matches
iso8601.c
Go to the documentation of this file.
1/*
2 * Copyright 2005-2025 the Pacemaker project contributors
3 *
4 * The version control history for this file may have further details.
5 *
6 * This source code is licensed under the GNU Lesser General Public License
7 * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
8 */
9
10/*
11 * References:
12 * https://en.wikipedia.org/wiki/ISO_8601
13 * http://www.staff.science.uu.nl/~gent0113/calendar/isocalendar.htm
14 */
15
16#include <crm_internal.h>
17#include <crm/crm.h>
18#include <time.h>
19#include <ctype.h>
20#include <inttypes.h>
21#include <limits.h> // INT_MIN, INT_MAX
22#include <string.h>
23#include <stdbool.h>
24#include <crm/common/iso8601.h>
26#include "crmcommon_private.h"
27
28/*
29 * Andrew's code was originally written for OSes whose "struct tm" contains:
30 * long tm_gmtoff; :: Seconds east of UTC
31 * const char *tm_zone; :: Timezone abbreviation
32 * Some OSes lack these, instead having:
33 * time_t (or long) timezone;
34 :: "difference between UTC and local standard time"
35 * char *tzname[2] = { "...", "..." };
36 * I (David Lee) confess to not understanding the details. So my attempted
37 * generalisations for where their use is necessary may be flawed.
38 *
39 * 1. Does "difference between ..." subtract the same or opposite way?
40 * 2. Should it use "altzone" instead of "timezone"?
41 * 3. Should it use tzname[0] or tzname[1]? Interaction with timezone/altzone?
42 */
43#if defined(HAVE_STRUCT_TM_TM_GMTOFF)
44# define GMTOFF(tm) ((tm)->tm_gmtoff)
45#else
46/* Note: extern variable; macro argument not actually used. */
47# define GMTOFF(tm) (-timezone+daylight)
48#endif
49
50#define HOUR_SECONDS (60 * 60)
51#define DAY_SECONDS (HOUR_SECONDS * 24)
52
65#define valid_sec_usec(sec, usec) \
66 ((QB_ABS(usec) < QB_TIME_US_IN_SEC) \
67 && (((sec) == 0) || ((usec) == 0) || (((sec) < 0) == ((usec) < 0))))
68
69// A date/time or duration
70struct crm_time_s {
71 int years; // Calendar year (date/time) or number of years (duration)
72 int months; // Number of months (duration only)
73 int days; // Ordinal day of year (date/time) or number of days (duration)
74 int seconds; // Seconds of day (date/time) or number of seconds (duration)
75 int offset; // Seconds offset from UTC (date/time only)
76 bool duration; // True if duration
77};
78
79static crm_time_t *parse_date(const char *date_str);
80
81static crm_time_t *
82crm_get_utc_time(const crm_time_t *dt)
83{
84 crm_time_t *utc = NULL;
85
86 if (dt == NULL) {
87 errno = EINVAL;
88 return NULL;
89 }
90
92 utc->years = dt->years;
93 utc->days = dt->days;
94 utc->seconds = dt->seconds;
95 utc->offset = 0;
96
97 if (dt->offset) {
98 crm_time_add_seconds(utc, -dt->offset);
99 } else {
100 /* Durations (which are the only things that can include months, never have a timezone */
101 utc->months = dt->months;
102 }
103
104 crm_time_log(LOG_TRACE, "utc-source", dt,
106 crm_time_log(LOG_TRACE, "utc-target", utc,
108 return utc;
109}
110
112crm_time_new(const char *date_time)
113{
114 tzset();
115 if (date_time == NULL) {
116 return pcmk__copy_timet(time(NULL));
117 }
118 return parse_date(date_time);
119}
120
130{
131 return (crm_time_t *) pcmk__assert_alloc(1, sizeof(crm_time_t));
132}
133
141bool
143{
144 // Any nonzero member indicates something has been done to t
145 return (t != NULL) && (t->years || t->months || t->days || t->seconds
146 || t->offset || t->duration);
147}
148
149void
151{
152 if (dt == NULL) {
153 return;
154 }
155 free(dt);
156}
157
158static int
159year_days(int year)
160{
161 int d = 365;
162
163 if (crm_time_leapyear(year)) {
164 d++;
165 }
166 return d;
167}
168
169/* From http://myweb.ecu.edu/mccartyr/ISOwdALG.txt :
170 *
171 * 5. Find the Jan1Weekday for Y (Monday=1, Sunday=7)
172 * YY = (Y-1) % 100
173 * C = (Y-1) - YY
174 * G = YY + YY/4
175 * Jan1Weekday = 1 + (((((C / 100) % 4) x 5) + G) % 7)
176 */
177int
179{
180 int YY = (year - 1) % 100;
181 int C = (year - 1) - YY;
182 int G = YY + YY / 4;
183 int jan1 = 1 + (((((C / 100) % 4) * 5) + G) % 7);
184
185 crm_trace("YY=%d, C=%d, G=%d", YY, C, G);
186 crm_trace("January 1 %.4d: %d", year, jan1);
187 return jan1;
188}
189
190int
192{
193 int weeks = 52;
194 int jan1 = crm_time_january1_weekday(year);
195
196 /* if jan1 == thursday */
197 if (jan1 == 4) {
198 weeks++;
199 } else {
200 jan1 = crm_time_january1_weekday(year + 1);
201 /* if dec31 == thursday aka. jan1 of next year is a friday */
202 if (jan1 == 5) {
203 weeks++;
204 }
205
206 }
207 return weeks;
208}
209
210// Jan-Dec plus Feb of leap years
211static int month_days[13] = {
212 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 29
213};
214
223int
224crm_time_days_in_month(int month, int year)
225{
226 if ((month < 1) || (month > 12) || (year < 1)) {
227 return 0;
228 }
229 if ((month == 2) && crm_time_leapyear(year)) {
230 month = 13;
231 }
232 return month_days[month - 1];
233}
234
235bool
237{
238 gboolean is_leap = FALSE;
239
240 if (year % 4 == 0) {
241 is_leap = TRUE;
242 }
243 if (year % 100 == 0 && year % 400 != 0) {
244 is_leap = FALSE;
245 }
246 return is_leap;
247}
248
260static int
261get_ordinal_days(uint32_t y, uint32_t m, uint32_t d)
262{
263 int result = 0;
264
265 CRM_CHECK((y > 0) && (y <= INT_MAX) && (m >= 1) && (m <= 12)
266 && (d >= 1) && (d <= 31), return 0);
267
268 result = d;
269 for (int lpc = 1; lpc < m; lpc++) {
271 }
272 return result;
273}
274
275void
276crm_time_log_alias(int log_level, const char *file, const char *function,
277 int line, const char *prefix, const crm_time_t *date_time,
278 int flags)
279{
280 char *date_s = crm_time_as_string(date_time, flags);
281
282 if (log_level == LOG_STDOUT) {
283 printf("%s%s%s\n",
284 (prefix? prefix : ""), (prefix? ": " : ""), date_s);
285 } else {
286 do_crm_log_alias(log_level, file, function, line, "%s%s%s",
287 (prefix? prefix : ""), (prefix? ": " : ""), date_s);
288 }
289 free(date_s);
290}
291
292static void
293crm_time_get_sec(int sec, uint32_t *h, uint32_t *m, uint32_t *s)
294{
295 uint32_t hours, minutes, seconds;
296
297 seconds = QB_ABS(sec);
298
299 hours = seconds / HOUR_SECONDS;
300 seconds -= HOUR_SECONDS * hours;
301
302 minutes = seconds / 60;
303 seconds -= 60 * minutes;
304
305 crm_trace("%d == %.2" PRIu32 ":%.2" PRIu32 ":%.2" PRIu32,
306 sec, hours, minutes, seconds);
307
308 *h = hours;
309 *m = minutes;
310 *s = seconds;
311}
312
313int
314crm_time_get_timeofday(const crm_time_t *dt, uint32_t *h, uint32_t *m,
315 uint32_t *s)
316{
317 crm_time_get_sec(dt->seconds, h, m, s);
318 return TRUE;
319}
320
321int
322crm_time_get_timezone(const crm_time_t *dt, uint32_t *h, uint32_t *m)
323{
324 uint32_t s;
325
326 crm_time_get_sec(dt->seconds, h, m, &s);
327 return TRUE;
328}
329
330long long
332{
333 int lpc;
334 crm_time_t *utc = NULL;
335 long long in_seconds = 0;
336
337 if (dt == NULL) {
338 return 0;
339 }
340
341 // @TODO This is inefficient if dt is already in UTC
342 utc = crm_get_utc_time(dt);
343 if (utc == NULL) {
344 return 0;
345 }
346
347 // @TODO We should probably use <= if dt is a duration
348 for (lpc = 1; lpc < utc->years; lpc++) {
349 long long dmax = year_days(lpc);
350
351 in_seconds += DAY_SECONDS * dmax;
352 }
353
354 /* utc->months can be set only for durations. By definition, the value
355 * varies depending on the (unknown) start date to which the duration will
356 * be applied. Assume 30-day months so that something vaguely sane happens
357 * in this case.
358 */
359 if (utc->months > 0) {
360 in_seconds += DAY_SECONDS * 30 * (long long) (utc->months);
361 }
362
363 if (utc->days > 0) {
364 in_seconds += DAY_SECONDS * (long long) (utc->days - 1);
365 }
366 in_seconds += utc->seconds;
367
368 crm_time_free(utc);
369 return in_seconds;
370}
371
372#define EPOCH_SECONDS 62135596800ULL /* Calculated using crm_time_get_seconds() */
373long long
375{
376 return (dt == NULL)? 0 : (crm_time_get_seconds(dt) - EPOCH_SECONDS);
377}
378
379int
380crm_time_get_gregorian(const crm_time_t *dt, uint32_t *y, uint32_t *m,
381 uint32_t *d)
382{
383 int months = 0;
384 int days = dt->days;
385
386 if(dt->years != 0) {
387 for (months = 1; months <= 12 && days > 0; months++) {
388 int mdays = crm_time_days_in_month(months, dt->years);
389
390 if (mdays >= days) {
391 break;
392 } else {
393 days -= mdays;
394 }
395 }
396
397 } else if (dt->months) {
398 /* This is a duration including months, don't convert the days field */
399 months = dt->months;
400
401 } else {
402 /* This is a duration not including months, still don't convert the days field */
403 }
404
405 *y = dt->years;
406 *m = months;
407 *d = days;
408 crm_trace("%.4d-%.3d -> %.4d-%.2d-%.2d", dt->years, dt->days, dt->years, months, days);
409 return TRUE;
410}
411
412int
413crm_time_get_ordinal(const crm_time_t *dt, uint32_t *y, uint32_t *d)
414{
415 *y = dt->years;
416 *d = dt->days;
417 return TRUE;
418}
419
420int
421crm_time_get_isoweek(const crm_time_t *dt, uint32_t *y, uint32_t *w,
422 uint32_t *d)
423{
424 /*
425 * Monday 29 December 2008 is written "2009-W01-1"
426 * Sunday 3 January 2010 is written "2009-W53-7"
427 */
428 int year_num = 0;
429 int jan1 = crm_time_january1_weekday(dt->years);
430 int h = -1;
431
432 CRM_CHECK(dt->days > 0, return FALSE);
433
434/* 6. Find the Weekday for Y M D */
435 h = dt->days + jan1 - 1;
436 *d = 1 + ((h - 1) % 7);
437
438/* 7. Find if Y M D falls in YearNumber Y-1, WeekNumber 52 or 53 */
439 if (dt->days <= (8 - jan1) && jan1 > 4) {
440 crm_trace("year--, jan1=%d", jan1);
441 year_num = dt->years - 1;
442 *w = crm_time_weeks_in_year(year_num);
443
444 } else {
445 year_num = dt->years;
446 }
447
448/* 8. Find if Y M D falls in YearNumber Y+1, WeekNumber 1 */
449 if (year_num == dt->years) {
450 int dmax = year_days(year_num);
451 int correction = 4 - *d;
452
453 if ((dmax - dt->days) < correction) {
454 crm_trace("year++, jan1=%d, i=%d vs. %d", jan1, dmax - dt->days, correction);
455 year_num = dt->years + 1;
456 *w = 1;
457 }
458 }
459
460/* 9. Find if Y M D falls in YearNumber Y, WeekNumber 1 through 53 */
461 if (year_num == dt->years) {
462 int j = dt->days + (7 - *d) + (jan1 - 1);
463
464 *w = j / 7;
465 if (jan1 > 4) {
466 *w -= 1;
467 }
468 }
469
470 *y = year_num;
471 crm_trace("Converted %.4d-%.3d to %.4" PRIu32 "-W%.2" PRIu32 "-%" PRIu32,
472 dt->years, dt->days, *y, *w, *d);
473 return TRUE;
474}
475
476#define DATE_MAX 128
477
488static inline void
489sec_usec_as_string(long long sec, int usec, char *buf, size_t *offset)
490{
491 *offset += snprintf(buf + *offset, DATE_MAX - *offset, "%s%lld.%06d",
492 ((sec == 0) && (usec < 0))? "-" : "",
493 sec, QB_ABS(usec));
494}
495
505static void
506crm_duration_as_string(const crm_time_t *dt, int usec, bool show_usec,
507 char *result)
508{
509 size_t offset = 0;
510
511 pcmk__assert(valid_sec_usec(dt->seconds, usec));
512
513 if (dt->years) {
514 offset += snprintf(result + offset, DATE_MAX - offset, "%4d year%s ",
515 dt->years, pcmk__plural_s(dt->years));
516 }
517 if (dt->months) {
518 offset += snprintf(result + offset, DATE_MAX - offset, "%2d month%s ",
519 dt->months, pcmk__plural_s(dt->months));
520 }
521 if (dt->days) {
522 offset += snprintf(result + offset, DATE_MAX - offset, "%2d day%s ",
523 dt->days, pcmk__plural_s(dt->days));
524 }
525
526 // At least print seconds (and optionally usecs)
527 if ((offset == 0) || (dt->seconds != 0) || (show_usec && (usec != 0))) {
528 if (show_usec) {
529 sec_usec_as_string(dt->seconds, usec, result, &offset);
530 } else {
531 offset += snprintf(result + offset, DATE_MAX - offset, "%d",
532 dt->seconds);
533 }
534 offset += snprintf(result + offset, DATE_MAX - offset, " second%s",
535 pcmk__plural_s(dt->seconds));
536 }
537
538 // More than one minute, so provide a more readable breakdown into units
539 if (QB_ABS(dt->seconds) >= 60) {
540 uint32_t h = 0;
541 uint32_t m = 0;
542 uint32_t s = 0;
543 uint32_t u = QB_ABS(usec);
544 bool print_sec_component = false;
545
546 crm_time_get_sec(dt->seconds, &h, &m, &s);
547 print_sec_component = ((s != 0) || (show_usec && (u != 0)));
548
549 offset += snprintf(result + offset, DATE_MAX - offset, " (");
550
551 if (h) {
552 offset += snprintf(result + offset, DATE_MAX - offset,
553 "%" PRIu32 " hour%s%s", h, pcmk__plural_s(h),
554 ((m != 0) || print_sec_component)? " " : "");
555 }
556
557 if (m) {
558 offset += snprintf(result + offset, DATE_MAX - offset,
559 "%" PRIu32 " minute%s%s", m, pcmk__plural_s(m),
560 print_sec_component? " " : "");
561 }
562
563 if (print_sec_component) {
564 if (show_usec) {
565 sec_usec_as_string(s, u, result, &offset);
566 } else {
567 offset += snprintf(result + offset, DATE_MAX - offset,
568 "%" PRIu32, s);
569 }
570 offset += snprintf(result + offset, DATE_MAX - offset, " second%s",
571 pcmk__plural_s(dt->seconds));
572 }
573
574 offset += snprintf(result + offset, DATE_MAX - offset, ")");
575 }
576}
577
589static void
590time_as_string_common(const crm_time_t *dt, int usec, uint32_t flags,
591 char *result)
592{
593 crm_time_t *utc = NULL;
594 size_t offset = 0;
595
596 if (!crm_time_is_defined(dt)) {
597 strcpy(result, "<undefined time>");
598 return;
599 }
600
601 pcmk__assert(valid_sec_usec(dt->seconds, usec));
602
603 /* Simple cases: as duration, seconds, or seconds since epoch.
604 * These never depend on time zone.
605 */
606
608 crm_duration_as_string(dt, usec, pcmk_is_set(flags, crm_time_usecs),
609 result);
610 return;
611 }
612
613 if (pcmk_any_flags_set(flags, crm_time_seconds|crm_time_epoch)) {
614 long long seconds = 0;
615
617 seconds = crm_time_get_seconds(dt);
618 } else {
620 }
621
623 sec_usec_as_string(seconds, usec, result, &offset);
624 } else {
625 snprintf(result, DATE_MAX, "%lld", seconds);
626 }
627 return;
628 }
629
630 // Convert to UTC if local timezone was not requested
631 if ((dt->offset != 0) && !pcmk_is_set(flags, crm_time_log_with_timezone)) {
632 crm_trace("UTC conversion");
633 utc = crm_get_utc_time(dt);
634 dt = utc;
635 }
636
637 // As readable string
638
640 if (pcmk_is_set(flags, crm_time_weeks)) { // YYYY-WW-D
641 uint32_t y = 0;
642 uint32_t w = 0;
643 uint32_t d = 0;
644
645 if (crm_time_get_isoweek(dt, &y, &w, &d)) {
646 offset += snprintf(result + offset, DATE_MAX - offset,
647 "%" PRIu32 "-W%.2" PRIu32 "-%" PRIu32,
648 y, w, d);
649 }
650
651 } else if (pcmk_is_set(flags, crm_time_ordinal)) { // YYYY-DDD
652 uint32_t y = 0;
653 uint32_t d = 0;
654
655 if (crm_time_get_ordinal(dt, &y, &d)) {
656 offset += snprintf(result + offset, DATE_MAX - offset,
657 "%" PRIu32 "-%.3" PRIu32, y, d);
658 }
659
660 } else { // YYYY-MM-DD
661 uint32_t y = 0;
662 uint32_t m = 0;
663 uint32_t d = 0;
664
665 if (crm_time_get_gregorian(dt, &y, &m, &d)) {
666 offset += snprintf(result + offset, DATE_MAX - offset,
667 "%.4" PRIu32 "-%.2" PRIu32 "-%.2" PRIu32,
668 y, m, d);
669 }
670 }
671 }
672
674 uint32_t h = 0, m = 0, s = 0;
675
676 if (offset > 0) {
677 offset += snprintf(result + offset, DATE_MAX - offset, " ");
678 }
679
680 if (crm_time_get_timeofday(dt, &h, &m, &s)) {
681 offset += snprintf(result + offset, DATE_MAX - offset,
682 "%.2" PRIu32 ":%.2" PRIu32 ":%.2" PRIu32,
683 h, m, s);
684
686 offset += snprintf(result + offset, DATE_MAX - offset,
687 ".%06" PRIu32, QB_ABS(usec));
688 }
689 }
690
692 && (dt->offset != 0)) {
693 crm_time_get_sec(dt->offset, &h, &m, &s);
694 offset += snprintf(result + offset, DATE_MAX - offset,
695 " %c%.2" PRIu32 ":%.2" PRIu32,
696 ((dt->offset < 0)? '-' : '+'), h, m);
697 } else {
698 offset += snprintf(result + offset, DATE_MAX - offset, "Z");
699 }
700 }
701
702 crm_time_free(utc);
703}
704
713char *
715{
716 char result[DATE_MAX] = { '\0', };
717
718 time_as_string_common(dt, 0, flags, result);
719 return pcmk__str_copy(result);
720}
721
733static bool
734crm_time_parse_sec(const char *time_str, int *result)
735{
736 int rc;
737 uint32_t hour = 0;
738 uint32_t minute = 0;
739 uint32_t second = 0;
740
741 *result = 0;
742
743 // Must have at least hour, but minutes and seconds are optional
744 rc = sscanf(time_str, "%" SCNu32 ":%" SCNu32 ":%" SCNu32,
745 &hour, &minute, &second);
746 if (rc == 1) {
747 rc = sscanf(time_str, "%2" SCNu32 "%2" SCNu32 "%2" SCNu32,
748 &hour, &minute, &second);
749 }
750 if (rc == 0) {
751 crm_err("%s is not a valid ISO 8601 time specification", time_str);
752 errno = EINVAL;
753 return FALSE;
754 }
755
756 crm_trace("Got valid time: %.2" PRIu32 ":%.2" PRIu32 ":%.2" PRIu32,
757 hour, minute, second);
758
759 if ((hour == 24) && (minute == 0) && (second == 0)) {
760 // Equivalent to 00:00:00 of next day, return number of seconds in day
761 } else if (hour >= 24) {
762 crm_err("%s is not a valid ISO 8601 time specification "
763 "because %" PRIu32 " is not a valid hour", time_str, hour);
764 errno = EINVAL;
765 return FALSE;
766 }
767 if (minute >= 60) {
768 crm_err("%s is not a valid ISO 8601 time specification "
769 "because %" PRIu32 " is not a valid minute", time_str, minute);
770 errno = EINVAL;
771 return FALSE;
772 }
773 if (second >= 60) {
774 crm_err("%s is not a valid ISO 8601 time specification "
775 "because %" PRIu32 " is not a valid second", time_str, second);
776 errno = EINVAL;
777 return FALSE;
778 }
779
780 *result = (hour * HOUR_SECONDS) + (minute * 60) + second;
781 return TRUE;
782}
783
784static bool
785crm_time_parse_offset(const char *offset_str, int *offset)
786{
787 tzset();
788
789 if (offset_str == NULL) {
790 // Use local offset
791#if defined(HAVE_STRUCT_TM_TM_GMTOFF)
792 time_t now = time(NULL);
793 struct tm *now_tm = localtime(&now);
794#endif
795 int h_offset = GMTOFF(now_tm) / HOUR_SECONDS;
796 int m_offset = (GMTOFF(now_tm) - (HOUR_SECONDS * h_offset)) / 60;
797
798 if (h_offset < 0 && m_offset < 0) {
799 m_offset = 0 - m_offset;
800 }
801 *offset = (HOUR_SECONDS * h_offset) + (60 * m_offset);
802 return TRUE;
803 }
804
805 if (offset_str[0] == 'Z') { // @TODO invalid if anything after?
806 *offset = 0;
807 return TRUE;
808 }
809
810 *offset = 0;
811 if ((offset_str[0] == '+') || (offset_str[0] == '-')
812 || isdigit((int)offset_str[0])) {
813
814 gboolean negate = FALSE;
815
816 if (offset_str[0] == '+') {
817 offset_str++;
818 } else if (offset_str[0] == '-') {
819 negate = TRUE;
820 offset_str++;
821 }
822 if (crm_time_parse_sec(offset_str, offset) == FALSE) {
823 return FALSE;
824 }
825 if (negate) {
826 *offset = 0 - *offset;
827 }
828 } // @TODO else invalid?
829 return TRUE;
830}
831
842static bool
843crm_time_parse(const char *time_str, crm_time_t *a_time)
844{
845 uint32_t h, m, s;
846 char *offset_s = NULL;
847
848 tzset();
849
850 if (time_str) {
851 if (crm_time_parse_sec(time_str, &(a_time->seconds)) == FALSE) {
852 return FALSE;
853 }
854 offset_s = strstr(time_str, "Z");
855 if (offset_s == NULL) {
856 offset_s = strstr(time_str, " ");
857 if (offset_s) {
858 while (isspace(offset_s[0])) {
859 offset_s++;
860 }
861 }
862 }
863 }
864
865 if (crm_time_parse_offset(offset_s, &(a_time->offset)) == FALSE) {
866 return FALSE;
867 }
868 crm_time_get_sec(a_time->offset, &h, &m, &s);
869 crm_trace("Got tz: %c%2." PRIu32 ":%.2" PRIu32,
870 (a_time->offset < 0)? '-' : '+', h, m);
871
872 if (a_time->seconds == DAY_SECONDS) {
873 // 24:00:00 == 00:00:00 of next day
874 a_time->seconds = 0;
875 crm_time_add_days(a_time, 1);
876 }
877 return TRUE;
878}
879
880/*
881 * \internal
882 * \brief Parse a time object from an ISO 8601 date/time specification
883 *
884 * \param[in] date_str ISO 8601 date/time specification (or
885 * \c PCMK__VALUE_EPOCH)
886 *
887 * \return New time object on success, NULL (and set errno) otherwise
888 */
889static crm_time_t *
890parse_date(const char *date_str)
891{
892 const char *time_s = NULL;
893 crm_time_t *dt = NULL;
894
895 uint32_t year = 0U;
896 uint32_t month = 0U;
897 uint32_t day = 0U;
898 uint32_t week = 0U;
899
900 int rc = 0;
901
902 if (pcmk__str_empty(date_str)) {
903 crm_err("No ISO 8601 date/time specification given");
904 goto invalid;
905 }
906
907 if ((date_str[0] == 'T')
908 || ((strlen(date_str) > 2) && (date_str[2] == ':'))) {
909 /* Just a time supplied - Infer current date */
910 dt = crm_time_new(NULL);
911 if (date_str[0] == 'T') {
912 time_s = date_str + 1;
913 } else {
914 time_s = date_str;
915 }
916 goto parse_time;
917 }
918
920
921 if ((strncasecmp(PCMK__VALUE_EPOCH, date_str, 5) == 0)
922 && ((date_str[5] == '\0')
923 || (date_str[5] == '/')
924 || isspace(date_str[5]))) {
925 dt->days = 1;
926 dt->years = 1970;
928 return dt;
929 }
930
931 /* YYYY-MM-DD */
932 rc = sscanf(date_str, "%" SCNu32 "-%" SCNu32 "-%" SCNu32 "",
933 &year, &month, &day);
934 if (rc == 1) {
935 /* YYYYMMDD */
936 rc = sscanf(date_str, "%4" SCNu32 "%2" SCNu32 "%2" SCNu32 "",
937 &year, &month, &day);
938 }
939 if (rc == 3) {
940 if ((month < 1U) || (month > 12U)) {
941 crm_err("'%s' is not a valid ISO 8601 date/time specification "
942 "because '%" PRIu32 "' is not a valid month",
943 date_str, month);
944 goto invalid;
945 } else if ((year < 1U) || (year > INT_MAX)) {
946 crm_err("'%s' is not a valid ISO 8601 date/time specification "
947 "because '%" PRIu32 "' is not a valid year",
948 date_str, year);
949 goto invalid;
950 } else if ((day < 1) || (day > INT_MAX)
951 || (day > crm_time_days_in_month(month, year))) {
952 crm_err("'%s' is not a valid ISO 8601 date/time specification "
953 "because '%" PRIu32 "' is not a valid day of the month",
954 date_str, day);
955 goto invalid;
956 } else {
957 dt->years = year;
958 dt->days = get_ordinal_days(year, month, day);
959 crm_trace("Parsed Gregorian date '%.4" PRIu32 "-%.3d' "
960 "from date string '%s'", year, dt->days, date_str);
961 }
962 goto parse_time;
963 }
964
965 /* YYYY-DDD */
966 rc = sscanf(date_str, "%" SCNu32 "-%" SCNu32, &year, &day);
967 if (rc == 2) {
968 if ((year < 1U) || (year > INT_MAX)) {
969 crm_err("'%s' is not a valid ISO 8601 date/time specification "
970 "because '%" PRIu32 "' is not a valid year",
971 date_str, year);
972 goto invalid;
973 } else if ((day < 1U) || (day > INT_MAX) || (day > year_days(year))) {
974 crm_err("'%s' is not a valid ISO 8601 date/time specification "
975 "because '%" PRIu32 "' is not a valid day of year %"
976 PRIu32 " (1-%d)",
977 date_str, day, year, year_days(year));
978 goto invalid;
979 }
980 crm_trace("Parsed ordinal year %d and days %d from date string '%s'",
981 year, day, date_str);
982 dt->days = day;
983 dt->years = year;
984 goto parse_time;
985 }
986
987 /* YYYY-Www-D */
988 rc = sscanf(date_str, "%" SCNu32 "-W%" SCNu32 "-%" SCNu32,
989 &year, &week, &day);
990 if (rc == 3) {
991 if ((week < 1U) || (week > crm_time_weeks_in_year(year))) {
992 crm_err("'%s' is not a valid ISO 8601 date/time specification "
993 "because '%" PRIu32 "' is not a valid week of year %"
994 PRIu32 " (1-%d)",
995 date_str, week, year, crm_time_weeks_in_year(year));
996 goto invalid;
997 } else if ((day < 1U) || (day > 7U)) {
998 crm_err("'%s' is not a valid ISO 8601 date/time specification "
999 "because '%" PRIu32 "' is not a valid day of the week",
1000 date_str, day);
1001 goto invalid;
1002 } else {
1003 /*
1004 * See https://en.wikipedia.org/wiki/ISO_week_date
1005 *
1006 * Monday 29 December 2008 is written "2009-W01-1"
1007 * Sunday 3 January 2010 is written "2009-W53-7"
1008 * Saturday 27 September 2008 is written "2008-W37-6"
1009 *
1010 * If 1 January is on a Monday, Tuesday, Wednesday or Thursday, it
1011 * is in week 1. If 1 January is on a Friday, Saturday or Sunday,
1012 * it is in week 52 or 53 of the previous year.
1013 */
1014 int jan1 = crm_time_january1_weekday(year);
1015
1016 crm_trace("Parsed year %" PRIu32 " (Jan 1 = %d), week %" PRIu32
1017 ", and day %" PRIu32 " from date string '%s'",
1018 year, jan1, week, day, date_str);
1019
1020 dt->years = year;
1021 crm_time_add_days(dt, (week - 1) * 7);
1022
1023 if (jan1 <= 4) {
1024 crm_time_add_days(dt, 1 - jan1);
1025 } else {
1026 crm_time_add_days(dt, 8 - jan1);
1027 }
1028
1029 crm_time_add_days(dt, day);
1030 }
1031 goto parse_time;
1032 }
1033
1034 crm_err("'%s' is not a valid ISO 8601 date/time specification", date_str);
1035 goto invalid;
1036
1037 parse_time:
1038
1039 if (time_s == NULL) {
1040 time_s = date_str + strspn(date_str, "0123456789-W");
1041 if ((time_s[0] == ' ') || (time_s[0] == 'T')) {
1042 ++time_s;
1043 } else {
1044 time_s = NULL;
1045 }
1046 }
1047 if ((time_s != NULL) && (crm_time_parse(time_s, dt) == FALSE)) {
1048 goto invalid;
1049 }
1050
1052 if (crm_time_check(dt) == FALSE) {
1053 crm_err("'%s' is not a valid ISO 8601 date/time specification",
1054 date_str);
1055 goto invalid;
1056 }
1057 return dt;
1058
1059invalid:
1060 crm_time_free(dt);
1061 errno = EINVAL;
1062 return NULL;
1063}
1064
1065// Parse an ISO 8601 numeric value and return number of characters consumed
1066static int
1067parse_int(const char *str, int *result)
1068{
1069 unsigned int lpc;
1070 int offset = (str[0] == 'T')? 1 : 0;
1071 bool negate = false;
1072
1073 *result = 0;
1074
1075 // @TODO This cannot handle combinations of these characters
1076 switch (str[offset]) {
1077 case '.':
1078 case ',':
1079 return 0; // Fractions are not supported
1080
1081 case '-':
1082 negate = true;
1083 offset++;
1084 break;
1085
1086 case '+':
1087 case ':':
1088 offset++;
1089 break;
1090
1091 default:
1092 break;
1093 }
1094
1095 for (lpc = 0; (lpc < 10) && isdigit(str[offset]); lpc++) {
1096 const int digit = str[offset++] - '0';
1097
1098 if ((*result * 10LL + digit) > INT_MAX) {
1099 return 0; // Overflow
1100 }
1101 *result = *result * 10 + digit;
1102 }
1103 if (negate) {
1104 *result = 0 - *result;
1105 }
1106 return (lpc > 0)? offset : 0;
1107}
1108
1120crm_time_t *
1121crm_time_parse_duration(const char *period_s)
1122{
1123 gboolean is_time = FALSE;
1124 crm_time_t *diff = NULL;
1125
1126 if (pcmk__str_empty(period_s)) {
1127 crm_err("No ISO 8601 time duration given");
1128 goto invalid;
1129 }
1130 if (period_s[0] != 'P') {
1131 crm_err("'%s' is not a valid ISO 8601 time duration "
1132 "because it does not start with a 'P'", period_s);
1133 goto invalid;
1134 }
1135 if ((period_s[1] == '\0') || isspace(period_s[1])) {
1136 crm_err("'%s' is not a valid ISO 8601 time duration "
1137 "because nothing follows 'P'", period_s);
1138 goto invalid;
1139 }
1140
1141 diff = crm_time_new_undefined();
1142
1143 for (const char *current = period_s + 1;
1144 current[0] && (current[0] != '/') && !isspace(current[0]);
1145 ++current) {
1146
1147 int an_int = 0, rc;
1148 long long result = 0LL;
1149
1150 if (current[0] == 'T') {
1151 /* A 'T' separates year/month/day from hour/minute/seconds. We don't
1152 * require it strictly, but just use it to differentiate month from
1153 * minutes.
1154 */
1155 is_time = TRUE;
1156 continue;
1157 }
1158
1159 // An integer must be next
1160 rc = parse_int(current, &an_int);
1161 if (rc == 0) {
1162 crm_err("'%s' is not a valid ISO 8601 time duration "
1163 "because no valid integer at '%s'", period_s, current);
1164 goto invalid;
1165 }
1166 current += rc;
1167
1168 // A time unit must be next (we're not strict about the order)
1169 switch (current[0]) {
1170 case 'Y':
1171 diff->years = an_int;
1172 break;
1173
1174 case 'M':
1175 if (!is_time) { // Months
1176 diff->months = an_int;
1177 } else { // Minutes
1178 result = diff->seconds + an_int * 60LL;
1179 if ((result < INT_MIN) || (result > INT_MAX)) {
1180 crm_err("'%s' is not a valid ISO 8601 time duration "
1181 "because integer at '%s' is too %s",
1182 period_s, current - rc,
1183 ((result > 0)? "large" : "small"));
1184 goto invalid;
1185 } else {
1186 diff->seconds = (int) result;
1187 }
1188 }
1189
1190 break;
1191
1192 case 'W':
1193 result = diff->days + an_int * 7LL;
1194 if ((result < INT_MIN) || (result > INT_MAX)) {
1195 crm_err("'%s' is not a valid ISO 8601 time duration "
1196 "because integer at '%s' is too %s",
1197 period_s, current - rc,
1198 ((result > 0)? "large" : "small"));
1199 goto invalid;
1200 } else {
1201 diff->days = (int) result;
1202 }
1203 break;
1204
1205 case 'D':
1206 result = diff->days + (long long) an_int;
1207 if ((result < INT_MIN) || (result > INT_MAX)) {
1208 crm_err("'%s' is not a valid ISO 8601 time duration "
1209 "because integer at '%s' is too %s",
1210 period_s, current - rc,
1211 ((result > 0)? "large" : "small"));
1212 goto invalid;
1213 } else {
1214 diff->days = (int) result;
1215 }
1216 break;
1217
1218 case 'H':
1219 result = diff->seconds + (long long) an_int * HOUR_SECONDS;
1220 if ((result < INT_MIN) || (result > INT_MAX)) {
1221 crm_err("'%s' is not a valid ISO 8601 time duration "
1222 "because integer at '%s' is too %s",
1223 period_s, current - rc,
1224 ((result > 0)? "large" : "small"));
1225 goto invalid;
1226 } else {
1227 diff->seconds = (int) result;
1228 }
1229 break;
1230
1231 case 'S':
1232 result = diff->seconds + (long long) an_int;
1233 if ((result < INT_MIN) || (result > INT_MAX)) {
1234 crm_err("'%s' is not a valid ISO 8601 time duration "
1235 "because integer at '%s' is too %s",
1236 period_s, current - rc,
1237 ((result > 0)? "large" : "small"));
1238 goto invalid;
1239 } else {
1240 diff->seconds = (int) result;
1241 }
1242 break;
1243
1244 case '\0':
1245 crm_err("'%s' is not a valid ISO 8601 time duration "
1246 "because no units after %d", period_s, an_int);
1247 goto invalid;
1248
1249 default:
1250 crm_err("'%s' is not a valid ISO 8601 time duration "
1251 "because '%c' is not a valid time unit",
1252 period_s, current[0]);
1253 goto invalid;
1254 }
1255 }
1256
1257 if (!crm_time_is_defined(diff)) {
1258 crm_err("'%s' is not a valid ISO 8601 time duration "
1259 "because no amounts and units given", period_s);
1260 goto invalid;
1261 }
1262
1263 diff->duration = TRUE;
1264 return diff;
1265
1266invalid:
1267 crm_time_free(diff);
1268 errno = EINVAL;
1269 return NULL;
1270}
1271
1283crm_time_parse_period(const char *period_str)
1284{
1285 const char *original = period_str;
1286 crm_time_period_t *period = NULL;
1287
1288 if (pcmk__str_empty(period_str)) {
1289 crm_err("No ISO 8601 time period given");
1290 goto invalid;
1291 }
1292
1293 tzset();
1294 period = pcmk__assert_alloc(1, sizeof(crm_time_period_t));
1295
1296 if (period_str[0] == 'P') {
1297 period->diff = crm_time_parse_duration(period_str);
1298 if (period->diff == NULL) {
1299 goto error;
1300 }
1301 } else {
1302 period->start = parse_date(period_str);
1303 if (period->start == NULL) {
1304 goto error;
1305 }
1306 }
1307
1308 period_str = strstr(original, "/");
1309 if (period_str) {
1310 ++period_str;
1311 if (period_str[0] == 'P') {
1312 if (period->diff != NULL) {
1313 crm_err("'%s' is not a valid ISO 8601 time period "
1314 "because it has two durations",
1315 original);
1316 goto invalid;
1317 }
1318 period->diff = crm_time_parse_duration(period_str);
1319 if (period->diff == NULL) {
1320 goto error;
1321 }
1322 } else {
1323 period->end = parse_date(period_str);
1324 if (period->end == NULL) {
1325 goto error;
1326 }
1327 }
1328
1329 } else if (period->diff != NULL) {
1330 // Only duration given, assume start is now
1331 period->start = crm_time_new(NULL);
1332
1333 } else {
1334 // Only start given
1335 crm_err("'%s' is not a valid ISO 8601 time period "
1336 "because it has no duration or ending time",
1337 original);
1338 goto invalid;
1339 }
1340
1341 if (period->start == NULL) {
1342 period->start = crm_time_subtract(period->end, period->diff);
1343
1344 } else if (period->end == NULL) {
1345 period->end = crm_time_add(period->start, period->diff);
1346 }
1347
1348 if (crm_time_check(period->start) == FALSE) {
1349 crm_err("'%s' is not a valid ISO 8601 time period "
1350 "because the start is invalid", period_str);
1351 goto invalid;
1352 }
1353 if (crm_time_check(period->end) == FALSE) {
1354 crm_err("'%s' is not a valid ISO 8601 time period "
1355 "because the end is invalid", period_str);
1356 goto invalid;
1357 }
1358 return period;
1359
1360invalid:
1361 errno = EINVAL;
1362error:
1363 crm_time_free_period(period);
1364 return NULL;
1365}
1366
1372void
1374{
1375 if (period) {
1376 crm_time_free(period->start);
1377 crm_time_free(period->end);
1378 crm_time_free(period->diff);
1379 free(period);
1380 }
1381}
1382
1383void
1385{
1386 crm_trace("target=%p, source=%p", target, source);
1387
1388 CRM_CHECK(target != NULL && source != NULL, return);
1389
1390 target->years = source->years;
1391 target->days = source->days;
1392 target->months = source->months; /* Only for durations */
1393 target->seconds = source->seconds;
1394 target->offset = source->offset;
1395
1396 crm_time_log(LOG_TRACE, "source", source,
1398 crm_time_log(LOG_TRACE, "target", target,
1400}
1401
1402static void
1403ha_set_tm_time(crm_time_t *target, const struct tm *source)
1404{
1405 int h_offset = 0;
1406 int m_offset = 0;
1407
1408 /* Ensure target is fully initialized */
1409 target->years = 0;
1410 target->months = 0;
1411 target->days = 0;
1412 target->seconds = 0;
1413 target->offset = 0;
1414 target->duration = FALSE;
1415
1416 if (source->tm_year > 0) {
1417 /* years since 1900 */
1418 target->years = 1900;
1419 crm_time_add_years(target, source->tm_year);
1420 }
1421
1422 if (source->tm_yday >= 0) {
1423 /* days since January 1 [0-365] */
1424 target->days = 1 + source->tm_yday;
1425 }
1426
1427 if (source->tm_hour >= 0) {
1428 target->seconds += HOUR_SECONDS * source->tm_hour;
1429 }
1430 if (source->tm_min >= 0) {
1431 target->seconds += 60 * source->tm_min;
1432 }
1433 if (source->tm_sec >= 0) {
1434 target->seconds += source->tm_sec;
1435 }
1436
1437 /* tm_gmtoff == offset from UTC in seconds */
1438 h_offset = GMTOFF(source) / HOUR_SECONDS;
1439 m_offset = (GMTOFF(source) - (HOUR_SECONDS * h_offset)) / 60;
1440 crm_trace("Time offset is %lds (%.2d:%.2d)",
1441 GMTOFF(source), h_offset, m_offset);
1442
1443 target->offset += HOUR_SECONDS * h_offset;
1444 target->offset += 60 * m_offset;
1445}
1446
1447void
1448crm_time_set_timet(crm_time_t *target, const time_t *source)
1449{
1450 ha_set_tm_time(target, localtime(source));
1451}
1452
1460void
1462{
1463 if ((target != NULL) && (source != NULL)
1465 || (crm_time_compare(source, target) < 0))) {
1466 crm_time_set(target, source);
1467 }
1468}
1469
1470crm_time_t *
1472{
1474
1475 crm_time_set(target, source);
1476 return target;
1477}
1478
1487crm_time_t *
1488pcmk__copy_timet(time_t source)
1489{
1491
1492 crm_time_set_timet(target, &source);
1493 return target;
1494}
1495
1496crm_time_t *
1497crm_time_add(const crm_time_t *dt, const crm_time_t *value)
1498{
1499 crm_time_t *utc = NULL;
1500 crm_time_t *answer = NULL;
1501
1502 if ((dt == NULL) || (value == NULL)) {
1503 errno = EINVAL;
1504 return NULL;
1505 }
1506
1507 answer = pcmk_copy_time(dt);
1508
1509 utc = crm_get_utc_time(value);
1510 if (utc == NULL) {
1511 crm_time_free(answer);
1512 return NULL;
1513 }
1514
1515 crm_time_add_years(answer, utc->years);
1516 crm_time_add_months(answer, utc->months);
1517 crm_time_add_days(answer, utc->days);
1518 crm_time_add_seconds(answer, utc->seconds);
1519
1520 crm_time_free(utc);
1521 return answer;
1522}
1523
1533const char *
1535{
1536 switch (component) {
1537 case pcmk__time_years:
1538 return PCMK_XA_YEARS;
1539
1540 case pcmk__time_months:
1541 return PCMK_XA_MONTHS;
1542
1543 case pcmk__time_weeks:
1544 return PCMK_XA_WEEKS;
1545
1546 case pcmk__time_days:
1547 return PCMK_XA_DAYS;
1548
1549 case pcmk__time_hours:
1550 return PCMK_XA_HOURS;
1551
1552 case pcmk__time_minutes:
1553 return PCMK_XA_MINUTES;
1554
1555 case pcmk__time_seconds:
1556 return PCMK_XA_SECONDS;
1557
1558 default:
1559 return NULL;
1560 }
1561}
1562
1563typedef void (*component_fn_t)(crm_time_t *, int);
1564
1573static component_fn_t
1574component_fn(enum pcmk__time_component component)
1575{
1576 switch (component) {
1577 case pcmk__time_years:
1578 return crm_time_add_years;
1579
1580 case pcmk__time_months:
1581 return crm_time_add_months;
1582
1583 case pcmk__time_weeks:
1584 return crm_time_add_weeks;
1585
1586 case pcmk__time_days:
1587 return crm_time_add_days;
1588
1589 case pcmk__time_hours:
1590 return crm_time_add_hours;
1591
1592 case pcmk__time_minutes:
1593 return crm_time_add_minutes;
1594
1595 case pcmk__time_seconds:
1596 return crm_time_add_seconds;
1597
1598 default:
1599 return NULL;
1600 }
1601
1602}
1603
1614int
1616 const xmlNode *xml)
1617{
1618 long long value;
1619 const char *attr = pcmk__time_component_attr(component);
1620 component_fn_t add = component_fn(component);
1621
1622 if ((t == NULL) || (attr == NULL) || (add == NULL)) {
1623 return EINVAL;
1624 }
1625
1626 if (xml == NULL) {
1627 return pcmk_rc_ok;
1628 }
1629
1630 if (pcmk__scan_ll(crm_element_value(xml, attr), &value,
1631 0LL) != pcmk_rc_ok) {
1632 return pcmk_rc_unpack_error;
1633 }
1634
1635 if ((value < INT_MIN) || (value > INT_MAX)) {
1636 return ERANGE;
1637 }
1638
1639 if (value != 0LL) {
1640 add(t, (int) value);
1641 }
1642 return pcmk_rc_ok;
1643}
1644
1645crm_time_t *
1647{
1648 crm_time_t *utc = NULL;
1649 crm_time_t *answer = NULL;
1650
1651 if ((dt == NULL) || (value == NULL)) {
1652 errno = EINVAL;
1653 return NULL;
1654 }
1655
1656 utc = crm_get_utc_time(value);
1657 if (utc == NULL) {
1658 return NULL;
1659 }
1660
1661 answer = crm_get_utc_time(dt);
1662 if (answer == NULL) {
1663 crm_time_free(utc);
1664 return NULL;
1665 }
1666 answer->duration = TRUE;
1667
1668 crm_time_add_years(answer, -utc->years);
1669 if(utc->months != 0) {
1670 crm_time_add_months(answer, -utc->months);
1671 }
1672 crm_time_add_days(answer, -utc->days);
1673 crm_time_add_seconds(answer, -utc->seconds);
1674
1675 crm_time_free(utc);
1676 return answer;
1677}
1678
1679crm_time_t *
1681{
1682 crm_time_t *utc = NULL;
1683 crm_time_t *answer = NULL;
1684
1685 if ((dt == NULL) || (value == NULL)) {
1686 errno = EINVAL;
1687 return NULL;
1688 }
1689
1690 utc = crm_get_utc_time(value);
1691 if (utc == NULL) {
1692 return NULL;
1693 }
1694
1695 answer = pcmk_copy_time(dt);
1696 crm_time_add_years(answer, -utc->years);
1697 if(utc->months != 0) {
1698 crm_time_add_months(answer, -utc->months);
1699 }
1700 crm_time_add_days(answer, -utc->days);
1701 crm_time_add_seconds(answer, -utc->seconds);
1702 crm_time_free(utc);
1703
1704 return answer;
1705}
1706
1714bool
1716{
1717 return (dt != NULL)
1718 && (dt->days > 0) && (dt->days <= year_days(dt->years))
1719 && (dt->seconds >= 0) && (dt->seconds < DAY_SECONDS);
1720}
1721
1722#define do_cmp_field(l, r, field) \
1723 if(rc == 0) { \
1724 if(l->field > r->field) { \
1725 crm_trace("%s: %d > %d", \
1726 #field, l->field, r->field); \
1727 rc = 1; \
1728 } else if(l->field < r->field) { \
1729 crm_trace("%s: %d < %d", \
1730 #field, l->field, r->field); \
1731 rc = -1; \
1732 } \
1733 }
1734
1735int
1737{
1738 int rc = 0;
1739 crm_time_t *t1 = crm_get_utc_time(a);
1740 crm_time_t *t2 = crm_get_utc_time(b);
1741
1742 if ((t1 == NULL) && (t2 == NULL)) {
1743 rc = 0;
1744 } else if (t1 == NULL) {
1745 rc = -1;
1746 } else if (t2 == NULL) {
1747 rc = 1;
1748 } else {
1749 do_cmp_field(t1, t2, years);
1750 do_cmp_field(t1, t2, days);
1751 do_cmp_field(t1, t2, seconds);
1752 }
1753
1754 crm_time_free(t1);
1755 crm_time_free(t2);
1756 return rc;
1757}
1758
1765void
1767{
1768 int days = extra / DAY_SECONDS;
1769
1770 pcmk__assert(a_time != NULL);
1771
1772 crm_trace("Adding %d seconds (including %d whole day%s) to %d",
1773 extra, days, pcmk__plural_s(days), a_time->seconds);
1774
1775 a_time->seconds += extra % DAY_SECONDS;
1776
1777 // Check whether the addition crossed a day boundary
1778 if (a_time->seconds > DAY_SECONDS) {
1779 ++days;
1780 a_time->seconds -= DAY_SECONDS;
1781
1782 } else if (a_time->seconds < 0) {
1783 --days;
1784 a_time->seconds += DAY_SECONDS;
1785 }
1786
1787 crm_time_add_days(a_time, days);
1788}
1789
1790#define ydays(t) (crm_time_leapyear((t)->years)? 366 : 365)
1791
1798void
1800{
1801 pcmk__assert(a_time != NULL);
1802
1803 crm_trace("Adding %d days to %.4d-%.3d", extra, a_time->years, a_time->days);
1804
1805 if (extra > 0) {
1806 while ((a_time->days + (long long) extra) > ydays(a_time)) {
1807 if ((a_time->years + 1LL) > INT_MAX) {
1808 a_time->days = ydays(a_time); // Clip to latest we can handle
1809 return;
1810 }
1811 extra -= ydays(a_time);
1812 a_time->years++;
1813 }
1814 } else if (extra < 0) {
1815 const int min_days = a_time->duration? 0 : 1;
1816
1817 while ((a_time->days + (long long) extra) < min_days) {
1818 if ((a_time->years - 1) < 1) {
1819 a_time->days = 1; // Clip to earliest we can handle (no BCE)
1820 return;
1821 }
1822 a_time->years--;
1823 extra += ydays(a_time);
1824 }
1825 }
1826 a_time->days += extra;
1827}
1828
1829void
1831{
1832 int lpc;
1833 uint32_t y, m, d, dmax;
1834
1835 crm_time_get_gregorian(a_time, &y, &m, &d);
1836 crm_trace("Adding %d months to %.4" PRIu32 "-%.2" PRIu32 "-%.2" PRIu32,
1837 extra, y, m, d);
1838
1839 if (extra > 0) {
1840 for (lpc = extra; lpc > 0; lpc--) {
1841 m++;
1842 if (m == 13) {
1843 m = 1;
1844 y++;
1845 }
1846 }
1847 } else {
1848 for (lpc = -extra; lpc > 0; lpc--) {
1849 m--;
1850 if (m == 0) {
1851 m = 12;
1852 y--;
1853 }
1854 }
1855 }
1856
1857 dmax = crm_time_days_in_month(m, y);
1858 if (dmax < d) {
1859 /* Preserve day-of-month unless the month doesn't have enough days */
1860 d = dmax;
1861 }
1862
1863 crm_trace("Calculated %.4" PRIu32 "-%.2" PRIu32 "-%.2" PRIu32, y, m, d);
1864
1865 a_time->years = y;
1866 a_time->days = get_ordinal_days(y, m, d);
1867
1868 crm_time_get_gregorian(a_time, &y, &m, &d);
1869 crm_trace("Got %.4" PRIu32 "-%.2" PRIu32 "-%.2" PRIu32, y, m, d);
1870}
1871
1872void
1874{
1875 crm_time_add_seconds(a_time, extra * 60);
1876}
1877
1878void
1879crm_time_add_hours(crm_time_t * a_time, int extra)
1880{
1881 crm_time_add_seconds(a_time, extra * HOUR_SECONDS);
1882}
1883
1884void
1885crm_time_add_weeks(crm_time_t * a_time, int extra)
1886{
1887 crm_time_add_days(a_time, extra * 7);
1888}
1889
1890void
1891crm_time_add_years(crm_time_t * a_time, int extra)
1892{
1893 pcmk__assert(a_time != NULL);
1894
1895 if ((extra > 0) && ((a_time->years + (long long) extra) > INT_MAX)) {
1896 a_time->years = INT_MAX;
1897 } else if ((extra < 0) && ((a_time->years + (long long) extra) < 1)) {
1898 a_time->years = 1; // Clip to earliest we can handle (no BCE)
1899 } else {
1900 a_time->years += extra;
1901 }
1902}
1903
1904static void
1905ha_get_tm_time(struct tm *target, const crm_time_t *source)
1906{
1907 *target = (struct tm) {
1908 .tm_year = source->years - 1900,
1909 .tm_mday = source->days,
1910 .tm_sec = source->seconds % 60,
1911 .tm_min = ( source->seconds / 60 ) % 60,
1912 .tm_hour = source->seconds / HOUR_SECONDS,
1913 .tm_isdst = -1, /* don't adjust */
1914
1915#if defined(HAVE_STRUCT_TM_TM_GMTOFF)
1916 .tm_gmtoff = source->offset
1917#endif
1918 };
1919 mktime(target);
1920}
1921
1922/* The high-resolution variant of time object was added to meet an immediate
1923 * need, and is kept internal API.
1924 *
1925 * @TODO The long-term goal is to come up with a clean, unified design for a
1926 * time type (or types) that meets all the various needs, to replace
1927 * crm_time_t, pcmk__time_hr_t, and struct timespec (in lrmd_cmd_t).
1928 */
1929
1932{
1933 pcmk__time_hr_t *hr_dt = NULL;
1934
1935 if (dt) {
1936 hr_dt = target;
1937 if (hr_dt == NULL) {
1938 hr_dt = pcmk__assert_alloc(1, sizeof(pcmk__time_hr_t));
1939 }
1940
1941 *hr_dt = (pcmk__time_hr_t) {
1942 .years = dt->years,
1943 .months = dt->months,
1944 .days = dt->days,
1945 .seconds = dt->seconds,
1946 .offset = dt->offset,
1947 .duration = dt->duration
1948 };
1949 }
1950
1951 return hr_dt;
1952}
1953
1954void
1956{
1957 pcmk__assert((target != NULL) && (hr_dt != NULL));
1958 *target = (crm_time_t) {
1959 .years = hr_dt->years,
1960 .months = hr_dt->months,
1961 .days = hr_dt->days,
1962 .seconds = hr_dt->seconds,
1963 .offset = hr_dt->offset,
1964 .duration = hr_dt->duration
1965 };
1966}
1967
1977pcmk__time_hr_now(time_t *epoch)
1978{
1979 struct timespec tv;
1980 crm_time_t dt;
1981 pcmk__time_hr_t *hr;
1982
1983 qb_util_timespec_from_epoch_get(&tv);
1984 if (epoch != NULL) {
1985 *epoch = tv.tv_sec;
1986 }
1987 crm_time_set_timet(&dt, &(tv.tv_sec));
1988 hr = pcmk__time_hr_convert(NULL, &dt);
1989 if (hr != NULL) {
1990 hr->useconds = tv.tv_nsec / QB_TIME_NS_IN_USEC;
1991 }
1992 return hr;
1993}
1994
1996pcmk__time_hr_new(const char *date_time)
1997{
1998 pcmk__time_hr_t *hr_dt = NULL;
1999
2000 if (date_time == NULL) {
2001 hr_dt = pcmk__time_hr_now(NULL);
2002 } else {
2003 crm_time_t *dt;
2004
2005 dt = parse_date(date_time);
2006 hr_dt = pcmk__time_hr_convert(NULL, dt);
2007 crm_time_free(dt);
2008 }
2009 return hr_dt;
2010}
2011
2012void
2014{
2015 free(hr_dt);
2016}
2017
2028char *
2029pcmk__time_format_hr(const char *format, const pcmk__time_hr_t *hr_dt)
2030{
2031 int scanned_pos = 0; // How many characters of format have been parsed
2032 int printed_pos = 0; // How many characters of format have been processed
2033 size_t date_len = 0;
2034
2035 char nano_s[10] = { '\0', };
2036 char date_s[128] = { '\0', };
2037
2038 struct tm tm = { 0, };
2039 crm_time_t dt = { 0, };
2040
2041 if (format == NULL) {
2042 return NULL;
2043 }
2044 pcmk__time_set_hr_dt(&dt, hr_dt);
2045 ha_get_tm_time(&tm, &dt);
2046 sprintf(nano_s, "%06d000", hr_dt->useconds);
2047
2048 while (format[scanned_pos] != '\0') {
2049 int fmt_pos; // Index after last character to pass as-is
2050 int nano_digits = 0; // Length of %N field width (if any)
2051 char *tmp_fmt_s = NULL;
2052 size_t nbytes = 0;
2053
2054 // Look for next format specifier
2055 const char *mark_s = strchr(&format[scanned_pos], '%');
2056
2057 if (mark_s == NULL) {
2058 // No more specifiers, so pass remaining string to strftime() as-is
2059 scanned_pos = strlen(format);
2060 fmt_pos = scanned_pos;
2061
2062 } else {
2063 fmt_pos = mark_s - format; // Index of %
2064
2065 // Skip % and any field width
2066 scanned_pos = fmt_pos + 1;
2067 while (isdigit(format[scanned_pos])) {
2068 scanned_pos++;
2069 }
2070
2071 switch (format[scanned_pos]) {
2072 case '\0': // Literal % and possibly digits at end of string
2073 fmt_pos = scanned_pos; // Pass remaining string as-is
2074 break;
2075
2076 case 'N': // %[width]N
2077 scanned_pos++;
2078
2079 // Parse field width
2080 nano_digits = atoi(&format[fmt_pos + 1]);
2081 nano_digits = QB_MAX(nano_digits, 0);
2082 nano_digits = QB_MIN(nano_digits, 6);
2083 break;
2084
2085 default: // Some other specifier
2086 if (format[++scanned_pos] != '\0') { // More to parse
2087 continue;
2088 }
2089 fmt_pos = scanned_pos; // Pass remaining string as-is
2090 break;
2091 }
2092 }
2093
2094 if (date_len >= sizeof(date_s)) {
2095 return NULL; // No room for remaining string
2096 }
2097
2098 tmp_fmt_s = strndup(&format[printed_pos], fmt_pos - printed_pos);
2099 if (tmp_fmt_s == NULL) {
2100 return NULL;
2101 }
2102
2103#ifdef HAVE_FORMAT_NONLITERAL
2104#pragma GCC diagnostic push
2105#pragma GCC diagnostic ignored "-Wformat-nonliteral"
2106#endif
2107 nbytes = strftime(&date_s[date_len], sizeof(date_s) - date_len,
2108 tmp_fmt_s, &tm);
2109#ifdef HAVE_FORMAT_NONLITERAL
2110#pragma GCC diagnostic pop
2111#endif
2112 free(tmp_fmt_s);
2113 if (nbytes == 0) { // Would overflow buffer
2114 return NULL;
2115 }
2116 date_len += nbytes;
2117 printed_pos = scanned_pos;
2118 if (nano_digits != 0) {
2119 int nc = 0;
2120
2121 if (date_len >= sizeof(date_s)) {
2122 return NULL; // No room to add nanoseconds
2123 }
2124 nc = snprintf(&date_s[date_len], sizeof(date_s) - date_len,
2125 "%.*s", nano_digits, nano_s);
2126
2127 if ((nc < 0) || (nc == (sizeof(date_s) - date_len))) {
2128 return NULL; // Error or would overflow buffer
2129 }
2130 date_len += nc;
2131 }
2132 }
2133
2134 return (date_len == 0)? NULL : pcmk__str_copy(date_s);
2135}
2136
2150char *
2151pcmk__epoch2str(const time_t *source, uint32_t flags)
2152{
2153 time_t epoch_time = (source == NULL)? time(NULL) : *source;
2154
2155 if (flags == 0) {
2156 return pcmk__str_copy(pcmk__trim(ctime(&epoch_time)));
2157 } else {
2158 crm_time_t dt;
2159
2160 crm_time_set_timet(&dt, &epoch_time);
2161 return crm_time_as_string(&dt, flags);
2162 }
2163}
2164
2182char *
2183pcmk__timespec2str(const struct timespec *ts, uint32_t flags)
2184{
2185 struct timespec tmp_ts;
2186 crm_time_t dt;
2187 char result[DATE_MAX] = { 0 };
2188
2189 if (ts == NULL) {
2190 qb_util_timespec_from_epoch_get(&tmp_ts);
2191 ts = &tmp_ts;
2192 }
2193 crm_time_set_timet(&dt, &ts->tv_sec);
2194 time_as_string_common(&dt, ts->tv_nsec / QB_TIME_NS_IN_USEC, flags, result);
2195 return pcmk__str_copy(result);
2196}
2197
2209const char *
2210pcmk__readable_interval(guint interval_ms)
2211{
2212#define MS_IN_S (1000)
2213#define MS_IN_M (MS_IN_S * 60)
2214#define MS_IN_H (MS_IN_M * 60)
2215#define MS_IN_D (MS_IN_H * 24)
2216#define MAXSTR sizeof("..d..h..m..s...ms")
2217 static char str[MAXSTR];
2218 int offset = 0;
2219
2220 str[0] = '\0';
2221 if (interval_ms >= MS_IN_D) {
2222 offset += snprintf(str + offset, MAXSTR - offset, "%ud",
2223 interval_ms / MS_IN_D);
2224 interval_ms -= (interval_ms / MS_IN_D) * MS_IN_D;
2225 }
2226 if (interval_ms >= MS_IN_H) {
2227 offset += snprintf(str + offset, MAXSTR - offset, "%uh",
2228 interval_ms / MS_IN_H);
2229 interval_ms -= (interval_ms / MS_IN_H) * MS_IN_H;
2230 }
2231 if (interval_ms >= MS_IN_M) {
2232 offset += snprintf(str + offset, MAXSTR - offset, "%um",
2233 interval_ms / MS_IN_M);
2234 interval_ms -= (interval_ms / MS_IN_M) * MS_IN_M;
2235 }
2236
2237 // Ns, N.NNNs, or NNNms
2238 if (interval_ms >= MS_IN_S) {
2239 offset += snprintf(str + offset, MAXSTR - offset, "%u",
2240 interval_ms / MS_IN_S);
2241 interval_ms -= (interval_ms / MS_IN_S) * MS_IN_S;
2242 if (interval_ms > 0) {
2243 offset += snprintf(str + offset, MAXSTR - offset, ".%03u",
2244 interval_ms);
2245 }
2246 (void) snprintf(str + offset, MAXSTR - offset, "s");
2247
2248 } else if (interval_ms > 0) {
2249 (void) snprintf(str + offset, MAXSTR - offset, "%ums", interval_ms);
2250
2251 } else if (str[0] == '\0') {
2252 strcpy(str, "0s");
2253 }
2254 return str;
2255}
#define pcmk__assert_alloc(nmemb, size)
Definition internal.h:246
uint64_t flags
Definition remote.c:3
#define pcmk_is_set(g, f)
Convenience alias for pcmk_all_flags_set(), to check single flag.
Definition util.h:80
A dumping ground.
pcmk__time_component
@ pcmk__time_hours
@ pcmk__time_minutes
@ pcmk__time_seconds
@ pcmk__time_days
@ pcmk__time_years
@ pcmk__time_months
@ pcmk__time_weeks
crm_time_t * crm_time_new(const char *date_time)
Definition iso8601.c:112
#define MS_IN_D
crm_time_t * pcmk__copy_timet(time_t source)
Definition iso8601.c:1488
#define MS_IN_S
#define DATE_MAX
Definition iso8601.c:476
void crm_time_set_timet(crm_time_t *target, const time_t *source)
Definition iso8601.c:1448
crm_time_t * crm_time_subtract(const crm_time_t *dt, const crm_time_t *value)
Definition iso8601.c:1680
const char * pcmk__time_component_attr(enum pcmk__time_component component)
Definition iso8601.c:1534
char * pcmk__time_format_hr(const char *format, const pcmk__time_hr_t *hr_dt)
Definition iso8601.c:2029
void(* component_fn_t)(crm_time_t *, int)
Definition iso8601.c:1563
#define DAY_SECONDS
Definition iso8601.c:51
void crm_time_set(crm_time_t *target, const crm_time_t *source)
Definition iso8601.c:1384
void crm_time_add_months(crm_time_t *a_time, int extra)
Definition iso8601.c:1830
pcmk__time_hr_t * pcmk__time_hr_convert(pcmk__time_hr_t *target, const crm_time_t *dt)
Definition iso8601.c:1931
char * pcmk__epoch2str(const time_t *source, uint32_t flags)
Definition iso8601.c:2151
#define GMTOFF(tm)
Definition iso8601.c:47
long long crm_time_get_seconds_since_epoch(const crm_time_t *dt)
Definition iso8601.c:374
#define MAXSTR
#define MS_IN_M
#define EPOCH_SECONDS
Definition iso8601.c:372
crm_time_period_t * crm_time_parse_period(const char *period_str)
Parse a time period from an ISO 8601 interval specification.
Definition iso8601.c:1283
int crm_time_january1_weekday(int year)
Definition iso8601.c:178
void crm_time_free(crm_time_t *dt)
Definition iso8601.c:150
void crm_time_free_period(crm_time_period_t *period)
Free a dynamically allocated time period object.
Definition iso8601.c:1373
int crm_time_get_ordinal(const crm_time_t *dt, uint32_t *y, uint32_t *d)
Definition iso8601.c:413
crm_time_t * pcmk_copy_time(const crm_time_t *source)
Definition iso8601.c:1471
void pcmk__set_time_if_earlier(crm_time_t *target, const crm_time_t *source)
Definition iso8601.c:1461
int crm_time_days_in_month(int month, int year)
Return number of days in given month of given year.
Definition iso8601.c:224
int pcmk__add_time_from_xml(crm_time_t *t, enum pcmk__time_component component, const xmlNode *xml)
Definition iso8601.c:1615
const char * pcmk__readable_interval(guint interval_ms)
Definition iso8601.c:2210
void crm_time_add_minutes(crm_time_t *a_time, int extra)
Definition iso8601.c:1873
pcmk__time_hr_t * pcmk__time_hr_new(const char *date_time)
Definition iso8601.c:1996
bool crm_time_is_defined(const crm_time_t *t)
Check whether a time object has been initialized yet.
Definition iso8601.c:142
void crm_time_add_seconds(crm_time_t *a_time, int extra)
Add a given number of seconds to a date/time or duration.
Definition iso8601.c:1766
int crm_time_weeks_in_year(int year)
Definition iso8601.c:191
char * crm_time_as_string(const crm_time_t *dt, int flags)
Get a string representation of a crm_time_t object.
Definition iso8601.c:714
crm_time_t * crm_time_calculate_duration(const crm_time_t *dt, const crm_time_t *value)
Definition iso8601.c:1646
#define ydays(t)
Definition iso8601.c:1790
#define HOUR_SECONDS
Definition iso8601.c:50
#define valid_sec_usec(sec, usec)
Definition iso8601.c:65
bool crm_time_check(const crm_time_t *dt)
Check whether a time object represents a sensible date/time.
Definition iso8601.c:1715
void pcmk__time_set_hr_dt(crm_time_t *target, const pcmk__time_hr_t *hr_dt)
Definition iso8601.c:1955
bool crm_time_leapyear(int year)
Definition iso8601.c:236
crm_time_t * crm_time_add(const crm_time_t *dt, const crm_time_t *value)
Definition iso8601.c:1497
long long crm_time_get_seconds(const crm_time_t *dt)
Definition iso8601.c:331
int crm_time_get_timezone(const crm_time_t *dt, uint32_t *h, uint32_t *m)
Definition iso8601.c:322
void crm_time_add_days(crm_time_t *a_time, int extra)
Add days to a date/time.
Definition iso8601.c:1799
void crm_time_add_weeks(crm_time_t *a_time, int extra)
Definition iso8601.c:1885
crm_time_t * crm_time_parse_duration(const char *period_s)
Parse a time duration from an ISO 8601 duration specification.
Definition iso8601.c:1121
void pcmk__time_hr_free(pcmk__time_hr_t *hr_dt)
Definition iso8601.c:2013
void crm_time_add_years(crm_time_t *a_time, int extra)
Definition iso8601.c:1891
int crm_time_get_timeofday(const crm_time_t *dt, uint32_t *h, uint32_t *m, uint32_t *s)
Definition iso8601.c:314
char * pcmk__timespec2str(const struct timespec *ts, uint32_t flags)
Definition iso8601.c:2183
crm_time_t * crm_time_new_undefined(void)
Allocate memory for an uninitialized time object.
Definition iso8601.c:129
pcmk__time_hr_t * pcmk__time_hr_now(time_t *epoch)
Definition iso8601.c:1977
int crm_time_get_gregorian(const crm_time_t *dt, uint32_t *y, uint32_t *m, uint32_t *d)
Definition iso8601.c:380
int crm_time_compare(const crm_time_t *a, const crm_time_t *b)
Definition iso8601.c:1736
void crm_time_add_hours(crm_time_t *a_time, int extra)
Definition iso8601.c:1879
#define do_cmp_field(l, r, field)
Definition iso8601.c:1722
void crm_time_log_alias(int log_level, const char *file, const char *function, int line, const char *prefix, const crm_time_t *date_time, int flags)
Definition iso8601.c:276
int crm_time_get_isoweek(const crm_time_t *dt, uint32_t *y, uint32_t *w, uint32_t *d)
Definition iso8601.c:421
#define MS_IN_H
ISO_8601 Date handling.
#define crm_time_log_duration
Definition iso8601.h:70
#define crm_time_log_timeofday
Definition iso8601.h:68
#define crm_time_ordinal
Definition iso8601.h:72
#define crm_time_seconds
Definition iso8601.h:74
#define crm_time_usecs
Definition iso8601.h:76
#define crm_time_epoch
Definition iso8601.h:75
#define crm_time_weeks
Definition iso8601.h:73
#define crm_time_log_with_timezone
Definition iso8601.h:69
#define crm_time_log_date
Definition iso8601.h:67
struct crm_time_s crm_time_t
Definition iso8601.h:32
#define crm_time_log(level, prefix, dt, flags)
Definition iso8601.h:60
struct pcmk__time_us pcmk__time_hr_t
#define LOG_STDOUT
Definition logging.h:43
#define CRM_CHECK(expr, failure_action)
Definition logging.h:213
#define crm_err(fmt, args...)
Definition logging.h:357
#define do_crm_log_alias(level, file, function, line, fmt, args...)
Log a message as if it came from a different code location.
Definition logging.h:268
#define crm_trace(fmt, args...)
Definition logging.h:370
#define LOG_TRACE
Definition logging.h:38
#define PCMK__VALUE_EPOCH
pcmk__action_result_t result
Definition pcmk_fence.c:37
const char * target
Definition pcmk_fence.c:31
@ pcmk_rc_ok
Definition results.h:159
@ pcmk_rc_unpack_error
Definition results.h:122
#define pcmk__assert(expr)
#define pcmk__plural_s(i)
char * pcmk__trim(char *str)
Definition strings.c:530
int pcmk__scan_ll(const char *text, long long *result, long long default_value)
Definition strings.c:92
#define pcmk__str_copy(str)
crm_time_t * end
Definition iso8601.h:36
crm_time_t * diff
Definition iso8601.h:37
crm_time_t * start
Definition iso8601.h:35
const char * crm_element_value(const xmlNode *data, const char *name)
Retrieve the value of an XML attribute.
#define PCMK_XA_MONTHS
Definition xml_names.h:328
#define PCMK_XA_YEARS
Definition xml_names.h:456
#define PCMK_XA_MINUTES
Definition xml_names.h:325
#define PCMK_XA_SECONDS
Definition xml_names.h:399
#define PCMK_XA_WEEKS
Definition xml_names.h:447
#define PCMK_XA_DAYS
Definition xml_names.h:257
#define PCMK_XA_HOURS
Definition xml_names.h:300