pacemaker 2.1.8-2.1.8~rc1
Scalable High-Availability cluster resource manager
Loading...
Searching...
No Matches
iso8601.c
Go to the documentation of this file.
1/*
2 * Copyright 2005-2024 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)) {
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
249static uint32_t
250get_ordinal_days(uint32_t y, uint32_t m, uint32_t d)
251{
252 int lpc;
253
254 for (lpc = 1; lpc < m; lpc++) {
255 d += crm_time_days_in_month(lpc, y);
256 }
257 return d;
258}
259
260void
261crm_time_log_alias(int log_level, const char *file, const char *function,
262 int line, const char *prefix, const crm_time_t *date_time,
263 int flags)
264{
265 char *date_s = crm_time_as_string(date_time, flags);
266
267 if (log_level == LOG_STDOUT) {
268 printf("%s%s%s\n",
269 (prefix? prefix : ""), (prefix? ": " : ""), date_s);
270 } else {
271 do_crm_log_alias(log_level, file, function, line, "%s%s%s",
272 (prefix? prefix : ""), (prefix? ": " : ""), date_s);
273 }
274 free(date_s);
275}
276
277static void
278crm_time_get_sec(int sec, uint32_t *h, uint32_t *m, uint32_t *s)
279{
280 uint32_t hours, minutes, seconds;
281
282 seconds = QB_ABS(sec);
283
284 hours = seconds / HOUR_SECONDS;
285 seconds -= HOUR_SECONDS * hours;
286
287 minutes = seconds / 60;
288 seconds -= 60 * minutes;
289
290 crm_trace("%d == %.2" PRIu32 ":%.2" PRIu32 ":%.2" PRIu32,
291 sec, hours, minutes, seconds);
292
293 *h = hours;
294 *m = minutes;
295 *s = seconds;
296}
297
298int
299crm_time_get_timeofday(const crm_time_t *dt, uint32_t *h, uint32_t *m,
300 uint32_t *s)
301{
302 crm_time_get_sec(dt->seconds, h, m, s);
303 return TRUE;
304}
305
306int
307crm_time_get_timezone(const crm_time_t *dt, uint32_t *h, uint32_t *m)
308{
309 uint32_t s;
310
311 crm_time_get_sec(dt->seconds, h, m, &s);
312 return TRUE;
313}
314
315long long
317{
318 int lpc;
319 crm_time_t *utc = NULL;
320 long long in_seconds = 0;
321
322 if (dt == NULL) {
323 return 0;
324 }
325
326 utc = crm_get_utc_time(dt);
327 if (utc == NULL) {
328 return 0;
329 }
330
331 for (lpc = 1; lpc < utc->years; lpc++) {
332 long long dmax = year_days(lpc);
333
334 in_seconds += DAY_SECONDS * dmax;
335 }
336
337 /* utc->months is an offset that can only be set for a duration.
338 * By definition, the value is variable depending on the date to
339 * which it is applied.
340 *
341 * Force 30-day months so that something vaguely sane happens
342 * for anyone that tries to use a month in this way.
343 */
344 if (utc->months > 0) {
345 in_seconds += DAY_SECONDS * 30 * (long long) (utc->months);
346 }
347
348 if (utc->days > 0) {
349 in_seconds += DAY_SECONDS * (long long) (utc->days - 1);
350 }
351 in_seconds += utc->seconds;
352
353 crm_time_free(utc);
354 return in_seconds;
355}
356
357#define EPOCH_SECONDS 62135596800ULL /* Calculated using crm_time_get_seconds() */
358long long
360{
361 return (dt == NULL)? 0 : (crm_time_get_seconds(dt) - EPOCH_SECONDS);
362}
363
364int
365crm_time_get_gregorian(const crm_time_t *dt, uint32_t *y, uint32_t *m,
366 uint32_t *d)
367{
368 int months = 0;
369 int days = dt->days;
370
371 if(dt->years != 0) {
372 for (months = 1; months <= 12 && days > 0; months++) {
373 int mdays = crm_time_days_in_month(months, dt->years);
374
375 if (mdays >= days) {
376 break;
377 } else {
378 days -= mdays;
379 }
380 }
381
382 } else if (dt->months) {
383 /* This is a duration including months, don't convert the days field */
384 months = dt->months;
385
386 } else {
387 /* This is a duration not including months, still don't convert the days field */
388 }
389
390 *y = dt->years;
391 *m = months;
392 *d = days;
393 crm_trace("%.4d-%.3d -> %.4d-%.2d-%.2d", dt->years, dt->days, dt->years, months, days);
394 return TRUE;
395}
396
397int
398crm_time_get_ordinal(const crm_time_t *dt, uint32_t *y, uint32_t *d)
399{
400 *y = dt->years;
401 *d = dt->days;
402 return TRUE;
403}
404
405int
406crm_time_get_isoweek(const crm_time_t *dt, uint32_t *y, uint32_t *w,
407 uint32_t *d)
408{
409 /*
410 * Monday 29 December 2008 is written "2009-W01-1"
411 * Sunday 3 January 2010 is written "2009-W53-7"
412 */
413 int year_num = 0;
414 int jan1 = crm_time_january1_weekday(dt->years);
415 int h = -1;
416
417 CRM_CHECK(dt->days > 0, return FALSE);
418
419/* 6. Find the Weekday for Y M D */
420 h = dt->days + jan1 - 1;
421 *d = 1 + ((h - 1) % 7);
422
423/* 7. Find if Y M D falls in YearNumber Y-1, WeekNumber 52 or 53 */
424 if (dt->days <= (8 - jan1) && jan1 > 4) {
425 crm_trace("year--, jan1=%d", jan1);
426 year_num = dt->years - 1;
427 *w = crm_time_weeks_in_year(year_num);
428
429 } else {
430 year_num = dt->years;
431 }
432
433/* 8. Find if Y M D falls in YearNumber Y+1, WeekNumber 1 */
434 if (year_num == dt->years) {
435 int dmax = year_days(year_num);
436 int correction = 4 - *d;
437
438 if ((dmax - dt->days) < correction) {
439 crm_trace("year++, jan1=%d, i=%d vs. %d", jan1, dmax - dt->days, correction);
440 year_num = dt->years + 1;
441 *w = 1;
442 }
443 }
444
445/* 9. Find if Y M D falls in YearNumber Y, WeekNumber 1 through 53 */
446 if (year_num == dt->years) {
447 int j = dt->days + (7 - *d) + (jan1 - 1);
448
449 *w = j / 7;
450 if (jan1 > 4) {
451 *w -= 1;
452 }
453 }
454
455 *y = year_num;
456 crm_trace("Converted %.4d-%.3d to %.4" PRIu32 "-W%.2" PRIu32 "-%" PRIu32,
457 dt->years, dt->days, *y, *w, *d);
458 return TRUE;
459}
460
461#define DATE_MAX 128
462
473static inline void
474sec_usec_as_string(long long sec, int usec, char *buf, size_t *offset)
475{
476 *offset += snprintf(buf + *offset, DATE_MAX - *offset, "%s%lld.%06d",
477 ((sec == 0) && (usec < 0))? "-" : "",
478 sec, QB_ABS(usec));
479}
480
490static void
491crm_duration_as_string(const crm_time_t *dt, int usec, bool show_usec,
492 char *result)
493{
494 size_t offset = 0;
495
496 CRM_ASSERT(valid_sec_usec(dt->seconds, usec));
497
498 if (dt->years) {
499 offset += snprintf(result + offset, DATE_MAX - offset, "%4d year%s ",
500 dt->years, pcmk__plural_s(dt->years));
501 }
502 if (dt->months) {
503 offset += snprintf(result + offset, DATE_MAX - offset, "%2d month%s ",
504 dt->months, pcmk__plural_s(dt->months));
505 }
506 if (dt->days) {
507 offset += snprintf(result + offset, DATE_MAX - offset, "%2d day%s ",
508 dt->days, pcmk__plural_s(dt->days));
509 }
510
511 // At least print seconds (and optionally usecs)
512 if ((offset == 0) || (dt->seconds != 0) || (show_usec && (usec != 0))) {
513 if (show_usec) {
514 sec_usec_as_string(dt->seconds, usec, result, &offset);
515 } else {
516 offset += snprintf(result + offset, DATE_MAX - offset, "%d",
517 dt->seconds);
518 }
519 offset += snprintf(result + offset, DATE_MAX - offset, " second%s",
520 pcmk__plural_s(dt->seconds));
521 }
522
523 // More than one minute, so provide a more readable breakdown into units
524 if (QB_ABS(dt->seconds) >= 60) {
525 uint32_t h = 0;
526 uint32_t m = 0;
527 uint32_t s = 0;
528 uint32_t u = QB_ABS(usec);
529 bool print_sec_component = false;
530
531 crm_time_get_sec(dt->seconds, &h, &m, &s);
532 print_sec_component = ((s != 0) || (show_usec && (u != 0)));
533
534 offset += snprintf(result + offset, DATE_MAX - offset, " (");
535
536 if (h) {
537 offset += snprintf(result + offset, DATE_MAX - offset,
538 "%" PRIu32 " hour%s%s", h, pcmk__plural_s(h),
539 ((m != 0) || print_sec_component)? " " : "");
540 }
541
542 if (m) {
543 offset += snprintf(result + offset, DATE_MAX - offset,
544 "%" PRIu32 " minute%s%s", m, pcmk__plural_s(m),
545 print_sec_component? " " : "");
546 }
547
548 if (print_sec_component) {
549 if (show_usec) {
550 sec_usec_as_string(s, u, result, &offset);
551 } else {
552 offset += snprintf(result + offset, DATE_MAX - offset,
553 "%" PRIu32, s);
554 }
555 offset += snprintf(result + offset, DATE_MAX - offset, " second%s",
556 pcmk__plural_s(dt->seconds));
557 }
558
559 offset += snprintf(result + offset, DATE_MAX - offset, ")");
560 }
561}
562
574static void
575time_as_string_common(const crm_time_t *dt, int usec, uint32_t flags,
576 char *result)
577{
578 crm_time_t *utc = NULL;
579 size_t offset = 0;
580
581 if (!crm_time_is_defined(dt)) {
582 strcpy(result, "<undefined time>");
583 return;
584 }
585
586 CRM_ASSERT(valid_sec_usec(dt->seconds, usec));
587
588 /* Simple cases: as duration, seconds, or seconds since epoch.
589 * These never depend on time zone.
590 */
591
593 crm_duration_as_string(dt, usec, pcmk_is_set(flags, crm_time_usecs),
594 result);
595 return;
596 }
597
598 if (pcmk_any_flags_set(flags, crm_time_seconds|crm_time_epoch)) {
599 long long seconds = 0;
600
602 seconds = crm_time_get_seconds(dt);
603 } else {
605 }
606
608 sec_usec_as_string(seconds, usec, result, &offset);
609 } else {
610 snprintf(result, DATE_MAX, "%lld", seconds);
611 }
612 return;
613 }
614
615 // Convert to UTC if local timezone was not requested
616 if ((dt->offset != 0) && !pcmk_is_set(flags, crm_time_log_with_timezone)) {
617 crm_trace("UTC conversion");
618 utc = crm_get_utc_time(dt);
619 dt = utc;
620 }
621
622 // As readable string
623
625 if (pcmk_is_set(flags, crm_time_weeks)) { // YYYY-WW-D
626 uint32_t y = 0;
627 uint32_t w = 0;
628 uint32_t d = 0;
629
630 if (crm_time_get_isoweek(dt, &y, &w, &d)) {
631 offset += snprintf(result + offset, DATE_MAX - offset,
632 "%" PRIu32 "-W%.2" PRIu32 "-%" PRIu32,
633 y, w, d);
634 }
635
636 } else if (pcmk_is_set(flags, crm_time_ordinal)) { // YYYY-DDD
637 uint32_t y = 0;
638 uint32_t d = 0;
639
640 if (crm_time_get_ordinal(dt, &y, &d)) {
641 offset += snprintf(result + offset, DATE_MAX - offset,
642 "%" PRIu32 "-%.3" PRIu32, y, d);
643 }
644
645 } else { // YYYY-MM-DD
646 uint32_t y = 0;
647 uint32_t m = 0;
648 uint32_t d = 0;
649
650 if (crm_time_get_gregorian(dt, &y, &m, &d)) {
651 offset += snprintf(result + offset, DATE_MAX - offset,
652 "%.4" PRIu32 "-%.2" PRIu32 "-%.2" PRIu32,
653 y, m, d);
654 }
655 }
656 }
657
659 uint32_t h = 0, m = 0, s = 0;
660
661 if (offset > 0) {
662 offset += snprintf(result + offset, DATE_MAX - offset, " ");
663 }
664
665 if (crm_time_get_timeofday(dt, &h, &m, &s)) {
666 offset += snprintf(result + offset, DATE_MAX - offset,
667 "%.2" PRIu32 ":%.2" PRIu32 ":%.2" PRIu32,
668 h, m, s);
669
671 offset += snprintf(result + offset, DATE_MAX - offset,
672 ".%06" PRIu32, QB_ABS(usec));
673 }
674 }
675
677 && (dt->offset != 0)) {
678 crm_time_get_sec(dt->offset, &h, &m, &s);
679 offset += snprintf(result + offset, DATE_MAX - offset,
680 " %c%.2" PRIu32 ":%.2" PRIu32,
681 ((dt->offset < 0)? '-' : '+'), h, m);
682 } else {
683 offset += snprintf(result + offset, DATE_MAX - offset, "Z");
684 }
685 }
686
687 crm_time_free(utc);
688}
689
698char *
700{
701 char result[DATE_MAX] = { '\0', };
702
703 time_as_string_common(dt, 0, flags, result);
704 return pcmk__str_copy(result);
705}
706
718static bool
719crm_time_parse_sec(const char *time_str, int *result)
720{
721 int rc;
722 uint32_t hour = 0;
723 uint32_t minute = 0;
724 uint32_t second = 0;
725
726 *result = 0;
727
728 // Must have at least hour, but minutes and seconds are optional
729 rc = sscanf(time_str, "%" SCNu32 ":%" SCNu32 ":%" SCNu32,
730 &hour, &minute, &second);
731 if (rc == 1) {
732 rc = sscanf(time_str, "%2" SCNu32 "%2" SCNu32 "%2" SCNu32,
733 &hour, &minute, &second);
734 }
735 if (rc == 0) {
736 crm_err("%s is not a valid ISO 8601 time specification", time_str);
737 errno = EINVAL;
738 return FALSE;
739 }
740
741 crm_trace("Got valid time: %.2" PRIu32 ":%.2" PRIu32 ":%.2" PRIu32,
742 hour, minute, second);
743
744 if ((hour == 24) && (minute == 0) && (second == 0)) {
745 // Equivalent to 00:00:00 of next day, return number of seconds in day
746 } else if (hour >= 24) {
747 crm_err("%s is not a valid ISO 8601 time specification "
748 "because %" PRIu32 " is not a valid hour", time_str, hour);
749 errno = EINVAL;
750 return FALSE;
751 }
752 if (minute >= 60) {
753 crm_err("%s is not a valid ISO 8601 time specification "
754 "because %" PRIu32 " is not a valid minute", time_str, minute);
755 errno = EINVAL;
756 return FALSE;
757 }
758 if (second >= 60) {
759 crm_err("%s is not a valid ISO 8601 time specification "
760 "because %" PRIu32 " is not a valid second", time_str, second);
761 errno = EINVAL;
762 return FALSE;
763 }
764
765 *result = (hour * HOUR_SECONDS) + (minute * 60) + second;
766 return TRUE;
767}
768
769static bool
770crm_time_parse_offset(const char *offset_str, int *offset)
771{
772 tzset();
773
774 if (offset_str == NULL) {
775 // Use local offset
776#if defined(HAVE_STRUCT_TM_TM_GMTOFF)
777 time_t now = time(NULL);
778 struct tm *now_tm = localtime(&now);
779#endif
780 int h_offset = GMTOFF(now_tm) / HOUR_SECONDS;
781 int m_offset = (GMTOFF(now_tm) - (HOUR_SECONDS * h_offset)) / 60;
782
783 if (h_offset < 0 && m_offset < 0) {
784 m_offset = 0 - m_offset;
785 }
786 *offset = (HOUR_SECONDS * h_offset) + (60 * m_offset);
787 return TRUE;
788 }
789
790 if (offset_str[0] == 'Z') { // @TODO invalid if anything after?
791 *offset = 0;
792 return TRUE;
793 }
794
795 *offset = 0;
796 if ((offset_str[0] == '+') || (offset_str[0] == '-')
797 || isdigit((int)offset_str[0])) {
798
799 gboolean negate = FALSE;
800
801 if (offset_str[0] == '+') {
802 offset_str++;
803 } else if (offset_str[0] == '-') {
804 negate = TRUE;
805 offset_str++;
806 }
807 if (crm_time_parse_sec(offset_str, offset) == FALSE) {
808 return FALSE;
809 }
810 if (negate) {
811 *offset = 0 - *offset;
812 }
813 } // @TODO else invalid?
814 return TRUE;
815}
816
827static bool
828crm_time_parse(const char *time_str, crm_time_t *a_time)
829{
830 uint32_t h, m, s;
831 char *offset_s = NULL;
832
833 tzset();
834
835 if (time_str) {
836 if (crm_time_parse_sec(time_str, &(a_time->seconds)) == FALSE) {
837 return FALSE;
838 }
839 offset_s = strstr(time_str, "Z");
840 if (offset_s == NULL) {
841 offset_s = strstr(time_str, " ");
842 if (offset_s) {
843 while (isspace(offset_s[0])) {
844 offset_s++;
845 }
846 }
847 }
848 }
849
850 if (crm_time_parse_offset(offset_s, &(a_time->offset)) == FALSE) {
851 return FALSE;
852 }
853 crm_time_get_sec(a_time->offset, &h, &m, &s);
854 crm_trace("Got tz: %c%2." PRIu32 ":%.2" PRIu32,
855 (a_time->offset < 0)? '-' : '+', h, m);
856
857 if (a_time->seconds == DAY_SECONDS) {
858 // 24:00:00 == 00:00:00 of next day
859 a_time->seconds = 0;
860 crm_time_add_days(a_time, 1);
861 }
862 return TRUE;
863}
864
865/*
866 * \internal
867 * \brief Parse a time object from an ISO 8601 date/time specification
868 *
869 * \param[in] date_str ISO 8601 date/time specification (or
870 * \c PCMK__VALUE_EPOCH)
871 *
872 * \return New time object on success, NULL (and set errno) otherwise
873 */
874static crm_time_t *
875parse_date(const char *date_str)
876{
877 const char *time_s = NULL;
878 crm_time_t *dt = NULL;
879
880 int year = 0;
881 int month = 0;
882 int week = 0;
883 int day = 0;
884 int rc = 0;
885
886 if (pcmk__str_empty(date_str)) {
887 crm_err("No ISO 8601 date/time specification given");
888 goto invalid;
889 }
890
891 if ((date_str[0] == 'T') || (date_str[2] == ':')) {
892 /* Just a time supplied - Infer current date */
893 dt = crm_time_new(NULL);
894 if (date_str[0] == 'T') {
895 time_s = date_str + 1;
896 } else {
897 time_s = date_str;
898 }
899 goto parse_time;
900 }
901
903
904 if ((strncasecmp(PCMK__VALUE_EPOCH, date_str, 5) == 0)
905 && ((date_str[5] == '\0')
906 || (date_str[5] == '/')
907 || isspace(date_str[5]))) {
908 dt->days = 1;
909 dt->years = 1970;
911 return dt;
912 }
913
914 /* YYYY-MM-DD */
915 rc = sscanf(date_str, "%d-%d-%d", &year, &month, &day);
916 if (rc == 1) {
917 /* YYYYMMDD */
918 rc = sscanf(date_str, "%4d%2d%2d", &year, &month, &day);
919 }
920 if (rc == 3) {
921 if (month > 12) {
922 crm_err("'%s' is not a valid ISO 8601 date/time specification "
923 "because '%d' is not a valid month", date_str, month);
924 goto invalid;
925 } else if (day > crm_time_days_in_month(month, year)) {
926 crm_err("'%s' is not a valid ISO 8601 date/time specification "
927 "because '%d' is not a valid day of the month",
928 date_str, day);
929 goto invalid;
930 } else {
931 dt->years = year;
932 dt->days = get_ordinal_days(year, month, day);
933 crm_trace("Parsed Gregorian date '%.4d-%.3d' from date string '%s'",
934 year, dt->days, date_str);
935 }
936 goto parse_time;
937 }
938
939 /* YYYY-DDD */
940 rc = sscanf(date_str, "%d-%d", &year, &day);
941 if (rc == 2) {
942 if (day > year_days(year)) {
943 crm_err("'%s' is not a valid ISO 8601 date/time specification "
944 "because '%d' is not a valid day of the year (max %d)",
945 date_str, day, year_days(year));
946 goto invalid;
947 }
948 crm_trace("Parsed ordinal year %d and days %d from date string '%s'",
949 year, day, date_str);
950 dt->days = day;
951 dt->years = year;
952 goto parse_time;
953 }
954
955 /* YYYY-Www-D */
956 rc = sscanf(date_str, "%d-W%d-%d", &year, &week, &day);
957 if (rc == 3) {
958 if (week > crm_time_weeks_in_year(year)) {
959 crm_err("'%s' is not a valid ISO 8601 date/time specification "
960 "because '%d' is not a valid week of the year (max %d)",
961 date_str, week, crm_time_weeks_in_year(year));
962 goto invalid;
963 } else if (day < 1 || day > 7) {
964 crm_err("'%s' is not a valid ISO 8601 date/time specification "
965 "because '%d' is not a valid day of the week",
966 date_str, day);
967 goto invalid;
968 } else {
969 /*
970 * See https://en.wikipedia.org/wiki/ISO_week_date
971 *
972 * Monday 29 December 2008 is written "2009-W01-1"
973 * Sunday 3 January 2010 is written "2009-W53-7"
974 * Saturday 27 September 2008 is written "2008-W37-6"
975 *
976 * If 1 January is on a Monday, Tuesday, Wednesday or Thursday, it is in week 01.
977 * If 1 January is on a Friday, Saturday or Sunday, it is in week 52 or 53 of the previous year.
978 */
979 int jan1 = crm_time_january1_weekday(year);
980
981 crm_trace("Got year %d (Jan 1 = %d), week %d, and day %d from date string '%s'",
982 year, jan1, week, day, date_str);
983
984 dt->years = year;
985 crm_time_add_days(dt, (week - 1) * 7);
986
987 if (jan1 <= 4) {
988 crm_time_add_days(dt, 1 - jan1);
989 } else {
990 crm_time_add_days(dt, 8 - jan1);
991 }
992
993 crm_time_add_days(dt, day);
994 }
995 goto parse_time;
996 }
997
998 crm_err("'%s' is not a valid ISO 8601 date/time specification", date_str);
999 goto invalid;
1000
1001 parse_time:
1002
1003 if (time_s == NULL) {
1004 time_s = date_str + strspn(date_str, "0123456789-W");
1005 if ((time_s[0] == ' ') || (time_s[0] == 'T')) {
1006 ++time_s;
1007 } else {
1008 time_s = NULL;
1009 }
1010 }
1011 if ((time_s != NULL) && (crm_time_parse(time_s, dt) == FALSE)) {
1012 goto invalid;
1013 }
1014
1016 if (crm_time_check(dt) == FALSE) {
1017 crm_err("'%s' is not a valid ISO 8601 date/time specification",
1018 date_str);
1019 goto invalid;
1020 }
1021 return dt;
1022
1023invalid:
1024 crm_time_free(dt);
1025 errno = EINVAL;
1026 return NULL;
1027}
1028
1029// Parse an ISO 8601 numeric value and return number of characters consumed
1030// @TODO This cannot handle >INT_MAX int values
1031// @TODO Fractions appear to be not working
1032// @TODO Error out on invalid specifications
1033static int
1034parse_int(const char *str, int field_width, int upper_bound, int *result)
1035{
1036 int lpc = 0;
1037 int offset = 0;
1038 int intermediate = 0;
1039 gboolean fraction = FALSE;
1040 gboolean negate = FALSE;
1041
1042 *result = 0;
1043 if (*str == '\0') {
1044 return 0;
1045 }
1046
1047 if (str[offset] == 'T') {
1048 offset++;
1049 }
1050
1051 if (str[offset] == '.' || str[offset] == ',') {
1052 fraction = TRUE;
1053 field_width = -1;
1054 offset++;
1055 } else if (str[offset] == '-') {
1056 negate = TRUE;
1057 offset++;
1058 } else if (str[offset] == '+' || str[offset] == ':') {
1059 offset++;
1060 }
1061
1062 for (; (fraction || lpc < field_width) && isdigit((int)str[offset]); lpc++) {
1063 if (fraction) {
1064 intermediate = (str[offset] - '0') / (10 ^ lpc);
1065 } else {
1066 *result *= 10;
1067 intermediate = str[offset] - '0';
1068 }
1069 *result += intermediate;
1070 offset++;
1071 }
1072 if (fraction) {
1073 *result = (int)(*result * upper_bound);
1074
1075 } else if (upper_bound > 0 && *result > upper_bound) {
1076 *result = upper_bound;
1077 }
1078 if (negate) {
1079 *result = 0 - *result;
1080 }
1081 if (lpc > 0) {
1082 crm_trace("Found int: %d. Stopped at str[%d]='%c'", *result, lpc, str[lpc]);
1083 return offset;
1084 }
1085 return 0;
1086}
1087
1099crm_time_t *
1100crm_time_parse_duration(const char *period_s)
1101{
1102 gboolean is_time = FALSE;
1103 crm_time_t *diff = NULL;
1104
1105 if (pcmk__str_empty(period_s)) {
1106 crm_err("No ISO 8601 time duration given");
1107 goto invalid;
1108 }
1109 if (period_s[0] != 'P') {
1110 crm_err("'%s' is not a valid ISO 8601 time duration "
1111 "because it does not start with a 'P'", period_s);
1112 goto invalid;
1113 }
1114 if ((period_s[1] == '\0') || isspace(period_s[1])) {
1115 crm_err("'%s' is not a valid ISO 8601 time duration "
1116 "because nothing follows 'P'", period_s);
1117 goto invalid;
1118 }
1119
1120 diff = crm_time_new_undefined();
1121 diff->duration = TRUE;
1122
1123 for (const char *current = period_s + 1;
1124 current[0] && (current[0] != '/') && !isspace(current[0]);
1125 ++current) {
1126
1127 int an_int = 0, rc;
1128
1129 if (current[0] == 'T') {
1130 /* A 'T' separates year/month/day from hour/minute/seconds. We don't
1131 * require it strictly, but just use it to differentiate month from
1132 * minutes.
1133 */
1134 is_time = TRUE;
1135 continue;
1136 }
1137
1138 // An integer must be next
1139 rc = parse_int(current, 10, 0, &an_int);
1140 if (rc == 0) {
1141 crm_err("'%s' is not a valid ISO 8601 time duration "
1142 "because no integer at '%s'", period_s, current);
1143 goto invalid;
1144 }
1145 current += rc;
1146
1147 // A time unit must be next (we're not strict about the order)
1148 switch (current[0]) {
1149 case 'Y':
1150 diff->years = an_int;
1151 break;
1152 case 'M':
1153 if (is_time) {
1154 /* Minutes */
1155 diff->seconds += an_int * 60;
1156 } else {
1157 diff->months = an_int;
1158 }
1159 break;
1160 case 'W':
1161 diff->days += an_int * 7;
1162 break;
1163 case 'D':
1164 diff->days += an_int;
1165 break;
1166 case 'H':
1167 diff->seconds += an_int * HOUR_SECONDS;
1168 break;
1169 case 'S':
1170 diff->seconds += an_int;
1171 break;
1172 case '\0':
1173 crm_err("'%s' is not a valid ISO 8601 time duration "
1174 "because no units after %d", period_s, an_int);
1175 goto invalid;
1176 default:
1177 crm_err("'%s' is not a valid ISO 8601 time duration "
1178 "because '%c' is not a valid time unit",
1179 period_s, current[0]);
1180 goto invalid;
1181 }
1182 }
1183
1184 if (!crm_time_is_defined(diff)) {
1185 crm_err("'%s' is not a valid ISO 8601 time duration "
1186 "because no amounts and units given", period_s);
1187 goto invalid;
1188 }
1189 return diff;
1190
1191invalid:
1192 crm_time_free(diff);
1193 errno = EINVAL;
1194 return NULL;
1195}
1196
1208crm_time_parse_period(const char *period_str)
1209{
1210 const char *original = period_str;
1211 crm_time_period_t *period = NULL;
1212
1213 if (pcmk__str_empty(period_str)) {
1214 crm_err("No ISO 8601 time period given");
1215 goto invalid;
1216 }
1217
1218 tzset();
1219 period = pcmk__assert_alloc(1, sizeof(crm_time_period_t));
1220
1221 if (period_str[0] == 'P') {
1222 period->diff = crm_time_parse_duration(period_str);
1223 if (period->diff == NULL) {
1224 goto error;
1225 }
1226 } else {
1227 period->start = parse_date(period_str);
1228 if (period->start == NULL) {
1229 goto error;
1230 }
1231 }
1232
1233 period_str = strstr(original, "/");
1234 if (period_str) {
1235 ++period_str;
1236 if (period_str[0] == 'P') {
1237 if (period->diff != NULL) {
1238 crm_err("'%s' is not a valid ISO 8601 time period "
1239 "because it has two durations",
1240 original);
1241 goto invalid;
1242 }
1243 period->diff = crm_time_parse_duration(period_str);
1244 if (period->diff == NULL) {
1245 goto error;
1246 }
1247 } else {
1248 period->end = parse_date(period_str);
1249 if (period->end == NULL) {
1250 goto error;
1251 }
1252 }
1253
1254 } else if (period->diff != NULL) {
1255 // Only duration given, assume start is now
1256 period->start = crm_time_new(NULL);
1257
1258 } else {
1259 // Only start given
1260 crm_err("'%s' is not a valid ISO 8601 time period "
1261 "because it has no duration or ending time",
1262 original);
1263 goto invalid;
1264 }
1265
1266 if (period->start == NULL) {
1267 period->start = crm_time_subtract(period->end, period->diff);
1268
1269 } else if (period->end == NULL) {
1270 period->end = crm_time_add(period->start, period->diff);
1271 }
1272
1273 if (crm_time_check(period->start) == FALSE) {
1274 crm_err("'%s' is not a valid ISO 8601 time period "
1275 "because the start is invalid", period_str);
1276 goto invalid;
1277 }
1278 if (crm_time_check(period->end) == FALSE) {
1279 crm_err("'%s' is not a valid ISO 8601 time period "
1280 "because the end is invalid", period_str);
1281 goto invalid;
1282 }
1283 return period;
1284
1285invalid:
1286 errno = EINVAL;
1287error:
1288 crm_time_free_period(period);
1289 return NULL;
1290}
1291
1297void
1299{
1300 if (period) {
1301 crm_time_free(period->start);
1302 crm_time_free(period->end);
1303 crm_time_free(period->diff);
1304 free(period);
1305 }
1306}
1307
1308void
1310{
1311 crm_trace("target=%p, source=%p", target, source);
1312
1313 CRM_CHECK(target != NULL && source != NULL, return);
1314
1315 target->years = source->years;
1316 target->days = source->days;
1317 target->months = source->months; /* Only for durations */
1318 target->seconds = source->seconds;
1319 target->offset = source->offset;
1320
1321 crm_time_log(LOG_TRACE, "source", source,
1323 crm_time_log(LOG_TRACE, "target", target,
1325}
1326
1327static void
1328ha_set_tm_time(crm_time_t *target, const struct tm *source)
1329{
1330 int h_offset = 0;
1331 int m_offset = 0;
1332
1333 /* Ensure target is fully initialized */
1334 target->years = 0;
1335 target->months = 0;
1336 target->days = 0;
1337 target->seconds = 0;
1338 target->offset = 0;
1339 target->duration = FALSE;
1340
1341 if (source->tm_year > 0) {
1342 /* years since 1900 */
1343 target->years = 1900 + source->tm_year;
1344 }
1345
1346 if (source->tm_yday >= 0) {
1347 /* days since January 1 [0-365] */
1348 target->days = 1 + source->tm_yday;
1349 }
1350
1351 if (source->tm_hour >= 0) {
1352 target->seconds += HOUR_SECONDS * source->tm_hour;
1353 }
1354 if (source->tm_min >= 0) {
1355 target->seconds += 60 * source->tm_min;
1356 }
1357 if (source->tm_sec >= 0) {
1358 target->seconds += source->tm_sec;
1359 }
1360
1361 /* tm_gmtoff == offset from UTC in seconds */
1362 h_offset = GMTOFF(source) / HOUR_SECONDS;
1363 m_offset = (GMTOFF(source) - (HOUR_SECONDS * h_offset)) / 60;
1364 crm_trace("Time offset is %lds (%.2d:%.2d)",
1365 GMTOFF(source), h_offset, m_offset);
1366
1367 target->offset += HOUR_SECONDS * h_offset;
1368 target->offset += 60 * m_offset;
1369}
1370
1371void
1372crm_time_set_timet(crm_time_t *target, const time_t *source)
1373{
1374 ha_set_tm_time(target, localtime(source));
1375}
1376
1384void
1386{
1387 if ((target != NULL) && (source != NULL)
1389 || (crm_time_compare(source, target) < 0))) {
1390 crm_time_set(target, source);
1391 }
1392}
1393
1394crm_time_t *
1396{
1398
1399 crm_time_set(target, source);
1400 return target;
1401}
1402
1411crm_time_t *
1412pcmk__copy_timet(time_t source)
1413{
1415
1416 crm_time_set_timet(target, &source);
1417 return target;
1418}
1419
1420crm_time_t *
1421crm_time_add(const crm_time_t *dt, const crm_time_t *value)
1422{
1423 crm_time_t *utc = NULL;
1424 crm_time_t *answer = NULL;
1425
1426 if ((dt == NULL) || (value == NULL)) {
1427 errno = EINVAL;
1428 return NULL;
1429 }
1430
1431 answer = pcmk_copy_time(dt);
1432
1433 utc = crm_get_utc_time(value);
1434 if (utc == NULL) {
1435 crm_time_free(answer);
1436 return NULL;
1437 }
1438
1439 answer->years += utc->years;
1440 crm_time_add_months(answer, utc->months);
1441 crm_time_add_days(answer, utc->days);
1442 crm_time_add_seconds(answer, utc->seconds);
1443
1444 crm_time_free(utc);
1445 return answer;
1446}
1447
1457const char *
1459{
1460 switch (component) {
1461 case pcmk__time_years:
1462 return PCMK_XA_YEARS;
1463
1464 case pcmk__time_months:
1465 return PCMK_XA_MONTHS;
1466
1467 case pcmk__time_weeks:
1468 return PCMK_XA_WEEKS;
1469
1470 case pcmk__time_days:
1471 return PCMK_XA_DAYS;
1472
1473 case pcmk__time_hours:
1474 return PCMK_XA_HOURS;
1475
1476 case pcmk__time_minutes:
1477 return PCMK_XA_MINUTES;
1478
1479 case pcmk__time_seconds:
1480 return PCMK_XA_SECONDS;
1481
1482 default:
1483 return NULL;
1484 }
1485}
1486
1487typedef void (*component_fn_t)(crm_time_t *, int);
1488
1497static component_fn_t
1498component_fn(enum pcmk__time_component component)
1499{
1500 switch (component) {
1501 case pcmk__time_years:
1502 return crm_time_add_years;
1503
1504 case pcmk__time_months:
1505 return crm_time_add_months;
1506
1507 case pcmk__time_weeks:
1508 return crm_time_add_weeks;
1509
1510 case pcmk__time_days:
1511 return crm_time_add_days;
1512
1513 case pcmk__time_hours:
1514 return crm_time_add_hours;
1515
1516 case pcmk__time_minutes:
1517 return crm_time_add_minutes;
1518
1519 case pcmk__time_seconds:
1520 return crm_time_add_seconds;
1521
1522 default:
1523 return NULL;
1524 }
1525
1526}
1527
1538int
1540 const xmlNode *xml)
1541{
1542 long long value;
1543 const char *attr = pcmk__time_component_attr(component);
1544 component_fn_t add = component_fn(component);
1545
1546 if ((t == NULL) || (attr == NULL) || (add == NULL)) {
1547 return EINVAL;
1548 }
1549
1550 if (xml == NULL) {
1551 return pcmk_rc_ok;
1552 }
1553
1554 if (pcmk__scan_ll(crm_element_value(xml, attr), &value,
1555 0LL) != pcmk_rc_ok) {
1556 return pcmk_rc_unpack_error;
1557 }
1558
1559 if ((value < INT_MIN) || (value > INT_MAX)) {
1560 return ERANGE;
1561 }
1562
1563 if (value != 0LL) {
1564 add(t, (int) value);
1565 }
1566 return pcmk_rc_ok;
1567}
1568
1569crm_time_t *
1571{
1572 crm_time_t *utc = NULL;
1573 crm_time_t *answer = NULL;
1574
1575 if ((dt == NULL) || (value == NULL)) {
1576 errno = EINVAL;
1577 return NULL;
1578 }
1579
1580 utc = crm_get_utc_time(value);
1581 if (utc == NULL) {
1582 return NULL;
1583 }
1584
1585 answer = crm_get_utc_time(dt);
1586 if (answer == NULL) {
1587 crm_time_free(utc);
1588 return NULL;
1589 }
1590 answer->duration = TRUE;
1591
1592 answer->years -= utc->years;
1593 if(utc->months != 0) {
1594 crm_time_add_months(answer, -utc->months);
1595 }
1596 crm_time_add_days(answer, -utc->days);
1597 crm_time_add_seconds(answer, -utc->seconds);
1598
1599 crm_time_free(utc);
1600 return answer;
1601}
1602
1603crm_time_t *
1605{
1606 crm_time_t *utc = NULL;
1607 crm_time_t *answer = NULL;
1608
1609 if ((dt == NULL) || (value == NULL)) {
1610 errno = EINVAL;
1611 return NULL;
1612 }
1613
1614 utc = crm_get_utc_time(value);
1615 if (utc == NULL) {
1616 return NULL;
1617 }
1618
1619 answer = pcmk_copy_time(dt);
1620 answer->years -= utc->years;
1621 if(utc->months != 0) {
1622 crm_time_add_months(answer, -utc->months);
1623 }
1624 crm_time_add_days(answer, -utc->days);
1625 crm_time_add_seconds(answer, -utc->seconds);
1626 crm_time_free(utc);
1627
1628 return answer;
1629}
1630
1638bool
1640{
1641 return (dt != NULL)
1642 && (dt->days > 0) && (dt->days <= year_days(dt->years))
1643 && (dt->seconds >= 0) && (dt->seconds < DAY_SECONDS);
1644}
1645
1646#define do_cmp_field(l, r, field) \
1647 if(rc == 0) { \
1648 if(l->field > r->field) { \
1649 crm_trace("%s: %d > %d", \
1650 #field, l->field, r->field); \
1651 rc = 1; \
1652 } else if(l->field < r->field) { \
1653 crm_trace("%s: %d < %d", \
1654 #field, l->field, r->field); \
1655 rc = -1; \
1656 } \
1657 }
1658
1659int
1661{
1662 int rc = 0;
1663 crm_time_t *t1 = crm_get_utc_time(a);
1664 crm_time_t *t2 = crm_get_utc_time(b);
1665
1666 if ((t1 == NULL) && (t2 == NULL)) {
1667 rc = 0;
1668 } else if (t1 == NULL) {
1669 rc = -1;
1670 } else if (t2 == NULL) {
1671 rc = 1;
1672 } else {
1673 do_cmp_field(t1, t2, years);
1674 do_cmp_field(t1, t2, days);
1675 do_cmp_field(t1, t2, seconds);
1676 }
1677
1678 crm_time_free(t1);
1679 crm_time_free(t2);
1680 return rc;
1681}
1682
1689void
1691{
1692 int days = 0;
1693
1694 crm_trace("Adding %d seconds to %d (max=%d)",
1695 extra, a_time->seconds, DAY_SECONDS);
1696 a_time->seconds += extra;
1697 days = a_time->seconds / DAY_SECONDS;
1698 a_time->seconds %= DAY_SECONDS;
1699
1700 // Don't have negative seconds
1701 if (a_time->seconds < 0) {
1702 a_time->seconds += DAY_SECONDS;
1703 --days;
1704 }
1705
1706 crm_time_add_days(a_time, days);
1707}
1708
1709void
1710crm_time_add_days(crm_time_t * a_time, int extra)
1711{
1712 int lower_bound = 1;
1713 int ydays = crm_time_leapyear(a_time->years) ? 366 : 365;
1714
1715 crm_trace("Adding %d days to %.4d-%.3d", extra, a_time->years, a_time->days);
1716
1717 a_time->days += extra;
1718 while (a_time->days > ydays) {
1719 a_time->years++;
1720 a_time->days -= ydays;
1721 ydays = crm_time_leapyear(a_time->years) ? 366 : 365;
1722 }
1723
1724 if(a_time->duration) {
1725 lower_bound = 0;
1726 }
1727
1728 while (a_time->days < lower_bound) {
1729 a_time->years--;
1730 a_time->days += crm_time_leapyear(a_time->years) ? 366 : 365;
1731 }
1732}
1733
1734void
1736{
1737 int lpc;
1738 uint32_t y, m, d, dmax;
1739
1740 crm_time_get_gregorian(a_time, &y, &m, &d);
1741 crm_trace("Adding %d months to %.4" PRIu32 "-%.2" PRIu32 "-%.2" PRIu32,
1742 extra, y, m, d);
1743
1744 if (extra > 0) {
1745 for (lpc = extra; lpc > 0; lpc--) {
1746 m++;
1747 if (m == 13) {
1748 m = 1;
1749 y++;
1750 }
1751 }
1752 } else {
1753 for (lpc = -extra; lpc > 0; lpc--) {
1754 m--;
1755 if (m == 0) {
1756 m = 12;
1757 y--;
1758 }
1759 }
1760 }
1761
1762 dmax = crm_time_days_in_month(m, y);
1763 if (dmax < d) {
1764 /* Preserve day-of-month unless the month doesn't have enough days */
1765 d = dmax;
1766 }
1767
1768 crm_trace("Calculated %.4" PRIu32 "-%.2" PRIu32 "-%.2" PRIu32, y, m, d);
1769
1770 a_time->years = y;
1771 a_time->days = get_ordinal_days(y, m, d);
1772
1773 crm_time_get_gregorian(a_time, &y, &m, &d);
1774 crm_trace("Got %.4" PRIu32 "-%.2" PRIu32 "-%.2" PRIu32, y, m, d);
1775}
1776
1777void
1779{
1780 crm_time_add_seconds(a_time, extra * 60);
1781}
1782
1783void
1784crm_time_add_hours(crm_time_t * a_time, int extra)
1785{
1786 crm_time_add_seconds(a_time, extra * HOUR_SECONDS);
1787}
1788
1789void
1790crm_time_add_weeks(crm_time_t * a_time, int extra)
1791{
1792 crm_time_add_days(a_time, extra * 7);
1793}
1794
1795void
1796crm_time_add_years(crm_time_t * a_time, int extra)
1797{
1798 a_time->years += extra;
1799}
1800
1801static void
1802ha_get_tm_time(struct tm *target, const crm_time_t *source)
1803{
1804 *target = (struct tm) {
1805 .tm_year = source->years - 1900,
1806 .tm_mday = source->days,
1807 .tm_sec = source->seconds % 60,
1808 .tm_min = ( source->seconds / 60 ) % 60,
1809 .tm_hour = source->seconds / HOUR_SECONDS,
1810 .tm_isdst = -1, /* don't adjust */
1811
1812#if defined(HAVE_STRUCT_TM_TM_GMTOFF)
1813 .tm_gmtoff = source->offset
1814#endif
1815 };
1816 mktime(target);
1817}
1818
1819/* The high-resolution variant of time object was added to meet an immediate
1820 * need, and is kept internal API.
1821 *
1822 * @TODO The long-term goal is to come up with a clean, unified design for a
1823 * time type (or types) that meets all the various needs, to replace
1824 * crm_time_t, pcmk__time_hr_t, and struct timespec (in lrmd_cmd_t).
1825 * Using glib's GDateTime is a possibility (if we are willing to require
1826 * glib >= 2.26).
1827 */
1828
1831{
1832 pcmk__time_hr_t *hr_dt = NULL;
1833
1834 if (dt) {
1835 hr_dt = target;
1836 if (hr_dt == NULL) {
1837 hr_dt = pcmk__assert_alloc(1, sizeof(pcmk__time_hr_t));
1838 }
1839
1840 *hr_dt = (pcmk__time_hr_t) {
1841 .years = dt->years,
1842 .months = dt->months,
1843 .days = dt->days,
1844 .seconds = dt->seconds,
1845 .offset = dt->offset,
1846 .duration = dt->duration
1847 };
1848 }
1849
1850 return hr_dt;
1851}
1852
1853void
1855{
1856 CRM_ASSERT((hr_dt) && (target));
1857 *target = (crm_time_t) {
1858 .years = hr_dt->years,
1859 .months = hr_dt->months,
1860 .days = hr_dt->days,
1861 .seconds = hr_dt->seconds,
1862 .offset = hr_dt->offset,
1863 .duration = hr_dt->duration
1864 };
1865}
1866
1876pcmk__time_hr_now(time_t *epoch)
1877{
1878 struct timespec tv;
1879 crm_time_t dt;
1880 pcmk__time_hr_t *hr;
1881
1882 qb_util_timespec_from_epoch_get(&tv);
1883 if (epoch != NULL) {
1884 *epoch = tv.tv_sec;
1885 }
1886 crm_time_set_timet(&dt, &(tv.tv_sec));
1887 hr = pcmk__time_hr_convert(NULL, &dt);
1888 if (hr != NULL) {
1889 hr->useconds = tv.tv_nsec / QB_TIME_NS_IN_USEC;
1890 }
1891 return hr;
1892}
1893
1895pcmk__time_hr_new(const char *date_time)
1896{
1897 pcmk__time_hr_t *hr_dt = NULL;
1898
1899 if (date_time == NULL) {
1900 hr_dt = pcmk__time_hr_now(NULL);
1901 } else {
1902 crm_time_t *dt;
1903
1904 dt = parse_date(date_time);
1905 hr_dt = pcmk__time_hr_convert(NULL, dt);
1906 crm_time_free(dt);
1907 }
1908 return hr_dt;
1909}
1910
1911void
1913{
1914 free(hr_dt);
1915}
1916
1917char *
1918pcmk__time_format_hr(const char *format, const pcmk__time_hr_t *hr_dt)
1919{
1920#define DATE_LEN_MAX 128
1921 const char *mark_s = NULL;
1922 int scanned_pos = 0;
1923 int printed_pos = 0;
1924 int fmt_pos = 0;
1925 size_t date_len = 0;
1926 int nano_digits = 0;
1927
1928 char nano_s[10] = { '\0', };
1929 char date_s[DATE_LEN_MAX] = { '\0', };
1930 char nanofmt_s[5] = "%";
1931 char *tmp_fmt_s = NULL;
1932
1933 struct tm tm = { 0, };
1934 crm_time_t dt = { 0, };
1935
1936 if (!format) {
1937 return NULL;
1938 }
1939 pcmk__time_set_hr_dt(&dt, hr_dt);
1940 ha_get_tm_time(&tm, &dt);
1941 sprintf(nano_s, "%06d000", hr_dt->useconds);
1942
1943 while ((format[scanned_pos]) != '\0') {
1944 mark_s = strchr(&format[scanned_pos], '%');
1945 if (mark_s) {
1946 int fmt_len = 1;
1947
1948 fmt_pos = mark_s - format;
1949 while ((format[fmt_pos+fmt_len] != '\0') &&
1950 (format[fmt_pos+fmt_len] >= '0') &&
1951 (format[fmt_pos+fmt_len] <= '9')) {
1952 fmt_len++;
1953 }
1954 scanned_pos = fmt_pos + fmt_len + 1;
1955 if (format[fmt_pos+fmt_len] == 'N') {
1956 nano_digits = atoi(&format[fmt_pos+1]);
1957 nano_digits = (nano_digits > 6)?6:nano_digits;
1958 nano_digits = (nano_digits < 0)?0:nano_digits;
1959 sprintf(&nanofmt_s[1], ".%ds", nano_digits);
1960 } else {
1961 if (format[scanned_pos] != '\0') {
1962 continue;
1963 }
1964 fmt_pos = scanned_pos; /* print till end */
1965 }
1966 } else {
1967 scanned_pos = strlen(format);
1968 fmt_pos = scanned_pos; /* print till end */
1969 }
1970 tmp_fmt_s = strndup(&format[printed_pos], fmt_pos - printed_pos);
1971#ifdef HAVE_FORMAT_NONLITERAL
1972#pragma GCC diagnostic push
1973#pragma GCC diagnostic ignored "-Wformat-nonliteral"
1974#endif
1975 date_len += strftime(&date_s[date_len], DATE_LEN_MAX - date_len,
1976 tmp_fmt_s, &tm);
1977#ifdef HAVE_FORMAT_NONLITERAL
1978#pragma GCC diagnostic pop
1979#endif
1980 printed_pos = scanned_pos;
1981 free(tmp_fmt_s);
1982 if (nano_digits) {
1983#ifdef HAVE_FORMAT_NONLITERAL
1984#pragma GCC diagnostic push
1985#pragma GCC diagnostic ignored "-Wformat-nonliteral"
1986#endif
1987 date_len += snprintf(&date_s[date_len], DATE_LEN_MAX - date_len,
1988 nanofmt_s, nano_s);
1989#ifdef HAVE_FORMAT_NONLITERAL
1990#pragma GCC diagnostic pop
1991#endif
1992 nano_digits = 0;
1993 }
1994 }
1995
1996 return (date_len == 0)?NULL:strdup(date_s);
1997#undef DATE_LEN_MAX
1998}
1999
2013char *
2014pcmk__epoch2str(const time_t *source, uint32_t flags)
2015{
2016 time_t epoch_time = (source == NULL)? time(NULL) : *source;
2017
2018 if (flags == 0) {
2019 return pcmk__str_copy(pcmk__trim(ctime(&epoch_time)));
2020 } else {
2021 crm_time_t dt;
2022
2023 crm_time_set_timet(&dt, &epoch_time);
2024 return crm_time_as_string(&dt, flags);
2025 }
2026}
2027
2045char *
2046pcmk__timespec2str(const struct timespec *ts, uint32_t flags)
2047{
2048 struct timespec tmp_ts;
2049 crm_time_t dt;
2050 char result[DATE_MAX] = { 0 };
2051
2052 if (ts == NULL) {
2053 qb_util_timespec_from_epoch_get(&tmp_ts);
2054 ts = &tmp_ts;
2055 }
2056 crm_time_set_timet(&dt, &ts->tv_sec);
2057 time_as_string_common(&dt, ts->tv_nsec / QB_TIME_NS_IN_USEC, flags, result);
2058 return pcmk__str_copy(result);
2059}
2060
2072const char *
2073pcmk__readable_interval(guint interval_ms)
2074{
2075#define MS_IN_S (1000)
2076#define MS_IN_M (MS_IN_S * 60)
2077#define MS_IN_H (MS_IN_M * 60)
2078#define MS_IN_D (MS_IN_H * 24)
2079#define MAXSTR sizeof("..d..h..m..s...ms")
2080 static char str[MAXSTR];
2081 int offset = 0;
2082
2083 str[0] = '\0';
2084 if (interval_ms >= MS_IN_D) {
2085 offset += snprintf(str + offset, MAXSTR - offset, "%ud",
2086 interval_ms / MS_IN_D);
2087 interval_ms -= (interval_ms / MS_IN_D) * MS_IN_D;
2088 }
2089 if (interval_ms >= MS_IN_H) {
2090 offset += snprintf(str + offset, MAXSTR - offset, "%uh",
2091 interval_ms / MS_IN_H);
2092 interval_ms -= (interval_ms / MS_IN_H) * MS_IN_H;
2093 }
2094 if (interval_ms >= MS_IN_M) {
2095 offset += snprintf(str + offset, MAXSTR - offset, "%um",
2096 interval_ms / MS_IN_M);
2097 interval_ms -= (interval_ms / MS_IN_M) * MS_IN_M;
2098 }
2099
2100 // Ns, N.NNNs, or NNNms
2101 if (interval_ms >= MS_IN_S) {
2102 offset += snprintf(str + offset, MAXSTR - offset, "%u",
2103 interval_ms / MS_IN_S);
2104 interval_ms -= (interval_ms / MS_IN_S) * MS_IN_S;
2105 if (interval_ms > 0) {
2106 offset += snprintf(str + offset, MAXSTR - offset, ".%03u",
2107 interval_ms);
2108 }
2109 (void) snprintf(str + offset, MAXSTR - offset, "s");
2110
2111 } else if (interval_ms > 0) {
2112 (void) snprintf(str + offset, MAXSTR - offset, "%ums", interval_ms);
2113
2114 } else if (str[0] == '\0') {
2115 strcpy(str, "0s");
2116 }
2117 return str;
2118}
#define pcmk__assert_alloc(nmemb, size)
Definition internal.h:297
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:100
#define HAVE_STRUCT_TM_TM_GMTOFF
Definition config.h:395
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:1412
#define MS_IN_S
#define DATE_MAX
Definition iso8601.c:461
void crm_time_set_timet(crm_time_t *target, const time_t *source)
Definition iso8601.c:1372
crm_time_t * crm_time_subtract(const crm_time_t *dt, const crm_time_t *value)
Definition iso8601.c:1604
const char * pcmk__time_component_attr(enum pcmk__time_component component)
Definition iso8601.c:1458
char * pcmk__time_format_hr(const char *format, const pcmk__time_hr_t *hr_dt)
Definition iso8601.c:1918
void(* component_fn_t)(crm_time_t *, int)
Definition iso8601.c:1487
#define DAY_SECONDS
Definition iso8601.c:51
void crm_time_set(crm_time_t *target, const crm_time_t *source)
Definition iso8601.c:1309
void crm_time_add_months(crm_time_t *a_time, int extra)
Definition iso8601.c:1735
pcmk__time_hr_t * pcmk__time_hr_convert(pcmk__time_hr_t *target, const crm_time_t *dt)
Definition iso8601.c:1830
char * pcmk__epoch2str(const time_t *source, uint32_t flags)
Definition iso8601.c:2014
#define GMTOFF(tm)
Definition iso8601.c:47
long long crm_time_get_seconds_since_epoch(const crm_time_t *dt)
Definition iso8601.c:359
#define MAXSTR
#define MS_IN_M
#define EPOCH_SECONDS
Definition iso8601.c:357
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:1208
#define DATE_LEN_MAX
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:1298
int crm_time_get_ordinal(const crm_time_t *dt, uint32_t *y, uint32_t *d)
Definition iso8601.c:398
crm_time_t * pcmk_copy_time(const crm_time_t *source)
Definition iso8601.c:1395
void pcmk__set_time_if_earlier(crm_time_t *target, const crm_time_t *source)
Definition iso8601.c:1385
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:1539
const char * pcmk__readable_interval(guint interval_ms)
Definition iso8601.c:2073
void crm_time_add_minutes(crm_time_t *a_time, int extra)
Definition iso8601.c:1778
pcmk__time_hr_t * pcmk__time_hr_new(const char *date_time)
Definition iso8601.c:1895
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:1690
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:699
crm_time_t * crm_time_calculate_duration(const crm_time_t *dt, const crm_time_t *value)
Definition iso8601.c:1570
#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:1639
void pcmk__time_set_hr_dt(crm_time_t *target, const pcmk__time_hr_t *hr_dt)
Definition iso8601.c:1854
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:1421
long long crm_time_get_seconds(const crm_time_t *dt)
Definition iso8601.c:316
int crm_time_get_timezone(const crm_time_t *dt, uint32_t *h, uint32_t *m)
Definition iso8601.c:307
void crm_time_add_days(crm_time_t *a_time, int extra)
Definition iso8601.c:1710
void crm_time_add_weeks(crm_time_t *a_time, int extra)
Definition iso8601.c:1790
crm_time_t * crm_time_parse_duration(const char *period_s)
Parse a time duration from an ISO 8601 duration specification.
Definition iso8601.c:1100
void pcmk__time_hr_free(pcmk__time_hr_t *hr_dt)
Definition iso8601.c:1912
void crm_time_add_years(crm_time_t *a_time, int extra)
Definition iso8601.c:1796
int crm_time_get_timeofday(const crm_time_t *dt, uint32_t *h, uint32_t *m, uint32_t *s)
Definition iso8601.c:299
char * pcmk__timespec2str(const struct timespec *ts, uint32_t flags)
Definition iso8601.c:2046
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:1876
int crm_time_get_gregorian(const crm_time_t *dt, uint32_t *y, uint32_t *m, uint32_t *d)
Definition iso8601.c:365
int crm_time_compare(const crm_time_t *a, const crm_time_t *b)
Definition iso8601.c:1660
void crm_time_add_hours(crm_time_t *a_time, int extra)
Definition iso8601.c:1784
#define do_cmp_field(l, r, field)
Definition iso8601.c:1646
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:261
int crm_time_get_isoweek(const crm_time_t *dt, uint32_t *y, uint32_t *w, uint32_t *d)
Definition iso8601.c:406
#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:245
#define crm_err(fmt, args...)
Definition logging.h:389
#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:300
#define crm_trace(fmt, args...)
Definition logging.h:402
#define LOG_TRACE
Definition logging.h:38
const char * crm_element_value(const xmlNode *data, const char *name)
Retrieve the value of an XML attribute.
Definition nvpair.c:446
#define PCMK__VALUE_EPOCH
pcmk__action_result_t result
Definition pcmk_fence.c:35
const char * target
Definition pcmk_fence.c:29
#define CRM_ASSERT(expr)
Definition results.h:42
@ pcmk_rc_ok
Definition results.h:162
@ pcmk_rc_unpack_error
Definition results.h:125
#define pcmk__plural_s(i)
char * pcmk__trim(char *str)
Definition strings.c:528
int pcmk__scan_ll(const char *text, long long *result, long long default_value)
Definition strings.c:97
#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
#define PCMK_XA_MONTHS
Definition xml_names.h:323
#define PCMK_XA_YEARS
Definition xml_names.h:451
#define PCMK_XA_MINUTES
Definition xml_names.h:320
#define PCMK_XA_SECONDS
Definition xml_names.h:394
#define PCMK_XA_WEEKS
Definition xml_names.h:442
#define PCMK_XA_DAYS
Definition xml_names.h:252
#define PCMK_XA_HOURS
Definition xml_names.h:295