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