/* * CDE - Common Desktop Environment * * Copyright (c) 1993-2012, The Open Group. All rights reserved. * * These libraries and programs are free software; you can * redistribute them and/or modify them under the terms of the GNU * Lesser General Public License as published by the Free Software * Foundation; either version 2 of the License, or (at your option) * any later version. * * These libraries and programs are distributed in the hope that * they will be useful, but WITHOUT ANY WARRANTY; without even the * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR * PURPOSE. See the GNU Lesser General Public License for more * details. * * You should have received a copy of the GNU Lesser General Public * License along with these libraries and programs; if not, write * to the Free Software Foundation, Inc., 51 Franklin Street, Fifth * Floor, Boston, MA 02110-1301 USA */ /* $TOG: iso8601.c /main/2 1997/12/29 10:46:50 bill $ */ /* * (c) Copyright 1993, 1994 Hewlett-Packard Company * (c) Copyright 1993, 1994 International Business Machines Corp. * (c) Copyright 1993, 1994 Novell, Inc. * (c) Copyright 1993, 1994 Sun Microsystems, Inc. */ #include #define XOS_USE_NO_LOCKING #define X_INCLUDE_TIME_H #include #include #include #include #include #include #include "iso8601.h" static void set_timezone(char *tzname) { static char tzenv[BUFSIZ]; if (tzname==NULL) (void) putenv("TZ"); else { snprintf(tzenv, sizeof tzenv, "TZ=%s", tzname); (void) putenv(tzenv); } tzset(); } static int validate_iso8601(char *buf) { /* validation rules: * - sscanf returns # of matches, which must be 6. * - crude range check on each numerical value scanned. * - length of input is fixed: strlen("CCYYMMDDThhmmssZ") * - last char must be Z, indicating UTC time */ int year, month, day, hour, min, sec; int scan_ret=0; static char tmp[] = "CCYYMMDDThhmmssZ"; scan_ret=sscanf(buf, "%4d%2d%2dT%2d%2d%2dZ", &year, &month, &day, &hour, &min, &sec); /* the rules: if any fail, whole test fails so return */ if (strlen(buf) != strlen(tmp)) return (-1); if (buf[strlen(buf)-1] != 'Z') return (-1); if (scan_ret != 6) return (-1); if ((year<1970) || (year>2038)) return (-1); if ((month<1) || (month>12)) return (-1); if ((day<1) || (day>31)) return (-1); if ((hour<0) || (hour>24)) return (-1); if ((min<0) || (min>59)) return (-1); if ((sec<0) || (sec>59)) return (-1); return (0); } /* * _csa_iso8601_to_tick - convert a standard date/time string to a tick * * Note 1:This function supports a limited subset of the iso8601 standard. * Only one of the variations described by the standard is * supported, namely: * * CCYYMMDDThhmmssZ * * ...known in the standard as "Complete Representation, Basic Format" * for calendar date and "Coordinated Universal Time (UTC), Basic Format" * for time. * * This can carry all the information required for date+time by * the CDE 1.0 Calendar component. More general support, if ever * needed (say for interoperability or finer granularity) can be * implemented inside this function without modifying its interface. * * Note 2:All output time information is in UTC, and all input * time information is assumed to be pre-converted to UTC. * * dac 19940728T224055Z :-) */ int _csa_iso8601_to_tick(char *buf, time_t *tick_out) { int year, month, day, hour, min, sec; struct tm time_str; char tz_orig[BUFSIZ]; boolean_t orig_tzset = B_FALSE; int scan_ret=0; scan_ret=sscanf(buf, "%4d%2d%2dT%2d%2d%2dZ", &year, &month, &day, &hour, &min, &sec); if (validate_iso8601(buf) != 0) return(-1); time_str.tm_year = year - 1900; time_str.tm_mon = month - 1; time_str.tm_mday = day; time_str.tm_hour = hour; time_str.tm_min = min; time_str.tm_sec = sec; time_str.tm_isdst = -1; if (getenv("TZ")) { strncpy(tz_orig, getenv("TZ"), sizeof(tz_orig)); tz_orig[sizeof(tz_orig)-1] = '\0'; orig_tzset = B_TRUE; } set_timezone("GMT"); *tick_out = mktime(&time_str); if (orig_tzset == B_TRUE) set_timezone(tz_orig); else set_timezone(NULL); if (*tick_out != (long)-1) return(0); else return(-1); } /* * _csa_tick_to_iso8601 - convert from tick to iso8601 time string * * Note 1: Similar comments to the above. This function complements * _csa_iso8601_to_tick, providing bi-directional conversion. * * Note 2: All input and output time information is UTC. */ int _csa_tick_to_iso8601(time_t tick, char *buf_out) { struct tm *time_str; time_t tk=tick; char tz_orig[BUFSIZ]; boolean_t orig_tzset = B_FALSE; _Xgtimeparams gmtime_buf; /* tick must be +ve to be valid */ if (tick < 0) { return(-1); } /* JET. This is horrible. */ #if !defined(__linux__) && !defined(CSRG_BASED) if (getenv("TZ")) { strncpy(tz_orig, getenv("TZ"), sizeof(tz_orig)); tz_orig[sizeof(tz_orig)-1] = '\0'; orig_tzset = B_TRUE; } set_timezone("GMT"); time_str = localtime(&tk); if (orig_tzset == B_TRUE) set_timezone(tz_orig); else set_timezone(NULL); #else /* let's use something a little more reasonable */ time_str = _XGmtime(&tk, gmtime_buf); #endif /* !linux && !CSGRC_BASED */ /* format string forces fixed width (zero-padded) fields */ sprintf(buf_out, "%04d%02d%02dT%02d%02d%02dZ", time_str->tm_year + 1900, time_str->tm_mon + 1, time_str->tm_mday, time_str->tm_hour, time_str->tm_min, time_str->tm_sec); return (0); } /* * Convert iso8601 date time range to a start tick and an end tick * * iso8601 range is: * "/" * * start and end are iso8601 strings in CCYYMMDDThhmmssZ format. */ int _csa_iso8601_to_range(char *buf, time_t *start, time_t *end) { int nchars; char tmpstr[BUFSIZ]; char *p; if ((p = strchr(buf, '/')) == NULL) { return (-1); } nchars=(p-buf); strncpy(tmpstr, buf, (size_t)nchars); tmpstr[nchars]='\0'; if (_csa_iso8601_to_tick(tmpstr, start) != 0) { return (-1); } p++; if (_csa_iso8601_to_tick(p, end) != 0) { return (-1); } if (end < start) return (-1); else return(0); } /* * Convert time range specified as start/end ticks to iso8601 format * * iso8601 result is: * "/" * * start and end are iso8601 strings in CCYYMMDDThhmmssZ format. */ int _csa_range_to_iso8601(time_t start, time_t end, char *buf) { char tmpstr1[BUFSIZ], tmpstr2[BUFSIZ]; /* validate: ticks must be +ve, and end can't precede start */ if ((start < 0) || (end < 0) || (end < start)) { return(-1); } if (_csa_tick_to_iso8601(start, tmpstr1) != 0) { return (-1); } if (_csa_tick_to_iso8601(end, tmpstr2) != 0) { return (-1); } if (sprintf(buf, "%s/%s", tmpstr1, tmpstr2) < 0) { return (-1); } else return(0); } static int not_sign(char c) { if ((c=='+') || (c=='-')) return (0); else return (1); } /* * This converts from a string representation of a quantity of time, * to (signed) integer number of * seconds. * The first character (byte) must be a '+' or * a '-', indicating * the sense of the period. This can be used however you like - it's * just a way of carrying round the sign, while keeping the main part * of the string in ISO 8601. * The string must be in the format described by ISO 8601, clause * 5.5.1 (b), with the added restriction that only seconds may be specified. * format: [+/-]PTnS */ int _csa_iso8601_to_duration(char *buf, time_t *sec) { /* buf must begin with '+' or '-', then 'P', end with 'S' */ char sign, *ptr, *ptr2, *numptr; int num=0; ptr2 = ptr = buf; sign = *ptr++; if (not_sign(sign)) { if (*ptr2++ != 'P' || *ptr2++ != 'T') { return (-1); } } else if (not_sign(sign) || *ptr++ != 'P' || *ptr++ != 'T') { return (-1); } if (not_sign(sign)) ptr = ptr2; numptr = ptr; while (*ptr >= '0' && *ptr <= '9') ptr++; if (numptr == ptr || !(*ptr && *ptr++ == 'S' && *ptr == '\0')) return (-1); else { num = atoi(numptr); *sec = (sign == '-') ? -num : num; return (0); } } /* * This converts from a (signed) integer number of seconds to a string * representation. The string format is a sign character followed by * an IS0 8601 string as described above for _csa_iso8601_to_duration. */ int _csa_duration_to_iso8601(time_t sec, char *buf) { sprintf(buf, "%cPT%dS", (sec < 0) ? '-': '+', abs(sec)); return(0); }