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