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