iso8601.c 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
  1. /*
  2. * CDE - Common Desktop Environment
  3. *
  4. * Copyright (c) 1993-2012, The Open Group. All rights reserved.
  5. *
  6. * These libraries and programs are free software; you can
  7. * redistribute them and/or modify them under the terms of the GNU
  8. * Lesser General Public License as published by the Free Software
  9. * Foundation; either version 2 of the License, or (at your option)
  10. * any later version.
  11. *
  12. * These libraries and programs are distributed in the hope that
  13. * they will be useful, but WITHOUT ANY WARRANTY; without even the
  14. * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
  15. * PURPOSE. See the GNU Lesser General Public License for more
  16. * details.
  17. *
  18. * You should have received a copy of the GNU Lesser General Public
  19. * License along with these libraries and programs; if not, write
  20. * to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
  21. * Floor, Boston, MA 02110-1301 USA
  22. */
  23. /* $TOG: iso8601.c /main/2 1997/12/29 10:46:50 bill $ */
  24. /*
  25. * (c) Copyright 1993, 1994 Hewlett-Packard Company
  26. * (c) Copyright 1993, 1994 International Business Machines Corp.
  27. * (c) Copyright 1993, 1994 Novell, Inc.
  28. * (c) Copyright 1993, 1994 Sun Microsystems, Inc.
  29. */
  30. #if defined(__linux__) || defined(CSRG_BASED)
  31. # define _GNU_SOURCE
  32. #endif
  33. #include <EUSCompat.h>
  34. #define XOS_USE_NO_LOCKING
  35. #define X_INCLUDE_TIME_H
  36. #include <X11/Xos_r.h>
  37. #include <stdlib.h>
  38. #include <stdio.h>
  39. #include <unistd.h>
  40. #include <time.h>
  41. #include <string.h>
  42. #include "iso8601.h"
  43. static void
  44. set_timezone(char *tzname)
  45. {
  46. static char tzenv[BUFSIZ];
  47. if (tzname==NULL)
  48. (void) putenv("TZ");
  49. else {
  50. snprintf(tzenv, sizeof tzenv, "TZ=%s", tzname);
  51. (void) putenv(tzenv);
  52. }
  53. tzset();
  54. }
  55. static int
  56. validate_iso8601(char *buf)
  57. {
  58. /* validation rules:
  59. * - sscanf returns # of matches, which must be 6.
  60. * - crude range check on each numerical value scanned.
  61. * - length of input is fixed: strlen("CCYYMMDDThhmmssZ")
  62. * - last char must be Z, indicating UTC time
  63. */
  64. int year, month, day, hour, min, sec;
  65. int scan_ret=0;
  66. static char tmp[] = "CCYYMMDDThhmmssZ";
  67. scan_ret=sscanf(buf, "%4d%2d%2dT%2d%2d%2dZ",
  68. &year, &month, &day, &hour, &min, &sec);
  69. /* the rules: if any fail, whole test fails so return */
  70. if (strlen(buf) != strlen(tmp)) return (-1);
  71. if (buf[strlen(buf)-1] != 'Z') return (-1);
  72. if (scan_ret != 6) return (-1);
  73. if ((year<1970) || (year>2038)) return (-1);
  74. if ((month<1) || (month>12)) return (-1);
  75. if ((day<1) || (day>31)) return (-1);
  76. if ((hour<0) || (hour>24)) return (-1);
  77. if ((min<0) || (min>59)) return (-1);
  78. if ((sec<0) || (sec>59)) return (-1);
  79. return (0);
  80. }
  81. /*
  82. * _csa_iso8601_to_tick - convert a standard date/time string to a tick
  83. *
  84. * Note 1:This function supports a limited subset of the iso8601 standard.
  85. * Only one of the variations described by the standard is
  86. * supported, namely:
  87. *
  88. * CCYYMMDDThhmmssZ
  89. *
  90. * ...known in the standard as "Complete Representation, Basic Format"
  91. * for calendar date and "Coordinated Universal Time (UTC), Basic Format"
  92. * for time.
  93. *
  94. * This can carry all the information required for date+time by
  95. * the CDE 1.0 Calendar component. More general support, if ever
  96. * needed (say for interoperability or finer granularity) can be
  97. * implemented inside this function without modifying its interface.
  98. *
  99. * Note 2:All output time information is in UTC, and all input
  100. * time information is assumed to be pre-converted to UTC.
  101. *
  102. * dac 19940728T224055Z :-)
  103. */
  104. int
  105. _csa_iso8601_to_tick(char *buf, time_t *tick_out)
  106. {
  107. int year, month, day, hour, min, sec;
  108. struct tm time_str;
  109. char tz_orig[BUFSIZ];
  110. boolean_t orig_tzset = B_FALSE;
  111. int scan_ret=0;
  112. scan_ret=sscanf(buf, "%4d%2d%2dT%2d%2d%2dZ",
  113. &year, &month, &day, &hour, &min, &sec);
  114. if (validate_iso8601(buf) != 0)
  115. return(-1);
  116. time_str.tm_year = year - 1900;
  117. time_str.tm_mon = month - 1;
  118. time_str.tm_mday = day;
  119. time_str.tm_hour = hour;
  120. time_str.tm_min = min;
  121. time_str.tm_sec = sec;
  122. time_str.tm_isdst = -1;
  123. if (getenv("TZ")) {
  124. strncpy(tz_orig, getenv("TZ"), sizeof(tz_orig));
  125. tz_orig[sizeof(tz_orig)-1] = '\0';
  126. orig_tzset = B_TRUE;
  127. }
  128. set_timezone("GMT");
  129. *tick_out = mktime(&time_str);
  130. if (orig_tzset == B_TRUE)
  131. set_timezone(tz_orig);
  132. else
  133. set_timezone(NULL);
  134. if (*tick_out != (long)-1)
  135. return(0);
  136. else
  137. return(-1);
  138. }
  139. /*
  140. * _csa_tick_to_iso8601 - convert from tick to iso8601 time string
  141. *
  142. * Note 1: Similar comments to the above. This function complements
  143. * _csa_iso8601_to_tick, providing bi-directional conversion.
  144. *
  145. * Note 2: All input and output time information is UTC.
  146. */
  147. int
  148. _csa_tick_to_iso8601(time_t tick, char *buf_out)
  149. {
  150. struct tm *time_str;
  151. time_t tk=tick;
  152. char tz_orig[BUFSIZ], *s;
  153. boolean_t orig_tzset = B_FALSE;
  154. _Xgtimeparams gmtime_buf;
  155. /* tick must be +ve to be valid */
  156. if (tick < 0) {
  157. return(-1);
  158. }
  159. /* JET. This is horrible. */
  160. #if !defined(__linux__) && !defined(CSRG_BASED)
  161. if (getenv("TZ")) {
  162. strncpy(tz_orig, getenv("TZ"), sizeof(tz_orig));
  163. tz_orig[sizeof(tz_orig)-1] = '\0';
  164. orig_tzset = B_TRUE;
  165. }
  166. set_timezone("GMT");
  167. time_str = localtime(&tk);
  168. if (orig_tzset == B_TRUE)
  169. set_timezone(tz_orig);
  170. else
  171. set_timezone(NULL);
  172. #else
  173. /* let's use something a little more reasonable */
  174. time_str = _XGmtime(&tk, gmtime_buf);
  175. #endif /* !linux && !CSGRC_BASED */
  176. /* format string forces fixed width (zero-padded) fields */
  177. asprintf(&s, "%04d%02d%02dT%02d%02d%02dZ",
  178. time_str->tm_year + 1900,
  179. time_str->tm_mon + 1,
  180. time_str->tm_mday,
  181. time_str->tm_hour,
  182. time_str->tm_min,
  183. time_str->tm_sec);
  184. strcpy(buf_out, s);
  185. free(s);
  186. return (0);
  187. }
  188. /*
  189. * Convert iso8601 date time range to a start tick and an end tick
  190. *
  191. * iso8601 range is:
  192. * <start> "/" <end>
  193. *
  194. * start and end are iso8601 strings in CCYYMMDDThhmmssZ format.
  195. */
  196. int
  197. _csa_iso8601_to_range(char *buf, time_t *start, time_t *end)
  198. {
  199. int nchars;
  200. char tmpstr[BUFSIZ];
  201. char *p;
  202. if ((p = strchr(buf, '/')) == NULL) {
  203. return (-1);
  204. }
  205. nchars=(p-buf);
  206. strncpy(tmpstr, buf, (size_t)nchars);
  207. tmpstr[nchars]='\0';
  208. if (_csa_iso8601_to_tick(tmpstr, start) != 0) {
  209. return (-1);
  210. }
  211. p++;
  212. if (_csa_iso8601_to_tick(p, end) != 0) {
  213. return (-1);
  214. }
  215. if (end < start)
  216. return (-1);
  217. else
  218. return(0);
  219. }
  220. /*
  221. * Convert time range specified as start/end ticks to iso8601 format
  222. *
  223. * iso8601 result is:
  224. * <start> "/" <end>
  225. *
  226. * start and end are iso8601 strings in CCYYMMDDThhmmssZ format.
  227. */
  228. int
  229. _csa_range_to_iso8601(time_t start, time_t end, char *buf)
  230. {
  231. char tmpstr1[BUFSIZ], tmpstr2[BUFSIZ], *s;
  232. /* validate: ticks must be +ve, and end can't precede start */
  233. if ((start < 0) || (end < 0) || (end < start)) {
  234. return(-1);
  235. }
  236. if (_csa_tick_to_iso8601(start, tmpstr1) != 0) {
  237. return (-1);
  238. }
  239. if (_csa_tick_to_iso8601(end, tmpstr2) != 0) {
  240. return (-1);
  241. }
  242. if (asprintf(&s, "%s/%s", tmpstr1, tmpstr2) < 0) {
  243. free(s);
  244. return (-1);
  245. }
  246. else {
  247. strcpy(buf, s);
  248. free(s);
  249. return(0);
  250. }
  251. }
  252. static int
  253. not_sign(char c)
  254. {
  255. if ((c=='+') || (c=='-'))
  256. return (0);
  257. else
  258. return (1);
  259. }
  260. /*
  261. * This converts from a string representation of a quantity of time,
  262. * to (signed) integer number of * seconds.
  263. * The first character (byte) must be a '+' or * a '-', indicating
  264. * the sense of the period. This can be used however you like - it's
  265. * just a way of carrying round the sign, while keeping the main part
  266. * of the string in ISO 8601.
  267. * The string must be in the format described by ISO 8601, clause
  268. * 5.5.1 (b), with the added restriction that only seconds may be specified.
  269. * format: [+/-]PTnS
  270. */
  271. int
  272. _csa_iso8601_to_duration(char *buf, time_t *sec)
  273. {
  274. /* buf must begin with '+' or '-', then 'P', end with 'S' */
  275. char sign, *ptr, *ptr2, *numptr;
  276. int num=0;
  277. ptr2 = ptr = buf;
  278. sign = *ptr++;
  279. if (not_sign(sign)) {
  280. if (*ptr2++ != 'P' || *ptr2++ != 'T') {
  281. return (-1);
  282. }
  283. } else if (not_sign(sign) || *ptr++ != 'P' || *ptr++ != 'T') {
  284. return (-1);
  285. }
  286. if (not_sign(sign))
  287. ptr = ptr2;
  288. numptr = ptr;
  289. while (*ptr >= '0' && *ptr <= '9') ptr++;
  290. if (numptr == ptr || !(*ptr && *ptr++ == 'S' && *ptr == '\0'))
  291. return (-1);
  292. else {
  293. num = atoi(numptr);
  294. *sec = (sign == '-') ? -num : num;
  295. return (0);
  296. }
  297. }
  298. /*
  299. * This converts from a (signed) integer number of seconds to a string
  300. * representation. The string format is a sign character followed by
  301. * an IS0 8601 string as described above for _csa_iso8601_to_duration.
  302. */
  303. int
  304. _csa_duration_to_iso8601(time_t sec, char *buf)
  305. {
  306. char *s;
  307. asprintf(&s, "%cPT%dS", (sec < 0) ? '-': '+', abs(sec));
  308. strcpy(buf, s);
  309. free(s);
  310. return(0);
  311. }