pacemaker  2.1.7-0f7f88312f
Scalable High-Availability cluster resource manager
iso8601.c
Go to the documentation of this file.
1 /*
2  * Copyright 2005-2022 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 <string.h>
22 #include <stdbool.h>
23 #include <crm/common/iso8601.h>
24 
25 /*
26  * Andrew's code was originally written for OSes whose "struct tm" contains:
27  * long tm_gmtoff; :: Seconds east of UTC
28  * const char *tm_zone; :: Timezone abbreviation
29  * Some OSes lack these, instead having:
30  * time_t (or long) timezone;
31  :: "difference between UTC and local standard time"
32  * char *tzname[2] = { "...", "..." };
33  * I (David Lee) confess to not understanding the details. So my attempted
34  * generalisations for where their use is necessary may be flawed.
35  *
36  * 1. Does "difference between ..." subtract the same or opposite way?
37  * 2. Should it use "altzone" instead of "timezone"?
38  * 3. Should it use tzname[0] or tzname[1]? Interaction with timezone/altzone?
39  */
40 #if defined(HAVE_STRUCT_TM_TM_GMTOFF)
41 # define GMTOFF(tm) ((tm)->tm_gmtoff)
42 #else
43 /* Note: extern variable; macro argument not actually used. */
44 # define GMTOFF(tm) (-timezone+daylight)
45 #endif
46 
47 #define HOUR_SECONDS (60 * 60)
48 #define DAY_SECONDS (HOUR_SECONDS * 24)
49 
62 #define valid_sec_usec(sec, usec) \
63  ((QB_ABS(usec) < QB_TIME_US_IN_SEC) \
64  && (((sec) == 0) || ((usec) == 0) || (((sec) < 0) == ((usec) < 0))))
65 
66 // A date/time or duration
67 struct crm_time_s {
68  int years; // Calendar year (date/time) or number of years (duration)
69  int months; // Number of months (duration only)
70  int days; // Ordinal day of year (date/time) or number of days (duration)
71  int seconds; // Seconds of day (date/time) or number of seconds (duration)
72  int offset; // Seconds offset from UTC (date/time only)
73  bool duration; // True if duration
74 };
75 
76 static crm_time_t *parse_date(const char *date_str);
77 
78 static crm_time_t *
79 crm_get_utc_time(const crm_time_t *dt)
80 {
81  crm_time_t *utc = NULL;
82 
83  if (dt == NULL) {
84  errno = EINVAL;
85  return NULL;
86  }
87 
88  utc = crm_time_new_undefined();
89  utc->years = dt->years;
90  utc->days = dt->days;
91  utc->seconds = dt->seconds;
92  utc->offset = 0;
93 
94  if (dt->offset) {
95  crm_time_add_seconds(utc, -dt->offset);
96  } else {
97  /* Durations (which are the only things that can include months, never have a timezone */
98  utc->months = dt->months;
99  }
100 
101  crm_time_log(LOG_TRACE, "utc-source", dt,
103  crm_time_log(LOG_TRACE, "utc-target", utc,
105  return utc;
106 }
107 
108 crm_time_t *
109 crm_time_new(const char *date_time)
110 {
111  tzset();
112  if (date_time == NULL) {
113  return pcmk__copy_timet(time(NULL));
114  }
115  return parse_date(date_time);
116 }
117 
125 crm_time_t *
127 {
128  crm_time_t *result = calloc(1, sizeof(crm_time_t));
129 
130  CRM_ASSERT(result != NULL);
131  return result;
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, w, d;
627 
628  if (crm_time_get_isoweek(dt, &y, &w, &d)) {
629  offset += snprintf(result + offset, DATE_MAX - offset,
630  "%" PRIu32 "-W%.2" PRIu32 "-%" PRIu32,
631  y, w, d);
632  }
633 
634  } else if (pcmk_is_set(flags, crm_time_ordinal)) { // YYYY-DDD
635  uint32_t y, d;
636 
637  if (crm_time_get_ordinal(dt, &y, &d)) {
638  offset += snprintf(result + offset, DATE_MAX - offset,
639  "%" PRIu32 "-%.3" PRIu32, y, d);
640  }
641 
642  } else { // YYYY-MM-DD
643  uint32_t y, m, d;
644 
645  if (crm_time_get_gregorian(dt, &y, &m, &d)) {
646  offset += snprintf(result + offset, DATE_MAX - offset,
647  "%.4" PRIu32 "-%.2" PRIu32 "-%.2" PRIu32,
648  y, m, d);
649  }
650  }
651  }
652 
654  uint32_t h = 0, m = 0, s = 0;
655 
656  if (offset > 0) {
657  offset += snprintf(result + offset, DATE_MAX - offset, " ");
658  }
659 
660  if (crm_time_get_timeofday(dt, &h, &m, &s)) {
661  offset += snprintf(result + offset, DATE_MAX - offset,
662  "%.2" PRIu32 ":%.2" PRIu32 ":%.2" PRIu32,
663  h, m, s);
664 
666  offset += snprintf(result + offset, DATE_MAX - offset,
667  ".%06" PRIu32, QB_ABS(usec));
668  }
669  }
670 
672  && (dt->offset != 0)) {
673  crm_time_get_sec(dt->offset, &h, &m, &s);
674  offset += snprintf(result + offset, DATE_MAX - offset,
675  " %c%.2" PRIu32 ":%.2" PRIu32,
676  ((dt->offset < 0)? '-' : '+'), h, m);
677  } else {
678  offset += snprintf(result + offset, DATE_MAX - offset, "Z");
679  }
680  }
681 
682  crm_time_free(utc);
683 }
684 
693 char *
695 {
696  char result[DATE_MAX] = { '\0', };
697  char *result_copy = NULL;
698 
699  time_as_string_common(dt, 0, flags, result);
700 
701  pcmk__str_update(&result_copy, result);
702  return result_copy;
703 }
704 
716 static bool
717 crm_time_parse_sec(const char *time_str, int *result)
718 {
719  int rc;
720  uint32_t hour = 0;
721  uint32_t minute = 0;
722  uint32_t second = 0;
723 
724  *result = 0;
725 
726  // Must have at least hour, but minutes and seconds are optional
727  rc = sscanf(time_str, "%" SCNu32 ":%" SCNu32 ":%" SCNu32,
728  &hour, &minute, &second);
729  if (rc == 1) {
730  rc = sscanf(time_str, "%2" SCNu32 "%2" SCNu32 "%2" SCNu32,
731  &hour, &minute, &second);
732  }
733  if (rc == 0) {
734  crm_err("%s is not a valid ISO 8601 time specification", time_str);
735  errno = EINVAL;
736  return FALSE;
737  }
738 
739  crm_trace("Got valid time: %.2" PRIu32 ":%.2" PRIu32 ":%.2" PRIu32,
740  hour, minute, second);
741 
742  if ((hour == 24) && (minute == 0) && (second == 0)) {
743  // Equivalent to 00:00:00 of next day, return number of seconds in day
744  } else if (hour >= 24) {
745  crm_err("%s is not a valid ISO 8601 time specification "
746  "because %" PRIu32 " is not a valid hour", time_str, hour);
747  errno = EINVAL;
748  return FALSE;
749  }
750  if (minute >= 60) {
751  crm_err("%s is not a valid ISO 8601 time specification "
752  "because %" PRIu32 " is not a valid minute", time_str, minute);
753  errno = EINVAL;
754  return FALSE;
755  }
756  if (second >= 60) {
757  crm_err("%s is not a valid ISO 8601 time specification "
758  "because %" PRIu32 " is not a valid second", time_str, second);
759  errno = EINVAL;
760  return FALSE;
761  }
762 
763  *result = (hour * HOUR_SECONDS) + (minute * 60) + second;
764  return TRUE;
765 }
766 
767 static bool
768 crm_time_parse_offset(const char *offset_str, int *offset)
769 {
770  tzset();
771 
772  if (offset_str == NULL) {
773  // Use local offset
774 #if defined(HAVE_STRUCT_TM_TM_GMTOFF)
775  time_t now = time(NULL);
776  struct tm *now_tm = localtime(&now);
777 #endif
778  int h_offset = GMTOFF(now_tm) / HOUR_SECONDS;
779  int m_offset = (GMTOFF(now_tm) - (HOUR_SECONDS * h_offset)) / 60;
780 
781  if (h_offset < 0 && m_offset < 0) {
782  m_offset = 0 - m_offset;
783  }
784  *offset = (HOUR_SECONDS * h_offset) + (60 * m_offset);
785  return TRUE;
786  }
787 
788  if (offset_str[0] == 'Z') { // @TODO invalid if anything after?
789  *offset = 0;
790  return TRUE;
791  }
792 
793  *offset = 0;
794  if ((offset_str[0] == '+') || (offset_str[0] == '-')
795  || isdigit((int)offset_str[0])) {
796 
797  gboolean negate = FALSE;
798 
799  if (offset_str[0] == '+') {
800  offset_str++;
801  } else if (offset_str[0] == '-') {
802  negate = TRUE;
803  offset_str++;
804  }
805  if (crm_time_parse_sec(offset_str, offset) == FALSE) {
806  return FALSE;
807  }
808  if (negate) {
809  *offset = 0 - *offset;
810  }
811  } // @TODO else invalid?
812  return TRUE;
813 }
814 
825 static bool
826 crm_time_parse(const char *time_str, crm_time_t *a_time)
827 {
828  uint32_t h, m, s;
829  char *offset_s = NULL;
830 
831  tzset();
832 
833  if (time_str) {
834  if (crm_time_parse_sec(time_str, &(a_time->seconds)) == FALSE) {
835  return FALSE;
836  }
837  offset_s = strstr(time_str, "Z");
838  if (offset_s == NULL) {
839  offset_s = strstr(time_str, " ");
840  if (offset_s) {
841  while (isspace(offset_s[0])) {
842  offset_s++;
843  }
844  }
845  }
846  }
847 
848  if (crm_time_parse_offset(offset_s, &(a_time->offset)) == FALSE) {
849  return FALSE;
850  }
851  crm_time_get_sec(a_time->offset, &h, &m, &s);
852  crm_trace("Got tz: %c%2." PRIu32 ":%.2" PRIu32,
853  (a_time->offset < 0)? '-' : '+', h, m);
854 
855  if (a_time->seconds == DAY_SECONDS) {
856  // 24:00:00 == 00:00:00 of next day
857  a_time->seconds = 0;
858  crm_time_add_days(a_time, 1);
859  }
860  return TRUE;
861 }
862 
863 /*
864  * \internal
865  * \brief Parse a time object from an ISO 8601 date/time specification
866  *
867  * \param[in] date_str ISO 8601 date/time specification (or "epoch")
868  *
869  * \return New time object on success, NULL (and set errno) otherwise
870  */
871 static crm_time_t *
872 parse_date(const char *date_str)
873 {
874  const char *time_s = NULL;
875  crm_time_t *dt = NULL;
876 
877  int year = 0;
878  int month = 0;
879  int week = 0;
880  int day = 0;
881  int rc = 0;
882 
883  if (pcmk__str_empty(date_str)) {
884  crm_err("No ISO 8601 date/time specification given");
885  goto invalid;
886  }
887 
888  if ((date_str[0] == 'T') || (date_str[2] == ':')) {
889  /* Just a time supplied - Infer current date */
890  dt = crm_time_new(NULL);
891  if (date_str[0] == 'T') {
892  time_s = date_str + 1;
893  } else {
894  time_s = date_str;
895  }
896  goto parse_time;
897  }
898 
899  dt = crm_time_new_undefined();
900 
901  if (!strncasecmp("epoch", date_str, 5)
902  && ((date_str[5] == '\0') || (date_str[5] == '/') || isspace(date_str[5]))) {
903  dt->days = 1;
904  dt->years = 1970;
906  return dt;
907  }
908 
909  /* YYYY-MM-DD */
910  rc = sscanf(date_str, "%d-%d-%d", &year, &month, &day);
911  if (rc == 1) {
912  /* YYYYMMDD */
913  rc = sscanf(date_str, "%4d%2d%2d", &year, &month, &day);
914  }
915  if (rc == 3) {
916  if (month > 12) {
917  crm_err("'%s' is not a valid ISO 8601 date/time specification "
918  "because '%d' is not a valid month", date_str, month);
919  goto invalid;
920  } else if (day > crm_time_days_in_month(month, year)) {
921  crm_err("'%s' is not a valid ISO 8601 date/time specification "
922  "because '%d' is not a valid day of the month",
923  date_str, day);
924  goto invalid;
925  } else {
926  dt->years = year;
927  dt->days = get_ordinal_days(year, month, day);
928  crm_trace("Parsed Gregorian date '%.4d-%.3d' from date string '%s'",
929  year, dt->days, date_str);
930  }
931  goto parse_time;
932  }
933 
934  /* YYYY-DDD */
935  rc = sscanf(date_str, "%d-%d", &year, &day);
936  if (rc == 2) {
937  if (day > year_days(year)) {
938  crm_err("'%s' is not a valid ISO 8601 date/time specification "
939  "because '%d' is not a valid day of the year (max %d)",
940  date_str, day, year_days(year));
941  goto invalid;
942  }
943  crm_trace("Parsed ordinal year %d and days %d from date string '%s'",
944  year, day, date_str);
945  dt->days = day;
946  dt->years = year;
947  goto parse_time;
948  }
949 
950  /* YYYY-Www-D */
951  rc = sscanf(date_str, "%d-W%d-%d", &year, &week, &day);
952  if (rc == 3) {
953  if (week > crm_time_weeks_in_year(year)) {
954  crm_err("'%s' is not a valid ISO 8601 date/time specification "
955  "because '%d' is not a valid week of the year (max %d)",
956  date_str, week, crm_time_weeks_in_year(year));
957  goto invalid;
958  } else if (day < 1 || day > 7) {
959  crm_err("'%s' is not a valid ISO 8601 date/time specification "
960  "because '%d' is not a valid day of the week",
961  date_str, day);
962  goto invalid;
963  } else {
964  /*
965  * See https://en.wikipedia.org/wiki/ISO_week_date
966  *
967  * Monday 29 December 2008 is written "2009-W01-1"
968  * Sunday 3 January 2010 is written "2009-W53-7"
969  * Saturday 27 September 2008 is written "2008-W37-6"
970  *
971  * If 1 January is on a Monday, Tuesday, Wednesday or Thursday, it is in week 01.
972  * If 1 January is on a Friday, Saturday or Sunday, it is in week 52 or 53 of the previous year.
973  */
974  int jan1 = crm_time_january1_weekday(year);
975 
976  crm_trace("Got year %d (Jan 1 = %d), week %d, and day %d from date string '%s'",
977  year, jan1, week, day, date_str);
978 
979  dt->years = year;
980  crm_time_add_days(dt, (week - 1) * 7);
981 
982  if (jan1 <= 4) {
983  crm_time_add_days(dt, 1 - jan1);
984  } else {
985  crm_time_add_days(dt, 8 - jan1);
986  }
987 
988  crm_time_add_days(dt, day);
989  }
990  goto parse_time;
991  }
992 
993  crm_err("'%s' is not a valid ISO 8601 date/time specification", date_str);
994  goto invalid;
995 
996  parse_time:
997 
998  if (time_s == NULL) {
999  time_s = date_str + strspn(date_str, "0123456789-W");
1000  if ((time_s[0] == ' ') || (time_s[0] == 'T')) {
1001  ++time_s;
1002  } else {
1003  time_s = NULL;
1004  }
1005  }
1006  if ((time_s != NULL) && (crm_time_parse(time_s, dt) == FALSE)) {
1007  goto invalid;
1008  }
1009 
1011  if (crm_time_check(dt) == FALSE) {
1012  crm_err("'%s' is not a valid ISO 8601 date/time specification",
1013  date_str);
1014  goto invalid;
1015  }
1016  return dt;
1017 
1018 invalid:
1019  crm_time_free(dt);
1020  errno = EINVAL;
1021  return NULL;
1022 }
1023 
1024 // Parse an ISO 8601 numeric value and return number of characters consumed
1025 // @TODO This cannot handle >INT_MAX int values
1026 // @TODO Fractions appear to be not working
1027 // @TODO Error out on invalid specifications
1028 static int
1029 parse_int(const char *str, int field_width, int upper_bound, int *result)
1030 {
1031  int lpc = 0;
1032  int offset = 0;
1033  int intermediate = 0;
1034  gboolean fraction = FALSE;
1035  gboolean negate = FALSE;
1036 
1037  *result = 0;
1038  if (*str == '\0') {
1039  return 0;
1040  }
1041 
1042  if (str[offset] == 'T') {
1043  offset++;
1044  }
1045 
1046  if (str[offset] == '.' || str[offset] == ',') {
1047  fraction = TRUE;
1048  field_width = -1;
1049  offset++;
1050  } else if (str[offset] == '-') {
1051  negate = TRUE;
1052  offset++;
1053  } else if (str[offset] == '+' || str[offset] == ':') {
1054  offset++;
1055  }
1056 
1057  for (; (fraction || lpc < field_width) && isdigit((int)str[offset]); lpc++) {
1058  if (fraction) {
1059  intermediate = (str[offset] - '0') / (10 ^ lpc);
1060  } else {
1061  *result *= 10;
1062  intermediate = str[offset] - '0';
1063  }
1064  *result += intermediate;
1065  offset++;
1066  }
1067  if (fraction) {
1068  *result = (int)(*result * upper_bound);
1069 
1070  } else if (upper_bound > 0 && *result > upper_bound) {
1071  *result = upper_bound;
1072  }
1073  if (negate) {
1074  *result = 0 - *result;
1075  }
1076  if (lpc > 0) {
1077  crm_trace("Found int: %d. Stopped at str[%d]='%c'", *result, lpc, str[lpc]);
1078  return offset;
1079  }
1080  return 0;
1081 }
1082 
1094 crm_time_t *
1095 crm_time_parse_duration(const char *period_s)
1096 {
1097  gboolean is_time = FALSE;
1098  crm_time_t *diff = NULL;
1099 
1100  if (pcmk__str_empty(period_s)) {
1101  crm_err("No ISO 8601 time duration given");
1102  goto invalid;
1103  }
1104  if (period_s[0] != 'P') {
1105  crm_err("'%s' is not a valid ISO 8601 time duration "
1106  "because it does not start with a 'P'", period_s);
1107  goto invalid;
1108  }
1109  if ((period_s[1] == '\0') || isspace(period_s[1])) {
1110  crm_err("'%s' is not a valid ISO 8601 time duration "
1111  "because nothing follows 'P'", period_s);
1112  goto invalid;
1113  }
1114 
1115  diff = crm_time_new_undefined();
1116  diff->duration = TRUE;
1117 
1118  for (const char *current = period_s + 1;
1119  current[0] && (current[0] != '/') && !isspace(current[0]);
1120  ++current) {
1121 
1122  int an_int = 0, rc;
1123 
1124  if (current[0] == 'T') {
1125  /* A 'T' separates year/month/day from hour/minute/seconds. We don't
1126  * require it strictly, but just use it to differentiate month from
1127  * minutes.
1128  */
1129  is_time = TRUE;
1130  continue;
1131  }
1132 
1133  // An integer must be next
1134  rc = parse_int(current, 10, 0, &an_int);
1135  if (rc == 0) {
1136  crm_err("'%s' is not a valid ISO 8601 time duration "
1137  "because no integer at '%s'", period_s, current);
1138  goto invalid;
1139  }
1140  current += rc;
1141 
1142  // A time unit must be next (we're not strict about the order)
1143  switch (current[0]) {
1144  case 'Y':
1145  diff->years = an_int;
1146  break;
1147  case 'M':
1148  if (is_time) {
1149  /* Minutes */
1150  diff->seconds += an_int * 60;
1151  } else {
1152  diff->months = an_int;
1153  }
1154  break;
1155  case 'W':
1156  diff->days += an_int * 7;
1157  break;
1158  case 'D':
1159  diff->days += an_int;
1160  break;
1161  case 'H':
1162  diff->seconds += an_int * HOUR_SECONDS;
1163  break;
1164  case 'S':
1165  diff->seconds += an_int;
1166  break;
1167  case '\0':
1168  crm_err("'%s' is not a valid ISO 8601 time duration "
1169  "because no units after %d", period_s, an_int);
1170  goto invalid;
1171  default:
1172  crm_err("'%s' is not a valid ISO 8601 time duration "
1173  "because '%c' is not a valid time unit",
1174  period_s, current[0]);
1175  goto invalid;
1176  }
1177  }
1178 
1179  if (!crm_time_is_defined(diff)) {
1180  crm_err("'%s' is not a valid ISO 8601 time duration "
1181  "because no amounts and units given", period_s);
1182  goto invalid;
1183  }
1184  return diff;
1185 
1186 invalid:
1187  crm_time_free(diff);
1188  errno = EINVAL;
1189  return NULL;
1190 }
1191 
1203 crm_time_parse_period(const char *period_str)
1204 {
1205  const char *original = period_str;
1206  crm_time_period_t *period = NULL;
1207 
1208  if (pcmk__str_empty(period_str)) {
1209  crm_err("No ISO 8601 time period given");
1210  goto invalid;
1211  }
1212 
1213  tzset();
1214  period = calloc(1, sizeof(crm_time_period_t));
1215  CRM_ASSERT(period != NULL);
1216 
1217  if (period_str[0] == 'P') {
1218  period->diff = crm_time_parse_duration(period_str);
1219  if (period->diff == NULL) {
1220  goto error;
1221  }
1222  } else {
1223  period->start = parse_date(period_str);
1224  if (period->start == NULL) {
1225  goto error;
1226  }
1227  }
1228 
1229  period_str = strstr(original, "/");
1230  if (period_str) {
1231  ++period_str;
1232  if (period_str[0] == 'P') {
1233  if (period->diff != NULL) {
1234  crm_err("'%s' is not a valid ISO 8601 time period "
1235  "because it has two durations",
1236  original);
1237  goto invalid;
1238  }
1239  period->diff = crm_time_parse_duration(period_str);
1240  if (period->diff == NULL) {
1241  goto error;
1242  }
1243  } else {
1244  period->end = parse_date(period_str);
1245  if (period->end == NULL) {
1246  goto error;
1247  }
1248  }
1249 
1250  } else if (period->diff != NULL) {
1251  // Only duration given, assume start is now
1252  period->start = crm_time_new(NULL);
1253 
1254  } else {
1255  // Only start given
1256  crm_err("'%s' is not a valid ISO 8601 time period "
1257  "because it has no duration or ending time",
1258  original);
1259  goto invalid;
1260  }
1261 
1262  if (period->start == NULL) {
1263  period->start = crm_time_subtract(period->end, period->diff);
1264 
1265  } else if (period->end == NULL) {
1266  period->end = crm_time_add(period->start, period->diff);
1267  }
1268 
1269  if (crm_time_check(period->start) == FALSE) {
1270  crm_err("'%s' is not a valid ISO 8601 time period "
1271  "because the start is invalid", period_str);
1272  goto invalid;
1273  }
1274  if (crm_time_check(period->end) == FALSE) {
1275  crm_err("'%s' is not a valid ISO 8601 time period "
1276  "because the end is invalid", period_str);
1277  goto invalid;
1278  }
1279  return period;
1280 
1281 invalid:
1282  errno = EINVAL;
1283 error:
1284  crm_time_free_period(period);
1285  return NULL;
1286 }
1287 
1293 void
1295 {
1296  if (period) {
1297  crm_time_free(period->start);
1298  crm_time_free(period->end);
1299  crm_time_free(period->diff);
1300  free(period);
1301  }
1302 }
1303 
1304 void
1306 {
1307  crm_trace("target=%p, source=%p", target, source);
1308 
1309  CRM_CHECK(target != NULL && source != NULL, return);
1310 
1311  target->years = source->years;
1312  target->days = source->days;
1313  target->months = source->months; /* Only for durations */
1314  target->seconds = source->seconds;
1315  target->offset = source->offset;
1316 
1317  crm_time_log(LOG_TRACE, "source", source,
1319  crm_time_log(LOG_TRACE, "target", target,
1321 }
1322 
1323 static void
1324 ha_set_tm_time(crm_time_t *target, const struct tm *source)
1325 {
1326  int h_offset = 0;
1327  int m_offset = 0;
1328 
1329  /* Ensure target is fully initialized */
1330  target->years = 0;
1331  target->months = 0;
1332  target->days = 0;
1333  target->seconds = 0;
1334  target->offset = 0;
1335  target->duration = FALSE;
1336 
1337  if (source->tm_year > 0) {
1338  /* years since 1900 */
1339  target->years = 1900 + source->tm_year;
1340  }
1341 
1342  if (source->tm_yday >= 0) {
1343  /* days since January 1 [0-365] */
1344  target->days = 1 + source->tm_yday;
1345  }
1346 
1347  if (source->tm_hour >= 0) {
1348  target->seconds += HOUR_SECONDS * source->tm_hour;
1349  }
1350  if (source->tm_min >= 0) {
1351  target->seconds += 60 * source->tm_min;
1352  }
1353  if (source->tm_sec >= 0) {
1354  target->seconds += source->tm_sec;
1355  }
1356 
1357  /* tm_gmtoff == offset from UTC in seconds */
1358  h_offset = GMTOFF(source) / HOUR_SECONDS;
1359  m_offset = (GMTOFF(source) - (HOUR_SECONDS * h_offset)) / 60;
1360  crm_trace("Time offset is %lds (%.2d:%.2d)",
1361  GMTOFF(source), h_offset, m_offset);
1362 
1363  target->offset += HOUR_SECONDS * h_offset;
1364  target->offset += 60 * m_offset;
1365 }
1366 
1367 void
1368 crm_time_set_timet(crm_time_t *target, const time_t *source)
1369 {
1370  ha_set_tm_time(target, localtime(source));
1371 }
1372 
1373 crm_time_t *
1375 {
1377 
1378  crm_time_set(target, source);
1379  return target;
1380 }
1381 
1390 crm_time_t *
1391 pcmk__copy_timet(time_t source)
1392 {
1394 
1395  crm_time_set_timet(target, &source);
1396  return target;
1397 }
1398 
1399 crm_time_t *
1400 crm_time_add(const crm_time_t *dt, const crm_time_t *value)
1401 {
1402  crm_time_t *utc = NULL;
1403  crm_time_t *answer = NULL;
1404 
1405  if ((dt == NULL) || (value == NULL)) {
1406  errno = EINVAL;
1407  return NULL;
1408  }
1409 
1410  answer = pcmk_copy_time(dt);
1411 
1412  utc = crm_get_utc_time(value);
1413  if (utc == NULL) {
1414  crm_time_free(answer);
1415  return NULL;
1416  }
1417 
1418  answer->years += utc->years;
1419  crm_time_add_months(answer, utc->months);
1420  crm_time_add_days(answer, utc->days);
1421  crm_time_add_seconds(answer, utc->seconds);
1422 
1423  crm_time_free(utc);
1424  return answer;
1425 }
1426 
1427 crm_time_t *
1429 {
1430  crm_time_t *utc = NULL;
1431  crm_time_t *answer = NULL;
1432 
1433  if ((dt == NULL) || (value == NULL)) {
1434  errno = EINVAL;
1435  return NULL;
1436  }
1437 
1438  utc = crm_get_utc_time(value);
1439  if (utc == NULL) {
1440  return NULL;
1441  }
1442 
1443  answer = crm_get_utc_time(dt);
1444  if (answer == NULL) {
1445  crm_time_free(utc);
1446  return NULL;
1447  }
1448  answer->duration = TRUE;
1449 
1450  answer->years -= utc->years;
1451  if(utc->months != 0) {
1452  crm_time_add_months(answer, -utc->months);
1453  }
1454  crm_time_add_days(answer, -utc->days);
1455  crm_time_add_seconds(answer, -utc->seconds);
1456 
1457  crm_time_free(utc);
1458  return answer;
1459 }
1460 
1461 crm_time_t *
1462 crm_time_subtract(const crm_time_t *dt, const crm_time_t *value)
1463 {
1464  crm_time_t *utc = NULL;
1465  crm_time_t *answer = NULL;
1466 
1467  if ((dt == NULL) || (value == NULL)) {
1468  errno = EINVAL;
1469  return NULL;
1470  }
1471 
1472  utc = crm_get_utc_time(value);
1473  if (utc == NULL) {
1474  return NULL;
1475  }
1476 
1477  answer = pcmk_copy_time(dt);
1478  answer->years -= utc->years;
1479  if(utc->months != 0) {
1480  crm_time_add_months(answer, -utc->months);
1481  }
1482  crm_time_add_days(answer, -utc->days);
1483  crm_time_add_seconds(answer, -utc->seconds);
1484  crm_time_free(utc);
1485 
1486  return answer;
1487 }
1488 
1496 bool
1498 {
1499  return (dt != NULL)
1500  && (dt->days > 0) && (dt->days <= year_days(dt->years))
1501  && (dt->seconds >= 0) && (dt->seconds < DAY_SECONDS);
1502 }
1503 
1504 #define do_cmp_field(l, r, field) \
1505  if(rc == 0) { \
1506  if(l->field > r->field) { \
1507  crm_trace("%s: %d > %d", \
1508  #field, l->field, r->field); \
1509  rc = 1; \
1510  } else if(l->field < r->field) { \
1511  crm_trace("%s: %d < %d", \
1512  #field, l->field, r->field); \
1513  rc = -1; \
1514  } \
1515  }
1516 
1517 int
1519 {
1520  int rc = 0;
1521  crm_time_t *t1 = crm_get_utc_time(a);
1522  crm_time_t *t2 = crm_get_utc_time(b);
1523 
1524  if ((t1 == NULL) && (t2 == NULL)) {
1525  rc = 0;
1526  } else if (t1 == NULL) {
1527  rc = -1;
1528  } else if (t2 == NULL) {
1529  rc = 1;
1530  } else {
1531  do_cmp_field(t1, t2, years);
1532  do_cmp_field(t1, t2, days);
1533  do_cmp_field(t1, t2, seconds);
1534  }
1535 
1536  crm_time_free(t1);
1537  crm_time_free(t2);
1538  return rc;
1539 }
1540 
1547 void
1549 {
1550  int days = 0;
1551 
1552  crm_trace("Adding %d seconds to %d (max=%d)",
1553  extra, a_time->seconds, DAY_SECONDS);
1554  a_time->seconds += extra;
1555  days = a_time->seconds / DAY_SECONDS;
1556  a_time->seconds %= DAY_SECONDS;
1557 
1558  // Don't have negative seconds
1559  if (a_time->seconds < 0) {
1560  a_time->seconds += DAY_SECONDS;
1561  --days;
1562  }
1563 
1564  crm_time_add_days(a_time, days);
1565 }
1566 
1567 void
1568 crm_time_add_days(crm_time_t * a_time, int extra)
1569 {
1570  int lower_bound = 1;
1571  int ydays = crm_time_leapyear(a_time->years) ? 366 : 365;
1572 
1573  crm_trace("Adding %d days to %.4d-%.3d", extra, a_time->years, a_time->days);
1574 
1575  a_time->days += extra;
1576  while (a_time->days > ydays) {
1577  a_time->years++;
1578  a_time->days -= ydays;
1579  ydays = crm_time_leapyear(a_time->years) ? 366 : 365;
1580  }
1581 
1582  if(a_time->duration) {
1583  lower_bound = 0;
1584  }
1585 
1586  while (a_time->days < lower_bound) {
1587  a_time->years--;
1588  a_time->days += crm_time_leapyear(a_time->years) ? 366 : 365;
1589  }
1590 }
1591 
1592 void
1593 crm_time_add_months(crm_time_t * a_time, int extra)
1594 {
1595  int lpc;
1596  uint32_t y, m, d, dmax;
1597 
1598  crm_time_get_gregorian(a_time, &y, &m, &d);
1599  crm_trace("Adding %d months to %.4" PRIu32 "-%.2" PRIu32 "-%.2" PRIu32,
1600  extra, y, m, d);
1601 
1602  if (extra > 0) {
1603  for (lpc = extra; lpc > 0; lpc--) {
1604  m++;
1605  if (m == 13) {
1606  m = 1;
1607  y++;
1608  }
1609  }
1610  } else {
1611  for (lpc = -extra; lpc > 0; lpc--) {
1612  m--;
1613  if (m == 0) {
1614  m = 12;
1615  y--;
1616  }
1617  }
1618  }
1619 
1620  dmax = crm_time_days_in_month(m, y);
1621  if (dmax < d) {
1622  /* Preserve day-of-month unless the month doesn't have enough days */
1623  d = dmax;
1624  }
1625 
1626  crm_trace("Calculated %.4" PRIu32 "-%.2" PRIu32 "-%.2" PRIu32, y, m, d);
1627 
1628  a_time->years = y;
1629  a_time->days = get_ordinal_days(y, m, d);
1630 
1631  crm_time_get_gregorian(a_time, &y, &m, &d);
1632  crm_trace("Got %.4" PRIu32 "-%.2" PRIu32 "-%.2" PRIu32, y, m, d);
1633 }
1634 
1635 void
1636 crm_time_add_minutes(crm_time_t * a_time, int extra)
1637 {
1638  crm_time_add_seconds(a_time, extra * 60);
1639 }
1640 
1641 void
1642 crm_time_add_hours(crm_time_t * a_time, int extra)
1643 {
1644  crm_time_add_seconds(a_time, extra * HOUR_SECONDS);
1645 }
1646 
1647 void
1648 crm_time_add_weeks(crm_time_t * a_time, int extra)
1649 {
1650  crm_time_add_days(a_time, extra * 7);
1651 }
1652 
1653 void
1654 crm_time_add_years(crm_time_t * a_time, int extra)
1655 {
1656  a_time->years += extra;
1657 }
1658 
1659 static void
1660 ha_get_tm_time(struct tm *target, const crm_time_t *source)
1661 {
1662  *target = (struct tm) {
1663  .tm_year = source->years - 1900,
1664  .tm_mday = source->days,
1665  .tm_sec = source->seconds % 60,
1666  .tm_min = ( source->seconds / 60 ) % 60,
1667  .tm_hour = source->seconds / HOUR_SECONDS,
1668  .tm_isdst = -1, /* don't adjust */
1669 
1670 #if defined(HAVE_STRUCT_TM_TM_GMTOFF)
1671  .tm_gmtoff = source->offset
1672 #endif
1673  };
1674  mktime(target);
1675 }
1676 
1677 /* The high-resolution variant of time object was added to meet an immediate
1678  * need, and is kept internal API.
1679  *
1680  * @TODO The long-term goal is to come up with a clean, unified design for a
1681  * time type (or types) that meets all the various needs, to replace
1682  * crm_time_t, pcmk__time_hr_t, and struct timespec (in lrmd_cmd_t).
1683  * Using glib's GDateTime is a possibility (if we are willing to require
1684  * glib >= 2.26).
1685  */
1686 
1689 {
1690  pcmk__time_hr_t *hr_dt = NULL;
1691 
1692  if (dt) {
1693  hr_dt = target?target:calloc(1, sizeof(pcmk__time_hr_t));
1694  CRM_ASSERT(hr_dt != NULL);
1695  *hr_dt = (pcmk__time_hr_t) {
1696  .years = dt->years,
1697  .months = dt->months,
1698  .days = dt->days,
1699  .seconds = dt->seconds,
1700  .offset = dt->offset,
1701  .duration = dt->duration
1702  };
1703  }
1704 
1705  return hr_dt;
1706 }
1707 
1708 void
1710 {
1711  CRM_ASSERT((hr_dt) && (target));
1712  *target = (crm_time_t) {
1713  .years = hr_dt->years,
1714  .months = hr_dt->months,
1715  .days = hr_dt->days,
1716  .seconds = hr_dt->seconds,
1717  .offset = hr_dt->offset,
1718  .duration = hr_dt->duration
1719  };
1720 }
1721 
1731 pcmk__time_hr_now(time_t *epoch)
1732 {
1733  struct timespec tv;
1734  crm_time_t dt;
1735  pcmk__time_hr_t *hr;
1736 
1737  qb_util_timespec_from_epoch_get(&tv);
1738  if (epoch != NULL) {
1739  *epoch = tv.tv_sec;
1740  }
1741  crm_time_set_timet(&dt, &(tv.tv_sec));
1742  hr = pcmk__time_hr_convert(NULL, &dt);
1743  if (hr != NULL) {
1744  hr->useconds = tv.tv_nsec / QB_TIME_NS_IN_USEC;
1745  }
1746  return hr;
1747 }
1748 
1750 pcmk__time_hr_new(const char *date_time)
1751 {
1752  pcmk__time_hr_t *hr_dt = NULL;
1753 
1754  if (date_time == NULL) {
1755  hr_dt = pcmk__time_hr_now(NULL);
1756  } else {
1757  crm_time_t *dt;
1758 
1759  dt = parse_date(date_time);
1760  hr_dt = pcmk__time_hr_convert(NULL, dt);
1761  crm_time_free(dt);
1762  }
1763  return hr_dt;
1764 }
1765 
1766 void
1768 {
1769  free(hr_dt);
1770 }
1771 
1772 char *
1773 pcmk__time_format_hr(const char *format, const pcmk__time_hr_t *hr_dt)
1774 {
1775  const char *mark_s;
1776  int max = 128, scanned_pos = 0, printed_pos = 0, fmt_pos = 0,
1777  date_len = 0, nano_digits = 0;
1778  char nano_s[10], date_s[max+1], nanofmt_s[5] = "%", *tmp_fmt_s;
1779  struct tm tm;
1780  crm_time_t dt;
1781 
1782  if (!format) {
1783  return NULL;
1784  }
1785  pcmk__time_set_hr_dt(&dt, hr_dt);
1786  ha_get_tm_time(&tm, &dt);
1787  sprintf(nano_s, "%06d000", hr_dt->useconds);
1788 
1789  while ((format[scanned_pos]) != '\0') {
1790  mark_s = strchr(&format[scanned_pos], '%');
1791  if (mark_s) {
1792  int fmt_len = 1;
1793 
1794  fmt_pos = mark_s - format;
1795  while ((format[fmt_pos+fmt_len] != '\0') &&
1796  (format[fmt_pos+fmt_len] >= '0') &&
1797  (format[fmt_pos+fmt_len] <= '9')) {
1798  fmt_len++;
1799  }
1800  scanned_pos = fmt_pos + fmt_len + 1;
1801  if (format[fmt_pos+fmt_len] == 'N') {
1802  nano_digits = atoi(&format[fmt_pos+1]);
1803  nano_digits = (nano_digits > 6)?6:nano_digits;
1804  nano_digits = (nano_digits < 0)?0:nano_digits;
1805  sprintf(&nanofmt_s[1], ".%ds", nano_digits);
1806  } else {
1807  if (format[scanned_pos] != '\0') {
1808  continue;
1809  }
1810  fmt_pos = scanned_pos; /* print till end */
1811  }
1812  } else {
1813  scanned_pos = strlen(format);
1814  fmt_pos = scanned_pos; /* print till end */
1815  }
1816  tmp_fmt_s = strndup(&format[printed_pos], fmt_pos - printed_pos);
1817 #ifdef HAVE_FORMAT_NONLITERAL
1818 #pragma GCC diagnostic push
1819 #pragma GCC diagnostic ignored "-Wformat-nonliteral"
1820 #endif
1821  date_len += strftime(&date_s[date_len], max-date_len, tmp_fmt_s, &tm);
1822 #ifdef HAVE_FORMAT_NONLITERAL
1823 #pragma GCC diagnostic pop
1824 #endif
1825  printed_pos = scanned_pos;
1826  free(tmp_fmt_s);
1827  if (nano_digits) {
1828 #ifdef HAVE_FORMAT_NONLITERAL
1829 #pragma GCC diagnostic push
1830 #pragma GCC diagnostic ignored "-Wformat-nonliteral"
1831 #endif
1832  date_len += snprintf(&date_s[date_len], max-date_len,
1833  nanofmt_s, nano_s);
1834 #ifdef HAVE_FORMAT_NONLITERAL
1835 #pragma GCC diagnostic pop
1836 #endif
1837  nano_digits = 0;
1838  }
1839  }
1840 
1841  return (date_len == 0)?NULL:strdup(date_s);
1842 }
1843 
1857 char *
1858 pcmk__epoch2str(const time_t *source, uint32_t flags)
1859 {
1860  time_t epoch_time = (source == NULL)? time(NULL) : *source;
1861  char *result = NULL;
1862 
1863  if (flags == 0) {
1864  const char *buf = pcmk__trim(ctime(&epoch_time));
1865 
1866  if (buf != NULL) {
1867  result = strdup(buf);
1868  CRM_ASSERT(result != NULL);
1869  }
1870  } else {
1871  crm_time_t dt;
1872 
1873  crm_time_set_timet(&dt, &epoch_time);
1875  }
1876  return result;
1877 }
1878 
1896 char *
1897 pcmk__timespec2str(const struct timespec *ts, uint32_t flags)
1898 {
1899  struct timespec tmp_ts;
1900  crm_time_t dt;
1901  char result[DATE_MAX] = { 0 };
1902  char *result_copy = NULL;
1903 
1904  if (ts == NULL) {
1905  qb_util_timespec_from_epoch_get(&tmp_ts);
1906  ts = &tmp_ts;
1907  }
1908  crm_time_set_timet(&dt, &ts->tv_sec);
1909  time_as_string_common(&dt, ts->tv_nsec / QB_TIME_NS_IN_USEC, flags, result);
1910  pcmk__str_update(&result_copy, result);
1911  return result_copy;
1912 }
1913 
1925 const char *
1926 pcmk__readable_interval(guint interval_ms)
1927 {
1928 #define MS_IN_S (1000)
1929 #define MS_IN_M (MS_IN_S * 60)
1930 #define MS_IN_H (MS_IN_M * 60)
1931 #define MS_IN_D (MS_IN_H * 24)
1932 #define MAXSTR sizeof("..d..h..m..s...ms")
1933  static char str[MAXSTR];
1934  int offset = 0;
1935 
1936  str[0] = '\0';
1937  if (interval_ms > MS_IN_D) {
1938  offset += snprintf(str + offset, MAXSTR - offset, "%ud",
1939  interval_ms / MS_IN_D);
1940  interval_ms -= (interval_ms / MS_IN_D) * MS_IN_D;
1941  }
1942  if (interval_ms > MS_IN_H) {
1943  offset += snprintf(str + offset, MAXSTR - offset, "%uh",
1944  interval_ms / MS_IN_H);
1945  interval_ms -= (interval_ms / MS_IN_H) * MS_IN_H;
1946  }
1947  if (interval_ms > MS_IN_M) {
1948  offset += snprintf(str + offset, MAXSTR - offset, "%um",
1949  interval_ms / MS_IN_M);
1950  interval_ms -= (interval_ms / MS_IN_M) * MS_IN_M;
1951  }
1952 
1953  // Ns, N.NNNs, or NNNms
1954  if (interval_ms > MS_IN_S) {
1955  offset += snprintf(str + offset, MAXSTR - offset, "%u",
1956  interval_ms / MS_IN_S);
1957  interval_ms -= (interval_ms / MS_IN_S) * MS_IN_S;
1958  if (interval_ms > 0) {
1959  offset += snprintf(str + offset, MAXSTR - offset, ".%03u",
1960  interval_ms);
1961  }
1962  (void) snprintf(str + offset, MAXSTR - offset, "s");
1963 
1964  } else if (interval_ms > 0) {
1965  (void) snprintf(str + offset, MAXSTR - offset, "%ums", interval_ms);
1966 
1967  } else if (str[0] == '\0') {
1968  strcpy(str, "0s");
1969  }
1970  return str;
1971 }
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 CRM_CHECK(expr, failure_action)
Definition: logging.h:238
void crm_time_set_timet(crm_time_t *target, const time_t *source)
Definition: iso8601.c:1368
A dumping ground.
bool crm_time_leapyear(int year)
Definition: iso8601.c:236
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 crm_time_epoch
Definition: iso8601.h:75
void pcmk__time_hr_free(pcmk__time_hr_t *hr_dt)
Definition: iso8601.c:1767
#define HOUR_SECONDS
Definition: iso8601.c:47
#define crm_time_log_timeofday
Definition: iso8601.h:68
crm_time_t * crm_time_new(const char *date_time)
Definition: iso8601.c:109
#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:1497
#define crm_time_ordinal
Definition: iso8601.h:72
void crm_time_add_minutes(crm_time_t *a_time, int extra)
Definition: iso8601.c:1636
#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:1428
const char * pcmk__readable_interval(guint interval_ms)
Definition: iso8601.c:1926
void crm_time_add_weeks(crm_time_t *a_time, int extra)
Definition: iso8601.c:1648
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:1858
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:1305
int crm_time_compare(const crm_time_t *a, const crm_time_t *b)
Definition: iso8601.c:1518
void crm_time_free(crm_time_t *dt)
Definition: iso8601.c:150
#define do_cmp_field(l, r, field)
Definition: iso8601.c:1504
crm_time_t * pcmk__copy_timet(time_t source)
Definition: iso8601.c:1391
#define valid_sec_usec(sec, usec)
Definition: iso8601.c:62
char * pcmk__timespec2str(const struct timespec *ts, uint32_t flags)
Definition: iso8601.c:1897
#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:293
crm_time_t * crm_time_add(const crm_time_t *dt, const crm_time_t *value)
Definition: iso8601.c:1400
#define crm_time_log_duration
Definition: iso8601.h:70
pcmk__time_hr_t * pcmk__time_hr_now(time_t *epoch)
Definition: iso8601.c:1731
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:1750
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:1709
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:1203
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:1548
crm_time_t * pcmk_copy_time(const crm_time_t *source)
Definition: iso8601.c:1374
crm_time_t * start
Definition: iso8601.h:35
void crm_time_add_days(crm_time_t *a_time, int extra)
Definition: iso8601.c:1568
#define crm_trace(fmt, args...)
Definition: logging.h:387
#define pcmk_is_set(g, f)
Convenience alias for pcmk_all_flags_set(), to check single flag.
Definition: util.h:99
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:1095
crm_time_t * end
Definition: iso8601.h:36
#define GMTOFF(tm)
Definition: iso8601.c:44
void pcmk__str_update(char **str, const char *value)
Definition: strings.c:1193
#define MS_IN_M
ISO_8601 Date handling.
#define DAY_SECONDS
Definition: iso8601.c:48
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:1642
#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
crm_time_t * crm_time_new_undefined(void)
Allocate memory for an uninitialized time object.
Definition: iso8601.c:126
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:694
#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:1773
crm_time_t * crm_time_subtract(const crm_time_t *dt, const crm_time_t *value)
Definition: iso8601.c:1462
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 MS_IN_H
void crm_time_add_years(crm_time_t *a_time, int extra)
Definition: iso8601.c:1654
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:381
#define CRM_ASSERT(expr)
Definition: results.h:42
void crm_time_add_months(crm_time_t *a_time, int extra)
Definition: iso8601.c:1593
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:1688
char * pcmk__trim(char *str)
Definition: strings.c:453
#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:1294
#define crm_time_log_date
Definition: iso8601.h:67
uint64_t flags
Definition: remote.c:215
#define crm_time_usecs
Definition: iso8601.h:76