iso8601.c 8.6 KB

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