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