LCOV - code coverage report
Current view: top level - common - iso8601.c (source / functions) Hit Total Coverage
Test: Pacemaker code coverage Lines: 123 954 12.9 %
Date: 2024-05-07 11:09:47 Functions: 8 63 12.7 %

          Line data    Source code
       1             : /*
       2             :  * Copyright 2005-2024 the Pacemaker project contributors
       3             :  *
       4             :  * The version control history for this file may have further details.
       5             :  *
       6             :  * This source code is licensed under the GNU Lesser General Public License
       7             :  * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
       8             :  */
       9             : 
      10             : /*
      11             :  * References:
      12             :  *      https://en.wikipedia.org/wiki/ISO_8601
      13             :  *      http://www.staff.science.uu.nl/~gent0113/calendar/isocalendar.htm
      14             :  */
      15             : 
      16             : #include <crm_internal.h>
      17             : #include <crm/crm.h>
      18             : #include <time.h>
      19             : #include <ctype.h>
      20             : #include <inttypes.h>
      21             : #include <limits.h>         // INT_MIN, INT_MAX
      22             : #include <string.h>
      23             : #include <stdbool.h>
      24             : #include <crm/common/iso8601.h>
      25             : #include <crm/common/iso8601_internal.h>
      26             : #include "crmcommon_private.h"
      27             : 
      28             : /*
      29             :  * Andrew's code was originally written for OSes whose "struct tm" contains:
      30             :  *      long tm_gmtoff;         :: Seconds east of UTC
      31             :  *      const char *tm_zone;    :: Timezone abbreviation
      32             :  * Some OSes lack these, instead having:
      33             :  *      time_t (or long) timezone;
      34             :                 :: "difference between UTC and local standard time"
      35             :  *      char *tzname[2] = { "...", "..." };
      36             :  * I (David Lee) confess to not understanding the details.  So my attempted
      37             :  * generalisations for where their use is necessary may be flawed.
      38             :  *
      39             :  * 1. Does "difference between ..." subtract the same or opposite way?
      40             :  * 2. Should it use "altzone" instead of "timezone"?
      41             :  * 3. Should it use tzname[0] or tzname[1]?  Interaction with timezone/altzone?
      42             :  */
      43             : #if defined(HAVE_STRUCT_TM_TM_GMTOFF)
      44             : #  define GMTOFF(tm) ((tm)->tm_gmtoff)
      45             : #else
      46             : /* Note: extern variable; macro argument not actually used.  */
      47             : #  define GMTOFF(tm) (-timezone+daylight)
      48             : #endif
      49             : 
      50             : #define HOUR_SECONDS    (60 * 60)
      51             : #define DAY_SECONDS     (HOUR_SECONDS * 24)
      52             : 
      53             : /*!
      54             :  * \internal
      55             :  * \brief Validate a seconds/microseconds tuple
      56             :  *
      57             :  * The microseconds value must be in the correct range, and if both are nonzero
      58             :  * they must have the same sign.
      59             :  *
      60             :  * \param[in] sec   Seconds
      61             :  * \param[in] usec  Microseconds
      62             :  *
      63             :  * \return true if the seconds/microseconds tuple is valid, or false otherwise
      64             :  */
      65             : #define valid_sec_usec(sec, usec)               \
      66             :         ((QB_ABS(usec) < QB_TIME_US_IN_SEC)     \
      67             :          && (((sec) == 0) || ((usec) == 0) || (((sec) < 0) == ((usec) < 0))))
      68             : 
      69             : // A date/time or duration
      70             : struct crm_time_s {
      71             :     int years;      // Calendar year (date/time) or number of years (duration)
      72             :     int months;     // Number of months (duration only)
      73             :     int days;       // Ordinal day of year (date/time) or number of days (duration)
      74             :     int seconds;    // Seconds of day (date/time) or number of seconds (duration)
      75             :     int offset;     // Seconds offset from UTC (date/time only)
      76             :     bool duration;  // True if duration
      77             : };
      78             : 
      79             : static crm_time_t *parse_date(const char *date_str);
      80             : 
      81             : static crm_time_t *
      82         652 : crm_get_utc_time(const crm_time_t *dt)
      83             : {
      84         652 :     crm_time_t *utc = NULL;
      85             : 
      86         652 :     if (dt == NULL) {
      87           0 :         errno = EINVAL;
      88           0 :         return NULL;
      89             :     }
      90             : 
      91         652 :     utc = crm_time_new_undefined();
      92         652 :     utc->years = dt->years;
      93         652 :     utc->days = dt->days;
      94         652 :     utc->seconds = dt->seconds;
      95         652 :     utc->offset = 0;
      96             : 
      97         652 :     if (dt->offset) {
      98         648 :         crm_time_add_seconds(utc, -dt->offset);
      99             :     } else {
     100             :         /* Durations (which are the only things that can include months, never have a timezone */
     101           4 :         utc->months = dt->months;
     102             :     }
     103             : 
     104         652 :     crm_time_log(LOG_TRACE, "utc-source", dt,
     105             :                  crm_time_log_date | crm_time_log_timeofday | crm_time_log_with_timezone);
     106         652 :     crm_time_log(LOG_TRACE, "utc-target", utc,
     107             :                  crm_time_log_date | crm_time_log_timeofday | crm_time_log_with_timezone);
     108         652 :     return utc;
     109             : }
     110             : 
     111             : crm_time_t *
     112           0 : crm_time_new(const char *date_time)
     113             : {
     114           0 :     tzset();
     115           0 :     if (date_time == NULL) {
     116           0 :         return pcmk__copy_timet(time(NULL));
     117             :     }
     118           0 :     return parse_date(date_time);
     119             : }
     120             : 
     121             : /*!
     122             :  * \brief Allocate memory for an uninitialized time object
     123             :  *
     124             :  * \return Newly allocated time object
     125             :  * \note The caller is responsible for freeing the return value using
     126             :  *       crm_time_free().
     127             :  */
     128             : crm_time_t *
     129           0 : crm_time_new_undefined(void)
     130             : {
     131           0 :     return (crm_time_t *) pcmk__assert_alloc(1, sizeof(crm_time_t));
     132             : }
     133             : 
     134             : /*!
     135             :  * \brief Check whether a time object has been initialized yet
     136             :  *
     137             :  * \param[in] t  Time object to check
     138             :  *
     139             :  * \return TRUE if time object has been initialized, FALSE otherwise
     140             :  */
     141             : bool
     142           0 : crm_time_is_defined(const crm_time_t *t)
     143             : {
     144             :     // Any nonzero member indicates something has been done to t
     145           0 :     return (t != NULL) && (t->years || t->months || t->days || t->seconds
     146           0 :                            || t->offset || t->duration);
     147             : }
     148             : 
     149             : void
     150           0 : crm_time_free(crm_time_t * dt)
     151             : {
     152           0 :     if (dt == NULL) {
     153           0 :         return;
     154             :     }
     155           0 :     free(dt);
     156             : }
     157             : 
     158             : static int
     159      145960 : year_days(int year)
     160             : {
     161      145960 :     int d = 365;
     162             : 
     163      145960 :     if (crm_time_leapyear(year)) {
     164       35569 :         d++;
     165             :     }
     166      145960 :     return d;
     167             : }
     168             : 
     169             : /* From http://myweb.ecu.edu/mccartyr/ISOwdALG.txt :
     170             :  *
     171             :  * 5. Find the Jan1Weekday for Y (Monday=1, Sunday=7)
     172             :  *  YY = (Y-1) % 100
     173             :  *  C = (Y-1) - YY
     174             :  *  G = YY + YY/4
     175             :  *  Jan1Weekday = 1 + (((((C / 100) % 4) x 5) + G) % 7)
     176             :  */
     177             : int
     178           0 : crm_time_january1_weekday(int year)
     179             : {
     180           0 :     int YY = (year - 1) % 100;
     181           0 :     int C = (year - 1) - YY;
     182           0 :     int G = YY + YY / 4;
     183           0 :     int jan1 = 1 + (((((C / 100) % 4) * 5) + G) % 7);
     184             : 
     185           0 :     crm_trace("YY=%d, C=%d, G=%d", YY, C, G);
     186           0 :     crm_trace("January 1 %.4d: %d", year, jan1);
     187           0 :     return jan1;
     188             : }
     189             : 
     190             : int
     191           0 : crm_time_weeks_in_year(int year)
     192             : {
     193           0 :     int weeks = 52;
     194           0 :     int jan1 = crm_time_january1_weekday(year);
     195             : 
     196             :     /* if jan1 == thursday */
     197           0 :     if (jan1 == 4) {
     198           0 :         weeks++;
     199             :     } else {
     200           0 :         jan1 = crm_time_january1_weekday(year + 1);
     201             :         /* if dec31 == thursday aka. jan1 of next year is a friday */
     202           0 :         if (jan1 == 5) {
     203           0 :             weeks++;
     204             :         }
     205             : 
     206             :     }
     207           0 :     return weeks;
     208             : }
     209             : 
     210             : // Jan-Dec plus Feb of leap years
     211             : static int month_days[13] = {
     212             :     31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 29
     213             : };
     214             : 
     215             : /*!
     216             :  * \brief Return number of days in given month of given year
     217             :  *
     218             :  * \param[in]  Ordinal month (1-12)
     219             :  * \param[in]  Gregorian year
     220             :  *
     221             :  * \return Number of days in given month (0 if given month is invalid)
     222             :  */
     223             : int
     224           0 : crm_time_days_in_month(int month, int year)
     225             : {
     226           0 :     if ((month < 1) || (month > 12)) {
     227           0 :         return 0;
     228             :     }
     229           0 :     if ((month == 2) && crm_time_leapyear(year)) {
     230           0 :         month = 13;
     231             :     }
     232           0 :     return month_days[month - 1];
     233             : }
     234             : 
     235             : bool
     236           0 : crm_time_leapyear(int year)
     237             : {
     238           0 :     gboolean is_leap = FALSE;
     239             : 
     240           0 :     if (year % 4 == 0) {
     241           0 :         is_leap = TRUE;
     242             :     }
     243           0 :     if (year % 100 == 0 && year % 400 != 0) {
     244           0 :         is_leap = FALSE;
     245             :     }
     246           0 :     return is_leap;
     247             : }
     248             : 
     249             : static uint32_t
     250           0 : get_ordinal_days(uint32_t y, uint32_t m, uint32_t d)
     251             : {
     252             :     int lpc;
     253             : 
     254           0 :     for (lpc = 1; lpc < m; lpc++) {
     255           0 :         d += crm_time_days_in_month(lpc, y);
     256             :     }
     257           0 :     return d;
     258             : }
     259             : 
     260             : void
     261           0 : crm_time_log_alias(int log_level, const char *file, const char *function,
     262             :                    int line, const char *prefix, const crm_time_t *date_time,
     263             :                    int flags)
     264             : {
     265           0 :     char *date_s = crm_time_as_string(date_time, flags);
     266             : 
     267           0 :     if (log_level == LOG_STDOUT) {
     268           0 :         printf("%s%s%s\n",
     269             :                (prefix? prefix : ""), (prefix? ": " : ""), date_s);
     270             :     } else {
     271           0 :         do_crm_log_alias(log_level, file, function, line, "%s%s%s",
     272             :                          (prefix? prefix : ""), (prefix? ": " : ""), date_s);
     273             :     }
     274           0 :     free(date_s);
     275           0 : }
     276             : 
     277             : static void
     278        2693 : crm_time_get_sec(int sec, uint32_t *h, uint32_t *m, uint32_t *s)
     279             : {
     280             :     uint32_t hours, minutes, seconds;
     281             : 
     282        2693 :     seconds = QB_ABS(sec);
     283             : 
     284        2693 :     hours = seconds / HOUR_SECONDS;
     285        2693 :     seconds -= HOUR_SECONDS * hours;
     286             : 
     287        2693 :     minutes = seconds / 60;
     288        2693 :     seconds -= 60 * minutes;
     289             : 
     290        2693 :     crm_trace("%d == %.2" PRIu32 ":%.2" PRIu32 ":%.2" PRIu32,
     291             :               sec, hours, minutes, seconds);
     292             : 
     293        2693 :     *h = hours;
     294        2693 :     *m = minutes;
     295        2693 :     *s = seconds;
     296        2693 : }
     297             : 
     298             : int
     299           0 : crm_time_get_timeofday(const crm_time_t *dt, uint32_t *h, uint32_t *m,
     300             :                        uint32_t *s)
     301             : {
     302           0 :     crm_time_get_sec(dt->seconds, h, m, s);
     303           0 :     return TRUE;
     304             : }
     305             : 
     306             : int
     307           0 : crm_time_get_timezone(const crm_time_t *dt, uint32_t *h, uint32_t *m)
     308             : {
     309             :     uint32_t s;
     310             : 
     311           0 :     crm_time_get_sec(dt->seconds, h, m, &s);
     312           0 :     return TRUE;
     313             : }
     314             : 
     315             : long long
     316           0 : crm_time_get_seconds(const crm_time_t *dt)
     317             : {
     318             :     int lpc;
     319           0 :     crm_time_t *utc = NULL;
     320           0 :     long long in_seconds = 0;
     321             : 
     322           0 :     if (dt == NULL) {
     323           0 :         return 0;
     324             :     }
     325             : 
     326           0 :     utc = crm_get_utc_time(dt);
     327           0 :     if (utc == NULL) {
     328           0 :         return 0;
     329             :     }
     330             : 
     331           0 :     for (lpc = 1; lpc < utc->years; lpc++) {
     332           0 :         long long dmax = year_days(lpc);
     333             : 
     334           0 :         in_seconds += DAY_SECONDS * dmax;
     335             :     }
     336             : 
     337             :     /* utc->months is an offset that can only be set for a duration.
     338             :      * By definition, the value is variable depending on the date to
     339             :      * which it is applied.
     340             :      *
     341             :      * Force 30-day months so that something vaguely sane happens
     342             :      * for anyone that tries to use a month in this way.
     343             :      */
     344           0 :     if (utc->months > 0) {
     345           0 :         in_seconds += DAY_SECONDS * 30 * (long long) (utc->months);
     346             :     }
     347             : 
     348           0 :     if (utc->days > 0) {
     349           0 :         in_seconds += DAY_SECONDS * (long long) (utc->days - 1);
     350             :     }
     351           0 :     in_seconds += utc->seconds;
     352             : 
     353           0 :     crm_time_free(utc);
     354           0 :     return in_seconds;
     355             : }
     356             : 
     357             : #define EPOCH_SECONDS 62135596800ULL    /* Calculated using crm_time_get_seconds() */
     358             : long long
     359           0 : crm_time_get_seconds_since_epoch(const crm_time_t *dt)
     360             : {
     361           0 :     return (dt == NULL)? 0 : (crm_time_get_seconds(dt) - EPOCH_SECONDS);
     362             : }
     363             : 
     364             : int
     365           0 : crm_time_get_gregorian(const crm_time_t *dt, uint32_t *y, uint32_t *m,
     366             :                        uint32_t *d)
     367             : {
     368           0 :     int months = 0;
     369           0 :     int days = dt->days;
     370             : 
     371           0 :     if(dt->years != 0) {
     372           0 :         for (months = 1; months <= 12 && days > 0; months++) {
     373           0 :             int mdays = crm_time_days_in_month(months, dt->years);
     374             : 
     375           0 :             if (mdays >= days) {
     376           0 :                 break;
     377             :             } else {
     378           0 :                 days -= mdays;
     379             :             }
     380             :         }
     381             : 
     382           0 :     } else if (dt->months) {
     383             :         /* This is a duration including months, don't convert the days field */
     384           0 :         months = dt->months;
     385             : 
     386             :     } else {
     387             :         /* This is a duration not including months, still don't convert the days field */
     388             :     }
     389             : 
     390           0 :     *y = dt->years;
     391           0 :     *m = months;
     392           0 :     *d = days;
     393           0 :     crm_trace("%.4d-%.3d -> %.4d-%.2d-%.2d", dt->years, dt->days, dt->years, months, days);
     394           0 :     return TRUE;
     395             : }
     396             : 
     397             : int
     398           0 : crm_time_get_ordinal(const crm_time_t *dt, uint32_t *y, uint32_t *d)
     399             : {
     400           0 :     *y = dt->years;
     401           0 :     *d = dt->days;
     402           0 :     return TRUE;
     403             : }
     404             : 
     405             : int
     406           0 : crm_time_get_isoweek(const crm_time_t *dt, uint32_t *y, uint32_t *w,
     407             :                      uint32_t *d)
     408             : {
     409             :     /*
     410             :      * Monday 29 December 2008 is written "2009-W01-1"
     411             :      * Sunday 3 January 2010 is written "2009-W53-7"
     412             :      */
     413           0 :     int year_num = 0;
     414           0 :     int jan1 = crm_time_january1_weekday(dt->years);
     415           0 :     int h = -1;
     416             : 
     417           0 :     CRM_CHECK(dt->days > 0, return FALSE);
     418             : 
     419             : /* 6. Find the Weekday for Y M D */
     420           0 :     h = dt->days + jan1 - 1;
     421           0 :     *d = 1 + ((h - 1) % 7);
     422             : 
     423             : /* 7. Find if Y M D falls in YearNumber Y-1, WeekNumber 52 or 53 */
     424           0 :     if (dt->days <= (8 - jan1) && jan1 > 4) {
     425           0 :         crm_trace("year--, jan1=%d", jan1);
     426           0 :         year_num = dt->years - 1;
     427           0 :         *w = crm_time_weeks_in_year(year_num);
     428             : 
     429             :     } else {
     430           0 :         year_num = dt->years;
     431             :     }
     432             : 
     433             : /* 8. Find if Y M D falls in YearNumber Y+1, WeekNumber 1 */
     434           0 :     if (year_num == dt->years) {
     435           0 :         int dmax = year_days(year_num);
     436           0 :         int correction = 4 - *d;
     437             : 
     438           0 :         if ((dmax - dt->days) < correction) {
     439           0 :             crm_trace("year++, jan1=%d, i=%d vs. %d", jan1, dmax - dt->days, correction);
     440           0 :             year_num = dt->years + 1;
     441           0 :             *w = 1;
     442             :         }
     443             :     }
     444             : 
     445             : /* 9. Find if Y M D falls in YearNumber Y, WeekNumber 1 through 53 */
     446           0 :     if (year_num == dt->years) {
     447           0 :         int j = dt->days + (7 - *d) + (jan1 - 1);
     448             : 
     449           0 :         *w = j / 7;
     450           0 :         if (jan1 > 4) {
     451           0 :             *w -= 1;
     452             :         }
     453             :     }
     454             : 
     455           0 :     *y = year_num;
     456           0 :     crm_trace("Converted %.4d-%.3d to %.4" PRIu32 "-W%.2" PRIu32 "-%" PRIu32,
     457             :               dt->years, dt->days, *y, *w, *d);
     458           0 :     return TRUE;
     459             : }
     460             : 
     461             : #define DATE_MAX 128
     462             : 
     463             : /*!
     464             :  * \internal
     465             :  * \brief Print "<seconds>.<microseconds>" to a buffer
     466             :  *
     467             :  * \param[in]     sec     Seconds
     468             :  * \param[in]     usec    Microseconds (must be of same sign as \p sec and of
     469             :  *                        absolute value less than \p QB_TIME_US_IN_SEC)
     470             :  * \param[in,out] buf     Result buffer
     471             :  * \param[in,out] offset  Current offset within \p buf
     472             :  */
     473             : static inline void
     474           0 : sec_usec_as_string(long long sec, int usec, char *buf, size_t *offset)
     475             : {
     476           0 :     *offset += snprintf(buf + *offset, DATE_MAX - *offset, "%s%lld.%06d",
     477           0 :                         ((sec == 0) && (usec < 0))? "-" : "",
     478             :                         sec, QB_ABS(usec));
     479           0 : }
     480             : 
     481             : /*!
     482             :  * \internal
     483             :  * \brief Get a string representation of a duration
     484             :  *
     485             :  * \param[in]  dt         Time object to interpret as a duration
     486             :  * \param[in]  usec       Microseconds to add to \p dt
     487             :  * \param[in]  show_usec  Whether to include microseconds in \p result
     488             :  * \param[out] result     Where to store the result string
     489             :  */
     490             : static void
     491           0 : crm_duration_as_string(const crm_time_t *dt, int usec, bool show_usec,
     492             :                        char *result)
     493             : {
     494           0 :     size_t offset = 0;
     495             : 
     496           0 :     CRM_ASSERT(valid_sec_usec(dt->seconds, usec));
     497             : 
     498           0 :     if (dt->years) {
     499           0 :         offset += snprintf(result + offset, DATE_MAX - offset, "%4d year%s ",
     500           0 :                            dt->years, pcmk__plural_s(dt->years));
     501             :     }
     502           0 :     if (dt->months) {
     503           0 :         offset += snprintf(result + offset, DATE_MAX - offset, "%2d month%s ",
     504           0 :                            dt->months, pcmk__plural_s(dt->months));
     505             :     }
     506           0 :     if (dt->days) {
     507           0 :         offset += snprintf(result + offset, DATE_MAX - offset, "%2d day%s ",
     508           0 :                            dt->days, pcmk__plural_s(dt->days));
     509             :     }
     510             : 
     511             :     // At least print seconds (and optionally usecs)
     512           0 :     if ((offset == 0) || (dt->seconds != 0) || (show_usec && (usec != 0))) {
     513           0 :         if (show_usec) {
     514           0 :             sec_usec_as_string(dt->seconds, usec, result, &offset);
     515             :         } else {
     516           0 :             offset += snprintf(result + offset, DATE_MAX - offset, "%d",
     517           0 :                                dt->seconds);
     518             :         }
     519           0 :         offset += snprintf(result + offset, DATE_MAX - offset, " second%s",
     520           0 :                            pcmk__plural_s(dt->seconds));
     521             :     }
     522             : 
     523             :     // More than one minute, so provide a more readable breakdown into units
     524           0 :     if (QB_ABS(dt->seconds) >= 60) {
     525           0 :         uint32_t h = 0;
     526           0 :         uint32_t m = 0;
     527           0 :         uint32_t s = 0;
     528           0 :         uint32_t u = QB_ABS(usec);
     529           0 :         bool print_sec_component = false;
     530             : 
     531           0 :         crm_time_get_sec(dt->seconds, &h, &m, &s);
     532           0 :         print_sec_component = ((s != 0) || (show_usec && (u != 0)));
     533             : 
     534           0 :         offset += snprintf(result + offset, DATE_MAX - offset, " (");
     535             : 
     536           0 :         if (h) {
     537           0 :             offset += snprintf(result + offset, DATE_MAX - offset,
     538           0 :                                "%" PRIu32 " hour%s%s", h, pcmk__plural_s(h),
     539           0 :                                ((m != 0) || print_sec_component)? " " : "");
     540             :         }
     541             : 
     542           0 :         if (m) {
     543           0 :             offset += snprintf(result + offset, DATE_MAX - offset,
     544           0 :                                "%" PRIu32 " minute%s%s", m, pcmk__plural_s(m),
     545             :                                print_sec_component? " " : "");
     546             :         }
     547             : 
     548           0 :         if (print_sec_component) {
     549           0 :             if (show_usec) {
     550           0 :                 sec_usec_as_string(s, u, result, &offset);
     551             :             } else {
     552           0 :                 offset += snprintf(result + offset, DATE_MAX - offset,
     553             :                                    "%" PRIu32, s);
     554             :             }
     555           0 :             offset += snprintf(result + offset, DATE_MAX - offset, " second%s",
     556           0 :                                pcmk__plural_s(dt->seconds));
     557             :         }
     558             : 
     559           0 :         offset += snprintf(result + offset, DATE_MAX - offset, ")");
     560             :     }
     561           0 : }
     562             : 
     563             : /*!
     564             :  * \internal
     565             :  * \brief Get a string representation of a time object
     566             :  *
     567             :  * \param[in]  dt      Time to convert to string
     568             :  * \param[in]  usec    Microseconds to add to \p dt
     569             :  * \param[in]  flags   Group of \p crm_time_* string format options
     570             :  * \param[out] result  Where to store the result string
     571             :  *
     572             :  * \note \p result must be of size \p DATE_MAX or larger.
     573             :  */
     574             : static void
     575        1677 : time_as_string_common(const crm_time_t *dt, int usec, uint32_t flags,
     576             :                       char *result)
     577             : {
     578        1677 :     crm_time_t *utc = NULL;
     579        1677 :     size_t offset = 0;
     580             : 
     581        1677 :     if (!crm_time_is_defined(dt)) {
     582           0 :         strcpy(result, "<undefined time>");
     583           0 :         return;
     584             :     }
     585             : 
     586        1677 :     CRM_ASSERT(valid_sec_usec(dt->seconds, usec));
     587             : 
     588             :     /* Simple cases: as duration, seconds, or seconds since epoch.
     589             :      * These never depend on time zone.
     590             :      */
     591             : 
     592        1677 :     if (pcmk_is_set(flags, crm_time_log_duration)) {
     593           0 :         crm_duration_as_string(dt, usec, pcmk_is_set(flags, crm_time_usecs),
     594             :                                result);
     595           0 :         return;
     596             :     }
     597             : 
     598        1677 :     if (pcmk_any_flags_set(flags, crm_time_seconds|crm_time_epoch)) {
     599           0 :         long long seconds = 0;
     600             : 
     601           0 :         if (pcmk_is_set(flags, crm_time_seconds)) {
     602           0 :             seconds = crm_time_get_seconds(dt);
     603             :         } else {
     604           0 :             seconds = crm_time_get_seconds_since_epoch(dt);
     605             :         }
     606             : 
     607           0 :         if (pcmk_is_set(flags, crm_time_usecs)) {
     608           0 :             sec_usec_as_string(seconds, usec, result, &offset);
     609             :         } else {
     610           0 :             snprintf(result, DATE_MAX, "%lld", seconds);
     611             :         }
     612           0 :         return;
     613             :     }
     614             : 
     615             :     // Convert to UTC if local timezone was not requested
     616        1677 :     if ((dt->offset != 0) && !pcmk_is_set(flags, crm_time_log_with_timezone)) {
     617         244 :         crm_trace("UTC conversion");
     618         244 :         utc = crm_get_utc_time(dt);
     619         244 :         dt = utc;
     620             :     }
     621             : 
     622             :     // As readable string
     623             : 
     624        1677 :     if (pcmk_is_set(flags, crm_time_log_date)) {
     625        1677 :         if (pcmk_is_set(flags, crm_time_weeks)) { // YYYY-WW-D
     626           0 :             uint32_t y = 0;
     627           0 :             uint32_t w = 0;
     628           0 :             uint32_t d = 0;
     629             : 
     630           0 :             if (crm_time_get_isoweek(dt, &y, &w, &d)) {
     631           0 :                 offset += snprintf(result + offset, DATE_MAX - offset,
     632             :                                    "%" PRIu32 "-W%.2" PRIu32 "-%" PRIu32,
     633             :                                    y, w, d);
     634             :             }
     635             : 
     636        1677 :         } else if (pcmk_is_set(flags, crm_time_ordinal)) { // YYYY-DDD
     637           0 :             uint32_t y = 0;
     638           0 :             uint32_t d = 0;
     639             : 
     640           0 :             if (crm_time_get_ordinal(dt, &y, &d)) {
     641           0 :                 offset += snprintf(result + offset, DATE_MAX - offset,
     642             :                                    "%" PRIu32 "-%.3" PRIu32, y, d);
     643             :             }
     644             : 
     645             :         } else { // YYYY-MM-DD
     646        1677 :             uint32_t y = 0;
     647        1677 :             uint32_t m = 0;
     648        1677 :             uint32_t d = 0;
     649             : 
     650        1677 :             if (crm_time_get_gregorian(dt, &y, &m, &d)) {
     651        1677 :                 offset += snprintf(result + offset, DATE_MAX - offset,
     652             :                                    "%.4" PRIu32 "-%.2" PRIu32 "-%.2" PRIu32,
     653             :                                    y, m, d);
     654             :             }
     655             :         }
     656             :     }
     657             : 
     658        1677 :     if (pcmk_is_set(flags, crm_time_log_timeofday)) {
     659        1677 :         uint32_t h = 0, m = 0, s = 0;
     660             : 
     661        1677 :         if (offset > 0) {
     662        1677 :             offset += snprintf(result + offset, DATE_MAX - offset, " ");
     663             :         }
     664             : 
     665        1677 :         if (crm_time_get_timeofday(dt, &h, &m, &s)) {
     666        1677 :             offset += snprintf(result + offset, DATE_MAX - offset,
     667             :                                "%.2" PRIu32 ":%.2" PRIu32 ":%.2" PRIu32,
     668             :                                h, m, s);
     669             : 
     670        1677 :             if (pcmk_is_set(flags, crm_time_usecs)) {
     671           0 :                 offset += snprintf(result + offset, DATE_MAX - offset,
     672             :                                    ".%06" PRIu32, QB_ABS(usec));
     673             :             }
     674             :         }
     675             : 
     676        1677 :         if (pcmk_is_set(flags, crm_time_log_with_timezone)
     677        1400 :             && (dt->offset != 0)) {
     678         744 :             crm_time_get_sec(dt->offset, &h, &m, &s);
     679         744 :             offset += snprintf(result + offset, DATE_MAX - offset,
     680             :                                " %c%.2" PRIu32 ":%.2" PRIu32,
     681        1488 :                                ((dt->offset < 0)? '-' : '+'), h, m);
     682             :         } else {
     683         933 :             offset += snprintf(result + offset, DATE_MAX - offset, "Z");
     684             :         }
     685             :     }
     686             : 
     687        1677 :     crm_time_free(utc);
     688             : }
     689             : 
     690             : /*!
     691             :  * \brief Get a string representation of a \p crm_time_t object
     692             :  *
     693             :  * \param[in]  dt      Time to convert to string
     694             :  * \param[in]  flags   Group of \p crm_time_* string format options
     695             :  *
     696             :  * \note The caller is responsible for freeing the return value using \p free().
     697             :  */
     698             : char *
     699           0 : crm_time_as_string(const crm_time_t *dt, int flags)
     700             : {
     701           0 :     char result[DATE_MAX] = { '\0', };
     702             : 
     703           0 :     time_as_string_common(dt, 0, flags, result);
     704           0 :     return pcmk__str_copy(result);
     705             : }
     706             : 
     707             : /*!
     708             :  * \internal
     709             :  * \brief Determine number of seconds from an hour:minute:second string
     710             :  *
     711             :  * \param[in]  time_str  Time specification string
     712             :  * \param[out] result    Number of seconds equivalent to time_str
     713             :  *
     714             :  * \return TRUE if specification was valid, FALSE (and set errno) otherwise
     715             :  * \note This may return the number of seconds in a day (which is out of bounds
     716             :  *       for a time object) if given 24:00:00.
     717             :  */
     718             : static bool
     719           0 : crm_time_parse_sec(const char *time_str, int *result)
     720             : {
     721             :     int rc;
     722           0 :     uint32_t hour = 0;
     723           0 :     uint32_t minute = 0;
     724           0 :     uint32_t second = 0;
     725             : 
     726           0 :     *result = 0;
     727             : 
     728             :     // Must have at least hour, but minutes and seconds are optional
     729           0 :     rc = sscanf(time_str, "%" SCNu32 ":%" SCNu32 ":%" SCNu32,
     730             :                 &hour, &minute, &second);
     731           0 :     if (rc == 1) {
     732           0 :         rc = sscanf(time_str, "%2" SCNu32 "%2" SCNu32 "%2" SCNu32,
     733             :                     &hour, &minute, &second);
     734             :     }
     735           0 :     if (rc == 0) {
     736           0 :         crm_err("%s is not a valid ISO 8601 time specification", time_str);
     737           0 :         errno = EINVAL;
     738           0 :         return FALSE;
     739             :     }
     740             : 
     741           0 :     crm_trace("Got valid time: %.2" PRIu32 ":%.2" PRIu32 ":%.2" PRIu32,
     742             :               hour, minute, second);
     743             : 
     744           0 :     if ((hour == 24) && (minute == 0) && (second == 0)) {
     745             :         // Equivalent to 00:00:00 of next day, return number of seconds in day
     746           0 :     } else if (hour >= 24) {
     747           0 :         crm_err("%s is not a valid ISO 8601 time specification "
     748             :                 "because %" PRIu32 " is not a valid hour", time_str, hour);
     749           0 :         errno = EINVAL;
     750           0 :         return FALSE;
     751             :     }
     752           0 :     if (minute >= 60) {
     753           0 :         crm_err("%s is not a valid ISO 8601 time specification "
     754             :                 "because %" PRIu32 " is not a valid minute", time_str, minute);
     755           0 :         errno = EINVAL;
     756           0 :         return FALSE;
     757             :     }
     758           0 :     if (second >= 60) {
     759           0 :         crm_err("%s is not a valid ISO 8601 time specification "
     760             :                 "because %" PRIu32 " is not a valid second", time_str, second);
     761           0 :         errno = EINVAL;
     762           0 :         return FALSE;
     763             :     }
     764             : 
     765           0 :     *result = (hour * HOUR_SECONDS) + (minute * 60) + second;
     766           0 :     return TRUE;
     767             : }
     768             : 
     769             : static bool
     770           0 : crm_time_parse_offset(const char *offset_str, int *offset)
     771             : {
     772           0 :     tzset();
     773             : 
     774           0 :     if (offset_str == NULL) {
     775             :         // Use local offset
     776             : #if defined(HAVE_STRUCT_TM_TM_GMTOFF)
     777           0 :         time_t now = time(NULL);
     778           0 :         struct tm *now_tm = localtime(&now);
     779             : #endif
     780           0 :         int h_offset = GMTOFF(now_tm) / HOUR_SECONDS;
     781           0 :         int m_offset = (GMTOFF(now_tm) - (HOUR_SECONDS * h_offset)) / 60;
     782             : 
     783           0 :         if (h_offset < 0 && m_offset < 0) {
     784           0 :             m_offset = 0 - m_offset;
     785             :         }
     786           0 :         *offset = (HOUR_SECONDS * h_offset) + (60 * m_offset);
     787           0 :         return TRUE;
     788             :     }
     789             : 
     790           0 :     if (offset_str[0] == 'Z') { // @TODO invalid if anything after?
     791           0 :         *offset = 0;
     792           0 :         return TRUE;
     793             :     }
     794             : 
     795           0 :     *offset = 0;
     796           0 :     if ((offset_str[0] == '+') || (offset_str[0] == '-')
     797           0 :         || isdigit((int)offset_str[0])) {
     798             : 
     799           0 :         gboolean negate = FALSE;
     800             : 
     801           0 :         if (offset_str[0] == '+') {
     802           0 :             offset_str++;
     803           0 :         } else if (offset_str[0] == '-') {
     804           0 :             negate = TRUE;
     805           0 :             offset_str++;
     806             :         }
     807           0 :         if (crm_time_parse_sec(offset_str, offset) == FALSE) {
     808           0 :             return FALSE;
     809             :         }
     810           0 :         if (negate) {
     811           0 :             *offset = 0 - *offset;
     812             :         }
     813             :     } // @TODO else invalid?
     814           0 :     return TRUE;
     815             : }
     816             : 
     817             : /*!
     818             :  * \internal
     819             :  * \brief Parse the time portion of an ISO 8601 date/time string
     820             :  *
     821             :  * \param[in]     time_str  Time portion of specification (after any 'T')
     822             :  * \param[in,out] a_time    Time object to parse into
     823             :  *
     824             :  * \return TRUE if valid time was parsed, FALSE (and set errno) otherwise
     825             :  * \note This may add a day to a_time (if the time is 24:00:00).
     826             :  */
     827             : static bool
     828           0 : crm_time_parse(const char *time_str, crm_time_t *a_time)
     829             : {
     830             :     uint32_t h, m, s;
     831           0 :     char *offset_s = NULL;
     832             : 
     833           0 :     tzset();
     834             : 
     835           0 :     if (time_str) {
     836           0 :         if (crm_time_parse_sec(time_str, &(a_time->seconds)) == FALSE) {
     837           0 :             return FALSE;
     838             :         }
     839           0 :         offset_s = strstr(time_str, "Z");
     840           0 :         if (offset_s == NULL) {
     841           0 :             offset_s = strstr(time_str, " ");
     842           0 :             if (offset_s) {
     843           0 :                 while (isspace(offset_s[0])) {
     844           0 :                     offset_s++;
     845             :                 }
     846             :             }
     847             :         }
     848             :     }
     849             : 
     850           0 :     if (crm_time_parse_offset(offset_s, &(a_time->offset)) == FALSE) {
     851           0 :         return FALSE;
     852             :     }
     853           0 :     crm_time_get_sec(a_time->offset, &h, &m, &s);
     854           0 :     crm_trace("Got tz: %c%2." PRIu32 ":%.2" PRIu32,
     855             :               (a_time->offset < 0)? '-' : '+', h, m);
     856             : 
     857           0 :     if (a_time->seconds == DAY_SECONDS) {
     858             :         // 24:00:00 == 00:00:00 of next day
     859           0 :         a_time->seconds = 0;
     860           0 :         crm_time_add_days(a_time, 1);
     861             :     }
     862           0 :     return TRUE;
     863             : }
     864             : 
     865             : /*
     866             :  * \internal
     867             :  * \brief Parse a time object from an ISO 8601 date/time specification
     868             :  *
     869             :  * \param[in] date_str  ISO 8601 date/time specification (or
     870             :  *                      \c PCMK__VALUE_EPOCH)
     871             :  *
     872             :  * \return New time object on success, NULL (and set errno) otherwise
     873             :  */
     874             : static crm_time_t *
     875           0 : parse_date(const char *date_str)
     876             : {
     877           0 :     const char *time_s = NULL;
     878           0 :     crm_time_t *dt = NULL;
     879             : 
     880           0 :     int year = 0;
     881           0 :     int month = 0;
     882           0 :     int week = 0;
     883           0 :     int day = 0;
     884           0 :     int rc = 0;
     885             : 
     886           0 :     if (pcmk__str_empty(date_str)) {
     887           0 :         crm_err("No ISO 8601 date/time specification given");
     888           0 :         goto invalid;
     889             :     }
     890             : 
     891           0 :     if ((date_str[0] == 'T') || (date_str[2] == ':')) {
     892             :         /* Just a time supplied - Infer current date */
     893           0 :         dt = crm_time_new(NULL);
     894           0 :         if (date_str[0] == 'T') {
     895           0 :             time_s = date_str + 1;
     896             :         } else {
     897           0 :             time_s = date_str;
     898             :         }
     899           0 :         goto parse_time;
     900             :     }
     901             : 
     902           0 :     dt = crm_time_new_undefined();
     903             : 
     904           0 :     if ((strncasecmp(PCMK__VALUE_EPOCH, date_str, 5) == 0)
     905           0 :         && ((date_str[5] == '\0')
     906           0 :             || (date_str[5] == '/')
     907           0 :             || isspace(date_str[5]))) {
     908           0 :         dt->days = 1;
     909           0 :         dt->years = 1970;
     910           0 :         crm_time_log(LOG_TRACE, "Unpacked", dt, crm_time_log_date | crm_time_log_timeofday);
     911           0 :         return dt;
     912             :     }
     913             : 
     914             :     /* YYYY-MM-DD */
     915           0 :     rc = sscanf(date_str, "%d-%d-%d", &year, &month, &day);
     916           0 :     if (rc == 1) {
     917             :         /* YYYYMMDD */
     918           0 :         rc = sscanf(date_str, "%4d%2d%2d", &year, &month, &day);
     919             :     }
     920           0 :     if (rc == 3) {
     921           0 :         if (month > 12) {
     922           0 :             crm_err("'%s' is not a valid ISO 8601 date/time specification "
     923             :                     "because '%d' is not a valid month", date_str, month);
     924           0 :             goto invalid;
     925           0 :         } else if (day > crm_time_days_in_month(month, year)) {
     926           0 :             crm_err("'%s' is not a valid ISO 8601 date/time specification "
     927             :                     "because '%d' is not a valid day of the month",
     928             :                     date_str, day);
     929           0 :             goto invalid;
     930             :         } else {
     931           0 :             dt->years = year;
     932           0 :             dt->days = get_ordinal_days(year, month, day);
     933           0 :             crm_trace("Parsed Gregorian date '%.4d-%.3d' from date string '%s'",
     934             :                       year, dt->days, date_str);
     935             :         }
     936           0 :         goto parse_time;
     937             :     }
     938             : 
     939             :     /* YYYY-DDD */
     940           0 :     rc = sscanf(date_str, "%d-%d", &year, &day);
     941           0 :     if (rc == 2) {
     942           0 :         if (day > year_days(year)) {
     943           0 :             crm_err("'%s' is not a valid ISO 8601 date/time specification "
     944             :                     "because '%d' is not a valid day of the year (max %d)",
     945             :                     date_str, day, year_days(year));
     946           0 :             goto invalid;
     947             :         }
     948           0 :         crm_trace("Parsed ordinal year %d and days %d from date string '%s'",
     949             :                   year, day, date_str);
     950           0 :         dt->days = day;
     951           0 :         dt->years = year;
     952           0 :         goto parse_time;
     953             :     }
     954             : 
     955             :     /* YYYY-Www-D */
     956           0 :     rc = sscanf(date_str, "%d-W%d-%d", &year, &week, &day);
     957           0 :     if (rc == 3) {
     958           0 :         if (week > crm_time_weeks_in_year(year)) {
     959           0 :             crm_err("'%s' is not a valid ISO 8601 date/time specification "
     960             :                     "because '%d' is not a valid week of the year (max %d)",
     961             :                     date_str, week, crm_time_weeks_in_year(year));
     962           0 :             goto invalid;
     963           0 :         } else if (day < 1 || day > 7) {
     964           0 :             crm_err("'%s' is not a valid ISO 8601 date/time specification "
     965             :                     "because '%d' is not a valid day of the week",
     966             :                     date_str, day);
     967           0 :             goto invalid;
     968             :         } else {
     969             :             /*
     970             :              * See https://en.wikipedia.org/wiki/ISO_week_date
     971             :              *
     972             :              * Monday 29 December 2008 is written "2009-W01-1"
     973             :              * Sunday 3 January 2010 is written "2009-W53-7"
     974             :              * Saturday 27 September 2008 is written "2008-W37-6"
     975             :              *
     976             :              * If 1 January is on a Monday, Tuesday, Wednesday or Thursday, it is in week 01.
     977             :              * If 1 January is on a Friday, Saturday or Sunday, it is in week 52 or 53 of the previous year.
     978             :              */
     979           0 :             int jan1 = crm_time_january1_weekday(year);
     980             : 
     981           0 :             crm_trace("Got year %d (Jan 1 = %d), week %d, and day %d from date string '%s'",
     982             :                       year, jan1, week, day, date_str);
     983             : 
     984           0 :             dt->years = year;
     985           0 :             crm_time_add_days(dt, (week - 1) * 7);
     986             : 
     987           0 :             if (jan1 <= 4) {
     988           0 :                 crm_time_add_days(dt, 1 - jan1);
     989             :             } else {
     990           0 :                 crm_time_add_days(dt, 8 - jan1);
     991             :             }
     992             : 
     993           0 :             crm_time_add_days(dt, day);
     994             :         }
     995           0 :         goto parse_time;
     996             :     }
     997             : 
     998           0 :     crm_err("'%s' is not a valid ISO 8601 date/time specification", date_str);
     999           0 :     goto invalid;
    1000             : 
    1001           0 :   parse_time:
    1002             : 
    1003           0 :     if (time_s == NULL) {
    1004           0 :         time_s = date_str + strspn(date_str, "0123456789-W");
    1005           0 :         if ((time_s[0] == ' ') || (time_s[0] == 'T')) {
    1006           0 :             ++time_s;
    1007             :         } else {
    1008           0 :             time_s = NULL;
    1009             :         }
    1010             :     }
    1011           0 :     if ((time_s != NULL) && (crm_time_parse(time_s, dt) == FALSE)) {
    1012           0 :         goto invalid;
    1013             :     }
    1014             : 
    1015           0 :     crm_time_log(LOG_TRACE, "Unpacked", dt, crm_time_log_date | crm_time_log_timeofday);
    1016           0 :     if (crm_time_check(dt) == FALSE) {
    1017           0 :         crm_err("'%s' is not a valid ISO 8601 date/time specification",
    1018             :                 date_str);
    1019           0 :         goto invalid;
    1020             :     }
    1021           0 :     return dt;
    1022             : 
    1023           0 : invalid:
    1024           0 :     crm_time_free(dt);
    1025           0 :     errno = EINVAL;
    1026           0 :     return NULL;
    1027             : }
    1028             : 
    1029             : // Parse an ISO 8601 numeric value and return number of characters consumed
    1030             : // @TODO This cannot handle >INT_MAX int values
    1031             : // @TODO Fractions appear to be not working
    1032             : // @TODO Error out on invalid specifications
    1033             : static int
    1034           0 : parse_int(const char *str, int field_width, int upper_bound, int *result)
    1035             : {
    1036           0 :     int lpc = 0;
    1037           0 :     int offset = 0;
    1038           0 :     int intermediate = 0;
    1039           0 :     gboolean fraction = FALSE;
    1040           0 :     gboolean negate = FALSE;
    1041             : 
    1042           0 :     *result = 0;
    1043           0 :     if (*str == '\0') {
    1044           0 :         return 0;
    1045             :     }
    1046             : 
    1047           0 :     if (str[offset] == 'T') {
    1048           0 :         offset++;
    1049             :     }
    1050             : 
    1051           0 :     if (str[offset] == '.' || str[offset] == ',') {
    1052           0 :         fraction = TRUE;
    1053           0 :         field_width = -1;
    1054           0 :         offset++;
    1055           0 :     } else if (str[offset] == '-') {
    1056           0 :         negate = TRUE;
    1057           0 :         offset++;
    1058           0 :     } else if (str[offset] == '+' || str[offset] == ':') {
    1059           0 :         offset++;
    1060             :     }
    1061             : 
    1062           0 :     for (; (fraction || lpc < field_width) && isdigit((int)str[offset]); lpc++) {
    1063           0 :         if (fraction) {
    1064           0 :             intermediate = (str[offset] - '0') / (10 ^ lpc);
    1065             :         } else {
    1066           0 :             *result *= 10;
    1067           0 :             intermediate = str[offset] - '0';
    1068             :         }
    1069           0 :         *result += intermediate;
    1070           0 :         offset++;
    1071             :     }
    1072           0 :     if (fraction) {
    1073           0 :         *result = (int)(*result * upper_bound);
    1074             : 
    1075           0 :     } else if (upper_bound > 0 && *result > upper_bound) {
    1076           0 :         *result = upper_bound;
    1077             :     }
    1078           0 :     if (negate) {
    1079           0 :         *result = 0 - *result;
    1080             :     }
    1081           0 :     if (lpc > 0) {
    1082           0 :         crm_trace("Found int: %d.  Stopped at str[%d]='%c'", *result, lpc, str[lpc]);
    1083           0 :         return offset;
    1084             :     }
    1085           0 :     return 0;
    1086             : }
    1087             : 
    1088             : /*!
    1089             :  * \brief Parse a time duration from an ISO 8601 duration specification
    1090             :  *
    1091             :  * \param[in] period_s  ISO 8601 duration specification (optionally followed by
    1092             :  *                      whitespace, after which the rest of the string will be
    1093             :  *                      ignored)
    1094             :  *
    1095             :  * \return New time object on success, NULL (and set errno) otherwise
    1096             :  * \note It is the caller's responsibility to return the result using
    1097             :  *       crm_time_free().
    1098             :  */
    1099             : crm_time_t *
    1100           0 : crm_time_parse_duration(const char *period_s)
    1101             : {
    1102           0 :     gboolean is_time = FALSE;
    1103           0 :     crm_time_t *diff = NULL;
    1104             : 
    1105           0 :     if (pcmk__str_empty(period_s)) {
    1106           0 :         crm_err("No ISO 8601 time duration given");
    1107           0 :         goto invalid;
    1108             :     }
    1109           0 :     if (period_s[0] != 'P') {
    1110           0 :         crm_err("'%s' is not a valid ISO 8601 time duration "
    1111             :                 "because it does not start with a 'P'", period_s);
    1112           0 :         goto invalid;
    1113             :     }
    1114           0 :     if ((period_s[1] == '\0') || isspace(period_s[1])) {
    1115           0 :         crm_err("'%s' is not a valid ISO 8601 time duration "
    1116             :                 "because nothing follows 'P'", period_s);
    1117           0 :         goto invalid;
    1118             :     }
    1119             : 
    1120           0 :     diff = crm_time_new_undefined();
    1121           0 :     diff->duration = TRUE;
    1122             : 
    1123           0 :     for (const char *current = period_s + 1;
    1124           0 :          current[0] && (current[0] != '/') && !isspace(current[0]);
    1125           0 :          ++current) {
    1126             : 
    1127           0 :         int an_int = 0, rc;
    1128             : 
    1129           0 :         if (current[0] == 'T') {
    1130             :             /* A 'T' separates year/month/day from hour/minute/seconds. We don't
    1131             :              * require it strictly, but just use it to differentiate month from
    1132             :              * minutes.
    1133             :              */
    1134           0 :             is_time = TRUE;
    1135           0 :             continue;
    1136             :         }
    1137             : 
    1138             :         // An integer must be next
    1139           0 :         rc = parse_int(current, 10, 0, &an_int);
    1140           0 :         if (rc == 0) {
    1141           0 :             crm_err("'%s' is not a valid ISO 8601 time duration "
    1142             :                     "because no integer at '%s'", period_s, current);
    1143           0 :             goto invalid;
    1144             :         }
    1145           0 :         current += rc;
    1146             : 
    1147             :         // A time unit must be next (we're not strict about the order)
    1148           0 :         switch (current[0]) {
    1149           0 :             case 'Y':
    1150           0 :                 diff->years = an_int;
    1151           0 :                 break;
    1152           0 :             case 'M':
    1153           0 :                 if (is_time) {
    1154             :                     /* Minutes */
    1155           0 :                     diff->seconds += an_int * 60;
    1156             :                 } else {
    1157           0 :                     diff->months = an_int;
    1158             :                 }
    1159           0 :                 break;
    1160           0 :             case 'W':
    1161           0 :                 diff->days += an_int * 7;
    1162           0 :                 break;
    1163           0 :             case 'D':
    1164           0 :                 diff->days += an_int;
    1165           0 :                 break;
    1166           0 :             case 'H':
    1167           0 :                 diff->seconds += an_int * HOUR_SECONDS;
    1168           0 :                 break;
    1169           0 :             case 'S':
    1170           0 :                 diff->seconds += an_int;
    1171           0 :                 break;
    1172           0 :             case '\0':
    1173           0 :                 crm_err("'%s' is not a valid ISO 8601 time duration "
    1174             :                         "because no units after %d", period_s, an_int);
    1175           0 :                 goto invalid;
    1176           0 :             default:
    1177           0 :                 crm_err("'%s' is not a valid ISO 8601 time duration "
    1178             :                         "because '%c' is not a valid time unit",
    1179             :                         period_s, current[0]);
    1180           0 :                 goto invalid;
    1181             :         }
    1182             :     }
    1183             : 
    1184           0 :     if (!crm_time_is_defined(diff)) {
    1185           0 :         crm_err("'%s' is not a valid ISO 8601 time duration "
    1186             :                 "because no amounts and units given", period_s);
    1187           0 :         goto invalid;
    1188             :     }
    1189           0 :     return diff;
    1190             : 
    1191           0 : invalid:
    1192           0 :     crm_time_free(diff);
    1193           0 :     errno = EINVAL;
    1194           0 :     return NULL;
    1195             : }
    1196             : 
    1197             : /*!
    1198             :  * \brief Parse a time period from an ISO 8601 interval specification
    1199             :  *
    1200             :  * \param[in] period_str  ISO 8601 interval specification (start/end,
    1201             :  *                        start/duration, or duration/end)
    1202             :  *
    1203             :  * \return New time period object on success, NULL (and set errno) otherwise
    1204             :  * \note The caller is responsible for freeing the result using
    1205             :  *       crm_time_free_period().
    1206             :  */
    1207             : crm_time_period_t *
    1208           0 : crm_time_parse_period(const char *period_str)
    1209             : {
    1210           0 :     const char *original = period_str;
    1211           0 :     crm_time_period_t *period = NULL;
    1212             : 
    1213           0 :     if (pcmk__str_empty(period_str)) {
    1214           0 :         crm_err("No ISO 8601 time period given");
    1215           0 :         goto invalid;
    1216             :     }
    1217             : 
    1218           0 :     tzset();
    1219           0 :     period = pcmk__assert_alloc(1, sizeof(crm_time_period_t));
    1220             : 
    1221           0 :     if (period_str[0] == 'P') {
    1222           0 :         period->diff = crm_time_parse_duration(period_str);
    1223           0 :         if (period->diff == NULL) {
    1224           0 :             goto error;
    1225             :         }
    1226             :     } else {
    1227           0 :         period->start = parse_date(period_str);
    1228           0 :         if (period->start == NULL) {
    1229           0 :             goto error;
    1230             :         }
    1231             :     }
    1232             : 
    1233           0 :     period_str = strstr(original, "/");
    1234           0 :     if (period_str) {
    1235           0 :         ++period_str;
    1236           0 :         if (period_str[0] == 'P') {
    1237           0 :             if (period->diff != NULL) {
    1238           0 :                 crm_err("'%s' is not a valid ISO 8601 time period "
    1239             :                         "because it has two durations",
    1240             :                         original);
    1241           0 :                 goto invalid;
    1242             :             }
    1243           0 :             period->diff = crm_time_parse_duration(period_str);
    1244           0 :             if (period->diff == NULL) {
    1245           0 :                 goto error;
    1246             :             }
    1247             :         } else {
    1248           0 :             period->end = parse_date(period_str);
    1249           0 :             if (period->end == NULL) {
    1250           0 :                 goto error;
    1251             :             }
    1252             :         }
    1253             : 
    1254           0 :     } else if (period->diff != NULL) {
    1255             :         // Only duration given, assume start is now
    1256           0 :         period->start = crm_time_new(NULL);
    1257             : 
    1258             :     } else {
    1259             :         // Only start given
    1260           0 :         crm_err("'%s' is not a valid ISO 8601 time period "
    1261             :                 "because it has no duration or ending time",
    1262             :                 original);
    1263           0 :         goto invalid;
    1264             :     }
    1265             : 
    1266           0 :     if (period->start == NULL) {
    1267           0 :         period->start = crm_time_subtract(period->end, period->diff);
    1268             : 
    1269           0 :     } else if (period->end == NULL) {
    1270           0 :         period->end = crm_time_add(period->start, period->diff);
    1271             :     }
    1272             : 
    1273           0 :     if (crm_time_check(period->start) == FALSE) {
    1274           0 :         crm_err("'%s' is not a valid ISO 8601 time period "
    1275             :                 "because the start is invalid", period_str);
    1276           0 :         goto invalid;
    1277             :     }
    1278           0 :     if (crm_time_check(period->end) == FALSE) {
    1279           0 :         crm_err("'%s' is not a valid ISO 8601 time period "
    1280             :                 "because the end is invalid", period_str);
    1281           0 :         goto invalid;
    1282             :     }
    1283           0 :     return period;
    1284             : 
    1285           0 : invalid:
    1286           0 :     errno = EINVAL;
    1287           0 : error:
    1288           0 :     crm_time_free_period(period);
    1289           0 :     return NULL;
    1290             : }
    1291             : 
    1292             : /*!
    1293             :  * \brief Free a dynamically allocated time period object
    1294             :  *
    1295             :  * \param[in,out] period  Time period to free
    1296             :  */
    1297             : void
    1298           0 : crm_time_free_period(crm_time_period_t *period)
    1299             : {
    1300           0 :     if (period) {
    1301           0 :         crm_time_free(period->start);
    1302           0 :         crm_time_free(period->end);
    1303           0 :         crm_time_free(period->diff);
    1304           0 :         free(period);
    1305             :     }
    1306           0 : }
    1307             : 
    1308             : void
    1309           0 : crm_time_set(crm_time_t *target, const crm_time_t *source)
    1310             : {
    1311           0 :     crm_trace("target=%p, source=%p", target, source);
    1312             : 
    1313           0 :     CRM_CHECK(target != NULL && source != NULL, return);
    1314             : 
    1315           0 :     target->years = source->years;
    1316           0 :     target->days = source->days;
    1317           0 :     target->months = source->months;    /* Only for durations */
    1318           0 :     target->seconds = source->seconds;
    1319           0 :     target->offset = source->offset;
    1320             : 
    1321           0 :     crm_time_log(LOG_TRACE, "source", source,
    1322             :                  crm_time_log_date | crm_time_log_timeofday | crm_time_log_with_timezone);
    1323           0 :     crm_time_log(LOG_TRACE, "target", target,
    1324             :                  crm_time_log_date | crm_time_log_timeofday | crm_time_log_with_timezone);
    1325             : }
    1326             : 
    1327             : static void
    1328           0 : ha_set_tm_time(crm_time_t *target, const struct tm *source)
    1329             : {
    1330           0 :     int h_offset = 0;
    1331           0 :     int m_offset = 0;
    1332             : 
    1333             :     /* Ensure target is fully initialized */
    1334           0 :     target->years = 0;
    1335           0 :     target->months = 0;
    1336           0 :     target->days = 0;
    1337           0 :     target->seconds = 0;
    1338           0 :     target->offset = 0;
    1339           0 :     target->duration = FALSE;
    1340             : 
    1341           0 :     if (source->tm_year > 0) {
    1342             :         /* years since 1900 */
    1343           0 :         target->years = 1900 + source->tm_year;
    1344             :     }
    1345             : 
    1346           0 :     if (source->tm_yday >= 0) {
    1347             :         /* days since January 1 [0-365] */
    1348           0 :         target->days = 1 + source->tm_yday;
    1349             :     }
    1350             : 
    1351           0 :     if (source->tm_hour >= 0) {
    1352           0 :         target->seconds += HOUR_SECONDS * source->tm_hour;
    1353             :     }
    1354           0 :     if (source->tm_min >= 0) {
    1355           0 :         target->seconds += 60 * source->tm_min;
    1356             :     }
    1357           0 :     if (source->tm_sec >= 0) {
    1358           0 :         target->seconds += source->tm_sec;
    1359             :     }
    1360             : 
    1361             :     /* tm_gmtoff == offset from UTC in seconds */
    1362           0 :     h_offset = GMTOFF(source) / HOUR_SECONDS;
    1363           0 :     m_offset = (GMTOFF(source) - (HOUR_SECONDS * h_offset)) / 60;
    1364           0 :     crm_trace("Time offset is %lds (%.2d:%.2d)",
    1365             :               GMTOFF(source), h_offset, m_offset);
    1366             : 
    1367           0 :     target->offset += HOUR_SECONDS * h_offset;
    1368           0 :     target->offset += 60 * m_offset;
    1369           0 : }
    1370             : 
    1371             : void
    1372           0 : crm_time_set_timet(crm_time_t *target, const time_t *source)
    1373             : {
    1374           0 :     ha_set_tm_time(target, localtime(source));
    1375           0 : }
    1376             : 
    1377             : /*!
    1378             :  * \internal
    1379             :  * \brief Set one time object to another if the other is earlier
    1380             :  *
    1381             :  * \param[in,out] target  Time object to set
    1382             :  * \param[in]     source  Time object to use if earlier
    1383             :  */
    1384             : void
    1385          42 : pcmk__set_time_if_earlier(crm_time_t *target, const crm_time_t *source)
    1386             : {
    1387          42 :     if ((target != NULL) && (source != NULL)
    1388          37 :         && (!crm_time_is_defined(target)
    1389          36 :             || (crm_time_compare(source, target) < 0))) {
    1390          23 :         crm_time_set(target, source);
    1391             :     }
    1392          42 : }
    1393             : 
    1394             : crm_time_t *
    1395           0 : pcmk_copy_time(const crm_time_t *source)
    1396             : {
    1397           0 :     crm_time_t *target = crm_time_new_undefined();
    1398             : 
    1399           0 :     crm_time_set(target, source);
    1400           0 :     return target;
    1401             : }
    1402             : 
    1403             : /*!
    1404             :  * \internal
    1405             :  * \brief Convert a \p time_t time to a \p crm_time_t time
    1406             :  *
    1407             :  * \param[in] source  Time to convert
    1408             :  *
    1409             :  * \return A \p crm_time_t object representing \p source
    1410             :  */
    1411             : crm_time_t *
    1412           0 : pcmk__copy_timet(time_t source)
    1413             : {
    1414           0 :     crm_time_t *target = crm_time_new_undefined();
    1415             : 
    1416           0 :     crm_time_set_timet(target, &source);
    1417           0 :     return target;
    1418             : }
    1419             : 
    1420             : crm_time_t *
    1421           0 : crm_time_add(const crm_time_t *dt, const crm_time_t *value)
    1422             : {
    1423           0 :     crm_time_t *utc = NULL;
    1424           0 :     crm_time_t *answer = NULL;
    1425             : 
    1426           0 :     if ((dt == NULL) || (value == NULL)) {
    1427           0 :         errno = EINVAL;
    1428           0 :         return NULL;
    1429             :     }
    1430             : 
    1431           0 :     answer = pcmk_copy_time(dt);
    1432             : 
    1433           0 :     utc = crm_get_utc_time(value);
    1434           0 :     if (utc == NULL) {
    1435           0 :         crm_time_free(answer);
    1436           0 :         return NULL;
    1437             :     }
    1438             : 
    1439           0 :     answer->years += utc->years;
    1440           0 :     crm_time_add_months(answer, utc->months);
    1441           0 :     crm_time_add_days(answer, utc->days);
    1442           0 :     crm_time_add_seconds(answer, utc->seconds);
    1443             : 
    1444           0 :     crm_time_free(utc);
    1445           0 :     return answer;
    1446             : }
    1447             : 
    1448             : /*!
    1449             :  * \internal
    1450             :  * \brief Return the XML attribute name corresponding to a time component
    1451             :  *
    1452             :  * \param[in] component  Component to check
    1453             :  *
    1454             :  * \return XML attribute name corresponding to \p component, or NULL if
    1455             :  *         \p component is invalid
    1456             :  */
    1457             : const char *
    1458           0 : pcmk__time_component_attr(enum pcmk__time_component component)
    1459             : {
    1460           0 :     switch (component) {
    1461           0 :         case pcmk__time_years:
    1462           0 :             return PCMK_XA_YEARS;
    1463             : 
    1464           0 :         case pcmk__time_months:
    1465           0 :             return PCMK_XA_MONTHS;
    1466             : 
    1467           0 :         case pcmk__time_weeks:
    1468           0 :             return PCMK_XA_WEEKS;
    1469             : 
    1470           0 :         case pcmk__time_days:
    1471           0 :             return PCMK_XA_DAYS;
    1472             : 
    1473           0 :         case pcmk__time_hours:
    1474           0 :             return PCMK_XA_HOURS;
    1475             : 
    1476           0 :         case pcmk__time_minutes:
    1477           0 :             return PCMK_XA_MINUTES;
    1478             : 
    1479           0 :         case pcmk__time_seconds:
    1480           0 :             return PCMK_XA_SECONDS;
    1481             : 
    1482           0 :         default:
    1483           0 :             return NULL;
    1484             :     }
    1485             : }
    1486             : 
    1487             : typedef void (*component_fn_t)(crm_time_t *, int);
    1488             : 
    1489             : /*!
    1490             :  * \internal
    1491             :  * \brief Get the addition function corresponding to a time component
    1492             :  * \param[in] component  Component to check
    1493             :  *
    1494             :  * \return Addition function corresponding to \p component, or NULL if
    1495             :  *         \p component is invalid
    1496             :  */
    1497             : static component_fn_t
    1498         147 : component_fn(enum pcmk__time_component component)
    1499             : {
    1500         147 :     switch (component) {
    1501          25 :         case pcmk__time_years:
    1502          25 :             return crm_time_add_years;
    1503             : 
    1504          21 :         case pcmk__time_months:
    1505          21 :             return crm_time_add_months;
    1506             : 
    1507          20 :         case pcmk__time_weeks:
    1508          20 :             return crm_time_add_weeks;
    1509             : 
    1510          20 :         case pcmk__time_days:
    1511          20 :             return crm_time_add_days;
    1512             : 
    1513          20 :         case pcmk__time_hours:
    1514          20 :             return crm_time_add_hours;
    1515             : 
    1516          20 :         case pcmk__time_minutes:
    1517          20 :             return crm_time_add_minutes;
    1518             : 
    1519          20 :         case pcmk__time_seconds:
    1520          20 :             return crm_time_add_seconds;
    1521             : 
    1522           1 :         default:
    1523           1 :             return NULL;
    1524             :     }
    1525             : 
    1526             : }
    1527             : 
    1528             : /*!
    1529             :  * \internal
    1530             :  * \brief Add the value of an XML attribute to a time object
    1531             :  *
    1532             :  * \param[in,out] t          Time object to add to
    1533             :  * \param[in]     component  Component of \p t to add to
    1534             :  * \param[in]     xml        XML with value to add
    1535             :  *
    1536             :  * \return Standard Pacemaker return code
    1537             :  */
    1538             : int
    1539         147 : pcmk__add_time_from_xml(crm_time_t *t, enum pcmk__time_component component,
    1540             :                         const xmlNode *xml)
    1541             : {
    1542             :     long long value;
    1543         147 :     const char *attr = pcmk__time_component_attr(component);
    1544         147 :     component_fn_t add = component_fn(component);
    1545             : 
    1546         147 :     if ((t == NULL) || (attr == NULL) || (add == NULL)) {
    1547           2 :         return EINVAL;
    1548             :     }
    1549             : 
    1550         145 :     if (xml == NULL) {
    1551           1 :         return pcmk_rc_ok;
    1552             :     }
    1553             : 
    1554         144 :     if (pcmk__scan_ll(crm_element_value(xml, attr), &value,
    1555             :                       0LL) != pcmk_rc_ok) {
    1556           6 :         return pcmk_rc_unpack_error;
    1557             :     }
    1558             : 
    1559         138 :     if ((value < INT_MIN) || (value > INT_MAX)) {
    1560           2 :         return ERANGE;
    1561             :     }
    1562             : 
    1563         136 :     if (value != 0LL) {
    1564          39 :         add(t, (int) value);
    1565             :     }
    1566         136 :     return pcmk_rc_ok;
    1567             : }
    1568             : 
    1569             : crm_time_t *
    1570           0 : crm_time_calculate_duration(const crm_time_t *dt, const crm_time_t *value)
    1571             : {
    1572           0 :     crm_time_t *utc = NULL;
    1573           0 :     crm_time_t *answer = NULL;
    1574             : 
    1575           0 :     if ((dt == NULL) || (value == NULL)) {
    1576           0 :         errno = EINVAL;
    1577           0 :         return NULL;
    1578             :     }
    1579             : 
    1580           0 :     utc = crm_get_utc_time(value);
    1581           0 :     if (utc == NULL) {
    1582           0 :         return NULL;
    1583             :     }
    1584             : 
    1585           0 :     answer = crm_get_utc_time(dt);
    1586           0 :     if (answer == NULL) {
    1587           0 :         crm_time_free(utc);
    1588           0 :         return NULL;
    1589             :     }
    1590           0 :     answer->duration = TRUE;
    1591             : 
    1592           0 :     answer->years -= utc->years;
    1593           0 :     if(utc->months != 0) {
    1594           0 :         crm_time_add_months(answer, -utc->months);
    1595             :     }
    1596           0 :     crm_time_add_days(answer, -utc->days);
    1597           0 :     crm_time_add_seconds(answer, -utc->seconds);
    1598             : 
    1599           0 :     crm_time_free(utc);
    1600           0 :     return answer;
    1601             : }
    1602             : 
    1603             : crm_time_t *
    1604           0 : crm_time_subtract(const crm_time_t *dt, const crm_time_t *value)
    1605             : {
    1606           0 :     crm_time_t *utc = NULL;
    1607           0 :     crm_time_t *answer = NULL;
    1608             : 
    1609           0 :     if ((dt == NULL) || (value == NULL)) {
    1610           0 :         errno = EINVAL;
    1611           0 :         return NULL;
    1612             :     }
    1613             : 
    1614           0 :     utc = crm_get_utc_time(value);
    1615           0 :     if (utc == NULL) {
    1616           0 :         return NULL;
    1617             :     }
    1618             : 
    1619           0 :     answer = pcmk_copy_time(dt);
    1620           0 :     answer->years -= utc->years;
    1621           0 :     if(utc->months != 0) {
    1622           0 :         crm_time_add_months(answer, -utc->months);
    1623             :     }
    1624           0 :     crm_time_add_days(answer, -utc->days);
    1625           0 :     crm_time_add_seconds(answer, -utc->seconds);
    1626           0 :     crm_time_free(utc);
    1627             : 
    1628           0 :     return answer;
    1629             : }
    1630             : 
    1631             : /*!
    1632             :  * \brief Check whether a time object represents a sensible date/time
    1633             :  *
    1634             :  * \param[in] dt  Date/time object to check
    1635             :  *
    1636             :  * \return \c true if years, days, and seconds are sensible, \c false otherwise
    1637             :  */
    1638             : bool
    1639           0 : crm_time_check(const crm_time_t *dt)
    1640             : {
    1641             :     return (dt != NULL)
    1642           0 :            && (dt->days > 0) && (dt->days <= year_days(dt->years))
    1643           0 :            && (dt->seconds >= 0) && (dt->seconds < DAY_SECONDS);
    1644             : }
    1645             : 
    1646             : #define do_cmp_field(l, r, field)                                       \
    1647             :     if(rc == 0) {                                                       \
    1648             :                 if(l->field > r->field) {                              \
    1649             :                         crm_trace("%s: %d > %d",                   \
    1650             :                                     #field, l->field, r->field);  \
    1651             :                         rc = 1;                                         \
    1652             :                 } else if(l->field < r->field) {                       \
    1653             :                         crm_trace("%s: %d < %d",                   \
    1654             :                                     #field, l->field, r->field);  \
    1655             :                         rc = -1;                                        \
    1656             :                 }                                                       \
    1657             :     }
    1658             : 
    1659             : int
    1660           0 : crm_time_compare(const crm_time_t *a, const crm_time_t *b)
    1661             : {
    1662           0 :     int rc = 0;
    1663           0 :     crm_time_t *t1 = crm_get_utc_time(a);
    1664           0 :     crm_time_t *t2 = crm_get_utc_time(b);
    1665             : 
    1666           0 :     if ((t1 == NULL) && (t2 == NULL)) {
    1667           0 :         rc = 0;
    1668           0 :     } else if (t1 == NULL) {
    1669           0 :         rc = -1;
    1670           0 :     } else if (t2 == NULL) {
    1671           0 :         rc = 1;
    1672             :     } else {
    1673           0 :         do_cmp_field(t1, t2, years);
    1674           0 :         do_cmp_field(t1, t2, days);
    1675           0 :         do_cmp_field(t1, t2, seconds);
    1676             :     }
    1677             : 
    1678           0 :     crm_time_free(t1);
    1679           0 :     crm_time_free(t2);
    1680           0 :     return rc;
    1681             : }
    1682             : 
    1683             : /*!
    1684             :  * \brief Add a given number of seconds to a date/time or duration
    1685             :  *
    1686             :  * \param[in,out] a_time  Date/time or duration to add seconds to
    1687             :  * \param[in]     extra   Number of seconds to add
    1688             :  */
    1689             : void
    1690           0 : crm_time_add_seconds(crm_time_t *a_time, int extra)
    1691             : {
    1692           0 :     int days = 0;
    1693             : 
    1694           0 :     crm_trace("Adding %d seconds to %d (max=%d)",
    1695             :               extra, a_time->seconds, DAY_SECONDS);
    1696           0 :     a_time->seconds += extra;
    1697           0 :     days = a_time->seconds / DAY_SECONDS;
    1698           0 :     a_time->seconds %= DAY_SECONDS;
    1699             : 
    1700             :     // Don't have negative seconds
    1701           0 :     if (a_time->seconds < 0) {
    1702           0 :         a_time->seconds += DAY_SECONDS;
    1703           0 :         --days;
    1704             :     }
    1705             : 
    1706           0 :     crm_time_add_days(a_time, days);
    1707           0 : }
    1708             : 
    1709             : void
    1710           0 : crm_time_add_days(crm_time_t * a_time, int extra)
    1711             : {
    1712           0 :     int lower_bound = 1;
    1713           0 :     int ydays = crm_time_leapyear(a_time->years) ? 366 : 365;
    1714             : 
    1715           0 :     crm_trace("Adding %d days to %.4d-%.3d", extra, a_time->years, a_time->days);
    1716             : 
    1717           0 :     a_time->days += extra;
    1718           0 :     while (a_time->days > ydays) {
    1719           0 :         a_time->years++;
    1720           0 :         a_time->days -= ydays;
    1721           0 :         ydays = crm_time_leapyear(a_time->years) ? 366 : 365;
    1722             :     }
    1723             : 
    1724           0 :     if(a_time->duration) {
    1725           0 :         lower_bound = 0;
    1726             :     }
    1727             : 
    1728           0 :     while (a_time->days < lower_bound) {
    1729           0 :         a_time->years--;
    1730           0 :         a_time->days += crm_time_leapyear(a_time->years) ? 366 : 365;
    1731             :     }
    1732           0 : }
    1733             : 
    1734             : void
    1735           0 : crm_time_add_months(crm_time_t * a_time, int extra)
    1736             : {
    1737             :     int lpc;
    1738             :     uint32_t y, m, d, dmax;
    1739             : 
    1740           0 :     crm_time_get_gregorian(a_time, &y, &m, &d);
    1741           0 :     crm_trace("Adding %d months to %.4" PRIu32 "-%.2" PRIu32 "-%.2" PRIu32,
    1742             :               extra, y, m, d);
    1743             : 
    1744           0 :     if (extra > 0) {
    1745           0 :         for (lpc = extra; lpc > 0; lpc--) {
    1746           0 :             m++;
    1747           0 :             if (m == 13) {
    1748           0 :                 m = 1;
    1749           0 :                 y++;
    1750             :             }
    1751             :         }
    1752             :     } else {
    1753           0 :         for (lpc = -extra; lpc > 0; lpc--) {
    1754           0 :             m--;
    1755           0 :             if (m == 0) {
    1756           0 :                 m = 12;
    1757           0 :                 y--;
    1758             :             }
    1759             :         }
    1760             :     }
    1761             : 
    1762           0 :     dmax = crm_time_days_in_month(m, y);
    1763           0 :     if (dmax < d) {
    1764             :         /* Preserve day-of-month unless the month doesn't have enough days */
    1765           0 :         d = dmax;
    1766             :     }
    1767             : 
    1768           0 :     crm_trace("Calculated %.4" PRIu32 "-%.2" PRIu32 "-%.2" PRIu32, y, m, d);
    1769             : 
    1770           0 :     a_time->years = y;
    1771           0 :     a_time->days = get_ordinal_days(y, m, d);
    1772             : 
    1773           0 :     crm_time_get_gregorian(a_time, &y, &m, &d);
    1774           0 :     crm_trace("Got %.4" PRIu32 "-%.2" PRIu32 "-%.2" PRIu32, y, m, d);
    1775           0 : }
    1776             : 
    1777             : void
    1778           0 : crm_time_add_minutes(crm_time_t * a_time, int extra)
    1779             : {
    1780           0 :     crm_time_add_seconds(a_time, extra * 60);
    1781           0 : }
    1782             : 
    1783             : void
    1784           0 : crm_time_add_hours(crm_time_t * a_time, int extra)
    1785             : {
    1786           0 :     crm_time_add_seconds(a_time, extra * HOUR_SECONDS);
    1787           0 : }
    1788             : 
    1789             : void
    1790           0 : crm_time_add_weeks(crm_time_t * a_time, int extra)
    1791             : {
    1792           0 :     crm_time_add_days(a_time, extra * 7);
    1793           0 : }
    1794             : 
    1795             : void
    1796           0 : crm_time_add_years(crm_time_t * a_time, int extra)
    1797             : {
    1798           0 :     a_time->years += extra;
    1799           0 : }
    1800             : 
    1801             : static void
    1802           0 : ha_get_tm_time(struct tm *target, const crm_time_t *source)
    1803             : {
    1804           0 :     *target = (struct tm) {
    1805           0 :         .tm_year = source->years - 1900,
    1806           0 :         .tm_mday = source->days,
    1807           0 :         .tm_sec = source->seconds % 60,
    1808           0 :         .tm_min = ( source->seconds / 60 ) % 60,
    1809           0 :         .tm_hour = source->seconds / HOUR_SECONDS,
    1810             :         .tm_isdst = -1, /* don't adjust */
    1811             : 
    1812             : #if defined(HAVE_STRUCT_TM_TM_GMTOFF)
    1813           0 :         .tm_gmtoff = source->offset
    1814             : #endif
    1815             :     };
    1816           0 :     mktime(target);
    1817           0 : }
    1818             : 
    1819             : /* The high-resolution variant of time object was added to meet an immediate
    1820             :  * need, and is kept internal API.
    1821             :  *
    1822             :  * @TODO The long-term goal is to come up with a clean, unified design for a
    1823             :  *       time type (or types) that meets all the various needs, to replace
    1824             :  *       crm_time_t, pcmk__time_hr_t, and struct timespec (in lrmd_cmd_t).
    1825             :  *       Using glib's GDateTime is a possibility (if we are willing to require
    1826             :  *       glib >= 2.26).
    1827             :  */
    1828             : 
    1829             : pcmk__time_hr_t *
    1830           0 : pcmk__time_hr_convert(pcmk__time_hr_t *target, const crm_time_t *dt)
    1831             : {
    1832           0 :     pcmk__time_hr_t *hr_dt = NULL;
    1833             : 
    1834           0 :     if (dt) {
    1835           0 :         hr_dt = target;
    1836           0 :         if (hr_dt == NULL) {
    1837           0 :             hr_dt = pcmk__assert_alloc(1, sizeof(pcmk__time_hr_t));
    1838             :         }
    1839             : 
    1840           0 :         *hr_dt = (pcmk__time_hr_t) {
    1841           0 :             .years = dt->years,
    1842           0 :             .months = dt->months,
    1843           0 :             .days = dt->days,
    1844           0 :             .seconds = dt->seconds,
    1845           0 :             .offset = dt->offset,
    1846           0 :             .duration = dt->duration
    1847             :         };
    1848             :     }
    1849             : 
    1850           0 :     return hr_dt;
    1851             : }
    1852             : 
    1853             : void
    1854           0 : pcmk__time_set_hr_dt(crm_time_t *target, const pcmk__time_hr_t *hr_dt)
    1855             : {
    1856           0 :     CRM_ASSERT((hr_dt) && (target));
    1857           0 :     *target = (crm_time_t) {
    1858           0 :         .years = hr_dt->years,
    1859           0 :         .months = hr_dt->months,
    1860           0 :         .days = hr_dt->days,
    1861           0 :         .seconds = hr_dt->seconds,
    1862           0 :         .offset = hr_dt->offset,
    1863           0 :         .duration = hr_dt->duration
    1864             :     };
    1865           0 : }
    1866             : 
    1867             : /*!
    1868             :  * \internal
    1869             :  * \brief Return the current time as a high-resolution time
    1870             :  *
    1871             :  * \param[out] epoch  If not NULL, this will be set to seconds since epoch
    1872             :  *
    1873             :  * \return Newly allocated high-resolution time set to the current time
    1874             :  */
    1875             : pcmk__time_hr_t *
    1876           0 : pcmk__time_hr_now(time_t *epoch)
    1877             : {
    1878             :     struct timespec tv;
    1879             :     crm_time_t dt;
    1880             :     pcmk__time_hr_t *hr;
    1881             : 
    1882           0 :     qb_util_timespec_from_epoch_get(&tv);
    1883           0 :     if (epoch != NULL) {
    1884           0 :         *epoch = tv.tv_sec;
    1885             :     }
    1886           0 :     crm_time_set_timet(&dt, &(tv.tv_sec));
    1887           0 :     hr = pcmk__time_hr_convert(NULL, &dt);
    1888           0 :     if (hr != NULL) {
    1889           0 :         hr->useconds = tv.tv_nsec / QB_TIME_NS_IN_USEC;
    1890             :     }
    1891           0 :     return hr;
    1892             : }
    1893             : 
    1894             : pcmk__time_hr_t *
    1895           0 : pcmk__time_hr_new(const char *date_time)
    1896             : {
    1897           0 :     pcmk__time_hr_t *hr_dt = NULL;
    1898             : 
    1899           0 :     if (date_time == NULL) {
    1900           0 :         hr_dt = pcmk__time_hr_now(NULL);
    1901             :     } else {
    1902             :         crm_time_t *dt;
    1903             : 
    1904           0 :         dt = parse_date(date_time);
    1905           0 :         hr_dt = pcmk__time_hr_convert(NULL, dt);
    1906           0 :         crm_time_free(dt);
    1907             :     }
    1908           0 :     return hr_dt;
    1909             : }
    1910             : 
    1911             : void
    1912           0 : pcmk__time_hr_free(pcmk__time_hr_t * hr_dt)
    1913             : {
    1914           0 :     free(hr_dt);
    1915           0 : }
    1916             : 
    1917             : char *
    1918           0 : pcmk__time_format_hr(const char *format, const pcmk__time_hr_t *hr_dt)
    1919             : {
    1920             : #define DATE_LEN_MAX 128
    1921           0 :     const char *mark_s = NULL;
    1922           0 :     int scanned_pos = 0;
    1923           0 :     int printed_pos = 0;
    1924           0 :     int fmt_pos = 0;
    1925           0 :     size_t date_len = 0;
    1926           0 :     int nano_digits = 0;
    1927             : 
    1928           0 :     char nano_s[10] = { '\0', };
    1929           0 :     char date_s[DATE_LEN_MAX] = { '\0', };
    1930           0 :     char nanofmt_s[5] = "%";
    1931           0 :     char *tmp_fmt_s = NULL;
    1932             : 
    1933           0 :     struct tm tm = { 0, };
    1934           0 :     crm_time_t dt = { 0, };
    1935             : 
    1936           0 :     if (!format) {
    1937           0 :         return NULL;
    1938             :     }
    1939           0 :     pcmk__time_set_hr_dt(&dt, hr_dt);
    1940           0 :     ha_get_tm_time(&tm, &dt);
    1941           0 :     sprintf(nano_s, "%06d000", hr_dt->useconds);
    1942             : 
    1943           0 :     while ((format[scanned_pos]) != '\0') {
    1944           0 :         mark_s = strchr(&format[scanned_pos], '%');
    1945           0 :         if (mark_s) {
    1946           0 :             int fmt_len = 1;
    1947             : 
    1948           0 :             fmt_pos = mark_s - format;
    1949           0 :             while ((format[fmt_pos+fmt_len] != '\0') &&
    1950           0 :                 (format[fmt_pos+fmt_len] >= '0') &&
    1951           0 :                 (format[fmt_pos+fmt_len] <= '9')) {
    1952           0 :                 fmt_len++;
    1953             :             }
    1954           0 :             scanned_pos = fmt_pos + fmt_len + 1;
    1955           0 :             if (format[fmt_pos+fmt_len] == 'N') {
    1956           0 :                 nano_digits = atoi(&format[fmt_pos+1]);
    1957           0 :                 nano_digits = (nano_digits > 6)?6:nano_digits;
    1958           0 :                 nano_digits = (nano_digits < 0)?0:nano_digits;
    1959           0 :                 sprintf(&nanofmt_s[1], ".%ds", nano_digits);
    1960             :             } else {
    1961           0 :                 if (format[scanned_pos] != '\0') {
    1962           0 :                     continue;
    1963             :                 }
    1964           0 :                 fmt_pos = scanned_pos; /* print till end */
    1965             :             }
    1966             :         } else {
    1967           0 :             scanned_pos = strlen(format);
    1968           0 :             fmt_pos = scanned_pos; /* print till end */
    1969             :         }
    1970           0 :         tmp_fmt_s = strndup(&format[printed_pos], fmt_pos - printed_pos);
    1971             : #ifdef HAVE_FORMAT_NONLITERAL
    1972             : #pragma GCC diagnostic push
    1973             : #pragma GCC diagnostic ignored "-Wformat-nonliteral"
    1974             : #endif
    1975           0 :         date_len += strftime(&date_s[date_len], DATE_LEN_MAX - date_len,
    1976             :                              tmp_fmt_s, &tm);
    1977             : #ifdef HAVE_FORMAT_NONLITERAL
    1978             : #pragma GCC diagnostic pop
    1979             : #endif
    1980           0 :         printed_pos = scanned_pos;
    1981           0 :         free(tmp_fmt_s);
    1982           0 :         if (nano_digits) {
    1983             : #ifdef HAVE_FORMAT_NONLITERAL
    1984             : #pragma GCC diagnostic push
    1985             : #pragma GCC diagnostic ignored "-Wformat-nonliteral"
    1986             : #endif
    1987           0 :             date_len += snprintf(&date_s[date_len], DATE_LEN_MAX - date_len,
    1988             :                                  nanofmt_s, nano_s);
    1989             : #ifdef HAVE_FORMAT_NONLITERAL
    1990             : #pragma GCC diagnostic pop
    1991             : #endif
    1992           0 :             nano_digits = 0;
    1993             :         }
    1994             :     }
    1995             : 
    1996           0 :     return (date_len == 0)?NULL:strdup(date_s);
    1997             : #undef DATE_LEN_MAX
    1998             : }
    1999             : 
    2000             : /*!
    2001             :  * \internal
    2002             :  * \brief Return a human-friendly string corresponding to an epoch time value
    2003             :  *
    2004             :  * \param[in]  source  Pointer to epoch time value (or \p NULL for current time)
    2005             :  * \param[in]  flags   Group of \p crm_time_* flags controlling display format
    2006             :  *                     (0 to use \p ctime() with newline removed)
    2007             :  *
    2008             :  * \return String representation of \p source on success (may be empty depending
    2009             :  *         on \p flags; guaranteed not to be \p NULL)
    2010             :  *
    2011             :  * \note The caller is responsible for freeing the return value using \p free().
    2012             :  */
    2013             : char *
    2014           0 : pcmk__epoch2str(const time_t *source, uint32_t flags)
    2015             : {
    2016           0 :     time_t epoch_time = (source == NULL)? time(NULL) : *source;
    2017             : 
    2018           0 :     if (flags == 0) {
    2019           0 :         return pcmk__str_copy(pcmk__trim(ctime(&epoch_time)));
    2020             :     } else {
    2021             :         crm_time_t dt;
    2022             : 
    2023           0 :         crm_time_set_timet(&dt, &epoch_time);
    2024           0 :         return crm_time_as_string(&dt, flags);
    2025             :     }
    2026             : }
    2027             : 
    2028             : /*!
    2029             :  * \internal
    2030             :  * \brief Return a human-friendly string corresponding to seconds-and-
    2031             :  *        nanoseconds value
    2032             :  *
    2033             :  * Time is shown with microsecond resolution if \p crm_time_usecs is in \p
    2034             :  * flags.
    2035             :  *
    2036             :  * \param[in]  ts     Time in seconds and nanoseconds (or \p NULL for current
    2037             :  *                    time)
    2038             :  * \param[in]  flags  Group of \p crm_time_* flags controlling display format
    2039             :  *
    2040             :  * \return String representation of \p ts on success (may be empty depending on
    2041             :  *         \p flags; guaranteed not to be \p NULL)
    2042             :  *
    2043             :  * \note The caller is responsible for freeing the return value using \p free().
    2044             :  */
    2045             : char *
    2046           0 : pcmk__timespec2str(const struct timespec *ts, uint32_t flags)
    2047             : {
    2048             :     struct timespec tmp_ts;
    2049             :     crm_time_t dt;
    2050           0 :     char result[DATE_MAX] = { 0 };
    2051             : 
    2052           0 :     if (ts == NULL) {
    2053           0 :         qb_util_timespec_from_epoch_get(&tmp_ts);
    2054           0 :         ts = &tmp_ts;
    2055             :     }
    2056           0 :     crm_time_set_timet(&dt, &ts->tv_sec);
    2057           0 :     time_as_string_common(&dt, ts->tv_nsec / QB_TIME_NS_IN_USEC, flags, result);
    2058           0 :     return pcmk__str_copy(result);
    2059             : }
    2060             : 
    2061             : /*!
    2062             :  * \internal
    2063             :  * \brief Given a millisecond interval, return a log-friendly string
    2064             :  *
    2065             :  * \param[in] interval_ms  Interval in milliseconds
    2066             :  *
    2067             :  * \return Readable version of \p interval_ms
    2068             :  *
    2069             :  * \note The return value is a pointer to static memory that will be
    2070             :  *       overwritten by later calls to this function.
    2071             :  */
    2072             : const char *
    2073           5 : pcmk__readable_interval(guint interval_ms)
    2074             : {
    2075             : #define MS_IN_S (1000)
    2076             : #define MS_IN_M (MS_IN_S * 60)
    2077             : #define MS_IN_H (MS_IN_M * 60)
    2078             : #define MS_IN_D (MS_IN_H * 24)
    2079             : #define MAXSTR sizeof("..d..h..m..s...ms")
    2080             :     static char str[MAXSTR];
    2081           5 :     int offset = 0;
    2082             : 
    2083           5 :     str[0] = '\0';
    2084           5 :     if (interval_ms > MS_IN_D) {
    2085           1 :         offset += snprintf(str + offset, MAXSTR - offset, "%ud",
    2086             :                            interval_ms / MS_IN_D);
    2087           1 :         interval_ms -= (interval_ms / MS_IN_D) * MS_IN_D;
    2088             :     }
    2089           5 :     if (interval_ms > MS_IN_H) {
    2090           1 :         offset += snprintf(str + offset, MAXSTR - offset, "%uh",
    2091             :                            interval_ms / MS_IN_H);
    2092           1 :         interval_ms -= (interval_ms / MS_IN_H) * MS_IN_H;
    2093             :     }
    2094           5 :     if (interval_ms > MS_IN_M) {
    2095           2 :         offset += snprintf(str + offset, MAXSTR - offset, "%um",
    2096             :                            interval_ms / MS_IN_M);
    2097           2 :         interval_ms -= (interval_ms / MS_IN_M) * MS_IN_M;
    2098             :     }
    2099             : 
    2100             :     // Ns, N.NNNs, or NNNms
    2101           5 :     if (interval_ms > MS_IN_S) {
    2102           4 :         offset += snprintf(str + offset, MAXSTR - offset, "%u",
    2103             :                            interval_ms / MS_IN_S);
    2104           4 :         interval_ms -= (interval_ms / MS_IN_S) * MS_IN_S;
    2105           4 :         if (interval_ms > 0) {
    2106           2 :             offset += snprintf(str + offset, MAXSTR - offset, ".%03u",
    2107             :                                interval_ms);
    2108             :         }
    2109           4 :         (void) snprintf(str + offset, MAXSTR - offset, "s");
    2110             : 
    2111           1 :     } else if (interval_ms > 0) {
    2112           0 :         (void) snprintf(str + offset, MAXSTR - offset, "%ums", interval_ms);
    2113             : 
    2114           1 :     } else if (str[0] == '\0') {
    2115           1 :         strcpy(str, "0s");
    2116             :     }
    2117           5 :     return str;
    2118             : }

Generated by: LCOV version 1.14