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