pacemaker  2.1.5-b7adf64e51
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 <string.h>
21 #include <stdbool.h>
22 #include <crm/common/iso8601.h>
23 
24 /*
25  * Andrew's code was originally written for OSes whose "struct tm" contains:
26  * long tm_gmtoff; :: Seconds east of UTC
27  * const char *tm_zone; :: Timezone abbreviation
28  * Some OSes lack these, instead having:
29  * time_t (or long) timezone;
30  :: "difference between UTC and local standard time"
31  * char *tzname[2] = { "...", "..." };
32  * I (David Lee) confess to not understanding the details. So my attempted
33  * generalisations for where their use is necessary may be flawed.
34  *
35  * 1. Does "difference between ..." subtract the same or opposite way?
36  * 2. Should it use "altzone" instead of "timezone"?
37  * 3. Should it use tzname[0] or tzname[1]? Interaction with timezone/altzone?
38  */
39 #if defined(HAVE_STRUCT_TM_TM_GMTOFF)
40 # define GMTOFF(tm) ((tm)->tm_gmtoff)
41 #else
42 /* Note: extern variable; macro argument not actually used. */
43 # define GMTOFF(tm) (-timezone+daylight)
44 #endif
45 
46 #define HOUR_SECONDS (60 * 60)
47 #define DAY_SECONDS (HOUR_SECONDS * 24)
48 
49 // A date/time or duration
50 struct crm_time_s {
51  int years; // Calendar year (date/time) or number of years (duration)
52  int months; // Number of months (duration only)
53  int days; // Ordinal day of year (date/time) or number of days (duration)
54  int seconds; // Seconds of day (date/time) or number of seconds (duration)
55  int offset; // Seconds offset from UTC (date/time only)
56  bool duration; // True if duration
57 };
58 
59 static crm_time_t *parse_date(const char *date_str);
60 
61 static crm_time_t *
62 crm_get_utc_time(const crm_time_t *dt)
63 {
64  crm_time_t *utc = NULL;
65 
66  if (dt == NULL) {
67  errno = EINVAL;
68  return NULL;
69  }
70 
71  utc = crm_time_new_undefined();
72  utc->years = dt->years;
73  utc->days = dt->days;
74  utc->seconds = dt->seconds;
75  utc->offset = 0;
76 
77  if (dt->offset) {
78  crm_time_add_seconds(utc, -dt->offset);
79  } else {
80  /* Durations (which are the only things that can include months, never have a timezone */
81  utc->months = dt->months;
82  }
83 
84  crm_time_log(LOG_TRACE, "utc-source", dt,
86  crm_time_log(LOG_TRACE, "utc-target", utc,
88  return utc;
89 }
90 
91 crm_time_t *
92 crm_time_new(const char *date_time)
93 {
94  time_t tm_now;
95  crm_time_t *dt = NULL;
96 
97  tzset();
98  if (date_time == NULL) {
99  tm_now = time(NULL);
100  dt = crm_time_new_undefined();
101  crm_time_set_timet(dt, &tm_now);
102  } else {
103  dt = parse_date(date_time);
104  }
105  return dt;
106 }
107 
115 crm_time_t *
117 {
118  crm_time_t *result = calloc(1, sizeof(crm_time_t));
119 
120  CRM_ASSERT(result != NULL);
121  return result;
122 }
123 
131 bool
133 {
134  // Any nonzero member indicates something has been done to t
135  return (t != NULL) && (t->years || t->months || t->days || t->seconds
136  || t->offset || t->duration);
137 }
138 
139 void
141 {
142  if (dt == NULL) {
143  return;
144  }
145  free(dt);
146 }
147 
148 static int
149 year_days(int year)
150 {
151  int d = 365;
152 
153  if (crm_time_leapyear(year)) {
154  d++;
155  }
156  return d;
157 }
158 
159 /* From http://myweb.ecu.edu/mccartyr/ISOwdALG.txt :
160  *
161  * 5. Find the Jan1Weekday for Y (Monday=1, Sunday=7)
162  * YY = (Y-1) % 100
163  * C = (Y-1) - YY
164  * G = YY + YY/4
165  * Jan1Weekday = 1 + (((((C / 100) % 4) x 5) + G) % 7)
166  */
167 int
169 {
170  int YY = (year - 1) % 100;
171  int C = (year - 1) - YY;
172  int G = YY + YY / 4;
173  int jan1 = 1 + (((((C / 100) % 4) * 5) + G) % 7);
174 
175  crm_trace("YY=%d, C=%d, G=%d", YY, C, G);
176  crm_trace("January 1 %.4d: %d", year, jan1);
177  return jan1;
178 }
179 
180 int
182 {
183  int weeks = 52;
184  int jan1 = crm_time_january1_weekday(year);
185 
186  /* if jan1 == thursday */
187  if (jan1 == 4) {
188  weeks++;
189  } else {
190  jan1 = crm_time_january1_weekday(year + 1);
191  /* if dec31 == thursday aka. jan1 of next year is a friday */
192  if (jan1 == 5) {
193  weeks++;
194  }
195 
196  }
197  return weeks;
198 }
199 
200 // Jan-Dec plus Feb of leap years
201 static int month_days[13] = {
202  31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 29
203 };
204 
213 int
214 crm_time_days_in_month(int month, int year)
215 {
216  if ((month < 1) || (month > 12)) {
217  return 0;
218  }
219  if ((month == 2) && crm_time_leapyear(year)) {
220  month = 13;
221  }
222  return month_days[month - 1];
223 }
224 
225 bool
227 {
228  gboolean is_leap = FALSE;
229 
230  if (year % 4 == 0) {
231  is_leap = TRUE;
232  }
233  if (year % 100 == 0 && year % 400 != 0) {
234  is_leap = FALSE;
235  }
236  return is_leap;
237 }
238 
239 static uint32_t
240 get_ordinal_days(uint32_t y, uint32_t m, uint32_t d)
241 {
242  int lpc;
243 
244  for (lpc = 1; lpc < m; lpc++) {
245  d += crm_time_days_in_month(lpc, y);
246  }
247  return d;
248 }
249 
250 void
251 crm_time_log_alias(int log_level, const char *file, const char *function,
252  int line, const char *prefix, const crm_time_t *date_time,
253  int flags)
254 {
255  char *date_s = crm_time_as_string(date_time, flags);
256 
257  if (log_level == LOG_STDOUT) {
258  printf("%s%s%s\n",
259  (prefix? prefix : ""), (prefix? ": " : ""), date_s);
260  } else {
261  do_crm_log_alias(log_level, file, function, line, "%s%s%s",
262  (prefix? prefix : ""), (prefix? ": " : ""), date_s);
263  }
264  free(date_s);
265 }
266 
267 static void
268 crm_time_get_sec(int sec, uint32_t *h, uint32_t *m, uint32_t *s)
269 {
270  uint32_t hours, minutes, seconds;
271 
272  if (sec < 0) {
273  seconds = 0 - sec;
274  } else {
275  seconds = sec;
276  }
277 
278  hours = seconds / HOUR_SECONDS;
279  seconds -= HOUR_SECONDS * hours;
280 
281  minutes = seconds / 60;
282  seconds -= 60 * minutes;
283 
284  crm_trace("%d == %.2d:%.2d:%.2d", sec, hours, minutes, seconds);
285 
286  *h = hours;
287  *m = minutes;
288  *s = seconds;
289 }
290 
291 int
292 crm_time_get_timeofday(const crm_time_t *dt, uint32_t *h, uint32_t *m,
293  uint32_t *s)
294 {
295  crm_time_get_sec(dt->seconds, h, m, s);
296  return TRUE;
297 }
298 
299 int
300 crm_time_get_timezone(const crm_time_t *dt, uint32_t *h, uint32_t *m)
301 {
302  uint32_t s;
303 
304  crm_time_get_sec(dt->seconds, h, m, &s);
305  return TRUE;
306 }
307 
308 long long
310 {
311  int lpc;
312  crm_time_t *utc = NULL;
313  long long in_seconds = 0;
314 
315  if (dt == NULL) {
316  return 0;
317  }
318 
319  utc = crm_get_utc_time(dt);
320  if (utc == NULL) {
321  return 0;
322  }
323 
324  for (lpc = 1; lpc < utc->years; lpc++) {
325  long long dmax = year_days(lpc);
326 
327  in_seconds += DAY_SECONDS * dmax;
328  }
329 
330  /* utc->months is an offset that can only be set for a duration.
331  * By definition, the value is variable depending on the date to
332  * which it is applied.
333  *
334  * Force 30-day months so that something vaguely sane happens
335  * for anyone that tries to use a month in this way.
336  */
337  if (utc->months > 0) {
338  in_seconds += DAY_SECONDS * 30 * (long long) (utc->months);
339  }
340 
341  if (utc->days > 0) {
342  in_seconds += DAY_SECONDS * (long long) (utc->days - 1);
343  }
344  in_seconds += utc->seconds;
345 
346  crm_time_free(utc);
347  return in_seconds;
348 }
349 
350 #define EPOCH_SECONDS 62135596800ULL /* Calculated using crm_time_get_seconds() */
351 long long
353 {
354  return (dt == NULL)? 0 : (crm_time_get_seconds(dt) - EPOCH_SECONDS);
355 }
356 
357 int
358 crm_time_get_gregorian(const crm_time_t *dt, uint32_t *y, uint32_t *m,
359  uint32_t *d)
360 {
361  int months = 0;
362  int days = dt->days;
363 
364  if(dt->years != 0) {
365  for (months = 1; months <= 12 && days > 0; months++) {
366  int mdays = crm_time_days_in_month(months, dt->years);
367 
368  if (mdays >= days) {
369  break;
370  } else {
371  days -= mdays;
372  }
373  }
374 
375  } else if (dt->months) {
376  /* This is a duration including months, don't convert the days field */
377  months = dt->months;
378 
379  } else {
380  /* This is a duration not including months, still don't convert the days field */
381  }
382 
383  *y = dt->years;
384  *m = months;
385  *d = days;
386  crm_trace("%.4d-%.3d -> %.4d-%.2d-%.2d", dt->years, dt->days, dt->years, months, days);
387  return TRUE;
388 }
389 
390 int
391 crm_time_get_ordinal(const crm_time_t *dt, uint32_t *y, uint32_t *d)
392 {
393  *y = dt->years;
394  *d = dt->days;
395  return TRUE;
396 }
397 
398 int
399 crm_time_get_isoweek(const crm_time_t *dt, uint32_t *y, uint32_t *w,
400  uint32_t *d)
401 {
402  /*
403  * Monday 29 December 2008 is written "2009-W01-1"
404  * Sunday 3 January 2010 is written "2009-W53-7"
405  */
406  int year_num = 0;
407  int jan1 = crm_time_january1_weekday(dt->years);
408  int h = -1;
409 
410  CRM_CHECK(dt->days > 0, return FALSE);
411 
412 /* 6. Find the Weekday for Y M D */
413  h = dt->days + jan1 - 1;
414  *d = 1 + ((h - 1) % 7);
415 
416 /* 7. Find if Y M D falls in YearNumber Y-1, WeekNumber 52 or 53 */
417  if (dt->days <= (8 - jan1) && jan1 > 4) {
418  crm_trace("year--, jan1=%d", jan1);
419  year_num = dt->years - 1;
420  *w = crm_time_weeks_in_year(year_num);
421 
422  } else {
423  year_num = dt->years;
424  }
425 
426 /* 8. Find if Y M D falls in YearNumber Y+1, WeekNumber 1 */
427  if (year_num == dt->years) {
428  int dmax = year_days(year_num);
429  int correction = 4 - *d;
430 
431  if ((dmax - dt->days) < correction) {
432  crm_trace("year++, jan1=%d, i=%d vs. %d", jan1, dmax - dt->days, correction);
433  year_num = dt->years + 1;
434  *w = 1;
435  }
436  }
437 
438 /* 9. Find if Y M D falls in YearNumber Y, WeekNumber 1 through 53 */
439  if (year_num == dt->years) {
440  int j = dt->days + (7 - *d) + (jan1 - 1);
441 
442  *w = j / 7;
443  if (jan1 > 4) {
444  *w -= 1;
445  }
446  }
447 
448  *y = year_num;
449  crm_trace("Converted %.4d-%.3d to %.4d-W%.2d-%d", dt->years, dt->days, *y, *w, *d);
450  return TRUE;
451 }
452 
453 #define DATE_MAX 128
454 
455 static void
456 crm_duration_as_string(const crm_time_t *dt, char *result)
457 {
458  size_t offset = 0;
459 
460  if (dt->years) {
461  offset += snprintf(result + offset, DATE_MAX - offset, "%4d year%s ",
462  dt->years, pcmk__plural_s(dt->years));
463  }
464  if (dt->months) {
465  offset += snprintf(result + offset, DATE_MAX - offset, "%2d month%s ",
466  dt->months, pcmk__plural_s(dt->months));
467  }
468  if (dt->days) {
469  offset += snprintf(result + offset, DATE_MAX - offset, "%2d day%s ",
470  dt->days, pcmk__plural_s(dt->days));
471  }
472 
473  if (((offset == 0) || (dt->seconds != 0))
474  && (dt->seconds > -60) && (dt->seconds < 60)) {
475  offset += snprintf(result + offset, DATE_MAX - offset, "%d second%s",
476  dt->seconds, pcmk__plural_s(dt->seconds));
477  } else if (dt->seconds) {
478  uint32_t h = 0, m = 0, s = 0;
479 
480  offset += snprintf(result + offset, DATE_MAX - offset, "%d seconds (",
481  dt->seconds);
482  crm_time_get_sec(dt->seconds, &h, &m, &s);
483  if (h) {
484  offset += snprintf(result + offset, DATE_MAX - offset, "%u hour%s%s",
485  h, pcmk__plural_s(h), ((m || s)? " " : ""));
486  }
487  if (m) {
488  offset += snprintf(result + offset, DATE_MAX - offset, "%u minute%s%s",
489  m, pcmk__plural_s(m), (s? " " : ""));
490  }
491  if (s) {
492  offset += snprintf(result + offset, DATE_MAX - offset, "%u second%s",
493  s, pcmk__plural_s(s));
494  }
495  offset += snprintf(result + offset, DATE_MAX - offset, ")");
496  }
497 }
498 
499 char *
500 crm_time_as_string(const crm_time_t *date_time, int flags)
501 {
502  const crm_time_t *dt = NULL;
503  crm_time_t *utc = NULL;
504  char result[DATE_MAX] = { '\0', };
505  char *result_copy = NULL;
506  size_t offset = 0;
507 
508  // Convert to UTC if local timezone was not requested
509  if (date_time && date_time->offset
511  crm_trace("UTC conversion");
512  utc = crm_get_utc_time(date_time);
513  dt = utc;
514  } else {
515  dt = date_time;
516  }
517 
518  if (!crm_time_is_defined(dt)) {
519  strcpy(result, "<undefined time>");
520  goto done;
521  }
522 
523  // Simple cases: as duration, seconds, or seconds since epoch
524 
526  crm_duration_as_string(date_time, result);
527  goto done;
528  }
529 
530  if (flags & crm_time_seconds) {
531  snprintf(result, DATE_MAX, "%lld", crm_time_get_seconds(date_time));
532  goto done;
533  }
534 
535  if (flags & crm_time_epoch) {
536  snprintf(result, DATE_MAX, "%lld",
538  goto done;
539  }
540 
541  // As readable string
542 
543  if (flags & crm_time_log_date) {
544  if (flags & crm_time_weeks) { // YYYY-WW-D
545  uint32_t y, w, d;
546 
547  if (crm_time_get_isoweek(dt, &y, &w, &d)) {
548  offset += snprintf(result + offset, DATE_MAX - offset,
549  "%u-W%.2u-%u", y, w, d);
550  }
551 
552  } else if (flags & crm_time_ordinal) { // YYYY-DDD
553  uint32_t y, d;
554 
555  if (crm_time_get_ordinal(dt, &y, &d)) {
556  offset += snprintf(result + offset, DATE_MAX - offset,
557  "%u-%.3u", y, d);
558  }
559 
560  } else { // YYYY-MM-DD
561  uint32_t y, m, d;
562 
563  if (crm_time_get_gregorian(dt, &y, &m, &d)) {
564  offset += snprintf(result + offset, DATE_MAX - offset,
565  "%.4u-%.2u-%.2u", y, m, d);
566  }
567  }
568  }
569 
571  uint32_t h = 0, m = 0, s = 0;
572 
573  if (offset > 0) {
574  offset += snprintf(result + offset, DATE_MAX - offset, " ");
575  }
576 
577  if (crm_time_get_timeofday(dt, &h, &m, &s)) {
578  offset += snprintf(result + offset, DATE_MAX - offset,
579  "%.2u:%.2u:%.2u", h, m, s);
580  }
581 
582  if ((flags & crm_time_log_with_timezone) && (dt->offset != 0)) {
583  crm_time_get_sec(dt->offset, &h, &m, &s);
584  offset += snprintf(result + offset, DATE_MAX - offset,
585  " %c%.2u:%.2u",
586  ((dt->offset < 0)? '-' : '+'), h, m);
587  } else {
588  offset += snprintf(result + offset, DATE_MAX - offset, "Z");
589  }
590  }
591 
592  done:
593  crm_time_free(utc);
594 
595  result_copy = strdup(result);
596  CRM_ASSERT(result_copy != NULL);
597  return result_copy;
598 }
599 
611 static bool
612 crm_time_parse_sec(const char *time_str, int *result)
613 {
614  int rc;
615  uint32_t hour = 0;
616  uint32_t minute = 0;
617  uint32_t second = 0;
618 
619  *result = 0;
620 
621  // Must have at least hour, but minutes and seconds are optional
622  rc = sscanf(time_str, "%d:%d:%d", &hour, &minute, &second);
623  if (rc == 1) {
624  rc = sscanf(time_str, "%2d%2d%2d", &hour, &minute, &second);
625  }
626  if (rc == 0) {
627  crm_err("%s is not a valid ISO 8601 time specification", time_str);
628  errno = EINVAL;
629  return FALSE;
630  }
631 
632  crm_trace("Got valid time: %.2d:%.2d:%.2d", hour, minute, second);
633 
634  if ((hour == 24) && (minute == 0) && (second == 0)) {
635  // Equivalent to 00:00:00 of next day, return number of seconds in day
636  } else if (hour >= 24) {
637  crm_err("%s is not a valid ISO 8601 time specification "
638  "because %d is not a valid hour", time_str, hour);
639  errno = EINVAL;
640  return FALSE;
641  }
642  if (minute >= 60) {
643  crm_err("%s is not a valid ISO 8601 time specification "
644  "because %d is not a valid minute", time_str, minute);
645  errno = EINVAL;
646  return FALSE;
647  }
648  if (second >= 60) {
649  crm_err("%s is not a valid ISO 8601 time specification "
650  "because %d is not a valid second", time_str, second);
651  errno = EINVAL;
652  return FALSE;
653  }
654 
655  *result = (hour * HOUR_SECONDS) + (minute * 60) + second;
656  return TRUE;
657 }
658 
659 static bool
660 crm_time_parse_offset(const char *offset_str, int *offset)
661 {
662  tzset();
663 
664  if (offset_str == NULL) {
665  // Use local offset
666 #if defined(HAVE_STRUCT_TM_TM_GMTOFF)
667  time_t now = time(NULL);
668  struct tm *now_tm = localtime(&now);
669 #endif
670  int h_offset = GMTOFF(now_tm) / HOUR_SECONDS;
671  int m_offset = (GMTOFF(now_tm) - (HOUR_SECONDS * h_offset)) / 60;
672 
673  if (h_offset < 0 && m_offset < 0) {
674  m_offset = 0 - m_offset;
675  }
676  *offset = (HOUR_SECONDS * h_offset) + (60 * m_offset);
677  return TRUE;
678  }
679 
680  if (offset_str[0] == 'Z') { // @TODO invalid if anything after?
681  *offset = 0;
682  return TRUE;
683  }
684 
685  *offset = 0;
686  if ((offset_str[0] == '+') || (offset_str[0] == '-')
687  || isdigit((int)offset_str[0])) {
688 
689  gboolean negate = FALSE;
690 
691  if (offset_str[0] == '+') {
692  offset_str++;
693  } else if (offset_str[0] == '-') {
694  negate = TRUE;
695  offset_str++;
696  }
697  if (crm_time_parse_sec(offset_str, offset) == FALSE) {
698  return FALSE;
699  }
700  if (negate) {
701  *offset = 0 - *offset;
702  }
703  } // @TODO else invalid?
704  return TRUE;
705 }
706 
717 static bool
718 crm_time_parse(const char *time_str, crm_time_t *a_time)
719 {
720  uint32_t h, m, s;
721  char *offset_s = NULL;
722 
723  tzset();
724 
725  if (time_str) {
726  if (crm_time_parse_sec(time_str, &(a_time->seconds)) == FALSE) {
727  return FALSE;
728  }
729  offset_s = strstr(time_str, "Z");
730  if (offset_s == NULL) {
731  offset_s = strstr(time_str, " ");
732  if (offset_s) {
733  while (isspace(offset_s[0])) {
734  offset_s++;
735  }
736  }
737  }
738  }
739 
740  if (crm_time_parse_offset(offset_s, &(a_time->offset)) == FALSE) {
741  return FALSE;
742  }
743  crm_time_get_sec(a_time->offset, &h, &m, &s);
744  crm_trace("Got tz: %c%2.d:%.2d", ((a_time->offset < 0)? '-' : '+'), h, m);
745 
746  if (a_time->seconds == DAY_SECONDS) {
747  // 24:00:00 == 00:00:00 of next day
748  a_time->seconds = 0;
749  crm_time_add_days(a_time, 1);
750  }
751  return TRUE;
752 }
753 
754 /*
755  * \internal
756  * \brief Parse a time object from an ISO 8601 date/time specification
757  *
758  * \param[in] date_str ISO 8601 date/time specification (or "epoch")
759  *
760  * \return New time object on success, NULL (and set errno) otherwise
761  */
762 static crm_time_t *
763 parse_date(const char *date_str)
764 {
765  const char *time_s = NULL;
766  crm_time_t *dt = NULL;
767 
768  int year = 0;
769  int month = 0;
770  int week = 0;
771  int day = 0;
772  int rc = 0;
773 
774  if (pcmk__str_empty(date_str)) {
775  crm_err("No ISO 8601 date/time specification given");
776  goto invalid;
777  }
778 
779  if ((date_str[0] == 'T') || (date_str[2] == ':')) {
780  /* Just a time supplied - Infer current date */
781  dt = crm_time_new(NULL);
782  if (date_str[0] == 'T') {
783  time_s = date_str + 1;
784  } else {
785  time_s = date_str;
786  }
787  goto parse_time;
788  }
789 
790  dt = crm_time_new_undefined();
791 
792  if (!strncasecmp("epoch", date_str, 5)
793  && ((date_str[5] == '\0') || (date_str[5] == '/') || isspace(date_str[5]))) {
794  dt->days = 1;
795  dt->years = 1970;
797  return dt;
798  }
799 
800  /* YYYY-MM-DD */
801  rc = sscanf(date_str, "%d-%d-%d", &year, &month, &day);
802  if (rc == 1) {
803  /* YYYYMMDD */
804  rc = sscanf(date_str, "%4d%2d%2d", &year, &month, &day);
805  }
806  if (rc == 3) {
807  if (month > 12) {
808  crm_err("'%s' is not a valid ISO 8601 date/time specification "
809  "because '%d' is not a valid month", date_str, month);
810  goto invalid;
811  } else if (day > crm_time_days_in_month(month, year)) {
812  crm_err("'%s' is not a valid ISO 8601 date/time specification "
813  "because '%d' is not a valid day of the month",
814  date_str, day);
815  goto invalid;
816  } else {
817  dt->years = year;
818  dt->days = get_ordinal_days(year, month, day);
819  crm_trace("Parsed Gregorian date '%.4d-%.3d' from date string '%s'",
820  year, dt->days, date_str);
821  }
822  goto parse_time;
823  }
824 
825  /* YYYY-DDD */
826  rc = sscanf(date_str, "%d-%d", &year, &day);
827  if (rc == 2) {
828  if (day > year_days(year)) {
829  crm_err("'%s' is not a valid ISO 8601 date/time specification "
830  "because '%d' is not a valid day of the year (max %d)",
831  date_str, day, year_days(year));
832  goto invalid;
833  }
834  crm_trace("Parsed ordinal year %d and days %d from date string '%s'",
835  year, day, date_str);
836  dt->days = day;
837  dt->years = year;
838  goto parse_time;
839  }
840 
841  /* YYYY-Www-D */
842  rc = sscanf(date_str, "%d-W%d-%d", &year, &week, &day);
843  if (rc == 3) {
844  if (week > crm_time_weeks_in_year(year)) {
845  crm_err("'%s' is not a valid ISO 8601 date/time specification "
846  "because '%d' is not a valid week of the year (max %d)",
847  date_str, week, crm_time_weeks_in_year(year));
848  goto invalid;
849  } else if (day < 1 || day > 7) {
850  crm_err("'%s' is not a valid ISO 8601 date/time specification "
851  "because '%d' is not a valid day of the week",
852  date_str, day);
853  goto invalid;
854  } else {
855  /*
856  * See https://en.wikipedia.org/wiki/ISO_week_date
857  *
858  * Monday 29 December 2008 is written "2009-W01-1"
859  * Sunday 3 January 2010 is written "2009-W53-7"
860  * Saturday 27 September 2008 is written "2008-W37-6"
861  *
862  * If 1 January is on a Monday, Tuesday, Wednesday or Thursday, it is in week 01.
863  * If 1 January is on a Friday, Saturday or Sunday, it is in week 52 or 53 of the previous year.
864  */
865  int jan1 = crm_time_january1_weekday(year);
866 
867  crm_trace("Got year %d (Jan 1 = %d), week %d, and day %d from date string '%s'",
868  year, jan1, week, day, date_str);
869 
870  dt->years = year;
871  crm_time_add_days(dt, (week - 1) * 7);
872 
873  if (jan1 <= 4) {
874  crm_time_add_days(dt, 1 - jan1);
875  } else {
876  crm_time_add_days(dt, 8 - jan1);
877  }
878 
879  crm_time_add_days(dt, day);
880  }
881  goto parse_time;
882  }
883 
884  crm_err("'%s' is not a valid ISO 8601 date/time specification", date_str);
885  goto invalid;
886 
887  parse_time:
888 
889  if (time_s == NULL) {
890  time_s = date_str + strspn(date_str, "0123456789-W");
891  if ((time_s[0] == ' ') || (time_s[0] == 'T')) {
892  ++time_s;
893  } else {
894  time_s = NULL;
895  }
896  }
897  if ((time_s != NULL) && (crm_time_parse(time_s, dt) == FALSE)) {
898  goto invalid;
899  }
900 
902  if (crm_time_check(dt) == FALSE) {
903  crm_err("'%s' is not a valid ISO 8601 date/time specification",
904  date_str);
905  goto invalid;
906  }
907  return dt;
908 
909 invalid:
910  crm_time_free(dt);
911  errno = EINVAL;
912  return NULL;
913 }
914 
915 // Parse an ISO 8601 numeric value and return number of characters consumed
916 // @TODO This cannot handle >INT_MAX int values
917 // @TODO Fractions appear to be not working
918 // @TODO Error out on invalid specifications
919 static int
920 parse_int(const char *str, int field_width, int upper_bound, int *result)
921 {
922  int lpc = 0;
923  int offset = 0;
924  int intermediate = 0;
925  gboolean fraction = FALSE;
926  gboolean negate = FALSE;
927 
928  *result = 0;
929  if (*str == '\0') {
930  return 0;
931  }
932 
933  if (str[offset] == 'T') {
934  offset++;
935  }
936 
937  if (str[offset] == '.' || str[offset] == ',') {
938  fraction = TRUE;
939  field_width = -1;
940  offset++;
941  } else if (str[offset] == '-') {
942  negate = TRUE;
943  offset++;
944  } else if (str[offset] == '+' || str[offset] == ':') {
945  offset++;
946  }
947 
948  for (; (fraction || lpc < field_width) && isdigit((int)str[offset]); lpc++) {
949  if (fraction) {
950  intermediate = (str[offset] - '0') / (10 ^ lpc);
951  } else {
952  *result *= 10;
953  intermediate = str[offset] - '0';
954  }
955  *result += intermediate;
956  offset++;
957  }
958  if (fraction) {
959  *result = (int)(*result * upper_bound);
960 
961  } else if (upper_bound > 0 && *result > upper_bound) {
962  *result = upper_bound;
963  }
964  if (negate) {
965  *result = 0 - *result;
966  }
967  if (lpc > 0) {
968  crm_trace("Found int: %d. Stopped at str[%d]='%c'", *result, lpc, str[lpc]);
969  return offset;
970  }
971  return 0;
972 }
973 
985 crm_time_t *
986 crm_time_parse_duration(const char *period_s)
987 {
988  gboolean is_time = FALSE;
989  crm_time_t *diff = NULL;
990 
991  if (pcmk__str_empty(period_s)) {
992  crm_err("No ISO 8601 time duration given");
993  goto invalid;
994  }
995  if (period_s[0] != 'P') {
996  crm_err("'%s' is not a valid ISO 8601 time duration "
997  "because it does not start with a 'P'", period_s);
998  goto invalid;
999  }
1000  if ((period_s[1] == '\0') || isspace(period_s[1])) {
1001  crm_err("'%s' is not a valid ISO 8601 time duration "
1002  "because nothing follows 'P'", period_s);
1003  goto invalid;
1004  }
1005 
1006  diff = crm_time_new_undefined();
1007  diff->duration = TRUE;
1008 
1009  for (const char *current = period_s + 1;
1010  current[0] && (current[0] != '/') && !isspace(current[0]);
1011  ++current) {
1012 
1013  int an_int = 0, rc;
1014 
1015  if (current[0] == 'T') {
1016  /* A 'T' separates year/month/day from hour/minute/seconds. We don't
1017  * require it strictly, but just use it to differentiate month from
1018  * minutes.
1019  */
1020  is_time = TRUE;
1021  continue;
1022  }
1023 
1024  // An integer must be next
1025  rc = parse_int(current, 10, 0, &an_int);
1026  if (rc == 0) {
1027  crm_err("'%s' is not a valid ISO 8601 time duration "
1028  "because no integer at '%s'", period_s, current);
1029  goto invalid;
1030  }
1031  current += rc;
1032 
1033  // A time unit must be next (we're not strict about the order)
1034  switch (current[0]) {
1035  case 'Y':
1036  diff->years = an_int;
1037  break;
1038  case 'M':
1039  if (is_time) {
1040  /* Minutes */
1041  diff->seconds += an_int * 60;
1042  } else {
1043  diff->months = an_int;
1044  }
1045  break;
1046  case 'W':
1047  diff->days += an_int * 7;
1048  break;
1049  case 'D':
1050  diff->days += an_int;
1051  break;
1052  case 'H':
1053  diff->seconds += an_int * HOUR_SECONDS;
1054  break;
1055  case 'S':
1056  diff->seconds += an_int;
1057  break;
1058  case '\0':
1059  crm_err("'%s' is not a valid ISO 8601 time duration "
1060  "because no units after %d", period_s, an_int);
1061  goto invalid;
1062  default:
1063  crm_err("'%s' is not a valid ISO 8601 time duration "
1064  "because '%c' is not a valid time unit",
1065  period_s, current[0]);
1066  goto invalid;
1067  }
1068  }
1069 
1070  if (!crm_time_is_defined(diff)) {
1071  crm_err("'%s' is not a valid ISO 8601 time duration "
1072  "because no amounts and units given", period_s);
1073  goto invalid;
1074  }
1075  return diff;
1076 
1077 invalid:
1078  crm_time_free(diff);
1079  errno = EINVAL;
1080  return NULL;
1081 }
1082 
1094 crm_time_parse_period(const char *period_str)
1095 {
1096  const char *original = period_str;
1097  crm_time_period_t *period = NULL;
1098 
1099  if (pcmk__str_empty(period_str)) {
1100  crm_err("No ISO 8601 time period given");
1101  goto invalid;
1102  }
1103 
1104  tzset();
1105  period = calloc(1, sizeof(crm_time_period_t));
1106  CRM_ASSERT(period != NULL);
1107 
1108  if (period_str[0] == 'P') {
1109  period->diff = crm_time_parse_duration(period_str);
1110  if (period->diff == NULL) {
1111  goto error;
1112  }
1113  } else {
1114  period->start = parse_date(period_str);
1115  if (period->start == NULL) {
1116  goto error;
1117  }
1118  }
1119 
1120  period_str = strstr(original, "/");
1121  if (period_str) {
1122  ++period_str;
1123  if (period_str[0] == 'P') {
1124  if (period->diff != NULL) {
1125  crm_err("'%s' is not a valid ISO 8601 time period "
1126  "because it has two durations",
1127  original);
1128  goto invalid;
1129  }
1130  period->diff = crm_time_parse_duration(period_str);
1131  if (period->diff == NULL) {
1132  goto error;
1133  }
1134  } else {
1135  period->end = parse_date(period_str);
1136  if (period->end == NULL) {
1137  goto error;
1138  }
1139  }
1140 
1141  } else if (period->diff != NULL) {
1142  // Only duration given, assume start is now
1143  period->start = crm_time_new(NULL);
1144 
1145  } else {
1146  // Only start given
1147  crm_err("'%s' is not a valid ISO 8601 time period "
1148  "because it has no duration or ending time",
1149  original);
1150  goto invalid;
1151  }
1152 
1153  if (period->start == NULL) {
1154  period->start = crm_time_subtract(period->end, period->diff);
1155 
1156  } else if (period->end == NULL) {
1157  period->end = crm_time_add(period->start, period->diff);
1158  }
1159 
1160  if (crm_time_check(period->start) == FALSE) {
1161  crm_err("'%s' is not a valid ISO 8601 time period "
1162  "because the start is invalid", period_str);
1163  goto invalid;
1164  }
1165  if (crm_time_check(period->end) == FALSE) {
1166  crm_err("'%s' is not a valid ISO 8601 time period "
1167  "because the end is invalid", period_str);
1168  goto invalid;
1169  }
1170  return period;
1171 
1172 invalid:
1173  errno = EINVAL;
1174 error:
1175  crm_time_free_period(period);
1176  return NULL;
1177 }
1178 
1184 void
1186 {
1187  if (period) {
1188  crm_time_free(period->start);
1189  crm_time_free(period->end);
1190  crm_time_free(period->diff);
1191  free(period);
1192  }
1193 }
1194 
1195 void
1197 {
1198  crm_trace("target=%p, source=%p", target, source);
1199 
1200  CRM_CHECK(target != NULL && source != NULL, return);
1201 
1202  target->years = source->years;
1203  target->days = source->days;
1204  target->months = source->months; /* Only for durations */
1205  target->seconds = source->seconds;
1206  target->offset = source->offset;
1207 
1208  crm_time_log(LOG_TRACE, "source", source,
1210  crm_time_log(LOG_TRACE, "target", target,
1212 }
1213 
1214 static void
1215 ha_set_tm_time(crm_time_t *target, const struct tm *source)
1216 {
1217  int h_offset = 0;
1218  int m_offset = 0;
1219 
1220  /* Ensure target is fully initialized */
1221  target->years = 0;
1222  target->months = 0;
1223  target->days = 0;
1224  target->seconds = 0;
1225  target->offset = 0;
1226  target->duration = FALSE;
1227 
1228  if (source->tm_year > 0) {
1229  /* years since 1900 */
1230  target->years = 1900 + source->tm_year;
1231  }
1232 
1233  if (source->tm_yday >= 0) {
1234  /* days since January 1 [0-365] */
1235  target->days = 1 + source->tm_yday;
1236  }
1237 
1238  if (source->tm_hour >= 0) {
1239  target->seconds += HOUR_SECONDS * source->tm_hour;
1240  }
1241  if (source->tm_min >= 0) {
1242  target->seconds += 60 * source->tm_min;
1243  }
1244  if (source->tm_sec >= 0) {
1245  target->seconds += source->tm_sec;
1246  }
1247 
1248  /* tm_gmtoff == offset from UTC in seconds */
1249  h_offset = GMTOFF(source) / HOUR_SECONDS;
1250  m_offset = (GMTOFF(source) - (HOUR_SECONDS * h_offset)) / 60;
1251  crm_trace("Time offset is %lds (%.2d:%.2d)",
1252  GMTOFF(source), h_offset, m_offset);
1253 
1254  target->offset += HOUR_SECONDS * h_offset;
1255  target->offset += 60 * m_offset;
1256 }
1257 
1258 void
1259 crm_time_set_timet(crm_time_t *target, const time_t *source)
1260 {
1261  ha_set_tm_time(target, localtime(source));
1262 }
1263 
1264 crm_time_t *
1266 {
1268 
1269  crm_time_set(target, source);
1270  return target;
1271 }
1272 
1273 crm_time_t *
1274 crm_time_add(const crm_time_t *dt, const crm_time_t *value)
1275 {
1276  crm_time_t *utc = NULL;
1277  crm_time_t *answer = NULL;
1278 
1279  if ((dt == NULL) || (value == NULL)) {
1280  errno = EINVAL;
1281  return NULL;
1282  }
1283 
1284  answer = pcmk_copy_time(dt);
1285 
1286  utc = crm_get_utc_time(value);
1287  if (utc == NULL) {
1288  crm_time_free(answer);
1289  return NULL;
1290  }
1291 
1292  answer->years += utc->years;
1293  crm_time_add_months(answer, utc->months);
1294  crm_time_add_days(answer, utc->days);
1295  crm_time_add_seconds(answer, utc->seconds);
1296 
1297  crm_time_free(utc);
1298  return answer;
1299 }
1300 
1301 crm_time_t *
1303 {
1304  crm_time_t *utc = NULL;
1305  crm_time_t *answer = NULL;
1306 
1307  if ((dt == NULL) || (value == NULL)) {
1308  errno = EINVAL;
1309  return NULL;
1310  }
1311 
1312  utc = crm_get_utc_time(value);
1313  if (utc == NULL) {
1314  return NULL;
1315  }
1316 
1317  answer = crm_get_utc_time(dt);
1318  if (answer == NULL) {
1319  crm_time_free(utc);
1320  return NULL;
1321  }
1322  answer->duration = TRUE;
1323 
1324  answer->years -= utc->years;
1325  if(utc->months != 0) {
1326  crm_time_add_months(answer, -utc->months);
1327  }
1328  crm_time_add_days(answer, -utc->days);
1329  crm_time_add_seconds(answer, -utc->seconds);
1330 
1331  crm_time_free(utc);
1332  return answer;
1333 }
1334 
1335 crm_time_t *
1336 crm_time_subtract(const crm_time_t *dt, const crm_time_t *value)
1337 {
1338  crm_time_t *utc = NULL;
1339  crm_time_t *answer = NULL;
1340 
1341  if ((dt == NULL) || (value == NULL)) {
1342  errno = EINVAL;
1343  return NULL;
1344  }
1345 
1346  utc = crm_get_utc_time(value);
1347  if (utc == NULL) {
1348  return NULL;
1349  }
1350 
1351  answer = pcmk_copy_time(dt);
1352  answer->years -= utc->years;
1353  if(utc->months != 0) {
1354  crm_time_add_months(answer, -utc->months);
1355  }
1356  crm_time_add_days(answer, -utc->days);
1357  crm_time_add_seconds(answer, -utc->seconds);
1358  crm_time_free(utc);
1359 
1360  return answer;
1361 }
1362 
1370 bool
1372 {
1373  return (dt != NULL)
1374  && (dt->days > 0) && (dt->days <= year_days(dt->years))
1375  && (dt->seconds >= 0) && (dt->seconds < DAY_SECONDS);
1376 }
1377 
1378 #define do_cmp_field(l, r, field) \
1379  if(rc == 0) { \
1380  if(l->field > r->field) { \
1381  crm_trace("%s: %d > %d", \
1382  #field, l->field, r->field); \
1383  rc = 1; \
1384  } else if(l->field < r->field) { \
1385  crm_trace("%s: %d < %d", \
1386  #field, l->field, r->field); \
1387  rc = -1; \
1388  } \
1389  }
1390 
1391 int
1393 {
1394  int rc = 0;
1395  crm_time_t *t1 = crm_get_utc_time(a);
1396  crm_time_t *t2 = crm_get_utc_time(b);
1397 
1398  if ((t1 == NULL) && (t2 == NULL)) {
1399  rc = 0;
1400  } else if (t1 == NULL) {
1401  rc = -1;
1402  } else if (t2 == NULL) {
1403  rc = 1;
1404  } else {
1405  do_cmp_field(t1, t2, years);
1406  do_cmp_field(t1, t2, days);
1407  do_cmp_field(t1, t2, seconds);
1408  }
1409 
1410  crm_time_free(t1);
1411  crm_time_free(t2);
1412  return rc;
1413 }
1414 
1421 void
1423 {
1424  int days = 0;
1425 
1426  crm_trace("Adding %d seconds to %d (max=%d)",
1427  extra, a_time->seconds, DAY_SECONDS);
1428  a_time->seconds += extra;
1429  days = a_time->seconds / DAY_SECONDS;
1430  a_time->seconds %= DAY_SECONDS;
1431 
1432  // Don't have negative seconds
1433  if (a_time->seconds < 0) {
1434  a_time->seconds += DAY_SECONDS;
1435  --days;
1436  }
1437 
1438  crm_time_add_days(a_time, days);
1439 }
1440 
1441 void
1442 crm_time_add_days(crm_time_t * a_time, int extra)
1443 {
1444  int lower_bound = 1;
1445  int ydays = crm_time_leapyear(a_time->years) ? 366 : 365;
1446 
1447  crm_trace("Adding %d days to %.4d-%.3d", extra, a_time->years, a_time->days);
1448 
1449  a_time->days += extra;
1450  while (a_time->days > ydays) {
1451  a_time->years++;
1452  a_time->days -= ydays;
1453  ydays = crm_time_leapyear(a_time->years) ? 366 : 365;
1454  }
1455 
1456  if(a_time->duration) {
1457  lower_bound = 0;
1458  }
1459 
1460  while (a_time->days < lower_bound) {
1461  a_time->years--;
1462  a_time->days += crm_time_leapyear(a_time->years) ? 366 : 365;
1463  }
1464 }
1465 
1466 void
1467 crm_time_add_months(crm_time_t * a_time, int extra)
1468 {
1469  int lpc;
1470  uint32_t y, m, d, dmax;
1471 
1472  crm_time_get_gregorian(a_time, &y, &m, &d);
1473  crm_trace("Adding %d months to %.4d-%.2d-%.2d", extra, y, m, d);
1474 
1475  if (extra > 0) {
1476  for (lpc = extra; lpc > 0; lpc--) {
1477  m++;
1478  if (m == 13) {
1479  m = 1;
1480  y++;
1481  }
1482  }
1483  } else {
1484  for (lpc = -extra; lpc > 0; lpc--) {
1485  m--;
1486  if (m == 0) {
1487  m = 12;
1488  y--;
1489  }
1490  }
1491  }
1492 
1493  dmax = crm_time_days_in_month(m, y);
1494  if (dmax < d) {
1495  /* Preserve day-of-month unless the month doesn't have enough days */
1496  d = dmax;
1497  }
1498 
1499  crm_trace("Calculated %.4d-%.2d-%.2d", y, m, d);
1500 
1501  a_time->years = y;
1502  a_time->days = get_ordinal_days(y, m, d);
1503 
1504  crm_time_get_gregorian(a_time, &y, &m, &d);
1505  crm_trace("Got %.4d-%.2d-%.2d", y, m, d);
1506 }
1507 
1508 void
1509 crm_time_add_minutes(crm_time_t * a_time, int extra)
1510 {
1511  crm_time_add_seconds(a_time, extra * 60);
1512 }
1513 
1514 void
1515 crm_time_add_hours(crm_time_t * a_time, int extra)
1516 {
1517  crm_time_add_seconds(a_time, extra * HOUR_SECONDS);
1518 }
1519 
1520 void
1521 crm_time_add_weeks(crm_time_t * a_time, int extra)
1522 {
1523  crm_time_add_days(a_time, extra * 7);
1524 }
1525 
1526 void
1527 crm_time_add_years(crm_time_t * a_time, int extra)
1528 {
1529  a_time->years += extra;
1530 }
1531 
1532 static void
1533 ha_get_tm_time(struct tm *target, const crm_time_t *source)
1534 {
1535  *target = (struct tm) {
1536  .tm_year = source->years - 1900,
1537  .tm_mday = source->days,
1538  .tm_sec = source->seconds % 60,
1539  .tm_min = ( source->seconds / 60 ) % 60,
1540  .tm_hour = source->seconds / HOUR_SECONDS,
1541  .tm_isdst = -1, /* don't adjust */
1542 
1543 #if defined(HAVE_STRUCT_TM_TM_GMTOFF)
1544  .tm_gmtoff = source->offset
1545 #endif
1546  };
1547  mktime(target);
1548 }
1549 
1550 /* The high-resolution variant of time object was added to meet an immediate
1551  * need, and is kept internal API.
1552  *
1553  * @TODO The long-term goal is to come up with a clean, unified design for a
1554  * time type (or types) that meets all the various needs, to replace
1555  * crm_time_t, pcmk__time_hr_t, and struct timespec (in lrmd_cmd_t).
1556  * Using glib's GDateTime is a possibility (if we are willing to require
1557  * glib >= 2.26).
1558  */
1559 
1562 {
1563  pcmk__time_hr_t *hr_dt = NULL;
1564 
1565  if (dt) {
1566  hr_dt = target?target:calloc(1, sizeof(pcmk__time_hr_t));
1567  CRM_ASSERT(hr_dt != NULL);
1568  *hr_dt = (pcmk__time_hr_t) {
1569  .years = dt->years,
1570  .months = dt->months,
1571  .days = dt->days,
1572  .seconds = dt->seconds,
1573  .offset = dt->offset,
1574  .duration = dt->duration
1575  };
1576  }
1577 
1578  return hr_dt;
1579 }
1580 
1581 void
1583 {
1584  CRM_ASSERT((hr_dt) && (target));
1585  *target = (crm_time_t) {
1586  .years = hr_dt->years,
1587  .months = hr_dt->months,
1588  .days = hr_dt->days,
1589  .seconds = hr_dt->seconds,
1590  .offset = hr_dt->offset,
1591  .duration = hr_dt->duration
1592  };
1593 }
1594 
1604 pcmk__time_hr_now(time_t *epoch)
1605 {
1606  struct timespec tv;
1607  crm_time_t dt;
1608  pcmk__time_hr_t *hr;
1609 
1610  qb_util_timespec_from_epoch_get(&tv);
1611  if (epoch != NULL) {
1612  *epoch = tv.tv_sec;
1613  }
1614  crm_time_set_timet(&dt, &(tv.tv_sec));
1615  hr = pcmk__time_hr_convert(NULL, &dt);
1616  if (hr != NULL) {
1617  hr->useconds = tv.tv_nsec / QB_TIME_NS_IN_USEC;
1618  }
1619  return hr;
1620 }
1621 
1623 pcmk__time_hr_new(const char *date_time)
1624 {
1625  pcmk__time_hr_t *hr_dt = NULL;
1626 
1627  if (date_time == NULL) {
1628  hr_dt = pcmk__time_hr_now(NULL);
1629  } else {
1630  crm_time_t *dt;
1631 
1632  dt = parse_date(date_time);
1633  hr_dt = pcmk__time_hr_convert(NULL, dt);
1634  crm_time_free(dt);
1635  }
1636  return hr_dt;
1637 }
1638 
1639 void
1641 {
1642  free(hr_dt);
1643 }
1644 
1645 char *
1646 pcmk__time_format_hr(const char *format, const pcmk__time_hr_t *hr_dt)
1647 {
1648  const char *mark_s;
1649  int max = 128, scanned_pos = 0, printed_pos = 0, fmt_pos = 0,
1650  date_len = 0, nano_digits = 0;
1651  char nano_s[10], date_s[max+1], nanofmt_s[5] = "%", *tmp_fmt_s;
1652  struct tm tm;
1653  crm_time_t dt;
1654 
1655  if (!format) {
1656  return NULL;
1657  }
1658  pcmk__time_set_hr_dt(&dt, hr_dt);
1659  ha_get_tm_time(&tm, &dt);
1660  sprintf(nano_s, "%06d000", hr_dt->useconds);
1661 
1662  while ((format[scanned_pos]) != '\0') {
1663  mark_s = strchr(&format[scanned_pos], '%');
1664  if (mark_s) {
1665  int fmt_len = 1;
1666 
1667  fmt_pos = mark_s - format;
1668  while ((format[fmt_pos+fmt_len] != '\0') &&
1669  (format[fmt_pos+fmt_len] >= '0') &&
1670  (format[fmt_pos+fmt_len] <= '9')) {
1671  fmt_len++;
1672  }
1673  scanned_pos = fmt_pos + fmt_len + 1;
1674  if (format[fmt_pos+fmt_len] == 'N') {
1675  nano_digits = atoi(&format[fmt_pos+1]);
1676  nano_digits = (nano_digits > 6)?6:nano_digits;
1677  nano_digits = (nano_digits < 0)?0:nano_digits;
1678  sprintf(&nanofmt_s[1], ".%ds", nano_digits);
1679  } else {
1680  if (format[scanned_pos] != '\0') {
1681  continue;
1682  }
1683  fmt_pos = scanned_pos; /* print till end */
1684  }
1685  } else {
1686  scanned_pos = strlen(format);
1687  fmt_pos = scanned_pos; /* print till end */
1688  }
1689  tmp_fmt_s = strndup(&format[printed_pos], fmt_pos - printed_pos);
1690 #ifdef HAVE_FORMAT_NONLITERAL
1691 #pragma GCC diagnostic push
1692 #pragma GCC diagnostic ignored "-Wformat-nonliteral"
1693 #endif
1694  date_len += strftime(&date_s[date_len], max-date_len, tmp_fmt_s, &tm);
1695 #ifdef HAVE_FORMAT_NONLITERAL
1696 #pragma GCC diagnostic pop
1697 #endif
1698  printed_pos = scanned_pos;
1699  free(tmp_fmt_s);
1700  if (nano_digits) {
1701 #ifdef HAVE_FORMAT_NONLITERAL
1702 #pragma GCC diagnostic push
1703 #pragma GCC diagnostic ignored "-Wformat-nonliteral"
1704 #endif
1705  date_len += snprintf(&date_s[date_len], max-date_len,
1706  nanofmt_s, nano_s);
1707 #ifdef HAVE_FORMAT_NONLITERAL
1708 #pragma GCC diagnostic pop
1709 #endif
1710  nano_digits = 0;
1711  }
1712  }
1713 
1714  return (date_len == 0)?NULL:strdup(date_s);
1715 }
1716 
1729 const char *
1730 pcmk__epoch2str(const time_t *when)
1731 {
1732  char *since_epoch = NULL;
1733 
1734  if (when == NULL) {
1735  time_t a_time = time(NULL);
1736 
1737  if (a_time == (time_t) -1) {
1738  return NULL;
1739  } else {
1740  since_epoch = ctime(&a_time);
1741  }
1742  } else {
1743  since_epoch = ctime(when);
1744  }
1745 
1746  if (since_epoch == NULL) {
1747  return NULL;
1748  } else {
1749  return pcmk__trim(since_epoch);
1750  }
1751 }
1752 
1764 const char *
1765 pcmk__readable_interval(guint interval_ms)
1766 {
1767 #define MS_IN_S (1000)
1768 #define MS_IN_M (MS_IN_S * 60)
1769 #define MS_IN_H (MS_IN_M * 60)
1770 #define MS_IN_D (MS_IN_H * 24)
1771 #define MAXSTR sizeof("..d..h..m..s...ms")
1772  static char str[MAXSTR] = { '\0', };
1773  int offset = 0;
1774 
1775  if (interval_ms > MS_IN_D) {
1776  offset += snprintf(str + offset, MAXSTR - offset, "%ud",
1777  interval_ms / MS_IN_D);
1778  interval_ms -= (interval_ms / MS_IN_D) * MS_IN_D;
1779  }
1780  if (interval_ms > MS_IN_H) {
1781  offset += snprintf(str + offset, MAXSTR - offset, "%uh",
1782  interval_ms / MS_IN_H);
1783  interval_ms -= (interval_ms / MS_IN_H) * MS_IN_H;
1784  }
1785  if (interval_ms > MS_IN_M) {
1786  offset += snprintf(str + offset, MAXSTR - offset, "%um",
1787  interval_ms / MS_IN_M);
1788  interval_ms -= (interval_ms / MS_IN_M) * MS_IN_M;
1789  }
1790 
1791  // Ns, N.NNNs, or NNNms
1792  if (interval_ms > MS_IN_S) {
1793  offset += snprintf(str + offset, MAXSTR - offset, "%u",
1794  interval_ms / MS_IN_S);
1795  interval_ms -= (interval_ms / MS_IN_S) * MS_IN_S;
1796  if (interval_ms > 0) {
1797  offset += snprintf(str + offset, MAXSTR - offset, ".%03u",
1798  interval_ms);
1799  }
1800  (void) snprintf(str + offset, MAXSTR - offset, "s");
1801 
1802  } else if (interval_ms > 0) {
1803  (void) snprintf(str + offset, MAXSTR - offset, "%ums", interval_ms);
1804 
1805  } else if (str[0] == '\0') {
1806  strcpy(str, "0s");
1807  }
1808  return str;
1809 }
int crm_time_get_gregorian(const crm_time_t *dt, uint32_t *y, uint32_t *m, uint32_t *d)
Definition: iso8601.c:358
#define LOG_TRACE
Definition: logging.h:37
#define CRM_CHECK(expr, failure_action)
Definition: logging.h:227
void crm_time_set_timet(crm_time_t *target, const time_t *source)
Definition: iso8601.c:1259
A dumping ground.
bool crm_time_leapyear(int year)
Definition: iso8601.c:226
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:251
#define crm_time_epoch
Definition: iso8601.h:75
void pcmk__time_hr_free(pcmk__time_hr_t *hr_dt)
Definition: iso8601.c:1640
#define HOUR_SECONDS
Definition: iso8601.c:46
#define crm_time_log_timeofday
Definition: iso8601.h:68
crm_time_t * crm_time_new(const char *date_time)
Definition: iso8601.c:92
#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:1371
#define crm_time_ordinal
Definition: iso8601.h:72
void crm_time_add_minutes(crm_time_t *a_time, int extra)
Definition: iso8601.c:1509
#define DATE_MAX
Definition: iso8601.c:453
crm_time_t * crm_time_calculate_duration(const crm_time_t *dt, const crm_time_t *value)
Definition: iso8601.c:1302
const char * pcmk__readable_interval(guint interval_ms)
Definition: iso8601.c:1765
void crm_time_add_weeks(crm_time_t *a_time, int extra)
Definition: iso8601.c:1521
int crm_time_get_timezone(const crm_time_t *dt, uint32_t *h, uint32_t *m)
Definition: iso8601.c:300
long long crm_time_get_seconds_since_epoch(const crm_time_t *dt)
Definition: iso8601.c:352
void crm_time_set(crm_time_t *target, const crm_time_t *source)
Definition: iso8601.c:1196
int crm_time_compare(const crm_time_t *a, const crm_time_t *b)
Definition: iso8601.c:1392
void crm_time_free(crm_time_t *dt)
Definition: iso8601.c:140
#define do_cmp_field(l, r, field)
Definition: iso8601.c:1378
#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:282
crm_time_t * crm_time_add(const crm_time_t *dt, const crm_time_t *value)
Definition: iso8601.c:1274
#define crm_time_log_duration
Definition: iso8601.h:70
char * strndup(const char *str, size_t len)
char * crm_time_as_string(const crm_time_t *date_time, int flags)
Definition: iso8601.c:500
pcmk__time_hr_t * pcmk__time_hr_now(time_t *epoch)
Definition: iso8601.c:1604
long long crm_time_get_seconds(const crm_time_t *dt)
Definition: iso8601.c:309
pcmk__time_hr_t * pcmk__time_hr_new(const char *date_time)
Definition: iso8601.c:1623
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:1582
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:1094
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:1422
crm_time_t * pcmk_copy_time(const crm_time_t *source)
Definition: iso8601.c:1265
crm_time_t * start
Definition: iso8601.h:35
void crm_time_add_days(crm_time_t *a_time, int extra)
Definition: iso8601.c:1442
#define crm_trace(fmt, args...)
Definition: logging.h:365
const char * pcmk__epoch2str(const time_t *when)
Definition: iso8601.c:1730
#define pcmk_is_set(g, f)
Convenience alias for pcmk_all_flags_set(), to check single flag.
Definition: util.h:121
int crm_time_get_ordinal(const crm_time_t *dt, uint32_t *y, uint32_t *d)
Definition: iso8601.c:391
crm_time_t * crm_time_parse_duration(const char *period_s)
Parse a time duration from an ISO 8601 duration specification.
Definition: iso8601.c:986
crm_time_t * end
Definition: iso8601.h:36
#define GMTOFF(tm)
Definition: iso8601.c:43
#define MS_IN_M
ISO_8601 Date handling.
#define DAY_SECONDS
Definition: iso8601.c:47
int crm_time_get_timeofday(const crm_time_t *dt, uint32_t *h, uint32_t *m, uint32_t *s)
Definition: iso8601.c:292
void crm_time_add_hours(crm_time_t *a_time, int extra)
Definition: iso8601.c:1515
#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:214
#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:116
#define EPOCH_SECONDS
Definition: iso8601.c:350
#define MS_IN_S
char * pcmk__time_format_hr(const char *format, const pcmk__time_hr_t *hr_dt)
Definition: iso8601.c:1646
crm_time_t * crm_time_subtract(const crm_time_t *dt, const crm_time_t *value)
Definition: iso8601.c:1336
int crm_time_get_isoweek(const crm_time_t *dt, uint32_t *y, uint32_t *w, uint32_t *d)
Definition: iso8601.c:399
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:1527
bool crm_time_is_defined(const crm_time_t *t)
Check whether a time object has been initialized yet.
Definition: iso8601.c:132
#define HAVE_STRUCT_TM_TM_GMTOFF
Definition: config.h:389
#define MAXSTR
pcmk__action_result_t result
Definition: pcmk_fence.c:35
#define crm_err(fmt, args...)
Definition: logging.h:359
#define CRM_ASSERT(expr)
Definition: results.h:42
void crm_time_add_months(crm_time_t *a_time, int extra)
Definition: iso8601.c:1467
int crm_time_weeks_in_year(int year)
Definition: iso8601.c:181
int crm_time_january1_weekday(int year)
Definition: iso8601.c:168
#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:1561
char * pcmk__trim(char *str)
Definition: strings.c:456
#define crm_time_weeks
Definition: iso8601.h:73
#define LOG_STDOUT
Definition: logging.h:42
void crm_time_free_period(crm_time_period_t *period)
Free a dynamically allocated time period object.
Definition: iso8601.c:1185
#define crm_time_log_date
Definition: iso8601.h:67
uint64_t flags
Definition: remote.c:215