pacemaker  2.1.9-49aab99839
Scalable High-Availability cluster resource manager
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
70 struct 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 
79 static crm_time_t *parse_date(const char *date_str);
80 
81 static crm_time_t *
82 crm_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 
91  utc = crm_time_new_undefined();
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 
111 crm_time_t *
112 crm_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 
128 crm_time_t *
130 {
131  return (crm_time_t *) pcmk__assert_alloc(1, sizeof(crm_time_t));
132 }
133 
141 bool
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 
149 void
151 {
152  if (dt == NULL) {
153  return;
154  }
155  free(dt);
156 }
157 
158 static int
159 year_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  */
177 int
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 
190 int
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
211 static int month_days[13] = {
212  31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 29
213 };
214 
223 int
224 crm_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 
235 bool
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 
260 static int
261 get_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++) {
270  result += crm_time_days_in_month(lpc, y);
271  }
272  return result;
273 }
274 
275 void
276 crm_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 
292 static void
293 crm_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 
313 int
314 crm_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 
321 int
322 crm_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 
330 long 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  utc = crm_get_utc_time(dt);
342  if (utc == NULL) {
343  return 0;
344  }
345 
346  for (lpc = 1; lpc < utc->years; lpc++) {
347  long long dmax = year_days(lpc);
348 
349  in_seconds += DAY_SECONDS * dmax;
350  }
351 
352  /* utc->months is an offset that can only be set for a duration.
353  * By definition, the value is variable depending on the date to
354  * which it is applied.
355  *
356  * Force 30-day months so that something vaguely sane happens
357  * for anyone that tries to use a month in this way.
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() */
373 long long
375 {
376  return (dt == NULL)? 0 : (crm_time_get_seconds(dt) - EPOCH_SECONDS);
377 }
378 
379 int
380 crm_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 
412 int
413 crm_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 
420 int
421 crm_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 
488 static inline void
489 sec_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 
505 static void
506 crm_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 
589 static void
590 time_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 {
619  seconds = crm_time_get_seconds_since_epoch(dt);
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 
713 char *
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 
733 static bool
734 crm_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 
784 static bool
785 crm_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 
842 static bool
843 crm_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  */
889 static crm_time_t *
890 parse_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 
919  dt = crm_time_new_undefined();
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 
1059 invalid:
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
1066 static int
1067 parse_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 
1120 crm_time_t *
1121 crm_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 
1266 invalid:
1267  crm_time_free(diff);
1268  errno = EINVAL;
1269  return NULL;
1270 }
1271 
1283 crm_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 
1360 invalid:
1361  errno = EINVAL;
1362 error:
1363  crm_time_free_period(period);
1364  return NULL;
1365 }
1366 
1372 void
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 
1383 void
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 
1402 static void
1403 ha_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 
1447 void
1448 crm_time_set_timet(crm_time_t *target, const time_t *source)
1449 {
1450  ha_set_tm_time(target, localtime(source));
1451 }
1452 
1460 void
1462 {
1463  if ((target != NULL) && (source != NULL)
1465  || (crm_time_compare(source, target) < 0))) {
1466  crm_time_set(target, source);
1467  }
1468 }
1469 
1470 crm_time_t *
1472 {
1474 
1475  crm_time_set(target, source);
1476  return target;
1477 }
1478 
1487 crm_time_t *
1488 pcmk__copy_timet(time_t source)
1489 {
1491 
1492  crm_time_set_timet(target, &source);
1493  return target;
1494 }
1495 
1496 crm_time_t *
1497 crm_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 
1533 const 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 
1563 typedef void (*component_fn_t)(crm_time_t *, int);
1564 
1573 static component_fn_t
1574 component_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 
1614 int
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 
1645 crm_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 
1679 crm_time_t *
1680 crm_time_subtract(const crm_time_t *dt, const crm_time_t *value)
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 
1714 bool
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 
1735 int
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 
1765 void
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 
1798 void
1799 crm_time_add_days(crm_time_t *a_time, int extra)
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 
1829 void
1830 crm_time_add_months(crm_time_t * a_time, int extra)
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 
1872 void
1873 crm_time_add_minutes(crm_time_t * a_time, int extra)
1874 {
1875  crm_time_add_seconds(a_time, extra * 60);
1876 }
1877 
1878 void
1879 crm_time_add_hours(crm_time_t * a_time, int extra)
1880 {
1881  crm_time_add_seconds(a_time, extra * HOUR_SECONDS);
1882 }
1883 
1884 void
1885 crm_time_add_weeks(crm_time_t * a_time, int extra)
1886 {
1887  crm_time_add_days(a_time, extra * 7);
1888 }
1889 
1890 void
1891 crm_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 
1904 static void
1905 ha_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 
1954 void
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 
1977 pcmk__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 
1996 pcmk__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 
2012 void
2014 {
2015  free(hr_dt);
2016 }
2017 
2028 char *
2029 pcmk__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 #ifdef HAVE_FORMAT_NONLITERAL
2100 #pragma GCC diagnostic push
2101 #pragma GCC diagnostic ignored "-Wformat-nonliteral"
2102 #endif
2103  nbytes = strftime(&date_s[date_len], sizeof(date_s) - date_len,
2104  tmp_fmt_s, &tm);
2105 #ifdef HAVE_FORMAT_NONLITERAL
2106 #pragma GCC diagnostic pop
2107 #endif
2108  free(tmp_fmt_s);
2109  if (nbytes == 0) { // Would overflow buffer
2110  return NULL;
2111  }
2112  date_len += nbytes;
2113  printed_pos = scanned_pos;
2114  if (nano_digits != 0) {
2115  int nc = 0;
2116 
2117  if (date_len >= sizeof(date_s)) {
2118  return NULL; // No room to add nanoseconds
2119  }
2120  nc = snprintf(&date_s[date_len], sizeof(date_s) - date_len,
2121  "%.*s", nano_digits, nano_s);
2122 
2123  if ((nc < 0) || (nc == (sizeof(date_s) - date_len))) {
2124  return NULL; // Error or would overflow buffer
2125  }
2126  date_len += nc;
2127  }
2128  }
2129 
2130  return (date_len == 0)? NULL : pcmk__str_copy(date_s);
2131 }
2132 
2146 char *
2147 pcmk__epoch2str(const time_t *source, uint32_t flags)
2148 {
2149  time_t epoch_time = (source == NULL)? time(NULL) : *source;
2150 
2151  if (flags == 0) {
2152  return pcmk__str_copy(pcmk__trim(ctime(&epoch_time)));
2153  } else {
2154  crm_time_t dt;
2155 
2156  crm_time_set_timet(&dt, &epoch_time);
2157  return crm_time_as_string(&dt, flags);
2158  }
2159 }
2160 
2178 char *
2179 pcmk__timespec2str(const struct timespec *ts, uint32_t flags)
2180 {
2181  struct timespec tmp_ts;
2182  crm_time_t dt;
2183  char result[DATE_MAX] = { 0 };
2184 
2185  if (ts == NULL) {
2186  qb_util_timespec_from_epoch_get(&tmp_ts);
2187  ts = &tmp_ts;
2188  }
2189  crm_time_set_timet(&dt, &ts->tv_sec);
2190  time_as_string_common(&dt, ts->tv_nsec / QB_TIME_NS_IN_USEC, flags, result);
2191  return pcmk__str_copy(result);
2192 }
2193 
2205 const char *
2206 pcmk__readable_interval(guint interval_ms)
2207 {
2208 #define MS_IN_S (1000)
2209 #define MS_IN_M (MS_IN_S * 60)
2210 #define MS_IN_H (MS_IN_M * 60)
2211 #define MS_IN_D (MS_IN_H * 24)
2212 #define MAXSTR sizeof("..d..h..m..s...ms")
2213  static char str[MAXSTR];
2214  int offset = 0;
2215 
2216  str[0] = '\0';
2217  if (interval_ms >= MS_IN_D) {
2218  offset += snprintf(str + offset, MAXSTR - offset, "%ud",
2219  interval_ms / MS_IN_D);
2220  interval_ms -= (interval_ms / MS_IN_D) * MS_IN_D;
2221  }
2222  if (interval_ms >= MS_IN_H) {
2223  offset += snprintf(str + offset, MAXSTR - offset, "%uh",
2224  interval_ms / MS_IN_H);
2225  interval_ms -= (interval_ms / MS_IN_H) * MS_IN_H;
2226  }
2227  if (interval_ms >= MS_IN_M) {
2228  offset += snprintf(str + offset, MAXSTR - offset, "%um",
2229  interval_ms / MS_IN_M);
2230  interval_ms -= (interval_ms / MS_IN_M) * MS_IN_M;
2231  }
2232 
2233  // Ns, N.NNNs, or NNNms
2234  if (interval_ms >= MS_IN_S) {
2235  offset += snprintf(str + offset, MAXSTR - offset, "%u",
2236  interval_ms / MS_IN_S);
2237  interval_ms -= (interval_ms / MS_IN_S) * MS_IN_S;
2238  if (interval_ms > 0) {
2239  offset += snprintf(str + offset, MAXSTR - offset, ".%03u",
2240  interval_ms);
2241  }
2242  (void) snprintf(str + offset, MAXSTR - offset, "s");
2243 
2244  } else if (interval_ms > 0) {
2245  (void) snprintf(str + offset, MAXSTR - offset, "%ums", interval_ms);
2246 
2247  } else if (str[0] == '\0') {
2248  strcpy(str, "0s");
2249  }
2250  return str;
2251 }
int crm_time_get_gregorian(const crm_time_t *dt, uint32_t *y, uint32_t *m, uint32_t *d)
Definition: iso8601.c:380
#define LOG_TRACE
Definition: logging.h:38
#define PCMK_XA_YEARS
Definition: xml_names.h:456
#define CRM_CHECK(expr, failure_action)
Definition: logging.h:245
void crm_time_set_timet(crm_time_t *target, const time_t *source)
Definition: iso8601.c:1448
A dumping ground.
bool crm_time_leapyear(int year)
Definition: iso8601.c:236
int pcmk__add_time_from_xml(crm_time_t *t, enum pcmk__time_component component, const xmlNode *xml)
Definition: iso8601.c:1615
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
#define PCMK_XA_HOURS
Definition: xml_names.h:300
#define crm_time_epoch
Definition: iso8601.h:75
void pcmk__time_hr_free(pcmk__time_hr_t *hr_dt)
Definition: iso8601.c:2013
#define HOUR_SECONDS
Definition: iso8601.c:50
#define PCMK_XA_DAYS
Definition: xml_names.h:257
#define crm_time_log_timeofday
Definition: iso8601.h:68
crm_time_t * crm_time_new(const char *date_time)
Definition: iso8601.c:112
void pcmk__set_time_if_earlier(crm_time_t *target, const crm_time_t *source)
Definition: iso8601.c:1461
#define MS_IN_D
struct crm_time_s crm_time_t
Definition: iso8601.h:32
bool crm_time_check(const crm_time_t *dt)
Check whether a time object represents a sensible date/time.
Definition: iso8601.c:1715
pcmk__time_component
#define crm_time_ordinal
Definition: iso8601.h:72
void crm_time_add_minutes(crm_time_t *a_time, int extra)
Definition: iso8601.c:1873
#define DATE_MAX
Definition: iso8601.c:476
crm_time_t * crm_time_calculate_duration(const crm_time_t *dt, const crm_time_t *value)
Definition: iso8601.c:1646
const char * pcmk__readable_interval(guint interval_ms)
Definition: iso8601.c:2206
void crm_time_add_weeks(crm_time_t *a_time, int extra)
Definition: iso8601.c:1885
int crm_time_get_timezone(const crm_time_t *dt, uint32_t *h, uint32_t *m)
Definition: iso8601.c:322
char * pcmk__epoch2str(const time_t *source, uint32_t flags)
Definition: iso8601.c:2147
long long crm_time_get_seconds_since_epoch(const crm_time_t *dt)
Definition: iso8601.c:374
void crm_time_set(crm_time_t *target, const crm_time_t *source)
Definition: iso8601.c:1384
int crm_time_compare(const crm_time_t *a, const crm_time_t *b)
Definition: iso8601.c:1736
void crm_time_free(crm_time_t *dt)
Definition: iso8601.c:150
#define do_cmp_field(l, r, field)
Definition: iso8601.c:1722
crm_time_t * pcmk__copy_timet(time_t source)
Definition: iso8601.c:1488
#define valid_sec_usec(sec, usec)
Definition: iso8601.c:65
char * pcmk__timespec2str(const struct timespec *ts, uint32_t flags)
Definition: iso8601.c:2179
#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
crm_time_t * crm_time_add(const crm_time_t *dt, const crm_time_t *value)
Definition: iso8601.c:1497
#define crm_time_log_duration
Definition: iso8601.h:70
#define ydays(t)
Definition: iso8601.c:1790
pcmk__time_hr_t * pcmk__time_hr_now(time_t *epoch)
Definition: iso8601.c:1977
long long crm_time_get_seconds(const crm_time_t *dt)
Definition: iso8601.c:331
pcmk__time_hr_t * pcmk__time_hr_new(const char *date_time)
Definition: iso8601.c:1996
int pcmk__scan_ll(const char *text, long long *result, long long default_value)
Definition: strings.c:96
struct pcmk__time_us pcmk__time_hr_t
void pcmk__time_set_hr_dt(crm_time_t *target, const pcmk__time_hr_t *hr_dt)
Definition: iso8601.c:1955
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
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
crm_time_t * pcmk_copy_time(const crm_time_t *source)
Definition: iso8601.c:1471
const char * crm_element_value(const xmlNode *data, const char *name)
Retrieve the value of an XML attribute.
Definition: nvpair.c:458
crm_time_t * start
Definition: iso8601.h:35
void crm_time_add_days(crm_time_t *a_time, int extra)
Add days to a date/time.
Definition: iso8601.c:1799
#define crm_trace(fmt, args...)
Definition: logging.h:404
#define PCMK_XA_WEEKS
Definition: xml_names.h:447
#define pcmk_is_set(g, f)
Convenience alias for pcmk_all_flags_set(), to check single flag.
Definition: util.h:94
int crm_time_get_ordinal(const crm_time_t *dt, uint32_t *y, uint32_t *d)
Definition: iso8601.c:413
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
crm_time_t * end
Definition: iso8601.h:36
#define GMTOFF(tm)
Definition: iso8601.c:47
#define MS_IN_M
ISO_8601 Date handling.
#define DAY_SECONDS
Definition: iso8601.c:51
int crm_time_get_timeofday(const crm_time_t *dt, uint32_t *h, uint32_t *m, uint32_t *s)
Definition: iso8601.c:314
void crm_time_add_hours(crm_time_t *a_time, int extra)
Definition: iso8601.c:1879
#define crm_time_log(level, prefix, dt, flags)
Definition: iso8601.h:60
crm_time_t * diff
Definition: iso8601.h:37
int crm_time_days_in_month(int month, int year)
Return number of days in given month of given year.
Definition: iso8601.c:224
#define crm_time_log_with_timezone
Definition: iso8601.h:69
const char * pcmk__time_component_attr(enum pcmk__time_component component)
Definition: iso8601.c:1534
crm_time_t * crm_time_new_undefined(void)
Allocate memory for an uninitialized time object.
Definition: iso8601.c:129
#define PCMK_XA_MINUTES
Definition: xml_names.h:325
#define pcmk__str_copy(str)
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
#define EPOCH_SECONDS
Definition: iso8601.c:372
#define MS_IN_S
char * pcmk__time_format_hr(const char *format, const pcmk__time_hr_t *hr_dt)
Definition: iso8601.c:2029
crm_time_t * crm_time_subtract(const crm_time_t *dt, const crm_time_t *value)
Definition: iso8601.c:1680
int crm_time_get_isoweek(const crm_time_t *dt, uint32_t *y, uint32_t *w, uint32_t *d)
Definition: iso8601.c:421
#define pcmk__assert(expr)
const char * target
Definition: pcmk_fence.c:29
#define crm_time_seconds
Definition: iso8601.h:74
#define PCMK_XA_SECONDS
Definition: xml_names.h:399
#define MS_IN_H
void crm_time_add_years(crm_time_t *a_time, int extra)
Definition: iso8601.c:1891
bool crm_time_is_defined(const crm_time_t *t)
Check whether a time object has been initialized yet.
Definition: iso8601.c:142
#define HAVE_STRUCT_TM_TM_GMTOFF
Definition: config.h:398
#define MAXSTR
pcmk__action_result_t result
Definition: pcmk_fence.c:35
#define crm_err(fmt, args...)
Definition: logging.h:391
void crm_time_add_months(crm_time_t *a_time, int extra)
Definition: iso8601.c:1830
int crm_time_weeks_in_year(int year)
Definition: iso8601.c:191
int crm_time_january1_weekday(int year)
Definition: iso8601.c:178
#define pcmk__plural_s(i)
pcmk__time_hr_t * pcmk__time_hr_convert(pcmk__time_hr_t *target, const crm_time_t *dt)
Definition: iso8601.c:1931
void(* component_fn_t)(crm_time_t *, int)
Definition: iso8601.c:1563
char * pcmk__trim(char *str)
Definition: strings.c:540
#define PCMK__VALUE_EPOCH
#define pcmk__assert_alloc(nmemb, size)
Definition: internal.h:297
#define crm_time_weeks
Definition: iso8601.h:73
#define LOG_STDOUT
Definition: logging.h:43
void crm_time_free_period(crm_time_period_t *period)
Free a dynamically allocated time period object.
Definition: iso8601.c:1373
#define crm_time_log_date
Definition: iso8601.h:67
uint64_t flags
Definition: remote.c:215
#define crm_time_usecs
Definition: iso8601.h:76
#define PCMK_XA_MONTHS
Definition: xml_names.h:328