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