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