cdesktopenv/cde/lib/csa/iso8601.c

342 lines
8.4 KiB
C

/*
* 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 librararies 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 <EUSCompat.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <time.h>
#include <string.h>
#include "iso8601.h"
static void
set_timezone(char *tzname)
{
static char tzenv[BUFSIZ];
if (tzname==NULL)
system("unset TZ\n");
else {
sprintf(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;
}
#ifdef __osf__
set_timezone("GMT0");
#else
set_timezone("GMT");
#endif
*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;
/* tick must be +ve to be valid */
if (tick < 0) {
return(-1);
}
if (getenv("TZ")) {
strncpy(tz_orig, getenv("TZ"), sizeof(tz_orig));
tz_orig[sizeof(tz_orig)-1] = '\0';
orig_tzset = B_TRUE;
}
#ifdef __osf__
set_timezone("GMT0");
#else
set_timezone("GMT");
#endif
time_str = localtime(&tk);
if (orig_tzset == B_TRUE)
set_timezone(tz_orig);
else
set_timezone(NULL);
/* 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> "/" <end>
*
* 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> "/" <end>
*
* 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 preceed 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 == NULL))
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);
}