Page Menu
Home
ClusterLabs Projects
Search
Configure Global Search
Log In
Files
F4525849
iso8601.c
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
43 KB
Referenced Files
None
Subscribers
None
iso8601.c
View Options
/*
* Copyright 2005-2019 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU Lesser General Public License
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
*/
/*
* References:
* https://en.wikipedia.org/wiki/ISO_8601
* http://www.staff.science.uu.nl/~gent0113/calendar/isocalendar.htm
*/
#include <crm_internal.h>
#include <crm/crm.h>
#include <time.h>
#include <ctype.h>
#include <stdbool.h>
#include <crm/common/iso8601.h>
#include <crm/common/iso8601_internal.h>
/*
* Andrew's code was originally written for OSes whose "struct tm" contains:
* long tm_gmtoff; :: Seconds east of UTC
* const char *tm_zone; :: Timezone abbreviation
* Some OSes lack these, instead having:
* time_t (or long) timezone;
:: "difference between UTC and local standard time"
* char *tzname[2] = { "...", "..." };
* I (David Lee) confess to not understanding the details. So my attempted
* generalisations for where their use is necessary may be flawed.
*
* 1. Does "difference between ..." subtract the same or opposite way?
* 2. Should it use "altzone" instead of "timezone"?
* 3. Should it use tzname[0] or tzname[1]? Interaction with timezone/altzone?
*/
#if defined(HAVE_STRUCT_TM_TM_GMTOFF)
# define GMTOFF(tm) ((tm)->tm_gmtoff)
#else
/* Note: extern variable; macro argument not actually used. */
# define GMTOFF(tm) (-timezone+daylight)
#endif
#define HOUR_SECONDS (60 * 60)
#define DAY_SECONDS (HOUR_SECONDS * 24)
struct crm_time_s {
int years;
int months; /* Only for durations */
int days;
int seconds;
int offset; /* Seconds */
bool duration;
};
char *crm_time_as_string(crm_time_t * date_time, int flags);
static crm_time_t *parse_date(const char *date_str);
gboolean check_for_ordinal(const char *str);
static crm_time_t *
crm_get_utc_time(crm_time_t * dt)
{
crm_time_t *utc = crm_time_new_undefined();
utc->years = dt->years;
utc->days = dt->days;
utc->seconds = dt->seconds;
utc->offset = 0;
if (dt->offset) {
crm_time_add_seconds(utc, -dt->offset);
} else {
/* Durations (which are the only things that can include months, never have a timezone */
utc->months = dt->months;
}
crm_time_log(LOG_TRACE, "utc-source", dt,
crm_time_log_date | crm_time_log_timeofday | crm_time_log_with_timezone);
crm_time_log(LOG_TRACE, "utc-target", utc,
crm_time_log_date | crm_time_log_timeofday | crm_time_log_with_timezone);
return utc;
}
crm_time_t *
crm_time_new(const char *date_time)
{
time_t tm_now;
crm_time_t *dt = NULL;
tzset();
if (date_time == NULL) {
tm_now = time(NULL);
dt = crm_time_new_undefined();
crm_time_set_timet(dt, &tm_now);
} else {
dt = parse_date(date_time);
}
return dt;
}
/*!
* \brief Allocate memory for an uninitialized time object
*
* \return Newly allocated time object
* \note The caller is responsible for freeing the return value using
* crm_time_free().
*/
crm_time_t *
crm_time_new_undefined()
{
crm_time_t *result = calloc(1, sizeof(crm_time_t));
CRM_ASSERT(result != NULL);
return result;
}
/*!
* \brief Check whether a time object has been initialized yet
*
* \param[in] t Time object to check
*
* \return TRUE if time object has been initialized, FALSE otherwise
*/
bool
crm_time_is_defined(const crm_time_t *t)
{
// Any nonzero member indicates something has been done to t
return (t != NULL) && (t->years || t->months || t->days || t->seconds
|| t->offset || t->duration);
}
void
crm_time_free(crm_time_t * dt)
{
if (dt == NULL) {
return;
}
free(dt);
}
static int
year_days(int year)
{
int d = 365;
if (crm_time_leapyear(year)) {
d++;
}
return d;
}
/* From http://myweb.ecu.edu/mccartyr/ISOwdALG.txt :
*
* 5. Find the Jan1Weekday for Y (Monday=1, Sunday=7)
* YY = (Y-1) % 100
* C = (Y-1) - YY
* G = YY + YY/4
* Jan1Weekday = 1 + (((((C / 100) % 4) x 5) + G) % 7)
*/
int
crm_time_january1_weekday(int year)
{
int YY = (year - 1) % 100;
int C = (year - 1) - YY;
int G = YY + YY / 4;
int jan1 = 1 + (((((C / 100) % 4) * 5) + G) % 7);
crm_trace("YY=%d, C=%d, G=%d", YY, C, G);
crm_trace("January 1 %.4d: %d", year, jan1);
return jan1;
}
int
crm_time_weeks_in_year(int year)
{
int weeks = 52;
int jan1 = crm_time_january1_weekday(year);
/* if jan1 == thursday */
if (jan1 == 4) {
weeks++;
} else {
jan1 = crm_time_january1_weekday(year + 1);
/* if dec31 == thursday aka. jan1 of next year is a friday */
if (jan1 == 5) {
weeks++;
}
}
return weeks;
}
// Jan-Dec plus Feb of leap years
static int month_days[13] = {
31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 29
};
/*!
* \brief Return number of days in given month of given year
*
* \param[in] Ordinal month (1-12)
* \param[in] Gregorian year
*
* \return Number of days in given month (0 if given month is invalid)
*/
int
crm_time_days_in_month(int month, int year)
{
if ((month < 1) || (month > 12)) {
return 0;
}
if ((month == 2) && crm_time_leapyear(year)) {
month = 13;
}
return month_days[month - 1];
}
bool
crm_time_leapyear(int year)
{
gboolean is_leap = FALSE;
if (year % 4 == 0) {
is_leap = TRUE;
}
if (year % 100 == 0 && year % 400 != 0) {
is_leap = FALSE;
}
return is_leap;
}
static uint32_t
get_ordinal_days(uint32_t y, uint32_t m, uint32_t d)
{
int lpc;
for (lpc = 1; lpc < m; lpc++) {
d += crm_time_days_in_month(lpc, y);
}
return d;
}
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)
{
char *date_s = crm_time_as_string(date_time, flags);
if (log_level < LOG_CRIT) {
printf("%s%s%s\n",
(prefix? prefix : ""), (prefix? ": " : ""), date_s);
} else {
do_crm_log_alias(log_level, file, function, line, "%s%s%s",
(prefix? prefix : ""), (prefix? ": " : ""), date_s);
}
free(date_s);
}
static int
crm_time_get_sec(int sec, uint * h, uint * m, uint * s)
{
uint hours, minutes, seconds;
if (sec < 0) {
seconds = 0 - sec;
} else {
seconds = sec;
}
hours = seconds / HOUR_SECONDS;
seconds -= HOUR_SECONDS * hours;
minutes = seconds / 60;
seconds -= 60 * minutes;
crm_trace("%d == %.2d:%.2d:%.2d", sec, hours, minutes, seconds);
*h = hours;
*m = minutes;
*s = seconds;
return TRUE;
}
int
crm_time_get_timeofday(crm_time_t * dt, uint * h, uint * m, uint * s)
{
return crm_time_get_sec(dt->seconds, h, m, s);
}
int
crm_time_get_timezone(crm_time_t * dt, uint * h, uint * m)
{
uint s;
return crm_time_get_sec(dt->seconds, h, m, &s);
}
long long
crm_time_get_seconds(crm_time_t * dt)
{
int lpc;
crm_time_t *utc = NULL;
long long in_seconds = 0;
utc = crm_get_utc_time(dt);
for (lpc = 1; lpc < utc->years; lpc++) {
int dmax = year_days(lpc);
in_seconds += DAY_SECONDS * dmax;
}
/* utc->months is an offset that can only be set for a duration.
* By definition, the value is variable depending on the date to
* which it is applied.
*
* Force 30-day months so that something vaguely sane happens
* for anyone that tries to use a month in this way.
*/
if (utc->months > 0) {
in_seconds += DAY_SECONDS * 30 * utc->months;
}
if (utc->days > 0) {
in_seconds += DAY_SECONDS * (utc->days - 1);
}
in_seconds += utc->seconds;
crm_time_free(utc);
return in_seconds;
}
#define EPOCH_SECONDS 62135596800ULL /* Calculated using crm_time_get_seconds() */
long long
crm_time_get_seconds_since_epoch(crm_time_t * dt)
{
return crm_time_get_seconds(dt) - EPOCH_SECONDS;
}
int
crm_time_get_gregorian(crm_time_t * dt, uint * y, uint * m, uint * d)
{
int months = 0;
int days = dt->days;
if(dt->years != 0) {
for (months = 1; months <= 12 && days > 0; months++) {
int mdays = crm_time_days_in_month(months, dt->years);
if (mdays >= days) {
break;
} else {
days -= mdays;
}
}
} else if (dt->months) {
/* This is a duration including months, don't convert the days field */
months = dt->months;
} else {
/* This is a duration not including months, still don't convert the days field */
}
*y = dt->years;
*m = months;
*d = days;
crm_trace("%.4d-%.3d -> %.4d-%.2d-%.2d", dt->years, dt->days, dt->years, months, days);
return TRUE;
}
int
crm_time_get_ordinal(crm_time_t * dt, uint * y, uint * d)
{
*y = dt->years;
*d = dt->days;
return TRUE;
}
int
crm_time_get_isoweek(crm_time_t * dt, uint * y, uint * w, uint * d)
{
/*
* Monday 29 December 2008 is written "2009-W01-1"
* Sunday 3 January 2010 is written "2009-W53-7"
*/
int year_num = 0;
int jan1 = crm_time_january1_weekday(dt->years);
int h = -1;
CRM_CHECK(dt->days > 0, return FALSE);
/* 6. Find the Weekday for Y M D */
h = dt->days + jan1 - 1;
*d = 1 + ((h - 1) % 7);
/* 7. Find if Y M D falls in YearNumber Y-1, WeekNumber 52 or 53 */
if (dt->days <= (8 - jan1) && jan1 > 4) {
crm_trace("year--, jan1=%d", jan1);
year_num = dt->years - 1;
*w = crm_time_weeks_in_year(year_num);
} else {
year_num = dt->years;
}
/* 8. Find if Y M D falls in YearNumber Y+1, WeekNumber 1 */
if (year_num == dt->years) {
int dmax = year_days(year_num);
int correction = 4 - *d;
if ((dmax - dt->days) < correction) {
crm_trace("year++, jan1=%d, i=%d vs. %d", jan1, dmax - dt->days, correction);
year_num = dt->years + 1;
*w = 1;
}
}
/* 9. Find if Y M D falls in YearNumber Y, WeekNumber 1 through 53 */
if (year_num == dt->years) {
int j = dt->days + (7 - *d) + (jan1 - 1);
*w = j / 7;
if (jan1 > 4) {
*w -= 1;
}
}
*y = year_num;
crm_trace("Converted %.4d-%.3d to %.4d-W%.2d-%d", dt->years, dt->days, *y, *w, *d);
return TRUE;
}
#define DATE_MAX 128
#define s_if_plural(i) (((i) == 1)? "" : "s")
static void
crm_duration_as_string(crm_time_t *dt, char *result)
{
size_t offset = 0;
if (dt->years) {
offset += snprintf(result + offset, DATE_MAX - offset, "%4d year%s ",
dt->years, s_if_plural(dt->years));
}
if (dt->months) {
offset += snprintf(result + offset, DATE_MAX - offset, "%2d month%s ",
dt->months, s_if_plural(dt->months));
}
if (dt->days) {
offset += snprintf(result + offset, DATE_MAX - offset, "%2d day%s ",
dt->days, s_if_plural(dt->days));
}
if (((offset == 0) || (dt->seconds != 0))
&& (dt->seconds > -60) && (dt->seconds < 60)) {
offset += snprintf(result + offset, DATE_MAX - offset, "%d second%s",
dt->seconds, s_if_plural(dt->seconds));
} else if (dt->seconds) {
uint h = 0, m = 0, s = 0;
offset += snprintf(result + offset, DATE_MAX - offset, "%d seconds (",
dt->seconds);
crm_time_get_sec(dt->seconds, &h, &m, &s);
if (h) {
offset += snprintf(result + offset, DATE_MAX - offset, "%u hour%s%s",
h, s_if_plural(h), ((m || s)? " " : ""));
}
if (m) {
offset += snprintf(result + offset, DATE_MAX - offset, "%u minute%s%s",
m, s_if_plural(m), (s? " " : ""));
}
if (s) {
offset += snprintf(result + offset, DATE_MAX - offset, "%u second%s",
s, s_if_plural(s));
}
offset += snprintf(result + offset, DATE_MAX - offset, ")");
}
}
char *
crm_time_as_string(crm_time_t * date_time, int flags)
{
crm_time_t *dt = NULL;
crm_time_t *utc = NULL;
char result[DATE_MAX] = { '\0', };
char *result_copy = NULL;
size_t offset = 0;
// Convert to UTC if local timezone was not requested
if (date_time && date_time->offset
&& is_not_set(flags, crm_time_log_with_timezone)) {
crm_trace("UTC conversion");
utc = crm_get_utc_time(date_time);
dt = utc;
} else {
dt = date_time;
}
if (!crm_time_is_defined(dt)) {
strcpy(result, "<undefined time>");
goto done;
}
// Simple cases: as duration, seconds, or seconds since epoch
if (flags & crm_time_log_duration) {
crm_duration_as_string(date_time, result);
goto done;
}
if (flags & crm_time_seconds) {
snprintf(result, DATE_MAX, "%lld", crm_time_get_seconds(date_time));
goto done;
}
if (flags & crm_time_epoch) {
snprintf(result, DATE_MAX, "%lld",
crm_time_get_seconds_since_epoch(date_time));
goto done;
}
// As readable string
if (flags & crm_time_log_date) {
if (flags & crm_time_weeks) { // YYYY-WW-D
uint y, w, d;
if (crm_time_get_isoweek(dt, &y, &w, &d)) {
offset += snprintf(result + offset, DATE_MAX, "%u-W%.2u-%u",
y, w, d);
}
} else if (flags & crm_time_ordinal) { // YYYY-DDD
uint y, d;
if (crm_time_get_ordinal(dt, &y, &d)) {
offset += snprintf(result + offset, DATE_MAX, "%u-%.3u", y, d);
}
} else { // YYYY-MM-DD
uint y, m, d;
if (crm_time_get_gregorian(dt, &y, &m, &d)) {
offset += snprintf(result + offset, DATE_MAX, "%.4u-%.2u-%.2u",
y, m, d);
}
}
}
if (flags & crm_time_log_timeofday) {
uint h = 0, m = 0, s = 0;
if (offset > 0) {
offset += snprintf(result + offset, DATE_MAX, " ");
}
if (crm_time_get_timeofday(dt, &h, &m, &s)) {
offset += snprintf(result + offset, DATE_MAX, "%.2u:%.2u:%.2u",
h, m, s);
}
if ((flags & crm_time_log_with_timezone) && (dt->offset != 0)) {
crm_time_get_sec(dt->offset, &h, &m, &s);
offset += snprintf(result + offset, DATE_MAX, " %c%.2u:%.2u",
((dt->offset < 0)? '-' : '+'), h, m);
} else {
offset += snprintf(result + offset, DATE_MAX, "Z");
}
}
done:
crm_time_free(utc);
result_copy = strdup(result);
CRM_ASSERT(result_copy != NULL);
return result_copy;
}
/*!
* \internal
* \brief Determine number of seconds from an hour:minute:second string
*
* \param[in] time_str Time specification string
* \param[out] result Number of seconds equivalent to time_str
*
* \return TRUE if specification was valid, FALSE (and set errno) otherwise
*/
static bool
crm_time_parse_sec(const char *time_str, int *result)
{
int rc;
uint hour = 0;
uint minute = 0;
uint second = 0;
*result = 0;
// Must have at least hour, but minutes and seconds are optional
rc = sscanf(time_str, "%d:%d:%d", &hour, &minute, &second);
if (rc == 1) {
rc = sscanf(time_str, "%2d%2d%2d", &hour, &minute, &second);
}
if (rc == 0) {
crm_err("%s is not a valid ISO 8601 time specification", time_str);
errno = EINVAL;
return FALSE;
}
crm_trace("Got valid time: %.2d:%.2d:%.2d", hour, minute, second);
// @TODO ISO 8601 allows YMD w/24:00:00 as equivalent to YMD+1 w/00:00:00
if (hour >= 24) {
crm_err("%s is not a valid ISO 8601 time specification "
"because %d is not a valid hour", time_str, hour);
errno = EINVAL;
return FALSE;
}
if (minute >= 60) {
crm_err("%s is not a valid ISO 8601 time specification "
"because %d is not a valid minute", time_str, minute);
errno = EINVAL;
return FALSE;
}
if (second >= 60) {
crm_err("%s is not a valid ISO 8601 time specification "
"because %d is not a valid second", time_str, second);
errno = EINVAL;
return FALSE;
}
*result = (hour * HOUR_SECONDS) + (minute * 60) + second;
return TRUE;
}
static bool
crm_time_parse_offset(const char *offset_str, int *offset)
{
tzset();
if (offset_str == NULL) {
// Use local offset
#if defined(HAVE_STRUCT_TM_TM_GMTOFF)
time_t now = time(NULL);
struct tm *now_tm = localtime(&now);
#endif
int h_offset = GMTOFF(now_tm) / HOUR_SECONDS;
int m_offset = (GMTOFF(now_tm) - (HOUR_SECONDS * h_offset)) / 60;
if (h_offset < 0 && m_offset < 0) {
m_offset = 0 - m_offset;
}
*offset = (HOUR_SECONDS * h_offset) + (60 * m_offset);
return TRUE;
}
if (offset_str[0] == 'Z') { // @TODO invalid if anything after?
*offset = 0;
return TRUE;
}
*offset = 0;
if ((offset_str[0] == '+') || (offset_str[0] == '-')
|| isdigit((int)offset_str[0])) {
gboolean negate = FALSE;
if (offset_str[0] == '-') {
negate = TRUE;
offset_str++;
}
if (crm_time_parse_sec(offset_str, offset) == FALSE) {
return FALSE;
}
if (negate) {
*offset = 0 - *offset;
}
} // @TODO else invalid?
return TRUE;
}
static bool
crm_time_parse(const char *time_str, crm_time_t *a_time)
{
uint h, m, s;
char *offset_s = NULL;
tzset();
if (time_str) {
if (crm_time_parse_sec(time_str, &(a_time->seconds)) == FALSE) {
return FALSE;
}
offset_s = strstr(time_str, "Z");
if (offset_s == NULL) {
offset_s = strstr(time_str, " ");
if (offset_s) {
while (isspace(offset_s[0])) {
offset_s++;
}
}
}
}
if (crm_time_parse_offset(offset_s, &(a_time->offset)) == FALSE) {
return FALSE;
}
crm_time_get_sec(a_time->offset, &h, &m, &s);
crm_trace("Got tz: %c%2.d:%.2d", ((a_time->offset < 0)? '-' : '+'), h, m);
return TRUE;
}
/*
* \internal
* \brief Parse a time object from an ISO 8601 date/time specification
*
* \param[in] date_str ISO 8601 date/time specification (or "epoch")
*
* \return New time object on success, NULL (and set errno) otherwise
*/
static crm_time_t *
parse_date(const char *date_str)
{
const char *time_s = NULL;
crm_time_t *dt = NULL;
int year = 0;
int month = 0;
int week = 0;
int day = 0;
int rc = 0;
if ((date_str == NULL) || (date_str[0] == '\0')) {
crm_err("No ISO 8601 date/time specification given");
goto invalid;
}
if ((date_str[0] == 'T') || (date_str[2] == ':')) {
/* Just a time supplied - Infer current date */
dt = crm_time_new(NULL);
if (date_str[0] == 'T') {
time_s = date_str + 1;
} else {
time_s = date_str;
}
goto parse_time;
}
dt = crm_time_new_undefined();
if (!strncasecmp("epoch", date_str, 5)
&& ((date_str[5] == '\0') || (date_str[5] == '/') || isspace(date_str[5]))) {
dt->days = 1;
dt->years = 1970;
crm_time_log(LOG_TRACE, "Unpacked", dt, crm_time_log_date | crm_time_log_timeofday);
return dt;
}
/* YYYY-MM-DD */
rc = sscanf(date_str, "%d-%d-%d", &year, &month, &day);
if (rc == 1) {
/* YYYYMMDD */
rc = sscanf(date_str, "%4d%2d%2d", &year, &month, &day);
}
if (rc == 3) {
if (month > 12) {
crm_err("'%s' is not a valid ISO 8601 date/time specification "
"because '%d' is not a valid month", date_str, month);
goto invalid;
} else if (day > crm_time_days_in_month(month, year)) {
crm_err("'%s' is not a valid ISO 8601 date/time specification "
"because '%d' is not a valid day of the month",
date_str, day);
goto invalid;
} else {
dt->years = year;
dt->days = get_ordinal_days(year, month, day);
crm_trace("Parsed Gregorian date '%.4d-%.3d' from date string '%s'",
year, dt->days, date_str);
}
goto parse_time;
}
/* YYYY-DDD */
rc = sscanf(date_str, "%d-%d", &year, &day);
if (rc == 2) {
if (day > year_days(year)) {
crm_err("'%s' is not a valid ISO 8601 date/time specification "
"because '%d' is not a valid day of the year (max %d)",
date_str, day, year_days(year));
goto invalid;
}
crm_trace("Parsed ordinal year %d and days %d from date string '%s'",
year, day, date_str);
dt->days = day;
dt->years = year;
goto parse_time;
}
/* YYYY-Www-D */
rc = sscanf(date_str, "%d-W%d-%d", &year, &week, &day);
if (rc == 3) {
if (week > crm_time_weeks_in_year(year)) {
crm_err("'%s' is not a valid ISO 8601 date/time specification "
"because '%d' is not a valid week of the year (max %d)",
date_str, week, crm_time_weeks_in_year(year));
goto invalid;
} else if (day < 1 || day > 7) {
crm_err("'%s' is not a valid ISO 8601 date/time specification "
"because '%d' is not a valid day of the week",
date_str, day);
goto invalid;
} else {
/*
* See https://en.wikipedia.org/wiki/ISO_week_date
*
* Monday 29 December 2008 is written "2009-W01-1"
* Sunday 3 January 2010 is written "2009-W53-7"
* Saturday 27 September 2008 is written "2008-W37-6"
*
* If 1 January is on a Monday, Tuesday, Wednesday or Thursday, it is in week 01.
* If 1 January is on a Friday, Saturday or Sunday, it is in week 52 or 53 of the previous year.
*/
int jan1 = crm_time_january1_weekday(year);
crm_trace("Got year %d (Jan 1 = %d), week %d, and day %d from date string '%s'",
year, jan1, week, day, date_str);
dt->years = year;
crm_time_add_days(dt, (week - 1) * 7);
if (jan1 <= 4) {
crm_time_add_days(dt, 1 - jan1);
} else {
crm_time_add_days(dt, 8 - jan1);
}
crm_time_add_days(dt, day);
}
goto parse_time;
}
crm_err("'%s' is not a valid ISO 8601 date/time specification", date_str);
goto invalid;
parse_time:
if (time_s == NULL) {
// @TODO look immediately after date spec rather than anywhere
time_s = strstr(date_str, " ");
if (time_s == NULL) {
time_s = strstr(date_str, "T");
}
if (time_s != NULL) {
++time_s;
}
}
if ((time_s != NULL) && (crm_time_parse(time_s, dt) == FALSE)) {
goto invalid;
}
crm_time_log(LOG_TRACE, "Unpacked", dt, crm_time_log_date | crm_time_log_timeofday);
if (crm_time_check(dt) == FALSE) {
crm_err("'%s' is not a valid ISO 8601 date/time specification",
date_str);
goto invalid;
}
return dt;
invalid:
crm_time_free(dt);
errno = EINVAL;
return NULL;
}
// Parse an ISO 8601 numeric value and return number of characters consumed
// @TODO This cannot handle >INT_MAX int values
// @TODO Fractions appear to be not working
// @TODO Error out on invalid specifications
static int
parse_int(const char *str, int field_width, int upper_bound, int *result)
{
int lpc = 0;
int offset = 0;
int intermediate = 0;
gboolean fraction = FALSE;
gboolean negate = FALSE;
*result = 0;
if (*str == '\0') {
return 0;
}
if (str[offset] == 'T') {
offset++;
}
if (str[offset] == '.' || str[offset] == ',') {
fraction = TRUE;
field_width = -1;
offset++;
} else if (str[offset] == '-') {
negate = TRUE;
offset++;
} else if (str[offset] == '+' || str[offset] == ':') {
offset++;
}
for (; (fraction || lpc < field_width) && isdigit((int)str[offset]); lpc++) {
if (fraction) {
intermediate = (str[offset] - '0') / (10 ^ lpc);
} else {
*result *= 10;
intermediate = str[offset] - '0';
}
*result += intermediate;
offset++;
}
if (fraction) {
*result = (int)(*result * upper_bound);
} else if (upper_bound > 0 && *result > upper_bound) {
*result = upper_bound;
}
if (negate) {
*result = 0 - *result;
}
if (lpc > 0) {
crm_trace("Found int: %d. Stopped at str[%d]='%c'", *result, lpc, str[lpc]);
return offset;
}
return 0;
}
/*!
* \brief Parse a time duration from an ISO 8601 duration specification
*
* \param[in] period_s ISO 8601 duration specification (optionally followed by
* whitespace, after which the rest of the string will be
* ignored)
*
* \return New time object on success, NULL (and set errno) otherwise
* \note It is the caller's responsibility to return the result using
* crm_time_free().
*/
crm_time_t *
crm_time_parse_duration(const char *period_s)
{
gboolean is_time = FALSE;
crm_time_t *diff = NULL;
if ((period_s == NULL) || (period_s[0] == '\0')) {
crm_err("No ISO 8601 time duration given");
goto invalid;
}
if (period_s[0] != 'P') {
crm_err("'%s' is not a valid ISO 8601 time duration "
"because it does not start with a 'P'", period_s);
goto invalid;
}
if ((period_s[1] == '\0') || isspace(period_s[1])) {
crm_err("'%s' is not a valid ISO 8601 time duration "
"because nothing follows 'P'", period_s);
goto invalid;
}
diff = crm_time_new_undefined();
diff->duration = TRUE;
for (const char *current = period_s + 1;
current[0] && (current[0] != '/') && !isspace(current[0]);
++current) {
int an_int = 0, rc;
if (current[0] == 'T') {
/* A 'T' separates year/month/day from hour/minute/seconds. We don't
* require it strictly, but just use it to differentiate month from
* minutes.
*/
is_time = TRUE;
continue;
}
// An integer must be next
rc = parse_int(current, 10, 0, &an_int);
if (rc == 0) {
crm_err("'%s' is not a valid ISO 8601 time duration "
"because no integer at '%s'", period_s, current);
goto invalid;
}
current += rc;
// A time unit must be next (we're not strict about the order)
switch (current[0]) {
case 'Y':
diff->years = an_int;
break;
case 'M':
if (is_time) {
/* Minutes */
diff->seconds += an_int * 60;
} else {
diff->months = an_int;
}
break;
case 'W':
diff->days += an_int * 7;
break;
case 'D':
diff->days += an_int;
break;
case 'H':
diff->seconds += an_int * HOUR_SECONDS;
break;
case 'S':
diff->seconds += an_int;
break;
case '\0':
crm_err("'%s' is not a valid ISO 8601 time duration "
"because no units after %d", period_s, an_int);
goto invalid;
default:
crm_err("'%s' is not a valid ISO 8601 time duration "
"because '%c' is not a valid time unit",
period_s, current[0]);
goto invalid;
}
}
if (!crm_time_is_defined(diff)) {
crm_err("'%s' is not a valid ISO 8601 time duration "
"because no amounts and units given", period_s);
goto invalid;
}
return diff;
invalid:
crm_time_free(diff);
errno = EINVAL;
return NULL;
}
/*!
* \brief Parse a time period from an ISO 8601 interval specification
*
* \param[in] period_str ISO 8601 interval specification (start/end,
* start/duration, or duration/end)
*
* \return New time period object on success, NULL (and set errno) otherwise
* \note The caller is responsible for freeing the result using
* crm_time_free_period().
*/
crm_time_period_t *
crm_time_parse_period(const char *period_str)
{
const char *original = period_str;
crm_time_period_t *period = NULL;
if ((period_str == NULL) || (period_str[0] == '\0')) {
crm_err("No ISO 8601 time period given");
goto invalid;
}
tzset();
period = calloc(1, sizeof(crm_time_period_t));
CRM_ASSERT(period != NULL);
if (period_str[0] == 'P') {
period->diff = crm_time_parse_duration(period_str);
if (period->diff == NULL) {
goto error;
}
} else {
period->start = parse_date(period_str);
if (period->start == NULL) {
goto error;
}
}
period_str = strstr(original, "/");
if (period_str) {
++period_str;
if (period_str[0] == 'P') {
if (period->diff != NULL) {
crm_err("'%s' is not a valid ISO 8601 time period "
"because it has two durations",
original);
goto invalid;
}
period->diff = crm_time_parse_duration(period_str);
if (period->diff == NULL) {
goto error;
}
} else {
period->end = parse_date(period_str);
if (period->end == NULL) {
goto error;
}
}
} else if (period->diff != NULL) {
// Only duration given, assume start is now
period->start = crm_time_new(NULL);
} else {
// Only start given
crm_err("'%s' is not a valid ISO 8601 time period "
"because it has no duration or ending time",
original);
goto invalid;
}
if (period->start == NULL) {
period->start = crm_time_subtract(period->end, period->diff);
} else if (period->end == NULL) {
period->end = crm_time_add(period->start, period->diff);
}
if (crm_time_check(period->start) == FALSE) {
crm_err("'%s' is not a valid ISO 8601 time period "
"because the start is invalid", period_str);
goto invalid;
}
if (crm_time_check(period->end) == FALSE) {
crm_err("'%s' is not a valid ISO 8601 time period "
"because the end is invalid", period_str);
goto invalid;
}
return period;
invalid:
errno = EINVAL;
error:
crm_time_free_period(period);
return NULL;
}
/*!
* \brief Free a dynamically allocated time period object
*
* \param[in] period Time period to free
*/
void
crm_time_free_period(crm_time_period_t *period)
{
if (period) {
crm_time_free(period->start);
crm_time_free(period->end);
crm_time_free(period->diff);
free(period);
}
}
void
crm_time_set(crm_time_t * target, crm_time_t * source)
{
crm_trace("target=%p, source=%p", target, source);
CRM_CHECK(target != NULL && source != NULL, return);
target->years = source->years;
target->days = source->days;
target->months = source->months; /* Only for durations */
target->seconds = source->seconds;
target->offset = source->offset;
crm_time_log(LOG_TRACE, "source", source,
crm_time_log_date | crm_time_log_timeofday | crm_time_log_with_timezone);
crm_time_log(LOG_TRACE, "target", target,
crm_time_log_date | crm_time_log_timeofday | crm_time_log_with_timezone);
}
static void
ha_set_tm_time(crm_time_t * target, struct tm *source)
{
int h_offset = 0;
int m_offset = 0;
/* Ensure target is fully initialized */
target->years = 0;
target->months = 0;
target->days = 0;
target->seconds = 0;
target->offset = 0;
target->duration = FALSE;
if (source->tm_year > 0) {
/* years since 1900 */
target->years = 1900 + source->tm_year;
}
if (source->tm_yday >= 0) {
/* days since January 1 [0-365] */
target->days = 1 + source->tm_yday;
}
if (source->tm_hour >= 0) {
target->seconds += HOUR_SECONDS * source->tm_hour;
}
if (source->tm_min >= 0) {
target->seconds += 60 * source->tm_min;
}
if (source->tm_sec >= 0) {
target->seconds += source->tm_sec;
}
/* tm_gmtoff == offset from UTC in seconds */
h_offset = GMTOFF(source) / HOUR_SECONDS;
m_offset = (GMTOFF(source) - (HOUR_SECONDS * h_offset)) / 60;
crm_trace("Offset (s): %ld, offset (hh:mm): %.2d:%.2d", GMTOFF(source), h_offset, m_offset);
target->offset += HOUR_SECONDS * h_offset;
target->offset += 60 * m_offset;
}
void
crm_time_set_timet(crm_time_t * target, time_t * source)
{
ha_set_tm_time(target, localtime(source));
}
crm_time_t *
crm_time_add(crm_time_t * dt, crm_time_t * value)
{
crm_time_t *utc = NULL;
crm_time_t *answer = NULL;
CRM_CHECK(dt != NULL && value != NULL, return NULL);
answer = crm_time_new_undefined();
crm_time_set(answer, dt);
utc = crm_get_utc_time(value);
answer->years += utc->years;
crm_time_add_months(answer, utc->months);
crm_time_add_days(answer, utc->days);
crm_time_add_seconds(answer, utc->seconds);
crm_time_free(utc);
return answer;
}
crm_time_t *
crm_time_calculate_duration(crm_time_t * dt, crm_time_t * value)
{
crm_time_t *utc = NULL;
crm_time_t *answer = NULL;
CRM_CHECK(dt != NULL && value != NULL, return NULL);
utc = crm_get_utc_time(value);
answer = crm_get_utc_time(dt);
answer->duration = TRUE;
answer->years -= utc->years;
if(utc->months != 0) {
crm_time_add_months(answer, -utc->months);
}
crm_time_add_days(answer, -utc->days);
crm_time_add_seconds(answer, -utc->seconds);
crm_time_free(utc);
return answer;
}
crm_time_t *
crm_time_subtract(crm_time_t * dt, crm_time_t * value)
{
crm_time_t *utc = NULL;
crm_time_t *answer = NULL;
CRM_CHECK(dt != NULL && value != NULL, return NULL);
answer = crm_time_new_undefined();
crm_time_set(answer, dt);
utc = crm_get_utc_time(value);
answer->years -= utc->years;
if(utc->months != 0) {
crm_time_add_months(answer, -utc->months);
}
crm_time_add_days(answer, -utc->days);
crm_time_add_seconds(answer, -utc->seconds);
return answer;
}
/*!
* \brief Check whether a time object represents a sensible date/time
*
* \param[in] dt Date/time object to check
*
* \return TRUE if years, days, and seconds are sensible, FALSE otherwise
*/
bool
crm_time_check(crm_time_t * dt)
{
return (dt != NULL)
&& (dt->days > 0) && (dt->days <= year_days(dt->years))
&& (dt->seconds >= 0) && (dt->seconds < DAY_SECONDS);
}
#define do_cmp_field(l, r, field) \
if(rc == 0) { \
if(l->field > r->field) { \
crm_trace("%s: %d > %d", \
#field, l->field, r->field); \
rc = 1; \
} else if(l->field < r->field) { \
crm_trace("%s: %d < %d", \
#field, l->field, r->field); \
rc = -1; \
} \
}
int
crm_time_compare(crm_time_t * a, crm_time_t * b)
{
int rc = 0;
crm_time_t *t1 = NULL;
crm_time_t *t2 = NULL;
if (a == NULL && b == NULL) {
return 0;
} else if (a == NULL) {
return -1;
} else if (b == NULL) {
return 1;
}
t1 = crm_get_utc_time(a);
t2 = crm_get_utc_time(b);
do_cmp_field(t1, t2, years);
do_cmp_field(t1, t2, days);
do_cmp_field(t1, t2, seconds);
crm_time_free(t1);
crm_time_free(t2);
return rc;
}
void
crm_time_add_seconds(crm_time_t * a_time, int extra)
{
int days = 0;
crm_trace("Adding %d seconds to %d (max=%d)", extra, a_time->seconds, DAY_SECONDS);
a_time->seconds += extra;
while (a_time->seconds >= DAY_SECONDS) {
a_time->seconds -= DAY_SECONDS;
days++;
}
while (a_time->seconds < 0) {
a_time->seconds += DAY_SECONDS;
days--;
}
crm_time_add_days(a_time, days);
}
void
crm_time_add_days(crm_time_t * a_time, int extra)
{
int lower_bound = 1;
int ydays = crm_time_leapyear(a_time->years) ? 366 : 365;
crm_trace("Adding %d days to %.4d-%.3d", extra, a_time->years, a_time->days);
a_time->days += extra;
while (a_time->days > ydays) {
a_time->years++;
a_time->days -= ydays;
ydays = crm_time_leapyear(a_time->years) ? 366 : 365;
}
if(a_time->duration) {
lower_bound = 0;
}
while (a_time->days < lower_bound) {
a_time->years--;
a_time->days += crm_time_leapyear(a_time->years) ? 366 : 365;
}
}
void
crm_time_add_months(crm_time_t * a_time, int extra)
{
int lpc;
uint32_t y, m, d, dmax;
crm_time_get_gregorian(a_time, &y, &m, &d);
crm_trace("Adding %d months to %.4d-%.2d-%.2d", extra, y, m, d);
if (extra > 0) {
for (lpc = extra; lpc > 0; lpc--) {
m++;
if (m == 13) {
m = 1;
y++;
}
}
} else {
for (lpc = -extra; lpc > 0; lpc--) {
m--;
if (m == 0) {
m = 12;
y--;
}
}
}
dmax = crm_time_days_in_month(m, y);
if (dmax < d) {
/* Preserve day-of-month unless the month doesn't have enough days */
d = dmax;
}
crm_trace("Calculated %.4d-%.2d-%.2d", y, m, d);
a_time->years = y;
a_time->days = get_ordinal_days(y, m, d);
crm_time_get_gregorian(a_time, &y, &m, &d);
crm_trace("Got %.4d-%.2d-%.2d", y, m, d);
}
void
crm_time_add_minutes(crm_time_t * a_time, int extra)
{
crm_time_add_seconds(a_time, extra * 60);
}
void
crm_time_add_hours(crm_time_t * a_time, int extra)
{
crm_time_add_seconds(a_time, extra * HOUR_SECONDS);
}
void
crm_time_add_weeks(crm_time_t * a_time, int extra)
{
crm_time_add_days(a_time, extra * 7);
}
void
crm_time_add_years(crm_time_t * a_time, int extra)
{
a_time->years += extra;
}
static void
ha_get_tm_time( struct tm *target, crm_time_t *source)
{
*target = (struct tm) {
.tm_year = source->years - 1900,
.tm_mday = source->days,
.tm_sec = source->seconds % 60,
.tm_min = ( source->seconds / 60 ) % 60,
.tm_hour = source->seconds / HOUR_SECONDS,
.tm_isdst = -1, /* don't adjust */
#if defined(HAVE_STRUCT_TM_TM_GMTOFF)
.tm_gmtoff = source->offset
#endif
};
mktime(target);
}
crm_time_hr_t *
crm_time_hr_convert(crm_time_hr_t *target, crm_time_t *dt)
{
crm_time_hr_t *hr_dt = NULL;
if (dt) {
hr_dt = target?target:calloc(1, sizeof(crm_time_hr_t));
CRM_ASSERT(hr_dt != NULL);
*hr_dt = (crm_time_hr_t) {
.years = dt->years,
.months = dt->months,
.days = dt->days,
.seconds = dt->seconds,
.offset = dt->offset,
.duration = dt->duration
};
}
return hr_dt;
}
void
crm_time_set_hr_dt(crm_time_t *target, crm_time_hr_t *hr_dt)
{
CRM_ASSERT((hr_dt) && (target));
*target = (crm_time_t) {
.years = hr_dt->years,
.months = hr_dt->months,
.days = hr_dt->days,
.seconds = hr_dt->seconds,
.offset = hr_dt->offset,
.duration = hr_dt->duration
};
}
crm_time_hr_t *
crm_time_timeval_hr_convert(crm_time_hr_t *target, struct timeval *tv)
{
crm_time_t dt;
crm_time_hr_t *ret;
crm_time_set_timet(&dt, &tv->tv_sec);
ret = crm_time_hr_convert(target, &dt);
if (ret) {
ret->useconds = tv->tv_usec;
}
return ret;
}
crm_time_hr_t *
crm_time_hr_new(const char *date_time)
{
crm_time_hr_t *hr_dt = NULL;
struct timeval tv_now;
if (!date_time) {
if (gettimeofday(&tv_now, NULL) == 0) {
hr_dt = crm_time_timeval_hr_convert(NULL, &tv_now);
}
} else {
crm_time_t *dt;
dt = parse_date(date_time);
hr_dt = crm_time_hr_convert(NULL, dt);
crm_time_free(dt);
}
return hr_dt;
}
void
crm_time_hr_free(crm_time_hr_t * hr_dt)
{
free(hr_dt);
}
char *
crm_time_format_hr(const char *format, crm_time_hr_t * hr_dt)
{
const char *mark_s;
int max = 128, scanned_pos = 0, printed_pos = 0, fmt_pos = 0,
date_len = 0, nano_digits = 0;
char nano_s[10], date_s[max+1], nanofmt_s[5] = "%", *tmp_fmt_s;
struct tm tm;
crm_time_t dt;
if (!format) {
return NULL;
}
crm_time_set_hr_dt(&dt, hr_dt);
ha_get_tm_time(&tm, &dt);
sprintf(nano_s, "%06d000", hr_dt->useconds);
while ((format[scanned_pos]) != '\0') {
mark_s = strchr(&format[scanned_pos], '%');
if (mark_s) {
int fmt_len = 1;
fmt_pos = mark_s - format;
while ((format[fmt_pos+fmt_len] != '\0') &&
(format[fmt_pos+fmt_len] >= '0') &&
(format[fmt_pos+fmt_len] <= '9')) {
fmt_len++;
}
scanned_pos = fmt_pos + fmt_len + 1;
if (format[fmt_pos+fmt_len] == 'N') {
nano_digits = atoi(&format[fmt_pos+1]);
nano_digits = (nano_digits > 6)?6:nano_digits;
nano_digits = (nano_digits < 0)?0:nano_digits;
sprintf(&nanofmt_s[1], ".%ds", nano_digits);
} else {
if (format[scanned_pos] != '\0') {
continue;
}
fmt_pos = scanned_pos; /* print till end */
}
} else {
scanned_pos = strlen(format);
fmt_pos = scanned_pos; /* print till end */
}
tmp_fmt_s = strndup(&format[printed_pos], fmt_pos - printed_pos);
#ifdef GCC_FORMAT_NONLITERAL_CHECKING_ENABLED
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
#endif
date_len += strftime(&date_s[date_len], max-date_len, tmp_fmt_s, &tm);
#ifdef GCC_FORMAT_NONLITERAL_CHECKING_ENABLED
#pragma GCC diagnostic pop
#endif
printed_pos = scanned_pos;
free(tmp_fmt_s);
if (nano_digits) {
#ifdef GCC_FORMAT_NONLITERAL_CHECKING_ENABLED
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
#endif
date_len += snprintf(&date_s[date_len], max-date_len,
nanofmt_s, nano_s);
#ifdef GCC_FORMAT_NONLITERAL_CHECKING_ENABLED
#pragma GCC diagnostic pop
#endif
nano_digits = 0;
}
}
return (date_len == 0)?NULL:strdup(date_s);
}
/*!
* \internal
* \brief Return human-friendly string representing current time
*
* \return Current time as string (as by ctime() but without newline) on success
* or "Could not determine current time" on error
* \note The return value points to a statically allocated string which might be
* overwritten by subsequent calls to any of the C library date and time functions.
*/
const char *
crm_now_string(time_t *when)
{
char *since_epoch = NULL;
if (when == NULL) {
time_t a_time = time(NULL);
if (a_time == (time_t) -1) {
return NULL;
} else {
since_epoch = ctime(&a_time);
}
} else {
since_epoch = ctime(when);
}
if (since_epoch == NULL) {
return NULL;
} else {
return crm_strip_trailing_newline(since_epoch);
}
}
File Metadata
Details
Attached
Mime Type
text/x-c
Expires
Thu, Jun 26, 8:18 PM (19 h, 29 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1959682
Default Alt Text
iso8601.c (43 KB)
Attached To
Mode
rP Pacemaker
Attached
Detach File
Event Timeline
Log In to Comment