Coverage Report

Created: 2026-09-28 06:52

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