root/lib/common/iso8601.c

/* [previous][next][first][last][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. crm_get_utc_time
  2. crm_time_new
  3. crm_time_new_undefined
  4. crm_time_is_defined
  5. crm_time_free
  6. year_days
  7. crm_time_january1_weekday
  8. crm_time_weeks_in_year
  9. crm_time_days_in_month
  10. crm_time_leapyear
  11. get_ordinal_days
  12. crm_time_log_alias
  13. crm_time_get_sec
  14. crm_time_get_timeofday
  15. crm_time_get_timezone
  16. crm_time_get_seconds
  17. crm_time_get_seconds_since_epoch
  18. crm_time_get_gregorian
  19. crm_time_get_ordinal
  20. crm_time_get_isoweek
  21. crm_duration_as_string
  22. crm_time_as_string
  23. crm_time_parse_sec
  24. crm_time_parse_offset
  25. crm_time_parse
  26. parse_date
  27. parse_int
  28. crm_time_parse_duration
  29. crm_time_parse_period
  30. crm_time_free_period
  31. crm_time_set
  32. ha_set_tm_time
  33. crm_time_set_timet
  34. pcmk_copy_time
  35. crm_time_add
  36. crm_time_calculate_duration
  37. crm_time_subtract
  38. crm_time_check
  39. crm_time_compare
  40. crm_time_add_seconds
  41. crm_time_add_days
  42. crm_time_add_months
  43. crm_time_add_minutes
  44. crm_time_add_hours
  45. crm_time_add_weeks
  46. crm_time_add_years
  47. ha_get_tm_time
  48. pcmk__time_hr_convert
  49. pcmk__time_set_hr_dt
  50. pcmk__time_hr_now
  51. pcmk__time_hr_new
  52. pcmk__time_hr_free
  53. pcmk__time_format_hr
  54. pcmk__epoch2str
  55. pcmk__readable_interval

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

/* [previous][next][first][last][top][bottom][index][help] */