pacemaker  2.1.8-3980678f03
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)) {
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 
249 static uint32_t
250 get_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 
260 void
261 crm_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 
277 static void
278 crm_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 
298 int
299 crm_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 
306 int
307 crm_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 
315 long 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() */
358 long long
360 {
361  return (dt == NULL)? 0 : (crm_time_get_seconds(dt) - EPOCH_SECONDS);
362 }
363 
364 int
365 crm_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 
397 int
398 crm_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 
405 int
406 crm_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 
473 static inline void
474 sec_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 
490 static void
491 crm_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 
574 static void
575 time_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 {
604  seconds = crm_time_get_seconds_since_epoch(dt);
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 
698 char *
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 
718 static bool
719 crm_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 
769 static bool
770 crm_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 
827 static bool
828 crm_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  */
874 static crm_time_t *
875 parse_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')
892  || ((strlen(date_str) > 2) && (date_str[2] == ':'))) {
893  /* Just a time supplied - Infer current date */
894  dt = crm_time_new(NULL);
895  if (date_str[0] == 'T') {
896  time_s = date_str + 1;
897  } else {
898  time_s = date_str;
899  }
900  goto parse_time;
901  }
902 
903  dt = crm_time_new_undefined();
904 
905  if ((strncasecmp(PCMK__VALUE_EPOCH, date_str, 5) == 0)
906  && ((date_str[5] == '\0')
907  || (date_str[5] == '/')
908  || isspace(date_str[5]))) {
909  dt->days = 1;
910  dt->years = 1970;
912  return dt;
913  }
914 
915  /* YYYY-MM-DD */
916  rc = sscanf(date_str, "%d-%d-%d", &year, &month, &day);
917  if (rc == 1) {
918  /* YYYYMMDD */
919  rc = sscanf(date_str, "%4d%2d%2d", &year, &month, &day);
920  }
921  if (rc == 3) {
922  if (month > 12) {
923  crm_err("'%s' is not a valid ISO 8601 date/time specification "
924  "because '%d' is not a valid month", date_str, month);
925  goto invalid;
926  } else if (day > crm_time_days_in_month(month, year)) {
927  crm_err("'%s' is not a valid ISO 8601 date/time specification "
928  "because '%d' is not a valid day of the month",
929  date_str, day);
930  goto invalid;
931  } else {
932  dt->years = year;
933  dt->days = get_ordinal_days(year, month, day);
934  crm_trace("Parsed Gregorian date '%.4d-%.3d' from date string '%s'",
935  year, dt->days, date_str);
936  }
937  goto parse_time;
938  }
939 
940  /* YYYY-DDD */
941  rc = sscanf(date_str, "%d-%d", &year, &day);
942  if (rc == 2) {
943  if (day > year_days(year)) {
944  crm_err("'%s' is not a valid ISO 8601 date/time specification "
945  "because '%d' is not a valid day of the year (max %d)",
946  date_str, day, year_days(year));
947  goto invalid;
948  }
949  crm_trace("Parsed ordinal year %d and days %d from date string '%s'",
950  year, day, date_str);
951  dt->days = day;
952  dt->years = year;
953  goto parse_time;
954  }
955 
956  /* YYYY-Www-D */
957  rc = sscanf(date_str, "%d-W%d-%d", &year, &week, &day);
958  if (rc == 3) {
959  if (week > crm_time_weeks_in_year(year)) {
960  crm_err("'%s' is not a valid ISO 8601 date/time specification "
961  "because '%d' is not a valid week of the year (max %d)",
962  date_str, week, crm_time_weeks_in_year(year));
963  goto invalid;
964  } else if (day < 1 || day > 7) {
965  crm_err("'%s' is not a valid ISO 8601 date/time specification "
966  "because '%d' is not a valid day of the week",
967  date_str, day);
968  goto invalid;
969  } else {
970  /*
971  * See https://en.wikipedia.org/wiki/ISO_week_date
972  *
973  * Monday 29 December 2008 is written "2009-W01-1"
974  * Sunday 3 January 2010 is written "2009-W53-7"
975  * Saturday 27 September 2008 is written "2008-W37-6"
976  *
977  * If 1 January is on a Monday, Tuesday, Wednesday or Thursday, it is in week 01.
978  * If 1 January is on a Friday, Saturday or Sunday, it is in week 52 or 53 of the previous year.
979  */
980  int jan1 = crm_time_january1_weekday(year);
981 
982  crm_trace("Got year %d (Jan 1 = %d), week %d, and day %d from date string '%s'",
983  year, jan1, week, day, date_str);
984 
985  dt->years = year;
986  crm_time_add_days(dt, (week - 1) * 7);
987 
988  if (jan1 <= 4) {
989  crm_time_add_days(dt, 1 - jan1);
990  } else {
991  crm_time_add_days(dt, 8 - jan1);
992  }
993 
994  crm_time_add_days(dt, day);
995  }
996  goto parse_time;
997  }
998 
999  crm_err("'%s' is not a valid ISO 8601 date/time specification", date_str);
1000  goto invalid;
1001 
1002  parse_time:
1003 
1004  if (time_s == NULL) {
1005  time_s = date_str + strspn(date_str, "0123456789-W");
1006  if ((time_s[0] == ' ') || (time_s[0] == 'T')) {
1007  ++time_s;
1008  } else {
1009  time_s = NULL;
1010  }
1011  }
1012  if ((time_s != NULL) && (crm_time_parse(time_s, dt) == FALSE)) {
1013  goto invalid;
1014  }
1015 
1017  if (crm_time_check(dt) == FALSE) {
1018  crm_err("'%s' is not a valid ISO 8601 date/time specification",
1019  date_str);
1020  goto invalid;
1021  }
1022  return dt;
1023 
1024 invalid:
1025  crm_time_free(dt);
1026  errno = EINVAL;
1027  return NULL;
1028 }
1029 
1030 // Parse an ISO 8601 numeric value and return number of characters consumed
1031 static int
1032 parse_int(const char *str, int *result)
1033 {
1034  unsigned int lpc;
1035  int offset = (str[0] == 'T')? 1 : 0;
1036  bool negate = false;
1037 
1038  *result = 0;
1039 
1040  // @TODO This cannot handle combinations of these characters
1041  switch (str[offset]) {
1042  case '.':
1043  case ',':
1044  return 0; // Fractions are not supported
1045 
1046  case '-':
1047  negate = true;
1048  offset++;
1049  break;
1050 
1051  case '+':
1052  case ':':
1053  offset++;
1054  break;
1055 
1056  default:
1057  break;
1058  }
1059 
1060  for (lpc = 0; (lpc < 10) && isdigit(str[offset]); lpc++) {
1061  const int digit = str[offset++] - '0';
1062 
1063  if ((*result * 10LL + digit) > INT_MAX) {
1064  return 0; // Overflow
1065  }
1066  *result = *result * 10 + digit;
1067  }
1068  if (negate) {
1069  *result = 0 - *result;
1070  }
1071  return (lpc > 0)? offset : 0;
1072 }
1073 
1085 crm_time_t *
1086 crm_time_parse_duration(const char *period_s)
1087 {
1088  gboolean is_time = FALSE;
1089  crm_time_t *diff = NULL;
1090 
1091  if (pcmk__str_empty(period_s)) {
1092  crm_err("No ISO 8601 time duration given");
1093  goto invalid;
1094  }
1095  if (period_s[0] != 'P') {
1096  crm_err("'%s' is not a valid ISO 8601 time duration "
1097  "because it does not start with a 'P'", period_s);
1098  goto invalid;
1099  }
1100  if ((period_s[1] == '\0') || isspace(period_s[1])) {
1101  crm_err("'%s' is not a valid ISO 8601 time duration "
1102  "because nothing follows 'P'", period_s);
1103  goto invalid;
1104  }
1105 
1106  diff = crm_time_new_undefined();
1107 
1108  for (const char *current = period_s + 1;
1109  current[0] && (current[0] != '/') && !isspace(current[0]);
1110  ++current) {
1111 
1112  int an_int = 0, rc;
1113 
1114  if (current[0] == 'T') {
1115  /* A 'T' separates year/month/day from hour/minute/seconds. We don't
1116  * require it strictly, but just use it to differentiate month from
1117  * minutes.
1118  */
1119  is_time = TRUE;
1120  continue;
1121  }
1122 
1123  // An integer must be next
1124  rc = parse_int(current, &an_int);
1125  if (rc == 0) {
1126  crm_err("'%s' is not a valid ISO 8601 time duration "
1127  "because no valid integer at '%s'", period_s, current);
1128  goto invalid;
1129  }
1130  current += rc;
1131 
1132  // A time unit must be next (we're not strict about the order)
1133  switch (current[0]) {
1134  case 'Y':
1135  diff->years = an_int;
1136  break;
1137 
1138  case 'M':
1139  if (!is_time) { // Months
1140  diff->months = an_int;
1141 
1142  // Minutes
1143  } else if ((diff->seconds + (an_int * 60LL)) > INT_MAX) {
1144  crm_err("'%s' is not a valid ISO 8601 time duration "
1145  "because integer at '%s' is too large",
1146  period_s, current - rc);
1147  goto invalid;
1148  } else {
1149  diff->seconds += an_int * 60;
1150  }
1151  break;
1152 
1153  case 'W':
1154  if ((diff->days + (an_int * 7LL)) > INT_MAX) {
1155  crm_err("'%s' is not a valid ISO 8601 time duration "
1156  "because integer at '%s' is too large",
1157  period_s, current - rc);
1158  goto invalid;
1159  } else {
1160  diff->days += an_int * 7;
1161  }
1162  break;
1163 
1164  case 'D':
1165  if ((diff->days + (long long) an_int) > INT_MAX) {
1166  crm_err("'%s' is not a valid ISO 8601 time duration "
1167  "because integer at '%s' is too large",
1168  period_s, current - rc);
1169  goto invalid;
1170  } else {
1171  diff->days += an_int;
1172  }
1173  break;
1174 
1175  case 'H':
1176  if ((diff->seconds + ((long long) an_int * HOUR_SECONDS))
1177  > INT_MAX) {
1178  crm_err("'%s' is not a valid ISO 8601 time duration "
1179  "because integer at '%s' is too large",
1180  period_s, current - rc);
1181  goto invalid;
1182  } else {
1183  diff->seconds += an_int * HOUR_SECONDS;
1184  }
1185  break;
1186 
1187  case 'S':
1188  if ((diff->seconds + (long long) an_int) > INT_MAX) {
1189  crm_err("'%s' is not a valid ISO 8601 time duration "
1190  "because integer at '%s' is too large",
1191  period_s, current - rc);
1192  goto invalid;
1193  } else {
1194  diff->seconds += an_int;
1195  }
1196  break;
1197 
1198  case '\0':
1199  crm_err("'%s' is not a valid ISO 8601 time duration "
1200  "because no units after %d", period_s, an_int);
1201  goto invalid;
1202 
1203  default:
1204  crm_err("'%s' is not a valid ISO 8601 time duration "
1205  "because '%c' is not a valid time unit",
1206  period_s, current[0]);
1207  goto invalid;
1208  }
1209  }
1210 
1211  if (!crm_time_is_defined(diff)) {
1212  crm_err("'%s' is not a valid ISO 8601 time duration "
1213  "because no amounts and units given", period_s);
1214  goto invalid;
1215  }
1216 
1217  diff->duration = TRUE;
1218  return diff;
1219 
1220 invalid:
1221  crm_time_free(diff);
1222  errno = EINVAL;
1223  return NULL;
1224 }
1225 
1237 crm_time_parse_period(const char *period_str)
1238 {
1239  const char *original = period_str;
1240  crm_time_period_t *period = NULL;
1241 
1242  if (pcmk__str_empty(period_str)) {
1243  crm_err("No ISO 8601 time period given");
1244  goto invalid;
1245  }
1246 
1247  tzset();
1248  period = pcmk__assert_alloc(1, sizeof(crm_time_period_t));
1249 
1250  if (period_str[0] == 'P') {
1251  period->diff = crm_time_parse_duration(period_str);
1252  if (period->diff == NULL) {
1253  goto error;
1254  }
1255  } else {
1256  period->start = parse_date(period_str);
1257  if (period->start == NULL) {
1258  goto error;
1259  }
1260  }
1261 
1262  period_str = strstr(original, "/");
1263  if (period_str) {
1264  ++period_str;
1265  if (period_str[0] == 'P') {
1266  if (period->diff != NULL) {
1267  crm_err("'%s' is not a valid ISO 8601 time period "
1268  "because it has two durations",
1269  original);
1270  goto invalid;
1271  }
1272  period->diff = crm_time_parse_duration(period_str);
1273  if (period->diff == NULL) {
1274  goto error;
1275  }
1276  } else {
1277  period->end = parse_date(period_str);
1278  if (period->end == NULL) {
1279  goto error;
1280  }
1281  }
1282 
1283  } else if (period->diff != NULL) {
1284  // Only duration given, assume start is now
1285  period->start = crm_time_new(NULL);
1286 
1287  } else {
1288  // Only start given
1289  crm_err("'%s' is not a valid ISO 8601 time period "
1290  "because it has no duration or ending time",
1291  original);
1292  goto invalid;
1293  }
1294 
1295  if (period->start == NULL) {
1296  period->start = crm_time_subtract(period->end, period->diff);
1297 
1298  } else if (period->end == NULL) {
1299  period->end = crm_time_add(period->start, period->diff);
1300  }
1301 
1302  if (crm_time_check(period->start) == FALSE) {
1303  crm_err("'%s' is not a valid ISO 8601 time period "
1304  "because the start is invalid", period_str);
1305  goto invalid;
1306  }
1307  if (crm_time_check(period->end) == FALSE) {
1308  crm_err("'%s' is not a valid ISO 8601 time period "
1309  "because the end is invalid", period_str);
1310  goto invalid;
1311  }
1312  return period;
1313 
1314 invalid:
1315  errno = EINVAL;
1316 error:
1317  crm_time_free_period(period);
1318  return NULL;
1319 }
1320 
1326 void
1328 {
1329  if (period) {
1330  crm_time_free(period->start);
1331  crm_time_free(period->end);
1332  crm_time_free(period->diff);
1333  free(period);
1334  }
1335 }
1336 
1337 void
1339 {
1340  crm_trace("target=%p, source=%p", target, source);
1341 
1342  CRM_CHECK(target != NULL && source != NULL, return);
1343 
1344  target->years = source->years;
1345  target->days = source->days;
1346  target->months = source->months; /* Only for durations */
1347  target->seconds = source->seconds;
1348  target->offset = source->offset;
1349 
1350  crm_time_log(LOG_TRACE, "source", source,
1352  crm_time_log(LOG_TRACE, "target", target,
1354 }
1355 
1356 static void
1357 ha_set_tm_time(crm_time_t *target, const struct tm *source)
1358 {
1359  int h_offset = 0;
1360  int m_offset = 0;
1361 
1362  /* Ensure target is fully initialized */
1363  target->years = 0;
1364  target->months = 0;
1365  target->days = 0;
1366  target->seconds = 0;
1367  target->offset = 0;
1368  target->duration = FALSE;
1369 
1370  if (source->tm_year > 0) {
1371  /* years since 1900 */
1372  target->years = 1900 + source->tm_year;
1373  }
1374 
1375  if (source->tm_yday >= 0) {
1376  /* days since January 1 [0-365] */
1377  target->days = 1 + source->tm_yday;
1378  }
1379 
1380  if (source->tm_hour >= 0) {
1381  target->seconds += HOUR_SECONDS * source->tm_hour;
1382  }
1383  if (source->tm_min >= 0) {
1384  target->seconds += 60 * source->tm_min;
1385  }
1386  if (source->tm_sec >= 0) {
1387  target->seconds += source->tm_sec;
1388  }
1389 
1390  /* tm_gmtoff == offset from UTC in seconds */
1391  h_offset = GMTOFF(source) / HOUR_SECONDS;
1392  m_offset = (GMTOFF(source) - (HOUR_SECONDS * h_offset)) / 60;
1393  crm_trace("Time offset is %lds (%.2d:%.2d)",
1394  GMTOFF(source), h_offset, m_offset);
1395 
1396  target->offset += HOUR_SECONDS * h_offset;
1397  target->offset += 60 * m_offset;
1398 }
1399 
1400 void
1401 crm_time_set_timet(crm_time_t *target, const time_t *source)
1402 {
1403  ha_set_tm_time(target, localtime(source));
1404 }
1405 
1413 void
1415 {
1416  if ((target != NULL) && (source != NULL)
1418  || (crm_time_compare(source, target) < 0))) {
1419  crm_time_set(target, source);
1420  }
1421 }
1422 
1423 crm_time_t *
1425 {
1427 
1428  crm_time_set(target, source);
1429  return target;
1430 }
1431 
1440 crm_time_t *
1441 pcmk__copy_timet(time_t source)
1442 {
1444 
1445  crm_time_set_timet(target, &source);
1446  return target;
1447 }
1448 
1449 crm_time_t *
1450 crm_time_add(const crm_time_t *dt, const crm_time_t *value)
1451 {
1452  crm_time_t *utc = NULL;
1453  crm_time_t *answer = NULL;
1454 
1455  if ((dt == NULL) || (value == NULL)) {
1456  errno = EINVAL;
1457  return NULL;
1458  }
1459 
1460  answer = pcmk_copy_time(dt);
1461 
1462  utc = crm_get_utc_time(value);
1463  if (utc == NULL) {
1464  crm_time_free(answer);
1465  return NULL;
1466  }
1467 
1468  answer->years += utc->years;
1469  crm_time_add_months(answer, utc->months);
1470  crm_time_add_days(answer, utc->days);
1471  crm_time_add_seconds(answer, utc->seconds);
1472 
1473  crm_time_free(utc);
1474  return answer;
1475 }
1476 
1486 const char *
1488 {
1489  switch (component) {
1490  case pcmk__time_years:
1491  return PCMK_XA_YEARS;
1492 
1493  case pcmk__time_months:
1494  return PCMK_XA_MONTHS;
1495 
1496  case pcmk__time_weeks:
1497  return PCMK_XA_WEEKS;
1498 
1499  case pcmk__time_days:
1500  return PCMK_XA_DAYS;
1501 
1502  case pcmk__time_hours:
1503  return PCMK_XA_HOURS;
1504 
1505  case pcmk__time_minutes:
1506  return PCMK_XA_MINUTES;
1507 
1508  case pcmk__time_seconds:
1509  return PCMK_XA_SECONDS;
1510 
1511  default:
1512  return NULL;
1513  }
1514 }
1515 
1516 typedef void (*component_fn_t)(crm_time_t *, int);
1517 
1526 static component_fn_t
1527 component_fn(enum pcmk__time_component component)
1528 {
1529  switch (component) {
1530  case pcmk__time_years:
1531  return crm_time_add_years;
1532 
1533  case pcmk__time_months:
1534  return crm_time_add_months;
1535 
1536  case pcmk__time_weeks:
1537  return crm_time_add_weeks;
1538 
1539  case pcmk__time_days:
1540  return crm_time_add_days;
1541 
1542  case pcmk__time_hours:
1543  return crm_time_add_hours;
1544 
1545  case pcmk__time_minutes:
1546  return crm_time_add_minutes;
1547 
1548  case pcmk__time_seconds:
1549  return crm_time_add_seconds;
1550 
1551  default:
1552  return NULL;
1553  }
1554 
1555 }
1556 
1567 int
1569  const xmlNode *xml)
1570 {
1571  long long value;
1572  const char *attr = pcmk__time_component_attr(component);
1573  component_fn_t add = component_fn(component);
1574 
1575  if ((t == NULL) || (attr == NULL) || (add == NULL)) {
1576  return EINVAL;
1577  }
1578 
1579  if (xml == NULL) {
1580  return pcmk_rc_ok;
1581  }
1582 
1583  if (pcmk__scan_ll(crm_element_value(xml, attr), &value,
1584  0LL) != pcmk_rc_ok) {
1585  return pcmk_rc_unpack_error;
1586  }
1587 
1588  if ((value < INT_MIN) || (value > INT_MAX)) {
1589  return ERANGE;
1590  }
1591 
1592  if (value != 0LL) {
1593  add(t, (int) value);
1594  }
1595  return pcmk_rc_ok;
1596 }
1597 
1598 crm_time_t *
1600 {
1601  crm_time_t *utc = NULL;
1602  crm_time_t *answer = NULL;
1603 
1604  if ((dt == NULL) || (value == NULL)) {
1605  errno = EINVAL;
1606  return NULL;
1607  }
1608 
1609  utc = crm_get_utc_time(value);
1610  if (utc == NULL) {
1611  return NULL;
1612  }
1613 
1614  answer = crm_get_utc_time(dt);
1615  if (answer == NULL) {
1616  crm_time_free(utc);
1617  return NULL;
1618  }
1619  answer->duration = TRUE;
1620 
1621  answer->years -= utc->years;
1622  if(utc->months != 0) {
1623  crm_time_add_months(answer, -utc->months);
1624  }
1625  crm_time_add_days(answer, -utc->days);
1626  crm_time_add_seconds(answer, -utc->seconds);
1627 
1628  crm_time_free(utc);
1629  return answer;
1630 }
1631 
1632 crm_time_t *
1633 crm_time_subtract(const crm_time_t *dt, const crm_time_t *value)
1634 {
1635  crm_time_t *utc = NULL;
1636  crm_time_t *answer = NULL;
1637 
1638  if ((dt == NULL) || (value == NULL)) {
1639  errno = EINVAL;
1640  return NULL;
1641  }
1642 
1643  utc = crm_get_utc_time(value);
1644  if (utc == NULL) {
1645  return NULL;
1646  }
1647 
1648  answer = pcmk_copy_time(dt);
1649  answer->years -= utc->years;
1650  if(utc->months != 0) {
1651  crm_time_add_months(answer, -utc->months);
1652  }
1653  crm_time_add_days(answer, -utc->days);
1654  crm_time_add_seconds(answer, -utc->seconds);
1655  crm_time_free(utc);
1656 
1657  return answer;
1658 }
1659 
1667 bool
1669 {
1670  return (dt != NULL)
1671  && (dt->days > 0) && (dt->days <= year_days(dt->years))
1672  && (dt->seconds >= 0) && (dt->seconds < DAY_SECONDS);
1673 }
1674 
1675 #define do_cmp_field(l, r, field) \
1676  if(rc == 0) { \
1677  if(l->field > r->field) { \
1678  crm_trace("%s: %d > %d", \
1679  #field, l->field, r->field); \
1680  rc = 1; \
1681  } else if(l->field < r->field) { \
1682  crm_trace("%s: %d < %d", \
1683  #field, l->field, r->field); \
1684  rc = -1; \
1685  } \
1686  }
1687 
1688 int
1690 {
1691  int rc = 0;
1692  crm_time_t *t1 = crm_get_utc_time(a);
1693  crm_time_t *t2 = crm_get_utc_time(b);
1694 
1695  if ((t1 == NULL) && (t2 == NULL)) {
1696  rc = 0;
1697  } else if (t1 == NULL) {
1698  rc = -1;
1699  } else if (t2 == NULL) {
1700  rc = 1;
1701  } else {
1702  do_cmp_field(t1, t2, years);
1703  do_cmp_field(t1, t2, days);
1704  do_cmp_field(t1, t2, seconds);
1705  }
1706 
1707  crm_time_free(t1);
1708  crm_time_free(t2);
1709  return rc;
1710 }
1711 
1718 void
1720 {
1721  int days = 0;
1722 
1723  crm_trace("Adding %d seconds to %d (max=%d)",
1724  extra, a_time->seconds, DAY_SECONDS);
1725  a_time->seconds += extra;
1726  days = a_time->seconds / DAY_SECONDS;
1727  a_time->seconds %= DAY_SECONDS;
1728 
1729  // Don't have negative seconds
1730  if (a_time->seconds < 0) {
1731  a_time->seconds += DAY_SECONDS;
1732  --days;
1733  }
1734 
1735  crm_time_add_days(a_time, days);
1736 }
1737 
1738 void
1739 crm_time_add_days(crm_time_t * a_time, int extra)
1740 {
1741  int lower_bound = 1;
1742  int ydays = crm_time_leapyear(a_time->years) ? 366 : 365;
1743 
1744  crm_trace("Adding %d days to %.4d-%.3d", extra, a_time->years, a_time->days);
1745 
1746  a_time->days += extra;
1747  while (a_time->days > ydays) {
1748  a_time->years++;
1749  a_time->days -= ydays;
1750  ydays = crm_time_leapyear(a_time->years) ? 366 : 365;
1751  }
1752 
1753  if(a_time->duration) {
1754  lower_bound = 0;
1755  }
1756 
1757  while (a_time->days < lower_bound) {
1758  a_time->years--;
1759  a_time->days += crm_time_leapyear(a_time->years) ? 366 : 365;
1760  }
1761 }
1762 
1763 void
1764 crm_time_add_months(crm_time_t * a_time, int extra)
1765 {
1766  int lpc;
1767  uint32_t y, m, d, dmax;
1768 
1769  crm_time_get_gregorian(a_time, &y, &m, &d);
1770  crm_trace("Adding %d months to %.4" PRIu32 "-%.2" PRIu32 "-%.2" PRIu32,
1771  extra, y, m, d);
1772 
1773  if (extra > 0) {
1774  for (lpc = extra; lpc > 0; lpc--) {
1775  m++;
1776  if (m == 13) {
1777  m = 1;
1778  y++;
1779  }
1780  }
1781  } else {
1782  for (lpc = -extra; lpc > 0; lpc--) {
1783  m--;
1784  if (m == 0) {
1785  m = 12;
1786  y--;
1787  }
1788  }
1789  }
1790 
1791  dmax = crm_time_days_in_month(m, y);
1792  if (dmax < d) {
1793  /* Preserve day-of-month unless the month doesn't have enough days */
1794  d = dmax;
1795  }
1796 
1797  crm_trace("Calculated %.4" PRIu32 "-%.2" PRIu32 "-%.2" PRIu32, y, m, d);
1798 
1799  a_time->years = y;
1800  a_time->days = get_ordinal_days(y, m, d);
1801 
1802  crm_time_get_gregorian(a_time, &y, &m, &d);
1803  crm_trace("Got %.4" PRIu32 "-%.2" PRIu32 "-%.2" PRIu32, y, m, d);
1804 }
1805 
1806 void
1807 crm_time_add_minutes(crm_time_t * a_time, int extra)
1808 {
1809  crm_time_add_seconds(a_time, extra * 60);
1810 }
1811 
1812 void
1813 crm_time_add_hours(crm_time_t * a_time, int extra)
1814 {
1815  crm_time_add_seconds(a_time, extra * HOUR_SECONDS);
1816 }
1817 
1818 void
1819 crm_time_add_weeks(crm_time_t * a_time, int extra)
1820 {
1821  crm_time_add_days(a_time, extra * 7);
1822 }
1823 
1824 void
1825 crm_time_add_years(crm_time_t * a_time, int extra)
1826 {
1827  a_time->years += extra;
1828 }
1829 
1830 static void
1831 ha_get_tm_time(struct tm *target, const crm_time_t *source)
1832 {
1833  *target = (struct tm) {
1834  .tm_year = source->years - 1900,
1835  .tm_mday = source->days,
1836  .tm_sec = source->seconds % 60,
1837  .tm_min = ( source->seconds / 60 ) % 60,
1838  .tm_hour = source->seconds / HOUR_SECONDS,
1839  .tm_isdst = -1, /* don't adjust */
1840 
1841 #if defined(HAVE_STRUCT_TM_TM_GMTOFF)
1842  .tm_gmtoff = source->offset
1843 #endif
1844  };
1845  mktime(target);
1846 }
1847 
1848 /* The high-resolution variant of time object was added to meet an immediate
1849  * need, and is kept internal API.
1850  *
1851  * @TODO The long-term goal is to come up with a clean, unified design for a
1852  * time type (or types) that meets all the various needs, to replace
1853  * crm_time_t, pcmk__time_hr_t, and struct timespec (in lrmd_cmd_t).
1854  * Using glib's GDateTime is a possibility (if we are willing to require
1855  * glib >= 2.26).
1856  */
1857 
1860 {
1861  pcmk__time_hr_t *hr_dt = NULL;
1862 
1863  if (dt) {
1864  hr_dt = target;
1865  if (hr_dt == NULL) {
1866  hr_dt = pcmk__assert_alloc(1, sizeof(pcmk__time_hr_t));
1867  }
1868 
1869  *hr_dt = (pcmk__time_hr_t) {
1870  .years = dt->years,
1871  .months = dt->months,
1872  .days = dt->days,
1873  .seconds = dt->seconds,
1874  .offset = dt->offset,
1875  .duration = dt->duration
1876  };
1877  }
1878 
1879  return hr_dt;
1880 }
1881 
1882 void
1884 {
1885  CRM_ASSERT((hr_dt) && (target));
1886  *target = (crm_time_t) {
1887  .years = hr_dt->years,
1888  .months = hr_dt->months,
1889  .days = hr_dt->days,
1890  .seconds = hr_dt->seconds,
1891  .offset = hr_dt->offset,
1892  .duration = hr_dt->duration
1893  };
1894 }
1895 
1905 pcmk__time_hr_now(time_t *epoch)
1906 {
1907  struct timespec tv;
1908  crm_time_t dt;
1909  pcmk__time_hr_t *hr;
1910 
1911  qb_util_timespec_from_epoch_get(&tv);
1912  if (epoch != NULL) {
1913  *epoch = tv.tv_sec;
1914  }
1915  crm_time_set_timet(&dt, &(tv.tv_sec));
1916  hr = pcmk__time_hr_convert(NULL, &dt);
1917  if (hr != NULL) {
1918  hr->useconds = tv.tv_nsec / QB_TIME_NS_IN_USEC;
1919  }
1920  return hr;
1921 }
1922 
1924 pcmk__time_hr_new(const char *date_time)
1925 {
1926  pcmk__time_hr_t *hr_dt = NULL;
1927 
1928  if (date_time == NULL) {
1929  hr_dt = pcmk__time_hr_now(NULL);
1930  } else {
1931  crm_time_t *dt;
1932 
1933  dt = parse_date(date_time);
1934  hr_dt = pcmk__time_hr_convert(NULL, dt);
1935  crm_time_free(dt);
1936  }
1937  return hr_dt;
1938 }
1939 
1940 void
1942 {
1943  free(hr_dt);
1944 }
1945 
1956 char *
1957 pcmk__time_format_hr(const char *format, const pcmk__time_hr_t *hr_dt)
1958 {
1959  int scanned_pos = 0; // How many characters of format have been parsed
1960  int printed_pos = 0; // How many characters of format have been processed
1961  size_t date_len = 0;
1962 
1963  char nano_s[10] = { '\0', };
1964  char date_s[128] = { '\0', };
1965 
1966  struct tm tm = { 0, };
1967  crm_time_t dt = { 0, };
1968 
1969  if (format == NULL) {
1970  return NULL;
1971  }
1972  pcmk__time_set_hr_dt(&dt, hr_dt);
1973  ha_get_tm_time(&tm, &dt);
1974  sprintf(nano_s, "%06d000", hr_dt->useconds);
1975 
1976  while (format[scanned_pos] != '\0') {
1977  int fmt_pos; // Index after last character to pass as-is
1978  int nano_digits = 0; // Length of %N field width (if any)
1979  char *tmp_fmt_s = NULL;
1980  size_t nbytes = 0;
1981 
1982  // Look for next format specifier
1983  const char *mark_s = strchr(&format[scanned_pos], '%');
1984 
1985  if (mark_s == NULL) {
1986  // No more specifiers, so pass remaining string to strftime() as-is
1987  scanned_pos = strlen(format);
1988  fmt_pos = scanned_pos;
1989 
1990  } else {
1991  fmt_pos = mark_s - format; // Index of %
1992 
1993  // Skip % and any field width
1994  scanned_pos = fmt_pos + 1;
1995  while (isdigit(format[scanned_pos])) {
1996  scanned_pos++;
1997  }
1998 
1999  switch (format[scanned_pos]) {
2000  case '\0': // Literal % and possibly digits at end of string
2001  fmt_pos = scanned_pos; // Pass remaining string as-is
2002  break;
2003 
2004  case 'N': // %[width]N
2005  scanned_pos++;
2006 
2007  // Parse field width
2008  nano_digits = atoi(&format[fmt_pos + 1]);
2009  nano_digits = QB_MAX(nano_digits, 0);
2010  nano_digits = QB_MIN(nano_digits, 6);
2011  break;
2012 
2013  default: // Some other specifier
2014  if (format[++scanned_pos] != '\0') { // More to parse
2015  continue;
2016  }
2017  fmt_pos = scanned_pos; // Pass remaining string as-is
2018  break;
2019  }
2020  }
2021 
2022  if (date_len >= sizeof(date_s)) {
2023  return NULL; // No room for remaining string
2024  }
2025 
2026  tmp_fmt_s = strndup(&format[printed_pos], fmt_pos - printed_pos);
2027 #ifdef HAVE_FORMAT_NONLITERAL
2028 #pragma GCC diagnostic push
2029 #pragma GCC diagnostic ignored "-Wformat-nonliteral"
2030 #endif
2031  nbytes = strftime(&date_s[date_len], sizeof(date_s) - date_len,
2032  tmp_fmt_s, &tm);
2033 #ifdef HAVE_FORMAT_NONLITERAL
2034 #pragma GCC diagnostic pop
2035 #endif
2036  free(tmp_fmt_s);
2037  if (nbytes == 0) { // Would overflow buffer
2038  return NULL;
2039  }
2040  date_len += nbytes;
2041  printed_pos = scanned_pos;
2042  if (nano_digits != 0) {
2043  int nc = 0;
2044 
2045  if (date_len >= sizeof(date_s)) {
2046  return NULL; // No room to add nanoseconds
2047  }
2048  nc = snprintf(&date_s[date_len], sizeof(date_s) - date_len,
2049  "%.*s", nano_digits, nano_s);
2050 
2051  if ((nc < 0) || (nc == (sizeof(date_s) - date_len))) {
2052  return NULL; // Error or would overflow buffer
2053  }
2054  date_len += nc;
2055  }
2056  }
2057 
2058  return (date_len == 0)? NULL : pcmk__str_copy(date_s);
2059 }
2060 
2074 char *
2075 pcmk__epoch2str(const time_t *source, uint32_t flags)
2076 {
2077  time_t epoch_time = (source == NULL)? time(NULL) : *source;
2078 
2079  if (flags == 0) {
2080  return pcmk__str_copy(pcmk__trim(ctime(&epoch_time)));
2081  } else {
2082  crm_time_t dt;
2083 
2084  crm_time_set_timet(&dt, &epoch_time);
2085  return crm_time_as_string(&dt, flags);
2086  }
2087 }
2088 
2106 char *
2107 pcmk__timespec2str(const struct timespec *ts, uint32_t flags)
2108 {
2109  struct timespec tmp_ts;
2110  crm_time_t dt;
2111  char result[DATE_MAX] = { 0 };
2112 
2113  if (ts == NULL) {
2114  qb_util_timespec_from_epoch_get(&tmp_ts);
2115  ts = &tmp_ts;
2116  }
2117  crm_time_set_timet(&dt, &ts->tv_sec);
2118  time_as_string_common(&dt, ts->tv_nsec / QB_TIME_NS_IN_USEC, flags, result);
2119  return pcmk__str_copy(result);
2120 }
2121 
2133 const char *
2134 pcmk__readable_interval(guint interval_ms)
2135 {
2136 #define MS_IN_S (1000)
2137 #define MS_IN_M (MS_IN_S * 60)
2138 #define MS_IN_H (MS_IN_M * 60)
2139 #define MS_IN_D (MS_IN_H * 24)
2140 #define MAXSTR sizeof("..d..h..m..s...ms")
2141  static char str[MAXSTR];
2142  int offset = 0;
2143 
2144  str[0] = '\0';
2145  if (interval_ms >= MS_IN_D) {
2146  offset += snprintf(str + offset, MAXSTR - offset, "%ud",
2147  interval_ms / MS_IN_D);
2148  interval_ms -= (interval_ms / MS_IN_D) * MS_IN_D;
2149  }
2150  if (interval_ms >= MS_IN_H) {
2151  offset += snprintf(str + offset, MAXSTR - offset, "%uh",
2152  interval_ms / MS_IN_H);
2153  interval_ms -= (interval_ms / MS_IN_H) * MS_IN_H;
2154  }
2155  if (interval_ms >= MS_IN_M) {
2156  offset += snprintf(str + offset, MAXSTR - offset, "%um",
2157  interval_ms / MS_IN_M);
2158  interval_ms -= (interval_ms / MS_IN_M) * MS_IN_M;
2159  }
2160 
2161  // Ns, N.NNNs, or NNNms
2162  if (interval_ms >= MS_IN_S) {
2163  offset += snprintf(str + offset, MAXSTR - offset, "%u",
2164  interval_ms / MS_IN_S);
2165  interval_ms -= (interval_ms / MS_IN_S) * MS_IN_S;
2166  if (interval_ms > 0) {
2167  offset += snprintf(str + offset, MAXSTR - offset, ".%03u",
2168  interval_ms);
2169  }
2170  (void) snprintf(str + offset, MAXSTR - offset, "s");
2171 
2172  } else if (interval_ms > 0) {
2173  (void) snprintf(str + offset, MAXSTR - offset, "%ums", interval_ms);
2174 
2175  } else if (str[0] == '\0') {
2176  strcpy(str, "0s");
2177  }
2178  return str;
2179 }
int crm_time_get_gregorian(const crm_time_t *dt, uint32_t *y, uint32_t *m, uint32_t *d)
Definition: iso8601.c:365
#define LOG_TRACE
Definition: logging.h:38
#define PCMK_XA_YEARS
Definition: xml_names.h:451
#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:1401
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:1568
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
#define PCMK_XA_HOURS
Definition: xml_names.h:295
#define crm_time_epoch
Definition: iso8601.h:75
void pcmk__time_hr_free(pcmk__time_hr_t *hr_dt)
Definition: iso8601.c:1941
#define HOUR_SECONDS
Definition: iso8601.c:50
#define PCMK_XA_DAYS
Definition: xml_names.h:252
#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:1414
#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:1668
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:1807
#define DATE_MAX
Definition: iso8601.c:461
crm_time_t * crm_time_calculate_duration(const crm_time_t *dt, const crm_time_t *value)
Definition: iso8601.c:1599
const char * pcmk__readable_interval(guint interval_ms)
Definition: iso8601.c:2134
void crm_time_add_weeks(crm_time_t *a_time, int extra)
Definition: iso8601.c:1819
int crm_time_get_timezone(const crm_time_t *dt, uint32_t *h, uint32_t *m)
Definition: iso8601.c:307
char * pcmk__epoch2str(const time_t *source, uint32_t flags)
Definition: iso8601.c:2075
long long crm_time_get_seconds_since_epoch(const crm_time_t *dt)
Definition: iso8601.c:359
void crm_time_set(crm_time_t *target, const crm_time_t *source)
Definition: iso8601.c:1338
int crm_time_compare(const crm_time_t *a, const crm_time_t *b)
Definition: iso8601.c:1689
void crm_time_free(crm_time_t *dt)
Definition: iso8601.c:150
#define do_cmp_field(l, r, field)
Definition: iso8601.c:1675
crm_time_t * pcmk__copy_timet(time_t source)
Definition: iso8601.c:1441
#define valid_sec_usec(sec, usec)
Definition: iso8601.c:65
char * pcmk__timespec2str(const struct timespec *ts, uint32_t flags)
Definition: iso8601.c:2107
#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:1450
#define crm_time_log_duration
Definition: iso8601.h:70
pcmk__time_hr_t * pcmk__time_hr_now(time_t *epoch)
Definition: iso8601.c:1905
long long crm_time_get_seconds(const crm_time_t *dt)
Definition: iso8601.c:316
pcmk__time_hr_t * pcmk__time_hr_new(const char *date_time)
Definition: iso8601.c:1924
int pcmk__scan_ll(const char *text, long long *result, long long default_value)
Definition: strings.c:97
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:1883
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:1237
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:1719
crm_time_t * pcmk_copy_time(const crm_time_t *source)
Definition: iso8601.c:1424
const char * crm_element_value(const xmlNode *data, const char *name)
Retrieve the value of an XML attribute.
Definition: nvpair.c:446
crm_time_t * start
Definition: iso8601.h:35
void crm_time_add_days(crm_time_t *a_time, int extra)
Definition: iso8601.c:1739
#define crm_trace(fmt, args...)
Definition: logging.h:404
#define PCMK_XA_WEEKS
Definition: xml_names.h:442
#define pcmk_is_set(g, f)
Convenience alias for pcmk_all_flags_set(), to check single flag.
Definition: util.h:98
int crm_time_get_ordinal(const crm_time_t *dt, uint32_t *y, uint32_t *d)
Definition: iso8601.c:398
crm_time_t * crm_time_parse_duration(const char *period_s)
Parse a time duration from an ISO 8601 duration specification.
Definition: iso8601.c:1086
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:299
void crm_time_add_hours(crm_time_t *a_time, int extra)
Definition: iso8601.c:1813
#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:1487
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:320
#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:699
#define EPOCH_SECONDS
Definition: iso8601.c:357
#define MS_IN_S
char * pcmk__time_format_hr(const char *format, const pcmk__time_hr_t *hr_dt)
Definition: iso8601.c:1957
crm_time_t * crm_time_subtract(const crm_time_t *dt, const crm_time_t *value)
Definition: iso8601.c:1633
int crm_time_get_isoweek(const crm_time_t *dt, uint32_t *y, uint32_t *w, uint32_t *d)
Definition: iso8601.c:406
const char * target
Definition: pcmk_fence.c:29
#define crm_time_seconds
Definition: iso8601.h:74
#define PCMK_XA_SECONDS
Definition: xml_names.h:394
#define MS_IN_H
void crm_time_add_years(crm_time_t *a_time, int extra)
Definition: iso8601.c:1825
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
#define CRM_ASSERT(expr)
Definition: results.h:42
void crm_time_add_months(crm_time_t *a_time, int extra)
Definition: iso8601.c:1764
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:1859
void(* component_fn_t)(crm_time_t *, int)
Definition: iso8601.c:1516
char * pcmk__trim(char *str)
Definition: strings.c:528
#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:1327
#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:323