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