1    	/*
2    	 * Copyright 2005-2026 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   	
25   	#include <glib.h>                           // g_strchomp()
26   	
27   	#include <crm/common/iso8601.h>
28   	#include "crmcommon_private.h"
29   	
30   	/*
31   	 * Andrew's code was originally written for OSes whose "struct tm" contains:
32   	 *	long tm_gmtoff;		:: Seconds east of UTC
33   	 *	const char *tm_zone;	:: Timezone abbreviation
34   	 * Some OSes lack these, instead having:
35   	 *	time_t (or long) timezone;
36   			:: "difference between UTC and local standard time"
37   	 *	char *tzname[2] = { "...", "..." };
38   	 * I (David Lee) confess to not understanding the details.  So my attempted
39   	 * generalisations for where their use is necessary may be flawed.
40   	 *
41   	 * 1. Does "difference between ..." subtract the same or opposite way?
42   	 * 2. Should it use "altzone" instead of "timezone"?
43   	 * 3. Should it use tzname[0] or tzname[1]?  Interaction with timezone/altzone?
44   	 */
45   	#if defined(HAVE_STRUCT_TM_TM_GMTOFF)
46   	#  define GMTOFF(tm) ((tm)->tm_gmtoff)
47   	#else
48   	/* Note: extern variable; macro argument not actually used.  */
49   	#  define GMTOFF(tm) (-timezone+daylight)
50   	#endif
51   	
52   	#define SECONDS_IN_MINUTE   60
53   	#define MINUTES_IN_HOUR     60
54   	#define SECONDS_IN_HOUR     (SECONDS_IN_MINUTE * MINUTES_IN_HOUR)
55   	#define HOURS_IN_DAY        24
56   	#define SECONDS_IN_DAY      (SECONDS_IN_HOUR * HOURS_IN_DAY)
57   	
58   	#define BEGIN_VALID_RANGE_S "0001-01-01T00:00:00"
59   	#define END_VALID_RANGE_S   "9999-12-31T23:59:59"
60   	
61   	/*!
62   	 * \internal
63   	 * \brief Validate a seconds/microseconds tuple
64   	 *
65   	 * The microseconds value must be in the correct range, and if both are nonzero
66   	 * they must have the same sign.
67   	 *
68   	 * \param[in] sec   Seconds
69   	 * \param[in] usec  Microseconds
70   	 *
71   	 * \return true if the seconds/microseconds tuple is valid, or false otherwise
72   	 */
73   	#define valid_sec_usec(sec, usec)               \
74   	        ((QB_ABS(usec) < QB_TIME_US_IN_SEC)     \
75   	         && (((sec) == 0) || ((usec) == 0) || (((sec) < 0) == ((usec) < 0))))
76   	
77   	/*!
78   	 * \internal
79   	 * \brief Check whether a year is positive and representable by four digits
80   	 *
81   	 * \param[in] year  Year
82   	 *
83   	 * \return \c true if \p year is between 1 and 9999 (inclusive), or \c false
84   	 *         otherwise
85   	 */
86   	bool
87   	pcmk__time_valid_year(int year)
88   	{
89   	    return (year >= 1) && (year <= 9999);
90   	}
91   	
92   	static bool
93   	is_leap_year(int year)
94   	{
95   	    /* @COMPAT Remove this fallback when we can ensure that the year argument is
96   	     * always in the range 1 to 9999.
97   	     */
98   	    if (!pcmk__time_valid_year(year)) {
99   	        return ((year % 4) == 0)
100  	                && (((year % 100) != 0) || (year % 400 == 0));
101  	    }
102  	
103  	    return g_date_is_leap_year(year);
104  	}
105  	
106  	/*!
107  	 * \internal
108  	 * \brief Return number of days in given month of given year
109  	 *
110  	 * \param[in] month  Ordinal month (1-12)
111  	 * \param[in] year   Gregorian year
112  	 *
113  	 * \return Number of days in given month (0 if given month or year is invalid)
114  	 */
115  	static int
116  	days_in_month_year(int month, int year)
117  	{
118  	    if (!g_date_valid_month(month)) {
119  	        return 0;
120  	    }
121  	
122  	    if (year < 1) {
123  	        return 0;
124  	    }
125  	
126  	    /* @COMPAT Remove this fallback when we can ensure that the year argument is
127  	     * always in the range 1 to 9999. g_date_get_days_in_month() takes a
128  	     * GDateYear, which is defined as guint16.
129  	     */
130  	    if (year > UINT16_MAX) {
131  	        static const int month_days[12] = {
132  	            31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
133  	        };
134  	
135  	        if ((month == 2) && is_leap_year(year)) {
136  	            return month_days[1] + 1;
137  	        }
138  	
139  	        return month_days[month - 1];
140  	    }
141  	
142  	    return g_date_get_days_in_month(month, year);
143  	}
144  	
145  	/*!
146  	 * \internal
147  	 * \brief Get ordinal day number of year corresponding to given date
148  	 *
149  	 * \param[in] year   Year
150  	 * \param[in] month  Month (1-12)
151  	 * \param[in] day    Day of month (1-31)
152  	 *
153  	 * \return Day number of year \p year corresponding to month \p month and day
154  	 *         \p day, or 0 for invalid arguments
155  	 */
156  	static int
157  	get_ordinal_days(uint32_t year, uint32_t month, uint32_t day)
158  	{
159  	    int prev_month_days = 0;
160  	
161  	    CRM_CHECK((year >= 1) && (year <= INT_MAX)
162  	              && (month >= 1) && (month <= 12)
163  	              && (day >= 1) && (day <= 31), return 0);
164  	
165  	    for (int i = 1; i < month; i++) {
166  	        prev_month_days += days_in_month_year(i, year);
167  	    }
168  	
169  	    return prev_month_days + day;
170  	}
171  	
172  	static int
173  	year_days(int year)
174  	{
175  	    return is_leap_year(year)? 366 : 365;
176  	}
177  	
178  	/* From http://myweb.ecu.edu/mccartyr/ISOwdALG.txt :
179  	 *
180  	 * 5. Find the Jan1Weekday for Y (Monday=1, Sunday=7)
181  	 *  YY = (Y-1) % 100
182  	 *  C = (Y-1) - YY
183  	 *  G = YY + YY/4
184  	 *  Jan1Weekday = 1 + (((((C / 100) % 4) x 5) + G) % 7)
185  	 */
186  	static int
187  	jan1_day_of_week(int year)
188  	{
189  	    int YY = (year - 1) % 100;
190  	    int C = (year - 1) - YY;
191  	    int G = YY + YY / 4;
192  	    int jan1 = 1 + (((((C / 100) % 4) * 5) + G) % 7);
193  	
194  	    pcmk__trace("YY=%d, C=%d, G=%d", YY, C, G);
195  	    pcmk__trace("January 1 %.4d: %d", year, jan1);
196  	    return jan1;
197  	}
198  	
199  	static int
200  	weeks_in_year(int year)
201  	{
202  	    int weeks = 52;
203  	    int jan1 = jan1_day_of_week(year);
204  	
205  	    /* if jan1 == thursday */
206  	    if (jan1 == 4) {
207  	        weeks++;
208  	    } else {
209  	        jan1 = jan1_day_of_week(year + 1);
210  	        /* if dec31 == thursday aka. jan1 of next year is a friday */
211  	        if (jan1 == 5) {
212  	            weeks++;
213  	        }
214  	
215  	    }
216  	    return weeks;
217  	}
218  	
219  	/*!
220  	 * \internal
221  	 * \brief Determine number of seconds from an hour:minute:second string
222  	 *
223  	 * \param[in]  time_str  Time specification string
224  	 * \param[out] result    Number of seconds equivalent to time_str
225  	 *
226  	 * \return \c true if specification was valid, or \c false otherwise
227  	 * \note This may return the number of seconds in a day (which is out of bounds
228  	 *       for a time object) if given 24:00:00.
229  	 */
230  	static bool
231  	parse_hms(const char *time_str, int *result)
232  	{
233  	    int rc;
234  	    uint32_t hour = 0;
235  	    uint32_t minute = 0;
236  	    uint32_t second = 0;
237  	
238  	    *result = 0;
239  	
240  	    // Must have at least hour, but minutes and seconds are optional
241  	    rc = sscanf(time_str, "%" SCNu32 ":%" SCNu32 ":%" SCNu32,
242  	                &hour, &minute, &second);
243  	    if (rc == 1) {
244  	        rc = sscanf(time_str, "%2" SCNu32 "%2" SCNu32 "%2" SCNu32,
245  	                    &hour, &minute, &second);
246  	    }
247  	    if (rc < 1) {
248  	        pcmk__err("'%s' is not a valid ISO 8601 time specification", time_str);
249  	        return false;
250  	    }
251  	
252  	    pcmk__trace("Got valid time: %.2" PRIu32 ":%.2" PRIu32 ":%.2" PRIu32,
253  	                hour, minute, second);
254  	
255  	    if ((hour == HOURS_IN_DAY) && (minute == 0) && (second == 0)) {
256  	        // Equivalent to 00:00:00 of next day, return number of seconds in day
257  	    } else if (hour >= HOURS_IN_DAY) {
258  	        pcmk__err("%s is not a valid ISO 8601 time specification "
259  	                  "because %" PRIu32 " is not a valid hour", time_str, hour);
260  	        return false;
261  	    }
262  	    if (minute >= MINUTES_IN_HOUR) {
263  	        pcmk__err("%s is not a valid ISO 8601 time specification "
264  	                  "because %" PRIu32 " is not a valid minute", time_str,
265  	                  minute);
266  	        return false;
267  	    }
268  	    if (second >= SECONDS_IN_MINUTE) {
269  	        pcmk__err("%s is not a valid ISO 8601 time specification "
270  	                  "because %" PRIu32 " is not a valid second", time_str,
271  	                  second);
272  	        return false;
273  	    }
274  	
275  	    *result = (hour * SECONDS_IN_HOUR) + (minute * SECONDS_IN_MINUTE) + second;
276  	    return true;
277  	}
278  	
279  	static bool
280  	parse_offset(const char *offset_str, int *offset)
281  	{
282  	    tzset();
283  	
284  	    if (offset_str == NULL) {
285  	        // Use local offset
286  	#if defined(HAVE_STRUCT_TM_TM_GMTOFF)
287  	        time_t now = time(NULL);
288  	        struct tm *now_tm = localtime(&now);
289  	#endif
290  	        int h_offset = GMTOFF(now_tm) / SECONDS_IN_HOUR;
291  	        int m_offset = (GMTOFF(now_tm) - (SECONDS_IN_HOUR * h_offset))
292  	                       / SECONDS_IN_MINUTE;
293  	
294  	        if (h_offset < 0 && m_offset < 0) {
295  	            m_offset = 0 - m_offset;
296  	        }
297  	        *offset = (SECONDS_IN_HOUR * h_offset) + (SECONDS_IN_MINUTE * m_offset);
298  	        return true;
299  	    }
300  	
301  	    if (offset_str[0] == 'Z') { // @TODO invalid if anything after?
302  	        *offset = 0;
303  	        return true;
304  	    }
305  	
306  	    *offset = 0;
307  	    if ((offset_str[0] == '+') || (offset_str[0] == '-')
308  	        || isdigit((int)offset_str[0])) {
309  	
310  	        bool negate = false;
311  	
312  	        if (offset_str[0] == '+') {
313  	            offset_str++;
314  	        } else if (offset_str[0] == '-') {
315  	            negate = true;
316  	            offset_str++;
317  	        }
318  	        if (!parse_hms(offset_str, offset)) {
319  	            return false;
320  	        }
321  	        if (negate) {
322  	            *offset = 0 - *offset;
323  	        }
324  	    } // @TODO else invalid?
325  	    return true;
326  	}
327  	
328  	/*!
329  	 * \internal
330  	 * \brief Convert seconds to hours, minutes, and seconds
331  	 *
332  	 * The resulting minutes and seconds are in the range [0, 59]. Accordingly, the
333  	 * number of hours is \p seconds_i divided by \c SECONDS_IN_HOUR.
334  	 *
335  	 * \param[in]  seconds_i  Seconds to convert
336  	 * \param[out] hours      Where to store hours
337  	 * \param[out] minutes    Where to store minutes
338  	 * \param[out] seconds    If not \c NULL, where to store seconds
339  	 */
340  	static void
341  	seconds_to_hms(int seconds_i, uint32_t *hours, uint32_t *minutes,
342  	               uint32_t *seconds)
343  	{
344  	    int hours_i = 0;
345  	    int minutes_i = 0;
346  	
347  	    hours_i = seconds_i / SECONDS_IN_HOUR;
348  	    seconds_i %= SECONDS_IN_HOUR;
349  	
350  	    minutes_i = seconds_i / SECONDS_IN_MINUTE;
351  	    seconds_i %= SECONDS_IN_MINUTE;
352  	
353  	    *hours = (uint32_t) QB_ABS(hours_i);
354  	    *minutes = (uint32_t) QB_ABS(minutes_i);
355  	    if (seconds != NULL) {
356  	        *seconds = (uint32_t) QB_ABS(seconds_i);
357  	    }
358  	}
359  	
360  	/*!
361  	 * \internal
362  	 * \brief Parse the time portion of an ISO 8601 date/time string
363  	 *
364  	 * \param[in]     time_str  Time portion of specification (after any 'T')
365  	 * \param[in,out] a_time    Time object to parse into
366  	 *
367  	 * \return \c true if valid time was parsed, \c false otherwise
368  	 * \note This may add a day to a_time (if the time is 24:00:00).
369  	 */
370  	static bool
371  	parse_time(const char *time_str, crm_time_t *a_time)
372  	{
373  	    uint32_t h = 0;
374  	    uint32_t m = 0;
375  	    const char *offset_s = NULL;
376  	
377  	    tzset();
378  	
379  	    if (!parse_hms(time_str, &a_time->seconds)) {
380  	        return false;
381  	    }
382  	
383  	    offset_s = strchr(time_str, 'Z');
384  	
385  	    /* @COMPAT: Spaces between the time and the offset are not supported by the
386  	     * standard according to section 3.4.1 and 4.2.5.2.
387  	     */
388  	    if (offset_s == NULL) {
389  	        offset_s = strpbrk(time_str, " +-");
390  	    }
391  	
392  	    if (offset_s != NULL) {
393  	        while (isspace(*offset_s)) {
394  	            offset_s++;
395  	        }
396  	    }
397  	
398  	    if (!parse_offset(offset_s, &a_time->offset)) {
399  	        return false;
400  	    }
401  	
402  	    seconds_to_hms(a_time->offset, &h, &m, NULL);
403  	    pcmk__trace("Got tz: %c%2." PRIu32 ":%.2" PRIu32,
404  	                (a_time->offset < 0)? '-' : '+', h, m);
405  	
406  	    if (a_time->seconds == SECONDS_IN_DAY) {
407  	        // 24:00:00 == 00:00:00 of next day
408  	        a_time->seconds = 0;
409  	        crm_time_add_days(a_time, 1);
410  	    }
411  	    return true;
412  	}
413  	
414  	/*!
415  	 * \internal
416  	 * \brief Check whether a time object represents a sensible date/time
417  	 *
418  	 * \param[in] dt  Date/time object to check
419  	 *
420  	 * \return \c true if days and seconds are valid given the year, or \c false
421  	 *         otherwise
422  	 */
423  	bool
424  	valid_time(const crm_time_t *dt)
425  	{
426  	    return (dt != NULL)
427  	           && (dt->days > 0) && (dt->days <= year_days(dt->years))
428  	           && (dt->seconds >= 0) && (dt->seconds < SECONDS_IN_DAY);
429  	}
430  	
431  	/*
432  	 * \internal
433  	 * \brief Parse a time object from an ISO 8601 date/time specification
434  	 *
435  	 * \param[in] date_str  ISO 8601 date/time specification (or
436  	 *                      \c PCMK__VALUE_EPOCH)
437  	 *
438  	 * \return New time object on success, NULL (and set errno) otherwise
439  	 */
440  	static crm_time_t *
441  	parse_date(const char *date_str)
442  	{
443  	    const uint32_t flags = pcmk__time_fmt_date|pcmk__time_fmt_time;
444  	    const char *time_s = NULL;
445  	    crm_time_t *dt = NULL;
446  	
447  	    uint32_t year = 0U;
448  	    uint32_t month = 0U;
449  	    uint32_t day = 0U;
450  	    uint32_t week = 0U;
451  	
452  	    int rc = 0;
453  	
454  	    if (pcmk__str_empty(date_str)) {
455  	        pcmk__err("No ISO 8601 date/time specification given");
456  	        goto invalid;
457  	    }
458  	
459  	    if ((date_str[0] == 'T')
460  	        || ((strlen(date_str) > 2) && (date_str[2] == ':'))) {
461  	        /* Just a time supplied - Infer current date */
462  	        dt = pcmk__copy_timet(time(NULL));
463  	        if (date_str[0] == 'T') {
464  	            time_s = date_str + 1;
465  	        } else {
466  	            time_s = date_str;
467  	        }
468  	        goto parse_time_segment;
469  	    }
470  	
471  	    dt = pcmk__assert_alloc(1, sizeof(crm_time_t));
472  	
473  	    if ((strncasecmp(PCMK__VALUE_EPOCH, date_str, 5) == 0)
474  	        && ((date_str[5] == '\0')
475  	            || (date_str[5] == '/')
476  	            || isspace(date_str[5]))) {
477  	
478  	        dt->days = 1;
479  	        dt->years = 1970;
480  	        pcmk__time_log(LOG_TRACE, "Unpacked", dt, flags);
481  	        return dt;
482  	    }
483  	
484  	    /* YYYY-MM-DD */
485  	    rc = sscanf(date_str, "%" SCNu32 "-%" SCNu32 "-%" SCNu32 "",
486  	                &year, &month, &day);
487  	    if (rc == 1) {
488  	        /* YYYYMMDD */
489  	        rc = sscanf(date_str, "%4" SCNu32 "%2" SCNu32 "%2" SCNu32 "",
490  	                    &year, &month, &day);
491  	    }
492  	    if (rc == 3) {
493  	        if ((month < 1U) || (month > 12U)) {
494  	            pcmk__err("'%s' is not a valid ISO 8601 date/time specification "
495  	                      "because '%" PRIu32 "' is not a valid month",
496  	                      date_str, month);
497  	            goto invalid;
498  	        } else if ((year < 1U) || (year > INT_MAX)) {
499  	            pcmk__err("'%s' is not a valid ISO 8601 date/time specification "
500  	                      "because '%" PRIu32 "' is not a valid year",
501  	                      date_str, year);
502  	            goto invalid;
503  	        } else if ((day < 1) || (day > INT_MAX)
504  	                   || (day > days_in_month_year(month, year))) {
505  	            pcmk__err("'%s' is not a valid ISO 8601 date/time specification "
506  	                      "because '%" PRIu32 "' is not a valid day of the month",
507  	                      date_str, day);
508  	            goto invalid;
509  	        } else {
510  	            dt->years = year;
511  	            dt->days = get_ordinal_days(year, month, day);
512  	            pcmk__trace("Parsed Gregorian date '%.4" PRIu32 "-%.3d' "
513  	                        "from date string '%s'", year, dt->days, date_str);
514  	        }
515  	        goto parse_time_segment;
516  	    }
517  	
518  	    /* YYYY-DDD */
519  	    rc = sscanf(date_str, "%" SCNu32 "-%" SCNu32, &year, &day);
520  	    if (rc == 2) {
521  	        if ((year < 1U) || (year > INT_MAX)) {
522  	            pcmk__err("'%s' is not a valid ISO 8601 date/time specification "
523  	                      "because '%" PRIu32 "' is not a valid year",
524  	                      date_str, year);
525  	            goto invalid;
526  	        } else if ((day < 1U) || (day > INT_MAX) || (day > year_days(year))) {
527  	            pcmk__err("'%s' is not a valid ISO 8601 date/time specification "
528  	                      "because '%" PRIu32 "' is not a valid day of year %"
529  	                      PRIu32 " (1-%d)",
530  	                      date_str, day, year, year_days(year));
531  	            goto invalid;
532  	        }
533  	        pcmk__trace("Parsed ordinal year %d and days %d from date string '%s'",
534  	                    year, day, date_str);
535  	        dt->days = day;
536  	        dt->years = year;
537  	        goto parse_time_segment;
538  	    }
539  	
540  	    /* YYYY-Www-D */
541  	    rc = sscanf(date_str, "%" SCNu32 "-W%" SCNu32 "-%" SCNu32,
542  	                &year, &week, &day);
543  	    if (rc == 3) {
544  	        if ((week < 1U) || (week > weeks_in_year(year))) {
545  	            pcmk__err("'%s' is not a valid ISO 8601 date/time specification "
546  	                      "because '%" PRIu32 "' is not a valid week of year %"
547  	                      PRIu32 " (1-%d)",
548  	                      date_str, week, year, weeks_in_year(year));
549  	            goto invalid;
550  	        } else if ((day < 1U) || (day > 7U)) {
551  	            pcmk__err("'%s' is not a valid ISO 8601 date/time specification "
552  	                      "because '%" PRIu32 "' is not a valid day of the week",
553  	                      date_str, day);
554  	            goto invalid;
555  	        } else {
556  	            /*
557  	             * See https://en.wikipedia.org/wiki/ISO_week_date
558  	             *
559  	             * Monday 29 December 2008 is written "2009-W01-1"
560  	             * Sunday 3 January 2010 is written "2009-W53-7"
561  	             * Saturday 27 September 2008 is written "2008-W37-6"
562  	             *
563  	             * If 1 January is on a Monday, Tuesday, Wednesday or Thursday, it
564  	             * is in week 1. If 1 January is on a Friday, Saturday or Sunday,
565  	             * it is in week 52 or 53 of the previous year.
566  	             */
567  	            int jan1 = jan1_day_of_week(year);
568  	
569  	            pcmk__trace("Parsed year %" PRIu32 " (Jan 1 = %d), week %" PRIu32
570  	                        ", and day %" PRIu32 " from date string '%s'",
571  	                        year, jan1, week, day, date_str);
572  	
573  	            dt->years = year;
574  	            crm_time_add_days(dt, (week - 1) * 7);
575  	
576  	            if (jan1 <= 4) {
577  	                crm_time_add_days(dt, 1 - jan1);
578  	            } else {
579  	                crm_time_add_days(dt, 8 - jan1);
580  	            }
581  	
582  	            crm_time_add_days(dt, day);
583  	        }
584  	        goto parse_time_segment;
585  	    }
586  	
587  	    pcmk__err("'%s' is not a valid ISO 8601 date/time specification", date_str);
588  	    goto invalid;
589  	
590  	parse_time_segment:
591  	    if (time_s == NULL) {
592  	        time_s = date_str + strspn(date_str, "0123456789-W");
593  	        if ((time_s[0] == ' ') || (time_s[0] == 'T')) {
594  	            ++time_s;
595  	        } else {
596  	            time_s = NULL;
597  	        }
598  	    }
599  	    if ((time_s != NULL) && !parse_time(time_s, dt)) {
600  	        goto invalid;
601  	    }
602  	
603  	    pcmk__time_log(LOG_TRACE, "Unpacked", dt, flags);
604  	
605  	    if (!valid_time(dt)) {
606  	        pcmk__err("'%s' is not a valid ISO 8601 date/time specification",
607  	                  date_str);
608  	        goto invalid;
609  	    }
610  	    return dt;
611  	
612  	invalid:
613  	    crm_time_free(dt);
614  	    errno = EINVAL;
615  	    return NULL;
616  	}
617  	
618  	// Return value is guaranteed not to be NULL
619  	static crm_time_t *
620  	copy_time_to_utc(const crm_time_t *dt)
621  	{
622  	    const uint32_t flags = pcmk__time_fmt_date
623  	                           |pcmk__time_fmt_time
624  	                           |pcmk__time_fmt_timezone;
625  	    crm_time_t *utc = NULL;
626  	
627  	    pcmk__assert(dt != NULL);
628  	
629  	    utc = pcmk__assert_alloc(1, sizeof(crm_time_t));
630  	    utc->years = dt->years;
631  	    utc->days = dt->days;
632  	    utc->seconds = dt->seconds;
633  	    utc->offset = 0;
634  	
635  	    if (dt->offset != 0) {
636  	        crm_time_add_seconds(utc, -dt->offset);
637  	
638  	    } else {
639  	        // Durations (the only things that can include months) never have a TZ
640  	        utc->months = dt->months;
641  	    }
642  	
643  	    pcmk__time_log(LOG_TRACE, "utc-source", dt, flags);
644  	    pcmk__time_log(LOG_TRACE, "utc-target", utc, flags);
645  	    return utc;
646  	}
647  	
648  	crm_time_t *
649  	crm_time_new(const char *date_time)
650  	{
651  	    tzset();
652  	    if (date_time == NULL) {
653  	        return pcmk__copy_timet(time(NULL));
654  	    }
655  	    return parse_date(date_time);
656  	}
657  	
658  	/*!
659  	 * \brief Check whether a time object has been initialized yet
660  	 *
661  	 * \param[in] dt  Time object to check
662  	 *
663  	 * \return \c true if time object has been initialized, or \c false otherwise
664  	 */
665  	bool
666  	pcmk__time_is_initialized(const crm_time_t *dt)
667  	{
668  	    // Any nonzero member indicates something has been done to dt
669  	    return (dt != NULL)
670  	           && ((dt->years != 0)
671  	               || (dt->months != 0)
672  	               || (dt->days != 0)
673  	               || (dt->seconds != 0)
674  	               || (dt->offset != 0)
675  	               || dt->duration);
676  	}
677  	
678  	void
679  	crm_time_free(crm_time_t * dt)
680  	{
681  	    if (dt == NULL) {
682  	        return;
683  	    }
684  	    free(dt);
685  	}
686  	
687  	void
688  	pcmk__time_log_as(const char *file, const char *function, int line,
689  	                  uint8_t level, const char *prefix, const crm_time_t *dt,
690  	                  uint32_t flags)
691  	{
692  	    char *date_s = pcmk__time_text(dt, flags);
693  	
694  	    if (prefix != NULL) {
695  	        char *old = date_s;
696  	
697  	        date_s = pcmk__assert_asprintf("%s: %s", prefix, date_s);
698  	        free(old);
699  	    }
700  	
701  	    if (level == PCMK__LOG_STDOUT) {
702  	        printf("%s\n", date_s);
703  	    } else {
704  	        do_crm_log_alias(level, file, function, line, "%s", date_s);
705  	    }
706  	    free(date_s);
707  	}
708  	
709  	/*!
710  	 * \internal
711  	 * \brief Convert a time object's seconds field to hours, minutes, and seconds
712  	 *
713  	 * The resulting minutes and seconds are in the range [0, 59]. Accordingly, the
714  	 * number of hours is \p dt->seconds divided by \c SECONDS_IN_HOUR. See
715  	 * \c seconds_to_hms().
716  	 *
717  	 * \param[in]   dt       Time object
718  	 * \param[out]  hours    Where to store hours
719  	 * \param[out]  minutes  Where to store minutes
720  	 * \param[out]  seconds  If not \c NULL, where to store seconds
721  	 */
722  	void
723  	pcmk__time_get_timeofday(const crm_time_t *dt, uint32_t *hours,
724  	                         uint32_t *minutes, uint32_t *seconds)
725  	{
726  	    pcmk__assert((dt != NULL) && (hours != NULL) && (minutes != NULL));
727  	    seconds_to_hms(dt->seconds, hours, minutes, seconds);
728  	}
729  	
730  	// Time in seconds since 0000-01-01 00:00:00Z
731  	long long
732  	pcmk__time_get_seconds(const crm_time_t *dt)
733  	{
734  	    crm_time_t *utc = NULL;
735  	    long long days = 0;
736  	    long long seconds = 0;
737  	
738  	    if (dt == NULL) {
739  	        return 0;
740  	    }
741  	
742  	    if (dt->offset != 0) {
743  	        utc = copy_time_to_utc(dt);
744  	        dt = utc;
745  	    }
746  	
747  	    if (dt->duration) {
748  	        /* Assume 365-day years and 30-day months. The correct number of days in
749  	         * years and months varies depending on the start date to which the
750  	         * duration will be applied, which is unknown.
751  	         */
752  	        days = (365 * (long long) dt->years)
753  	               + (30 * (long long) dt->months)
754  	               + dt->days;
755  	
756  	    } else {
757  	        // The months field can be set only for durations, so ignore it here
758  	        for (int i = 1; i < dt->years; i++) {
759  	            days += year_days(i);
760  	        }
761  	
762  	        // This is probably always true
763  	        if (dt->days > 0) {
764  	            days += dt->days - 1;
765  	        }
766  	    }
767  	
768  	    seconds = dt->seconds + (SECONDS_IN_DAY * days);
769  	
770  	    crm_time_free(utc);
771  	    return seconds;
772  	}
773  	
774  	#define EPOCH_SECONDS 62135596800ULL // Calculated using pcmk__time_get_seconds
775  	long long
776  	crm_time_get_seconds_since_epoch(const crm_time_t *dt)
777  	{
778  	    return (dt == NULL)? 0 : (pcmk__time_get_seconds(dt) - EPOCH_SECONDS);
779  	}
780  	
781  	/*!
782  	 * \internal
783  	 * \brief Convert a time object's years and seconds to year, month, and day
784  	 *
785  	 * \param[in]   dt     Time object
786  	 * \param[out]  year   Where to store year
787  	 * \param[out]  month  Where to store month
788  	 * \param[out]  day    Where to store day
789  	 *
790  	 * \note This has some incorrect behavior. See FIXME comments, and be mindful of
791  	 *       public API when fixing these issues.
792  	 */
793  	void
794  	pcmk__time_get_ymd(const crm_time_t *dt, uint32_t *year, uint32_t *month,
795  	                   uint32_t *day)
796  	{
797  	    pcmk__assert((dt != NULL) && (year != NULL) && (month != NULL)
798  	                 && (day != NULL));
799  	
800  	    if (dt->years == 0) {
801  	        CRM_LOG_ASSERT(dt->duration);
802  	
803  	        // Don't convert the days field of a duration to months
804  	        *year = 0;
805  	        *month = dt->months;
806  	        *day = dt->days;
807  	        return;
808  	    }
809  	
810  	    *year = dt->years;
811  	    *day = dt->days;
812  	
813  	    /* @FIXME This could also be a duration. It's incorrect to convert days to
814  	     * months in that case.
815  	     *
816  	     * @FIXME This could end with *month set to 13.
817  	     */
818  	    for (*month = 1; *month <= 12; (*month)++) {
819  	        int mdays = days_in_month_year(*month, dt->years);
820  	
821  	        if (mdays >= *day) {
822  	            return;
823  	        }
824  	
825  	        *day -= mdays;
826  	    }
827  	}
828  	
829  	void
830  	pcmk__time_get_ywd(const crm_time_t *dt, uint32_t *y, uint32_t *w, uint32_t *d)
831  	{
832  	    // Based on ISO week date: https://en.wikipedia.org/wiki/ISO_week_date
833  	    int year_num = 0;
834  	    int jan1 = 0;
835  	    int h = -1;
836  	
837  	    pcmk__assert((dt != NULL) && (y != NULL) && (w != NULL) && (d != NULL));
838  	
839  	    if (dt->days <= 0) {
840  	        return;
841  	    }
842  	
843  	    jan1 = jan1_day_of_week(dt->years);
844  	
845  	/* 6. Find the Weekday for Y M D */
846  	    h = dt->days + jan1 - 1;
847  	    *d = 1 + ((h - 1) % 7);
848  	
849  	/* 7. Find if Y M D falls in YearNumber Y-1, WeekNumber 52 or 53 */
850  	    if (dt->days <= (8 - jan1) && jan1 > 4) {
851  	        pcmk__trace("year--, jan1=%d", jan1);
852  	        year_num = dt->years - 1;
853  	        *w = weeks_in_year(year_num);
854  	
855  	    } else {
856  	        year_num = dt->years;
857  	    }
858  	
859  	/* 8. Find if Y M D falls in YearNumber Y+1, WeekNumber 1 */
860  	    if (year_num == dt->years) {
861  	        int dmax = year_days(year_num);
862  	        int correction = 4 - *d;
863  	
864  	        if ((dmax - dt->days) < correction) {
865  	            pcmk__trace("year++, jan1=%d, i=%d vs. %d", jan1, dmax - dt->days,
866  	                        correction);
867  	            year_num = dt->years + 1;
868  	            *w = 1;
869  	        }
870  	    }
871  	
872  	/* 9. Find if Y M D falls in YearNumber Y, WeekNumber 1 through 53 */
873  	    if (year_num == dt->years) {
874  	        int j = dt->days + (7 - *d) + (jan1 - 1);
875  	
876  	        *w = j / 7;
877  	        if (jan1 > 4) {
878  	            *w -= 1;
879  	        }
880  	    }
881  	
882  	    *y = year_num;
883  	    pcmk__trace("Converted %.4d-%.3d to %.4" PRIu32 "-W%.2" PRIu32 "-%" PRIu32,
884  	                dt->years, dt->days, *y, *w, *d);
885  	}
886  	
887  	/*!
888  	 * \internal
889  	 * \brief Print "<seconds>.<microseconds>" to a buffer
890  	 *
891  	 * \param[in]     sec   Seconds
892  	 * \param[in]     usec  Microseconds (must be of same sign as \p sec and of
893  	 *                      absolute value less than \c QB_TIME_US_IN_SEC)
894  	 * \param[in,out] buf   Result buffer
895  	 */
896  	static inline void
897  	sec_usec_as_string(long long sec, int usec, GString *buf)
898  	{
899  	    /* A negative value smaller than -1 second should have the negative sign
900  	     * before the 0, not before the usec part
901  	     */
902  	    if ((sec == 0) && (usec < 0)) {
903  	        g_string_append_c(buf, '-');
904  	    }
905  	    g_string_append_printf(buf, "%lld.%06d", sec, QB_ABS(usec));
906  	}
907  	
908  	/*!
909  	 * \internal
910  	 * \brief Get a string representation of a duration
911  	 *
912  	 * \param[in]     dt         Time object to interpret as a duration
913  	 * \param[in]     usec       Microseconds to add to \p dt
914  	 * \param[in]     show_usec  Whether to include microseconds in \p buf
915  	 * \param[in,out] buf        Result buffer
916  	 *
917  	 * \note This looks like it would produce incorrect output when \p dt has one or
918  	 *       more negative fields. As a duration, however, it generally should not.
919  	 */
920  	static void
921  	duration_as_string(const crm_time_t *dt, int usec, bool show_usec, GString *buf)
922  	{
923  	    pcmk__assert(valid_sec_usec(dt->seconds, usec));
924  	
925  	    if (dt->years != 0) {
926  	        g_string_append_printf(buf, "%4d year%s ", dt->years,
927  	                               pcmk__plural_s(dt->years));
928  	    }
929  	
930  	    if (dt->months != 0) {
931  	        g_string_append_printf(buf, "%2d month%s ", dt->months,
932  	                               pcmk__plural_s(dt->months));
933  	    }
934  	
935  	    if (dt->days != 0) {
936  	        g_string_append_printf(buf, "%2d day%s ", dt->days,
937  	                               pcmk__plural_s(dt->days));
938  	    }
939  	
940  	    // At least print seconds (and optionally usecs)
941  	    if ((buf->len == 0) || (dt->seconds != 0) || (show_usec && (usec != 0))) {
942  	        if (show_usec) {
943  	            sec_usec_as_string(dt->seconds, usec, buf);
944  	        } else {
945  	            g_string_append_printf(buf, "%d", dt->seconds);
946  	        }
947  	
948  	        g_string_append_printf(buf, " second%s", pcmk__plural_s(dt->seconds));
949  	    }
950  	
951  	    // More than one minute, so provide a more readable breakdown into units
952  	    if (QB_ABS(dt->seconds) >= SECONDS_IN_MINUTE) {
953  	        uint32_t hours = 0;
954  	        uint32_t minutes = 0;
955  	        uint32_t seconds = 0;
956  	        bool print_sec_component = false;
957  	
958  	        seconds_to_hms(dt->seconds, &hours, &minutes, &seconds);
959  	        print_sec_component = ((seconds != 0) || (show_usec && (usec != 0)));
960  	
961  	        g_string_append(buf, " (");
962  	
963  	        if (hours != 0) {
964  	            g_string_append_printf(buf, "%" PRIu32 " hour%s", hours,
965  	                                   pcmk__plural_s(hours));
966  	
967  	            if ((minutes != 0) || print_sec_component) {
968  	                g_string_append_c(buf, ' ');
969  	            }
970  	        }
971  	
972  	        if (minutes != 0) {
973  	            g_string_append_printf(buf, "%" PRIu32 " minute%s", minutes,
974  	                                   pcmk__plural_s(minutes));
975  	
976  	            if (print_sec_component) {
977  	                g_string_append_c(buf, ' ');
978  	            }
979  	        }
980  	
981  	        if (print_sec_component) {
982  	            if (show_usec) {
983  	                sec_usec_as_string(seconds, usec, buf);
984  	            } else {
985  	                g_string_append_printf(buf, "%" PRIu32, seconds);
986  	            }
987  	
988  	            g_string_append_printf(buf, " second%s",
989  	                                   pcmk__plural_s(dt->seconds));
990  	        }
991  	
992  	        g_string_append_c(buf, ')');
993  	    }
994  	}
995  	
996  	/*!
997  	 * \internal
998  	 * \brief Get a string representation of a time object
999  	 *
1000 	 * \param[in] dt     Time to convert to string
1001 	 * \param[in] usec   Microseconds to add to \p dt
1002 	 * \param[in] flags  Group of \c crm_time_* string format options
1003 	 *
1004 	 * \return Newly allocated string representation of \p dt plus \p usec
1005 	 *
1006 	 * \note The caller is responsible for freeing the return value using \c free().
1007 	 */
1008 	static char *
1009 	time_as_string_common(const crm_time_t *dt, int usec, uint32_t flags)
1010 	{
1011 	    crm_time_t *utc = NULL;
1012 	    GString *buf = NULL;
1013 	    char *result = NULL;
1014 	
1015 	    if (!pcmk__time_is_initialized(dt)) {
1016 	        return pcmk__str_copy("<undefined time>");
1017 	    }
1018 	
1019 	    pcmk__assert(valid_sec_usec(dt->seconds, usec));
1020 	
1021 	    buf = g_string_sized_new(128);
1022 	
1023 	    /* Simple cases: as duration, seconds, or seconds since epoch.
1024 	     * These never depend on time zone.
1025 	     */
1026 	
1027 	    if (pcmk__is_set(flags, pcmk__time_fmt_duration)) {
1028 	        duration_as_string(dt, usec, pcmk__is_set(flags, pcmk__time_fmt_usecs),
1029 	                           buf);
1030 	        goto done;
1031 	    }
1032 	
1033 	    if (pcmk__any_flags_set(flags,
1034 	                            pcmk__time_fmt_seconds|pcmk__time_fmt_epoch)) {
1035 	        long long seconds = 0;
1036 	
1037 	        if (pcmk__is_set(flags, pcmk__time_fmt_seconds)) {
1038 	            seconds = pcmk__time_get_seconds(dt);
1039 	        } else {
1040 	            seconds = crm_time_get_seconds_since_epoch(dt);
1041 	        }
1042 	
1043 	        if (pcmk__is_set(flags, pcmk__time_fmt_usecs)) {
1044 	            sec_usec_as_string(seconds, usec, buf);
1045 	        } else {
1046 	            g_string_append_printf(buf, "%lld", seconds);
1047 	        }
1048 	        goto done;
1049 	    }
1050 	
1051 	    // Convert to UTC if local timezone was not requested
1052 	    if ((dt->offset != 0) && !pcmk__is_set(flags, pcmk__time_fmt_timezone)) {
1053 	        utc = copy_time_to_utc(dt);
1054 	        dt = utc;
1055 	    }
1056 	
1057 	    // As readable string
1058 	
1059 	    if (pcmk__is_set(flags, pcmk__time_fmt_date)) {
1060 	        if (pcmk__is_set(flags, pcmk__time_fmt_weeks)) { // YYYY-WW-D
1061 	            if (dt->days > 0) {
1062 	                uint32_t y = 0;
1063 	                uint32_t w = 0;
1064 	                uint32_t d = 0;
1065 	
1066 	                pcmk__time_get_ywd(dt, &y, &w, &d);
1067 	                g_string_append_printf(buf,
1068 	                                       "%" PRIu32 "-W%.2" PRIu32 "-%" PRIu32,
1069 	                                       y, w, d);
1070 	            }
1071 	
1072 	        } else if (pcmk__is_set(flags, pcmk__time_fmt_ordinal)) { // YYYY-DDD
1073 	            g_string_append_printf(buf, "%d-%.3d", dt->years, dt->days);
1074 	
1075 	        } else { // YYYY-MM-DD
1076 	            uint32_t y = 0;
1077 	            uint32_t m = 0;
1078 	            uint32_t d = 0;
1079 	
1080 	            pcmk__time_get_ymd(dt, &y, &m, &d);
1081 	            g_string_append_printf(buf,
1082 	                                   "%.4" PRIu32 "-%.2" PRIu32 "-%.2" PRIu32,
1083 	                                   y, m, d);
1084 	        }
1085 	    }
1086 	
1087 	    if (pcmk__is_set(flags, pcmk__time_fmt_time)) {
1088 	        uint32_t h = 0, m = 0, s = 0;
1089 	
1090 	        if (buf->len > 0) {
1091 	            g_string_append_c(buf, ' ');
1092 	        }
1093 	
1094 	        pcmk__time_get_timeofday(dt, &h, &m, &s);
1095 	        g_string_append_printf(buf, "%.2" PRIu32 ":%.2" PRIu32 ":%.2" PRIu32,
1096 	                               h, m, s);
1097 	
1098 	        if (pcmk__is_set(flags, pcmk__time_fmt_usecs)) {
1099 	            g_string_append_printf(buf, ".%06" PRIu32, QB_ABS(usec));
1100 	        }
1101 	
1102 	        if (pcmk__is_set(flags, pcmk__time_fmt_timezone) && (dt->offset != 0)) {
1103 	            seconds_to_hms(dt->offset, &h, &m, NULL);
1104 	            g_string_append_printf(buf, " %c%.2" PRIu32 ":%.2" PRIu32,
1105 	                                   ((dt->offset < 0)? '-' : '+'), h, m);
1106 	
1107 	        } else {
1108 	            g_string_append_c(buf, 'Z');
1109 	        }
1110 	    }
1111 	
1112 	done:
1113 	    crm_time_free(utc);
1114 	    result = pcmk__str_copy(buf->str);
1115 	    g_string_free(buf, TRUE);
1116 	    return result;
1117 	}
1118 	
1119 	/*!
1120 	 * \internal
1121 	 * \brief Get a string representation of a \c crm_time_t object
1122 	 *
1123 	 * \param[in]  dt     Time to convert to string
1124 	 * \param[in]  flags  Group of \c crm_time_* string format options
1125 	 *
1126 	 * \note The caller is responsible for freeing the return value using \c free().
1127 	 */
1128 	char *
1129 	pcmk__time_text(const crm_time_t *dt, int flags)
1130 	{
1131 	    return time_as_string_common(dt, 0, flags);
1132 	}
1133 	
1134 	// Parse an ISO 8601 numeric value and return number of characters consumed
1135 	static int
1136 	parse_int(const char *str, int *result)
1137 	{
1138 	    unsigned int lpc;
1139 	    int offset = 0;
1140 	    bool negate = false;
1141 	
1142 	    *result = 0;
1143 	
1144 	    // @TODO This cannot handle combinations of these characters
1145 	    switch (str[0]) {
1146 	        case '.':
1147 	        case ',':
1148 	            return 0; // Fractions are not supported
1149 	
1150 	        case '-':
1151 	            negate = true;
1152 	            offset = 1;
1153 	            break;
1154 	
1155 	        case '+':
1156 	        case ':':
1157 	            offset = 1;
1158 	            break;
1159 	
1160 	        default:
1161 	            break;
1162 	    }
1163 	
1164 	    for (lpc = 0; (lpc < 10) && isdigit(str[offset]); lpc++) {
1165 	        const int digit = str[offset++] - '0';
1166 	
1167 	        if ((*result * 10LL + digit) > INT_MAX) {
1168 	            return 0; // Overflow
1169 	        }
1170 	        *result = *result * 10 + digit;
1171 	    }
1172 	    if (negate) {
1173 	        *result = -*result;
1174 	    }
1175 	    return (lpc > 0)? offset : 0;
1176 	}
1177 	
1178 	/*!
1179 	 * \internal
1180 	 * \brief Parse an element of an ISO 8601 duration string
1181 	 *
1182 	 * \param[in,out] element     Element to parse (within \p duration_s)
1183 	 * \param[in]     duration_s  Full duration string (for logging only)
1184 	 * \param[in,out] duration    Where to add result of parsing \p element
1185 	 * \param[in]     as_time     If \c true, \c 'M' indicates minutes; otherwise,
1186 	 *                            it indicates months
1187 	 *
1188 	 * \return Standard Pacemaker return code
1189 	 *
1190 	 * \note On successful return, \p element points to the unit designator of the
1191 	 *       element just parsed. This is a bit confusing but will suffice for now.
1192 	 * \note \p as_time is set to \c true if the caller has encountered a \c 'T'
1193 	 *       already while parsing \p duration_s.
1194 	 */
1195 	static int
1196 	parse_duration_element(const char **element, const char *duration_s,
1197 	                       crm_time_t *duration, bool as_time)
1198 	{
1199 	    int value = 0;
1200 	    int consumed = 0;
1201 	    long long result = 0;
1202 	    const char *start = *element;
1203 	
1204 	    // Component must begin with an integer
1205 	    consumed = parse_int(*element, &value);
1206 	    if (consumed == 0) {
1207 	        pcmk__err("'%s' is not a valid ISO 8601 duration because no valid "
1208 	                  "integer at '%s'", duration_s, *element);
1209 	        return pcmk_rc_bad_input;
1210 	    }
1211 	
1212 	    *element += consumed;
1213 	
1214 	    // A unit designator must be next (we're not strict about the order)
1215 	    switch (**element) {
1216 	        case 'Y':
1217 	            duration->years = value;
1218 	            return pcmk_rc_ok;
1219 	
1220 	        case 'M':
1221 	            if (!as_time) { // Months
1222 	                duration->months = value;
1223 	                return pcmk_rc_ok;
1224 	            }
1225 	
1226 	            // Minutes
1227 	            result = duration->seconds + (value * 60LL);
1228 	            if ((result < INT_MIN) || (result > INT_MAX)) {
1229 	                break;
1230 	            }
1231 	
1232 	            duration->seconds = (int) result;
1233 	            return pcmk_rc_ok;
1234 	
1235 	        case 'W':
1236 	            result = duration->days + (value * 7LL);
1237 	            if ((result < INT_MIN) || (result > INT_MAX)) {
1238 	                break;
1239 	            }
1240 	
1241 	            duration->days = (int) result;
1242 	            return pcmk_rc_ok;
1243 	
1244 	        case 'D':
1245 	            result = duration->days + (long long) value;
1246 	            if ((result < INT_MIN) || (result > INT_MAX)) {
1247 	                break;
1248 	            }
1249 	
1250 	            duration->days = (int) result;
1251 	            return pcmk_rc_ok;
1252 	
1253 	        case 'H':
1254 	            result = duration->seconds + ((long long) value * SECONDS_IN_HOUR);
1255 	            if ((result < INT_MIN) || (result > INT_MAX)) {
1256 	                break;
1257 	            }
1258 	
1259 	            duration->seconds = (int) result;
1260 	            return pcmk_rc_ok;
1261 	
1262 	        case 'S':
1263 	            result = duration->seconds + (long long) value;
1264 	            if ((result < INT_MIN) || (result > INT_MAX)) {
1265 	                break;
1266 	            }
1267 	
1268 	            duration->seconds = (int) result;
1269 	            return pcmk_rc_ok;
1270 	
1271 	        case '\0':
1272 	            pcmk__err("'%s' is not a valid ISO 8601 duration because no units "
1273 	                      "after %s", duration_s, start);
1274 	            return pcmk_rc_bad_input;
1275 	
1276 	        default:
1277 	            pcmk__err("'%s' is not a valid ISO 8601 duration because '%c' is "
1278 	                      "not a valid time unit", duration_s, **element);
1279 	            return pcmk_rc_bad_input;
1280 	    }
1281 	
1282 	    pcmk__err("'%s' could not be parsed as an ISO 8601 duration because the "
1283 	              "the parsed value for one or more time units is too large",
1284 	              duration_s);
1285 	    return pcmk_rc_bad_input;
1286 	}
1287 	
1288 	/*!
1289 	 * \internal
1290 	 * \brief Parse a time duration from an ISO 8601 duration specification
1291 	 *
1292 	 * \param[in] period_s  ISO 8601 duration specification (optionally followed by
1293 	 *                      whitespace, after which the rest of the string will be
1294 	 *                      ignored)
1295 	 *
1296 	 * \return New time object on success, or \c NULL (and set \c errno) otherwise
1297 	 * \note It is the caller's responsibility to free the result using
1298 	 *       \c crm_time_free().
1299 	 */
1300 	crm_time_t *
1301 	pcmk__time_parse_duration(const char *period_s)
1302 	{
1303 	    bool is_time = false;
1304 	    crm_time_t *diff = NULL;
1305 	
1306 	    if (pcmk__str_empty(period_s)) {
1307 	        pcmk__err("No ISO 8601 time duration given");
1308 	        goto invalid;
1309 	    }
1310 	    if (period_s[0] != 'P') {
1311 	        pcmk__err("'%s' is not a valid ISO 8601 time duration because it does "
1312 	                  "not start with a 'P'",
1313 	                  period_s);
1314 	        goto invalid;
1315 	    }
1316 	    if ((period_s[1] == '\0') || isspace(period_s[1])) {
1317 	        pcmk__err("'%s' is not a valid ISO 8601 time duration because nothing "
1318 	                  "follows 'P'",
1319 	                  period_s);
1320 	        goto invalid;
1321 	    }
1322 	
1323 	    diff = pcmk__assert_alloc(1, sizeof(crm_time_t));
1324 	
1325 	    for (const char *current = period_s + 1;
1326 	         current[0] && (current[0] != '/') && !isspace(current[0]);
1327 	         ++current) {
1328 	
1329 	        if (current[0] == 'T') {
1330 	            /* A 'T' separates year/month/day from hour/minute/seconds. We don't
1331 	             * require it strictly, but just use it to differentiate month from
1332 	             * minutes.
1333 	             */
1334 	            is_time = true;
1335 	            continue;
1336 	        }
1337 	
1338 	        // current points to last character of current element on success
1339 	        if (parse_duration_element(&current, period_s, diff,
1340 	                                   is_time) != pcmk_rc_ok) {
1341 	            goto invalid;
1342 	        }
1343 	    }
1344 	
1345 	    if (!pcmk__time_is_initialized(diff)) {
1346 	        pcmk__err("'%s' is not a valid ISO 8601 time duration because no "
1347 	                  "amounts and units given",
1348 	                  period_s);
1349 	        goto invalid;
1350 	    }
1351 	
1352 	    diff->duration = true;
1353 	    return diff;
1354 	
1355 	invalid:
1356 	    /* @COMPAT Setting errno is required only for backward compatibility with
1357 	     * crm_time_parse_duration()
1358 	     */
1359 	    crm_time_free(diff);
1360 	    errno = EINVAL;
1361 	    return NULL;
1362 	}
1363 	
1364 	/*!
1365 	 * \internal
1366 	 * \brief Set one time object to another if the other is earlier
1367 	 *
1368 	 * \param[in,out] target  Time object to set
1369 	 * \param[in]     source  Time object to use if earlier
1370 	 */
1371 	void
1372 	pcmk__set_time_if_earlier(crm_time_t *target, const crm_time_t *source)
1373 	{
1374 	    const int flags = pcmk__time_fmt_date
1375 	                      |pcmk__time_fmt_time
1376 	                      |pcmk__time_fmt_timezone;
1377 	
1378 	    if ((target == NULL)
1379 	        || (source == NULL)
1380 	        || (pcmk__time_is_initialized(target)
1381 	            && (pcmk__time_compare(source, target) >= 0))) {
1382 	
1383 	        return;
1384 	    }
1385 	
1386 	    *target = *source;
1387 	    pcmk__time_log(LOG_TRACE, "source", source, flags);
1388 	    pcmk__time_log(LOG_TRACE, "target", target, flags);
1389 	}
1390 	
1391 	crm_time_t *
1392 	pcmk_copy_time(const crm_time_t *source)
1393 	{
1394 	    crm_time_t *target = pcmk__assert_alloc(1, sizeof(crm_time_t));
1395 	
1396 	    *target = *source;
1397 	    return target;
1398 	}
1399 	
1400 	/*!
1401 	 * \internal
1402 	 * \brief Convert a \c time_t time to a \c crm_time_t time
1403 	 *
1404 	 * \param[in] source_sec  Time to convert (as seconds since epoch)
1405 	 *
1406 	 * \return Newly allocated \c crm_time_t object representing \p source_sec
1407 	 *
1408 	 * \note The caller is responsible for freeing the return value using
1409 	 *       \c crm_time_free().
1410 	 */
1411 	crm_time_t *
1412 	pcmk__copy_timet(time_t source_sec)
1413 	{
1414 	    const struct tm *source = localtime(&source_sec);
1415 	    crm_time_t *target = pcmk__assert_alloc(1, sizeof(crm_time_t));
1416 	
1417 	    int h_offset = 0;
1418 	    int m_offset = 0;
1419 	
1420 	    if (source->tm_year > 0) {
1421 	        // Years since 1900
1422 	        target->years = 1900;
1423 	        crm_time_add_years(target, source->tm_year);
1424 	    }
1425 	
1426 	    if (source->tm_yday >= 0) {
1427 	        // Days since January 1 (0-365)
1428 	        target->days = 1 + source->tm_yday;
1429 	    }
1430 	
1431 	    if (source->tm_hour >= 0) {
1432 	        target->seconds += SECONDS_IN_HOUR * source->tm_hour;
1433 	    }
1434 	
1435 	    if (source->tm_min >= 0) {
1436 	        target->seconds += SECONDS_IN_MINUTE * source->tm_min;
1437 	    }
1438 	
1439 	    if (source->tm_sec >= 0) {
1440 	        target->seconds += source->tm_sec;
1441 	    }
1442 	
1443 	    // GMTOFF(source) == offset from UTC in seconds
1444 	    h_offset = GMTOFF(source) / SECONDS_IN_HOUR;
1445 	    m_offset = (GMTOFF(source) - (SECONDS_IN_HOUR * h_offset))
1446 	               / SECONDS_IN_MINUTE;
1447 	    pcmk__trace("Time offset is %lds (%.2d:%.2d)", GMTOFF(source), h_offset,
1448 	                m_offset);
1449 	
1450 	    target->offset += SECONDS_IN_HOUR * h_offset;
1451 	    target->offset += SECONDS_IN_MINUTE * m_offset;
1452 	
1453 	    return target;
1454 	}
1455 	
1456 	crm_time_t *
1457 	crm_time_add(const crm_time_t *dt, const crm_time_t *value)
1458 	{
1459 	    crm_time_t *utc = NULL;
1460 	    crm_time_t *answer = NULL;
1461 	
1462 	    if ((dt == NULL) || (value == NULL)) {
1463 	        errno = EINVAL;
1464 	        return NULL;
1465 	    }
1466 	
1467 	    answer = pcmk_copy_time(dt);
1468 	    utc = copy_time_to_utc(value);
1469 	
1470 	    crm_time_add_years(answer, utc->years);
1471 	    crm_time_add_months(answer, utc->months);
1472 	    crm_time_add_days(answer, utc->days);
1473 	    crm_time_add_seconds(answer, utc->seconds);
1474 	
1475 	    crm_time_free(utc);
1476 	    return answer;
1477 	}
1478 	
1479 	/*!
1480 	 * \internal
1481 	 * \brief Return the XML attribute name corresponding to a time component
1482 	 *
1483 	 * \param[in] component  Component to check
1484 	 *
1485 	 * \return XML attribute name corresponding to \p component, or NULL if
1486 	 *         \p component is invalid
1487 	 */
1488 	const char *
1489 	pcmk__time_component_attr(enum pcmk__time_component component)
1490 	{
1491 	    switch (component) {
1492 	        case pcmk__time_years:
1493 	            return PCMK_XA_YEARS;
1494 	
1495 	        case pcmk__time_months:
1496 	            return PCMK_XA_MONTHS;
1497 	
1498 	        case pcmk__time_weeks:
1499 	            return PCMK_XA_WEEKS;
1500 	
1501 	        case pcmk__time_days:
1502 	            return PCMK_XA_DAYS;
1503 	
1504 	        case pcmk__time_hours:
1505 	            return PCMK_XA_HOURS;
1506 	
1507 	        case pcmk__time_minutes:
1508 	            return PCMK_XA_MINUTES;
1509 	
1510 	        case pcmk__time_seconds:
1511 	            return PCMK_XA_SECONDS;
1512 	
1513 	        default:
1514 	            return NULL;
1515 	    }
1516 	}
1517 	
1518 	typedef void (*component_fn_t)(crm_time_t *, int);
1519 	
1520 	/*!
1521 	 * \internal
1522 	 * \brief Get the addition function corresponding to a time component
1523 	 * \param[in] component  Component to check
1524 	 *
1525 	 * \return Addition function corresponding to \p component, or NULL if
1526 	 *         \p component is invalid
1527 	 */
1528 	static component_fn_t
1529 	component_fn(enum pcmk__time_component component)
1530 	{
1531 	    switch (component) {
1532 	        case pcmk__time_years:
1533 	            return crm_time_add_years;
1534 	
1535 	        case pcmk__time_months:
1536 	            return crm_time_add_months;
1537 	
1538 	        case pcmk__time_weeks:
1539 	            return crm_time_add_weeks;
1540 	
1541 	        case pcmk__time_days:
1542 	            return crm_time_add_days;
1543 	
1544 	        case pcmk__time_hours:
1545 	            return crm_time_add_hours;
1546 	
1547 	        case pcmk__time_minutes:
1548 	            return crm_time_add_minutes;
1549 	
1550 	        case pcmk__time_seconds:
1551 	            return crm_time_add_seconds;
1552 	
1553 	        default:
1554 	            return NULL;
1555 	    }
1556 	
1557 	}
1558 	
1559 	/*!
1560 	 * \internal
1561 	 * \brief Add the value of an XML attribute to a time object
1562 	 *
1563 	 * \param[in,out] t          Time object to add to
1564 	 * \param[in]     component  Component of \p t to add to
1565 	 * \param[in]     xml        XML with value to add
1566 	 *
1567 	 * \return Standard Pacemaker return code
1568 	 */
1569 	int
1570 	pcmk__add_time_from_xml(crm_time_t *t, enum pcmk__time_component component,
1571 	                        const xmlNode *xml)
1572 	{
1573 	    long long value;
1574 	    const char *attr = pcmk__time_component_attr(component);
1575 	    component_fn_t add = component_fn(component);
1576 	
1577 	    if ((t == NULL) || (attr == NULL) || (add == NULL)) {
1578 	        return EINVAL;
1579 	    }
1580 	
1581 	    if (xml == NULL) {
1582 	        return pcmk_rc_ok;
1583 	    }
1584 	
1585 	    if (pcmk__scan_ll(pcmk__xe_get(xml, attr), &value, 0LL) != pcmk_rc_ok) {
1586 	        return pcmk_rc_unpack_error;
1587 	    }
1588 	
1589 	    if ((value < INT_MIN) || (value > INT_MAX)) {
1590 	        return ERANGE;
1591 	    }
1592 	
1593 	    if (value != 0LL) {
1594 	        add(t, (int) value);
1595 	    }
1596 	    return pcmk_rc_ok;
1597 	}
1598 	
1599 	static crm_time_t *
1600 	subtract_time(const crm_time_t *dt1, const crm_time_t *dt2, bool as_duration)
1601 	{
1602 	    crm_time_t *result = NULL;
1603 	    crm_time_t *utc = NULL;
1604 	
1605 	    if ((dt1 == NULL) || (dt2 == NULL)) {
1606 	        errno = EINVAL;
1607 	        return NULL;
1608 	    }
1609 	
1610 	    result = (as_duration? copy_time_to_utc(dt1) : pcmk_copy_time(dt1));
1611 	    result->duration = as_duration;
1612 	
1613 	    utc = copy_time_to_utc(dt2);
1614 	
1615 	    // Avoid overflow when negating INT_MIN in calculations below
1616 	
1617 	    if (utc->years == INT_MIN) {
1618 	        crm_time_add_years(result, -1);
1619 	        utc->years++;
1620 	    }
1621 	    crm_time_add_years(result, -utc->years);
1622 	
1623 	    if (utc->months == INT_MIN) {
1624 	        crm_time_add_months(result, -1);
1625 	        utc->months++;
1626 	    }
1627 	    crm_time_add_months(result, -utc->months);
1628 	
1629 	    if (utc->days == INT_MIN) {
1630 	        crm_time_add_days(result, -1);
1631 	        utc->days++;
1632 	    }
1633 	    crm_time_add_days(result, -utc->days);
1634 	
1635 	    if (utc->seconds == INT_MIN) {
1636 	        crm_time_add_seconds(result, -1);
1637 	        utc->seconds++;
1638 	    }
1639 	    crm_time_add_seconds(result, -utc->seconds);
1640 	
1641 	    crm_time_free(utc);
1642 	    return result;
1643 	}
1644 	
1645 	crm_time_t *
1646 	crm_time_subtract(const crm_time_t *dt, const crm_time_t *value)
1647 	{
1648 	    return subtract_time(dt, value, false);
1649 	}
1650 	
1651 	/*!
1652 	 * \internal
1653 	 * \brief Compare two time objects
1654 	 *
1655 	 * Two time objects are equal if they're both \c NULL or if their corresponding
1656 	 * \c years, \c days, and \c seconds fields are all equal.
1657 	 *
1658 	 * If only one time object is \c NULL, then it is considered to be the earlier
1659 	 * time.
1660 	 *
1661 	 * Comparisons are performed after converting both time objects to UTC.
1662 	 *
1663 	 * \param[in] time1  First time object to compare
1664 	 * \param[in] time2  Second time object to compare
1665 	 *
1666 	 * \retval -1  if \p time1 is earlier than \p time2
1667 	 * \retval  1  if \p time1 is later than \p time2
1668 	 * \retval  0  if \p time1 and \p time2 are equal
1669 	 */
1670 	int
1671 	pcmk__time_compare(const crm_time_t *time1, const crm_time_t *time2)
1672 	{
1673 	    int rc = 0;
1674 	    crm_time_t *utc1 = NULL;
1675 	    crm_time_t *utc2 = NULL;
1676 	
1677 	    if ((time1 == NULL) && (time2 == NULL)) {
1678 	        goto done;
1679 	    }
1680 	
1681 	    if (time1 == NULL) {
1682 	        rc = -1;
1683 	        goto done;
1684 	    }
1685 	
1686 	    if (time2 == NULL) {
1687 	        rc = 1;
1688 	        goto done;
1689 	    }
1690 	
1691 	    utc1 = copy_time_to_utc(time1);
1692 	    utc2 = copy_time_to_utc(time2);
1693 	
1694 	    if (utc1->years < utc2->years) {
1695 	        pcmk__trace("Years: %d < %d", utc1->years, utc2->years);
1696 	        rc = -1;
1697 	        goto done;
1698 	    }
1699 	
1700 	    if (utc1->years > utc2->years) {
1701 	        pcmk__trace("Years: %d > %d", utc1->years, utc2->years);
1702 	        rc = 1;
1703 	        goto done;
1704 	    }
1705 	
1706 	    if (utc1->days < utc2->days) {
1707 	        pcmk__trace("Days: %d < %d", utc1->days, utc2->days);
1708 	        rc = -1;
1709 	        goto done;
1710 	    }
1711 	
1712 	    if (utc1->days > utc2->days) {
1713 	        pcmk__trace("Days: %d > %d", utc1->days, utc2->days);
1714 	        rc = 1;
1715 	        goto done;
1716 	    }
1717 	
1718 	    if (utc1->seconds < utc2->seconds) {
1719 	        pcmk__trace("Seconds: %d < %d", utc1->seconds, utc2->seconds);
1720 	        rc = -1;
1721 	        goto done;
1722 	    }
1723 	
1724 	    if (utc1->seconds > utc2->seconds) {
1725 	        pcmk__trace("Seconds: %d > %d", utc1->seconds, utc2->seconds);
1726 	        rc = 1;
1727 	        goto done;
1728 	    }
1729 	
1730 	    pcmk__trace("Times equal: %d years, %d days, %d seconds",
1731 	                utc1->years, utc1->days, utc1->seconds);
1732 	
1733 	done:
1734 	    crm_time_free(utc1);
1735 	    crm_time_free(utc2);
1736 	    return rc;
1737 	}
1738 	
1739 	/*!
1740 	 * \brief Add a given number of seconds to a date/time or duration
1741 	 *
1742 	 * \param[in,out] dt     Date/time or duration to add seconds to
1743 	 * \param[in]     value  Number of seconds to add
1744 	 */
1745 	void
1746 	crm_time_add_seconds(crm_time_t *dt, int value)
1747 	{
1748 	    int days = value / SECONDS_IN_DAY;
1749 	
1750 	    pcmk__assert(dt != NULL);
1751 	
1752 	    pcmk__trace("Adding %d seconds (including %d whole day%s) to %d", value,
1753 	                days, pcmk__plural_s(days), dt->seconds);
1754 	
1755 	    dt->seconds += value % SECONDS_IN_DAY;
1756 	
1757 	    // Check whether the addition crossed a day boundary
1758 	    if (dt->seconds > SECONDS_IN_DAY) {
1759 	        ++days;
1760 	        dt->seconds -= SECONDS_IN_DAY;
1761 	
1762 	    } else if (dt->seconds < 0) {
1763 	        --days;
1764 	        dt->seconds += SECONDS_IN_DAY;
1765 	    }
1766 	
1767 	    crm_time_add_days(dt, days);
1768 	}
1769 	
1770 	/*!
1771 	 * \brief Add days to a date/time
1772 	 *
1773 	 * \param[in,out] dt     Time to modify
1774 	 * \param[in]     value  Number of days to add (may be negative to subtract)
1775 	 */
1776 	void
1777 	crm_time_add_days(crm_time_t *dt, int value)
1778 	{
1779 	    pcmk__assert(dt != NULL);
1780 	
1781 	    pcmk__trace("Adding %d days to %.4d-%.3d", value, dt->years, dt->days);
1782 	
1783 	    if (value > 0) {
1784 	        while ((dt->days + (long long) value) > year_days(dt->years)) {
1785 	            if (dt->years == INT_MAX) {
1786 	                // Clip to latest we can handle
1787 	                dt->days = year_days(dt->years);
1788 	                return;
1789 	            }
1790 	            value -= year_days(dt->years);
1791 	            dt->years++;
1792 	        }
1793 	    } else if (value < 0) {
1794 	        const int min_days = dt->duration? 0 : 1;
1795 	
1796 	        while ((dt->days + (long long) value) < min_days) {
1797 	            if (dt->years <= 1) {
1798 	                dt->days = 1; // Clip to earliest we can handle (no BCE)
1799 	                return;
1800 	            }
1801 	            dt->years--;
1802 	            value += year_days(dt->years);
1803 	        }
1804 	    }
1805 	    dt->days += value;
1806 	}
1807 	
1808 	void
1809 	crm_time_add_months(crm_time_t *dt, int value)
1810 	{
1811 	    uint32_t year = 0;
1812 	    uint32_t month = 0;
1813 	    uint32_t day = 0;
1814 	    int days_in_month = 0;
1815 	
(1) Event function_return: Function "pcmk__time_get_ymd(dt, &year, &month, &day)" modifies its argument, assigning 0 to "year".
Also see events: [known_value_assign][overflow_const]
1816 	    pcmk__time_get_ymd(dt, &year, &month, &day);
1817 	
(2) Event path: Condition "value > 0", taking false branch.
1818 	    if (value > 0) {
1819 	        for (int i = value; i > 0; i--) {
1820 	            month++;
1821 	            if (month == 13) {
1822 	                month = 1;
1823 	                year++;
1824 	            }
1825 	        }
1826 	    } else {
(3) Event path: Condition "i < 0", taking true branch.
(7) Event path: Condition "i < 0", taking true branch.
(10) Event path: Condition "i < 0", taking true branch.
(13) Event path: Condition "i < 0", taking false branch.
1827 	        for (int i = value; i < 0; i++) {
1828 	            month--;
(4) Event path: Condition "month == 0", taking true branch.
(8) Event path: Condition "month == 0", taking false branch.
(11) Event path: Condition "month == 0", taking false branch.
1829 	            if (month == 0) {
1830 	                month = 12;
(5) Event known_value_assign: "year" = "year", its value is now 4294967295.
Also see events: [function_return][overflow_const]
1831 	                year--;
1832 	            }
(6) Event path: Jumping back to the beginning of the loop.
(9) Event path: Jumping back to the beginning of the loop.
(12) Event path: Jumping back to the beginning of the loop.
1833 	        }
1834 	    }
1835 	
1836 	    days_in_month = days_in_month_year(month, year);
1837 	
(14) Event path: Condition "days_in_month < day", taking true branch.
1838 	    if (days_in_month < day) {
1839 	        // Preserve day-of-month unless the month doesn't have enough days
1840 	        day = days_in_month;
1841 	    }
1842 	
CID (unavailable; MK=c9a82a46eb71754bfb342d710a63d0ab) (#1 of 1): Overflowed constant (INTEGER_OVERFLOW):
(15) Event overflow_const: Expression "dt->years", where "year" is known to be equal to 4294967295, overflows the type of "dt->years", which is type "int".
Also see events: [function_return][known_value_assign]
1843 	    dt->years = year;
1844 	    dt->days = get_ordinal_days(year, month, day);
1845 	}
1846 	
1847 	void
1848 	crm_time_add_minutes(crm_time_t *dt, int value)
1849 	{
1850 	    crm_time_add_seconds(dt, value * SECONDS_IN_MINUTE);
1851 	}
1852 	
1853 	void
1854 	crm_time_add_hours(crm_time_t *dt, int value)
1855 	{
1856 	    crm_time_add_seconds(dt, value * SECONDS_IN_HOUR);
1857 	}
1858 	
1859 	void
1860 	crm_time_add_weeks(crm_time_t *dt, int value)
1861 	{
1862 	    crm_time_add_days(dt, value * 7);
1863 	}
1864 	
1865 	void
1866 	crm_time_add_years(crm_time_t *dt, int value)
1867 	{
1868 	    pcmk__assert(dt != NULL);
1869 	
1870 	    if ((value > 0) && ((dt->years + (long long) value) > INT_MAX)) {
1871 	        dt->years = INT_MAX;
1872 	
1873 	    } else if ((value < 0) && ((dt->years + (long long) value) < 1)) {
1874 	        dt->years = 1; // Clip to earliest we can handle (no BCE)
1875 	
1876 	    } else {
1877 	        dt->years += value;
1878 	    }
1879 	}
1880 	
1881 	static void
1882 	ha_get_tm_time(struct tm *target, const crm_time_t *source)
1883 	{
1884 	    *target = (struct tm) {
1885 	        .tm_year = source->years - 1900,
1886 	
1887 	        /* source->days is day of year, but we assign it to tm_mday instead of
1888 	         * tm_yday. mktime() fixes it. See the mktime(3) man page for details.
1889 	         */
1890 	        .tm_mday = source->days,
1891 	
1892 	        // mktime() converts this to hours/minutes/seconds appropriately
1893 	        .tm_sec = source->seconds,
1894 	
1895 	        // Don't adjust DST here; let mktime() try to determine DST status
1896 	        .tm_isdst = -1,
1897 	
1898 	#if defined(HAVE_STRUCT_TM_TM_GMTOFF)
1899 	        .tm_gmtoff = source->offset
1900 	#endif
1901 	    };
1902 	    mktime(target);
1903 	}
1904 	
1905 	static char *
1906 	offset_text(int offset)
1907 	{
1908 	    uint32_t hours = 0;
1909 	    uint32_t minutes = 0;
1910 	
1911 	    // If offset is out of range, default to NULL
1912 	    CRM_CHECK(QB_ABS(offset) <= SECONDS_IN_DAY, return NULL);
1913 	
1914 	    seconds_to_hms(offset, &hours, &minutes, NULL);
1915 	
1916 	    return pcmk__assert_asprintf("%c%02" PRIu32 ":%02" PRIu32,
1917 	                                 ((offset >= 0)? '+' : '-'), hours, minutes);
1918 	}
1919 	
1920 	/*!
1921 	 * \internal
1922 	 * \brief Convert a <tt>struct tm</tt> to a \c GDateTime
1923 	 *
1924 	 * \param[in] tm      Time object to convert
1925 	 * \param[in] offset  Offset from UTC (in seconds)
1926 	 *
1927 	 * \return Newly allocated \c GDateTime object corresponding to \p tm, or
1928 	 *         \c NULL on error
1929 	 *
1930 	 * \note The caller is responsible for freeing the return value using
1931 	 *       \c g_date_time_unref().
1932 	 */
1933 	static GDateTime *
1934 	get_g_date_time(const struct tm *tm, int offset)
1935 	{
1936 	    // Accept an offset argument in case tm lacks a tm_gmtoff member
1937 	    char *offset_s = offset_text(offset);
1938 	    GTimeZone *tz = NULL;
1939 	    GDateTime *dt = NULL;
1940 	
1941 	    // @COMPAT Starting in GLib 2.58, we can use g_time_zone_new_offset()
1942 	    tz = g_time_zone_new(offset_s);
1943 	    if (tz == NULL) {
1944 	        goto done;
1945 	    }
1946 	
1947 	    dt = g_date_time_new(tz, tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
1948 	                         tm->tm_hour, tm->tm_min, tm->tm_sec);
1949 	
1950 	done:
1951 	    free(offset_s);
1952 	
1953 	    if (tz != NULL) {
1954 	        g_time_zone_unref(tz);
1955 	    }
1956 	
1957 	    return dt;
1958 	}
1959 	
1960 	/*!
1961 	 * \internal
1962 	 * \brief Expand a date/time format string, with support for fractional seconds
1963 	 *
1964 	 * \param[in] format  Date/time format string compatible with
1965 	 *                    \c g_date_time_format(), with additional support for
1966 	 *                    \c "%N" for fractional seconds
1967 	 * \param[in] dt      Time value to format (at seconds resolution)
1968 	 * \param[in] usec    Microseconds to add to \p dt when formatting
1969 	 *
1970 	 * \return Newly allocated string with formatted string, or \c NULL on error
1971 	 *
1972 	 * \note This function falls back to trying \c strftime() with a fixed-size
1973 	 *       buffer if \c g_date_time_format() fails. This fallback will be removed
1974 	 *       in a future release.
1975 	 */
1976 	char *
1977 	pcmk__time_format_hr(const char *format, const crm_time_t *dt, int usec)
1978 	{
1979 	    int scanned_pos = 0; // How many characters of format have been parsed
1980 	    int printed_pos = 0; // How many characters of format have been processed
1981 	    GString *buf = NULL;
1982 	    char *result = NULL;
1983 	
1984 	    struct tm tm = { 0, };
1985 	    GDateTime *gdt = NULL;
1986 	
1987 	    if (format == NULL) {
1988 	        return NULL;
1989 	    }
1990 	
1991 	    buf = g_string_sized_new(128);
1992 	
1993 	    ha_get_tm_time(&tm, dt);
1994 	    gdt = get_g_date_time(&tm, dt->offset);
1995 	    if (gdt == NULL) {
1996 	        goto done;
1997 	    }
1998 	
1999 	    while (format[scanned_pos] != '\0') {
2000 	        int fmt_pos = 0;        // Index after last character to pass as-is
2001 	        int frac_digits = 0;    // %N specifier's width field value (if any)
2002 	        gchar *tmp_fmt_s = NULL;
2003 	        gchar *date_s = NULL;
2004 	
2005 	        // Look for next format specifier
2006 	        const char *mark_s = strchr(&format[scanned_pos], '%');
2007 	
2008 	        if (mark_s == NULL) {
2009 	            // No more specifiers, so pass remaining string to strftime() as-is
2010 	            scanned_pos = strlen(format);
2011 	            fmt_pos = scanned_pos;
2012 	
2013 	        } else {
2014 	            fmt_pos = mark_s - format; // Index of %
2015 	
2016 	            // Skip % and any width field
2017 	            scanned_pos = fmt_pos + 1;
2018 	            while (isdigit(format[scanned_pos])) {
2019 	                scanned_pos++;
2020 	            }
2021 	
2022 	            switch (format[scanned_pos]) {
2023 	                case '\0': // Literal % and possibly digits at end of string
2024 	                    fmt_pos = scanned_pos; // Pass remaining string as-is
2025 	                    break;
2026 	
2027 	                case 'N': // %[width]N
2028 	                    /* Fractional seconds. This was supposed to represent
2029 	                     * nanoseconds. However, we only store times at microsecond
2030 	                     * resolution, and the width field support makes this a
2031 	                     * general fractional component specifier rather than a
2032 	                     * nanoseconds specifier.
2033 	                     *
2034 	                     * Further, since we cap the width at 6 digits, a user
2035 	                     * cannot display times at greater than microsecond
2036 	                     * resolution.
2037 	                     *
2038 	                     * A leading zero in the width field is ignored, not treated
2039 	                     * as "use zero-padding." For example, "%03N" and "%3N"
2040 	                     * produce the same result.
2041 	                     */
2042 	                    scanned_pos++;
2043 	
2044 	                    // Parse width field
2045 	                    frac_digits = atoi(&format[fmt_pos + 1]);
2046 	                    frac_digits = QB_MAX(frac_digits, 0);
2047 	                    frac_digits = QB_MIN(frac_digits, 6);
2048 	                    break;
2049 	
2050 	                default: // Some other specifier
2051 	                    if (format[++scanned_pos] != '\0') { // More to parse
2052 	                        continue;
2053 	                    }
2054 	                    fmt_pos = scanned_pos; // Pass remaining string as-is
2055 	                    break;
2056 	            }
2057 	        }
2058 	
2059 	        tmp_fmt_s = g_strndup(&format[printed_pos], fmt_pos - printed_pos);
2060 	        date_s = g_date_time_format(gdt, tmp_fmt_s);
2061 	
2062 	        if (date_s == NULL) {
2063 	            char compat_date_s[1024] = { '\0' };
2064 	            size_t nbytes = 0;
2065 	
2066 	            // @COMPAT Drop this fallback
2067 	            pcmk__warn("Could not format time using format string '%s' with "
2068 	                       "g_date_time_format(); trying strftime(). In a future "
2069 	                       "release, use of strftime() as a fallback will be "
2070 	                       "removed", format);
2071 	
2072 	#ifdef HAVE_FORMAT_NONLITERAL
2073 	#pragma GCC diagnostic push
2074 	#pragma GCC diagnostic ignored "-Wformat-nonliteral"
2075 	#endif  // HAVE_FORMAT_NONLITERAL
2076 	            nbytes = strftime(compat_date_s, sizeof(compat_date_s), tmp_fmt_s,
2077 	                              &tm);
2078 	#ifdef HAVE_FORMAT_NONLITERAL
2079 	#pragma GCC diagnostic pop
2080 	#endif  // HAVE_FORMAT_NONLITERAL
2081 	
2082 	            if (nbytes == 0) {
2083 	                // Truncation, empty string, or error; impossible to discern
2084 	                pcmk__err("Could not format time using format string '%s'",
2085 	                          format);
2086 	
2087 	                // Ensure we return NULL
2088 	                g_string_truncate(buf, 0);
2089 	                g_free(tmp_fmt_s);
2090 	                goto done;
2091 	            }
2092 	            date_s = g_strdup(compat_date_s);
2093 	        }
2094 	
2095 	        g_string_append(buf, date_s);
2096 	        g_free(date_s);
2097 	        g_free(tmp_fmt_s);
2098 	
2099 	        printed_pos = scanned_pos;
2100 	
2101 	        if (frac_digits != 0) {
2102 	            // Descending powers of 10 (10^5 down to 10^0)
2103 	            static const int powers[6] = { 1e5, 1e4, 1e3, 1e2, 1e1, 1e0 };
2104 	
2105 	            // Sanity check to ensure array access is in bounds
2106 	            pcmk__assert((frac_digits > 0) && (frac_digits <= 6));
2107 	
2108 	            /* Append fractional seconds at the requested resolution, truncated
2109 	             * toward zero. We're basically converting from microseconds to
2110 	             * another unit here. For example, suppose the width field
2111 	             * (frac_digits) is 3. This means "use millisecond resolution." Then
2112 	             * we need to divide our microseconds value by 10^3, which is
2113 	             * powers[3 - 1].
2114 	             *
2115 	             * If the width field is 6 (microsecond resolution), then we divide
2116 	             * our microseconds value by 10^0 == 1, which is powers[6 - 1].
2117 	             */
2118 	            g_string_append_printf(buf, "%0*d", frac_digits,
2119 	                                   usec / powers[frac_digits - 1]);
2120 	        }
2121 	    }
2122 	
2123 	done:
2124 	    if (buf->len > 0) {
2125 	        result = pcmk__str_copy(buf->str);
2126 	    }
2127 	    g_string_free(buf, TRUE);
2128 	
2129 	    if (gdt != NULL) {
2130 	        g_date_time_unref(gdt);
2131 	    }
2132 	    return result;
2133 	}
2134 	
2135 	/*!
2136 	 * \internal
2137 	 * \brief Return a human-friendly string corresponding to an epoch time value
2138 	 *
2139 	 * \param[in]  source  Pointer to epoch time value (or \p NULL for current time)
2140 	 * \param[in]  flags   Group of \p crm_time_* flags controlling display format
2141 	 *                     (0 to use \p ctime() with newline removed)
2142 	 *
2143 	 * \return String representation of \p source on success (may be empty depending
2144 	 *         on \p flags; guaranteed not to be \p NULL)
2145 	 *
2146 	 * \note The caller is responsible for freeing the return value using \p free().
2147 	 */
2148 	char *
2149 	pcmk__epoch2str(const time_t *source, uint32_t flags)
2150 	{
2151 	    time_t epoch_time = (source == NULL)? time(NULL) : *source;
2152 	    crm_time_t *dt = NULL;
2153 	    char *result = NULL;
2154 	
2155 	    if (flags == 0) {
2156 	        return pcmk__str_copy(g_strchomp(ctime(&epoch_time)));
2157 	    }
2158 	
2159 	    dt = pcmk__copy_timet(epoch_time);
2160 	    result = pcmk__time_text(dt, flags);
2161 	
2162 	    crm_time_free(dt);
2163 	    return result;
2164 	}
2165 	
2166 	/*!
2167 	 * \internal
2168 	 * \brief Return a human-friendly string corresponding to seconds-and-
2169 	 *        nanoseconds value
2170 	 *
2171 	 * Time is shown with microsecond resolution if \c pcmk__time_fmt_usecs is in
2172 	 * \p flags.
2173 	 *
2174 	 * \param[in]  ts     Time in seconds and nanoseconds (or \p NULL for current
2175 	 *                    time)
2176 	 * \param[in]  flags  Group of \p crm_time_* flags controlling display format
2177 	 *
2178 	 * \return String representation of \p ts on success (may be empty depending on
2179 	 *         \p flags; guaranteed not to be \p NULL)
2180 	 *
2181 	 * \note The caller is responsible for freeing the return value using \p free().
2182 	 */
2183 	char *
2184 	pcmk__timespec2str(const struct timespec *ts, uint32_t flags)
2185 	{
2186 	    struct timespec tmp_ts;
2187 	    crm_time_t *dt = NULL;
2188 	    char *result = NULL;
2189 	
2190 	    if (ts == NULL) {
2191 	        qb_util_timespec_from_epoch_get(&tmp_ts);
2192 	        ts = &tmp_ts;
2193 	    }
2194 	
2195 	    dt = pcmk__copy_timet(ts->tv_sec);
2196 	    result = time_as_string_common(dt, ts->tv_nsec / QB_TIME_NS_IN_USEC, flags);
2197 	
2198 	    crm_time_free(dt);
2199 	    return result;
2200 	}
2201 	
2202 	/*!
2203 	 * \internal
2204 	 * \brief Given a millisecond interval, return a log-friendly string
2205 	 *
2206 	 * \param[in] interval_ms  Interval in milliseconds
2207 	 *
2208 	 * \return Readable version of \p interval_ms
2209 	 *
2210 	 * \note The return value is a pointer to static memory that may be overwritten
2211 	 *       by later calls to this function.
2212 	 */
2213 	const char *
2214 	pcmk__readable_interval(unsigned int interval_ms)
2215 	{
2216 	#define MS_IN_S (1000)
2217 	#define MS_IN_M (MS_IN_S * SECONDS_IN_MINUTE)
2218 	#define MS_IN_H (MS_IN_M * MINUTES_IN_HOUR)
2219 	#define MS_IN_D (MS_IN_H * HOURS_IN_DAY)
2220 	#define MAXSTR sizeof("..d..h..m..s...ms")
2221 	    static char str[MAXSTR];
2222 	    GString *buf = NULL;
2223 	
2224 	    if (interval_ms == 0) {
2225 	        return "0s";
2226 	    }
2227 	
2228 	    buf = g_string_sized_new(128);
2229 	
2230 	    if (interval_ms >= MS_IN_D) {
2231 	        g_string_append_printf(buf, "%ud", interval_ms / MS_IN_D);
2232 	        interval_ms -= (interval_ms / MS_IN_D) * MS_IN_D;
2233 	    }
2234 	    if (interval_ms >= MS_IN_H) {
2235 	        g_string_append_printf(buf, "%uh", interval_ms / MS_IN_H);
2236 	        interval_ms -= (interval_ms / MS_IN_H) * MS_IN_H;
2237 	    }
2238 	    if (interval_ms >= MS_IN_M) {
2239 	        g_string_append_printf(buf, "%um", interval_ms / MS_IN_M);
2240 	        interval_ms -= (interval_ms / MS_IN_M) * MS_IN_M;
2241 	    }
2242 	
2243 	    // Ns, N.NNNs, or NNNms
2244 	    if (interval_ms >= MS_IN_S) {
2245 	        g_string_append_printf(buf, "%u", interval_ms / MS_IN_S);
2246 	        interval_ms -= (interval_ms / MS_IN_S) * MS_IN_S;
2247 	
2248 	        if (interval_ms > 0) {
2249 	            g_string_append_printf(buf, ".%03u", interval_ms);
2250 	        }
2251 	        g_string_append_c(buf, 's');
2252 	
2253 	    } else if (interval_ms > 0) {
2254 	        g_string_append_printf(buf, "%ums", interval_ms);
2255 	    }
2256 	
2257 	    pcmk__assert(buf->len < sizeof(str));
2258 	    strncpy(str, buf->str, sizeof(str) - 1);
2259 	    g_string_free(buf, TRUE);
2260 	    return str;
2261 	}
2262 	
2263 	// Deprecated functions kept only for backward API compatibility
2264 	// LCOV_EXCL_START
2265 	
2266 	#include <crm/common/iso8601_compat.h>
2267 	
2268 	bool
2269 	crm_time_leapyear(int year)
2270 	{
2271 	    return is_leap_year(year);
2272 	}
2273 	
2274 	int
2275 	crm_time_days_in_month(int month, int year)
2276 	{
2277 	    return days_in_month_year(month, year);
2278 	}
2279 	
2280 	int
2281 	crm_time_get_timezone(const crm_time_t *dt, uint32_t *h, uint32_t *m)
2282 	{
2283 	    seconds_to_hms(dt->seconds, h, m, NULL);
2284 	    return TRUE;
2285 	}
2286 	
2287 	int
2288 	crm_time_weeks_in_year(int year)
2289 	{
2290 	    return weeks_in_year(year);
2291 	}
2292 	
2293 	int
2294 	crm_time_january1_weekday(int year)
2295 	{
2296 	    return jan1_day_of_week(year);
2297 	}
2298 	
2299 	void
2300 	crm_time_set(crm_time_t *target, const crm_time_t *source)
2301 	{
2302 	    const uint32_t flags = pcmk__time_fmt_date
2303 	                           |pcmk__time_fmt_time
2304 	                           |pcmk__time_fmt_timezone;
2305 	
2306 	    pcmk__trace("target=%p, source=%p", target, source);
2307 	
2308 	    CRM_CHECK(target != NULL && source != NULL, return);
2309 	
2310 	    target->years = source->years;
2311 	    target->days = source->days;
2312 	    target->months = source->months;    /* Only for durations */
2313 	    target->seconds = source->seconds;
2314 	    target->offset = source->offset;
2315 	
2316 	    pcmk__time_log(LOG_TRACE, "source", source, flags);
2317 	    pcmk__time_log(LOG_TRACE, "target", target, flags);
2318 	}
2319 	
2320 	bool
2321 	crm_time_check(const crm_time_t *dt)
2322 	{
2323 	    return valid_time(dt);
2324 	}
2325 	
2326 	void
2327 	crm_time_set_timet(crm_time_t *target, const time_t *source_sec)
2328 	{
2329 	    crm_time_t *source = NULL;
2330 	
2331 	    if (source_sec == NULL) {
2332 	        return;
2333 	    }
2334 	
2335 	    source = pcmk__copy_timet(*source_sec);
2336 	    *target = *source;
2337 	    crm_time_free(source);
2338 	}
2339 	
2340 	int
2341 	crm_time_get_isoweek(const crm_time_t *dt, uint32_t *y, uint32_t *w,
2342 	                     uint32_t *d)
2343 	{
2344 	    pcmk__assert((dt != NULL) && (y != NULL) && (w != NULL) && (d != NULL));
2345 	
2346 	    CRM_CHECK(dt->days > 0, return FALSE);
2347 	    pcmk__time_get_ywd(dt, y, w, d);
2348 	    return TRUE;
2349 	}
2350 	
2351 	void
2352 	crm_time_log_alias(int log_level, const char *file, const char *function,
2353 	                   int line, const char *prefix, const crm_time_t *date_time,
2354 	                   int flags)
2355 	{
2356 	    pcmk__time_log_as(file, function, line, pcmk__clip_log_level(log_level),
2357 	                      prefix, date_time, flags);
2358 	}
2359 	
2360 	void
2361 	crm_time_free_period(crm_time_period_t *period)
2362 	{
2363 	    if (period) {
2364 	        crm_time_free(period->start);
2365 	        crm_time_free(period->end);
2366 	        crm_time_free(period->diff);
2367 	        free(period);
2368 	    }
2369 	}
2370 	
2371 	crm_time_period_t *
2372 	crm_time_parse_period(const char *period_str)
2373 	{
2374 	    const char *original = period_str;
2375 	    crm_time_period_t *period = NULL;
2376 	
2377 	    if (pcmk__str_empty(period_str)) {
2378 	        pcmk__err("No ISO 8601 time period given");
2379 	        goto invalid;
2380 	    }
2381 	
2382 	    tzset();
2383 	    period = pcmk__assert_alloc(1, sizeof(crm_time_period_t));
2384 	
2385 	    if (period_str[0] == 'P') {
2386 	        period->diff = pcmk__time_parse_duration(period_str);
2387 	        if (period->diff == NULL) {
2388 	            goto invalid;
2389 	        }
2390 	    } else {
2391 	        period->start = parse_date(period_str);
2392 	        if (period->start == NULL) {
2393 	            goto invalid;
2394 	        }
2395 	    }
2396 	
2397 	    period_str = strchr(original, '/');
2398 	    if (period_str != NULL) {
2399 	        ++period_str;
2400 	        if (period_str[0] == 'P') {
2401 	            if (period->diff != NULL) {
2402 	                pcmk__err("'%s' is not a valid ISO 8601 time period because it "
2403 	                          "has two durations",
2404 	                          original);
2405 	                goto invalid;
2406 	            }
2407 	            period->diff = pcmk__time_parse_duration(period_str);
2408 	            if (period->diff == NULL) {
2409 	                goto invalid;
2410 	            }
2411 	        } else {
2412 	            period->end = parse_date(period_str);
2413 	            if (period->end == NULL) {
2414 	                goto invalid;
2415 	            }
2416 	        }
2417 	
2418 	    } else if (period->diff != NULL) {
2419 	        // Only duration given, assume start is now
2420 	        period->start = pcmk__copy_timet(time(NULL));
2421 	
2422 	    } else {
2423 	        // Only start given
2424 	        pcmk__err("'%s' is not a valid ISO 8601 time period because it has no "
2425 	                  "duration or ending time",
2426 	                  original);
2427 	        goto invalid;
2428 	    }
2429 	
2430 	    if (period->start == NULL) {
2431 	        period->start = crm_time_subtract(period->end, period->diff);
2432 	
2433 	    } else if (period->end == NULL) {
2434 	        period->end = crm_time_add(period->start, period->diff);
2435 	    }
2436 	
2437 	    if (!pcmk__time_valid_year(period->start->years)
2438 	        || !valid_time(period->start)) {
2439 	
2440 	        pcmk__err("'%s' is not a valid ISO 8601 time period because the start "
2441 	                  "is invalid (must be between " BEGIN_VALID_RANGE_S " and "
2442 	                  END_VALID_RANGE_S ")", period_str);
2443 	        goto invalid;
2444 	    }
2445 	
2446 	    if (!pcmk__time_valid_year(period->end->years)
2447 	        || !valid_time(period->end)) {
2448 	
2449 	        pcmk__err("'%s' is not a valid ISO 8601 time period because the end is "
2450 	                  "invalid (must be between " BEGIN_VALID_RANGE_S " and "
2451 	                  END_VALID_RANGE_S ")", period_str);
2452 	        goto invalid;
2453 	    }
2454 	
2455 	    return period;
2456 	
2457 	invalid:
2458 	    errno = EINVAL;
2459 	    crm_time_free_period(period);
2460 	    return NULL;
2461 	}
2462 	
2463 	crm_time_t *
2464 	crm_time_calculate_duration(const crm_time_t *dt, const crm_time_t *value)
2465 	{
2466 	    return subtract_time(dt, value, true);
2467 	}
2468 	
2469 	crm_time_t *
2470 	crm_time_parse_duration(const char *period_s)
2471 	{
2472 	    return pcmk__time_parse_duration(period_s);
2473 	}
2474 	
2475 	crm_time_t *
2476 	crm_time_new_undefined(void)
2477 	{
2478 	    return pcmk__assert_alloc(1, sizeof(crm_time_t));
2479 	}
2480 	
2481 	bool
2482 	crm_time_is_defined(const crm_time_t *t)
2483 	{
2484 	    return pcmk__time_is_initialized(t);
2485 	}
2486 	
2487 	char *
2488 	crm_time_as_string(const crm_time_t *dt, int flags)
2489 	{
2490 	    return pcmk__time_text(dt, flags);
2491 	}
2492 	
2493 	int
2494 	crm_time_compare(const crm_time_t *a, const crm_time_t *b)
2495 	{
2496 	    return pcmk__time_compare(a, b);
2497 	}
2498 	
2499 	int
2500 	crm_time_get_timeofday(const crm_time_t *dt, uint32_t *h, uint32_t *m,
2501 	                       uint32_t *s)
2502 	{
2503 	    pcmk__time_get_timeofday(dt, h, m, s);
2504 	    return TRUE;
2505 	}
2506 	
2507 	int
2508 	crm_time_get_gregorian(const crm_time_t *dt, uint32_t *y, uint32_t *m,
2509 	                       uint32_t *d)
2510 	{
2511 	    pcmk__time_get_ymd(dt, y, m, d);
2512 	    return TRUE;
2513 	}
2514 	
2515 	int
2516 	crm_time_get_ordinal(const crm_time_t *dt, uint32_t *y, uint32_t *d)
2517 	{
2518 	    pcmk__assert((dt != NULL) && (y != NULL) && (d != NULL));
2519 	
2520 	    *y = dt->years;
2521 	    *d = dt->days;
2522 	    return TRUE;
2523 	}
2524 	
2525 	long long
2526 	crm_time_get_seconds(const crm_time_t *dt)
2527 	{
2528 	    return pcmk__time_get_seconds(dt);
2529 	}
2530 	
2531 	// LCOV_EXCL_STOP
2532 	// End deprecated API
2533