strptime.c revision 233294
1/*
2 * Copyright (c) 1999, 2003, 2005 Kungliga Tekniska H��gskolan
3 * (Royal Institute of Technology, Stockholm, Sweden).
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 *
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 *
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the distribution.
16 *
17 * 3. Neither the name of KTH nor the names of its contributors may be
18 *    used to endorse or promote products derived from this software without
19 *    specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY KTH AND ITS CONTRIBUTORS ``AS IS'' AND ANY
22 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
24 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL KTH OR ITS CONTRIBUTORS BE
25 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
28 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
29 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
30 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
31 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
32
33#include <config.h>
34#include "roken.h"
35#ifdef TEST_STRPFTIME
36#include "strpftime-test.h"
37#endif
38#include <ctype.h>
39
40static const char *abb_weekdays[] = {
41    "Sun",
42    "Mon",
43    "Tue",
44    "Wed",
45    "Thu",
46    "Fri",
47    "Sat",
48    NULL
49};
50
51static const char *full_weekdays[] = {
52    "Sunday",
53    "Monday",
54    "Tuesday",
55    "Wednesday",
56    "Thursday",
57    "Friday",
58    "Saturday",
59    NULL
60};
61
62static const char *abb_month[] = {
63    "Jan",
64    "Feb",
65    "Mar",
66    "Apr",
67    "May",
68    "Jun",
69    "Jul",
70    "Aug",
71    "Sep",
72    "Oct",
73    "Nov",
74    "Dec",
75    NULL
76};
77
78static const char *full_month[] = {
79    "January",
80    "February",
81    "March",
82    "April",
83    "May",
84    "June",
85    "July",
86    "August",
87    "September",
88    "October",
89    "November",
90    "December",
91    NULL,
92};
93
94static const char *ampm[] = {
95    "am",
96    "pm",
97    NULL
98};
99
100/*
101 * Try to match `*buf' to one of the strings in `strs'.  Return the
102 * index of the matching string (or -1 if none).  Also advance buf.
103 */
104
105static int
106match_string (const char **buf, const char **strs)
107{
108    int i = 0;
109
110    for (i = 0; strs[i] != NULL; ++i) {
111	int len = strlen (strs[i]);
112
113	if (strncasecmp (*buf, strs[i], len) == 0) {
114	    *buf += len;
115	    return i;
116	}
117    }
118    return -1;
119}
120
121/*
122 * Try to match `*buf' to at the most `n' characters and return the
123 * resulting number in `num'. Returns 0 or an error.  Also advance
124 * buf.
125 */
126
127static int
128parse_number (const char **buf, int n, int *num)
129{
130    char *s, *str;
131    int i;
132
133    str = malloc(n + 1);
134    if (str == NULL)
135	return -1;
136
137    /* skip whitespace */
138    for (; **buf != '\0' && isspace((unsigned char)(**buf)); (*buf)++)
139	;
140
141    /* parse at least n characters */
142    for (i = 0; **buf != '\0' && i < n && isdigit((unsigned char)(**buf)); i++, (*buf)++)
143	str[i] = **buf;
144    str[i] = '\0';
145
146    *num = strtol (str, &s, 10);
147    free(str);
148    if (s == str)
149	return -1;
150
151    return 0;
152}
153
154/*
155 * tm_year is relative this year
156 */
157
158const int tm_year_base = 1900;
159
160/*
161 * Return TRUE iff `year' was a leap year.
162 */
163
164static int
165is_leap_year (int year)
166{
167    return (year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0);
168}
169
170/*
171 * Return the weekday [0,6] (0 = Sunday) of the first day of `year'
172 */
173
174static int
175first_day (int year)
176{
177    int ret = 4;
178
179    for (; year > 1970; --year)
180	ret = (ret + (is_leap_year (year) ? 366 : 365)) % 7;
181    return ret;
182}
183
184/*
185 * Set `timeptr' given `wnum' (week number [0, 53])
186 */
187
188static void
189set_week_number_sun (struct tm *timeptr, int wnum)
190{
191    int fday = first_day (timeptr->tm_year + tm_year_base);
192
193    timeptr->tm_yday = wnum * 7 + timeptr->tm_wday - fday;
194    if (timeptr->tm_yday < 0) {
195	timeptr->tm_wday = fday;
196	timeptr->tm_yday = 0;
197    }
198}
199
200/*
201 * Set `timeptr' given `wnum' (week number [0, 53])
202 */
203
204static void
205set_week_number_mon (struct tm *timeptr, int wnum)
206{
207    int fday = (first_day (timeptr->tm_year + tm_year_base) + 6) % 7;
208
209    timeptr->tm_yday = wnum * 7 + (timeptr->tm_wday + 6) % 7 - fday;
210    if (timeptr->tm_yday < 0) {
211	timeptr->tm_wday = (fday + 1) % 7;
212	timeptr->tm_yday = 0;
213    }
214}
215
216/*
217 * Set `timeptr' given `wnum' (week number [0, 53])
218 */
219
220static void
221set_week_number_mon4 (struct tm *timeptr, int wnum)
222{
223    int fday = (first_day (timeptr->tm_year + tm_year_base) + 6) % 7;
224    int offset = 0;
225
226    if (fday < 4)
227	offset += 7;
228
229    timeptr->tm_yday = offset + (wnum - 1) * 7 + timeptr->tm_wday - fday;
230    if (timeptr->tm_yday < 0) {
231	timeptr->tm_wday = fday;
232	timeptr->tm_yday = 0;
233    }
234}
235
236/*
237 *
238 */
239
240ROKEN_LIB_FUNCTION char * ROKEN_LIB_CALL
241strptime (const char *buf, const char *format, struct tm *timeptr)
242{
243    char c;
244
245    for (; (c = *format) != '\0'; ++format) {
246	char *s;
247	int ret;
248
249	if (isspace ((unsigned char)c)) {
250	    while (isspace ((unsigned char)*buf))
251		++buf;
252	} else if (c == '%' && format[1] != '\0') {
253	    c = *++format;
254	    if (c == 'E' || c == 'O')
255		c = *++format;
256	    switch (c) {
257	    case 'A' :
258		ret = match_string (&buf, full_weekdays);
259		if (ret < 0)
260		    return NULL;
261		timeptr->tm_wday = ret;
262		break;
263	    case 'a' :
264		ret = match_string (&buf, abb_weekdays);
265		if (ret < 0)
266		    return NULL;
267		timeptr->tm_wday = ret;
268		break;
269	    case 'B' :
270		ret = match_string (&buf, full_month);
271		if (ret < 0)
272		    return NULL;
273		timeptr->tm_mon = ret;
274		break;
275	    case 'b' :
276	    case 'h' :
277		ret = match_string (&buf, abb_month);
278		if (ret < 0)
279		    return NULL;
280		timeptr->tm_mon = ret;
281		break;
282	    case 'C' :
283		if (parse_number(&buf, 2, &ret))
284		    return NULL;
285		timeptr->tm_year = (ret * 100) - tm_year_base;
286		break;
287	    case 'c' :
288		abort ();
289	    case 'D' :		/* %m/%d/%y */
290		s = strptime (buf, "%m/%d/%y", timeptr);
291		if (s == NULL)
292		    return NULL;
293		buf = s;
294		break;
295	    case 'd' :
296	    case 'e' :
297		if (parse_number(&buf, 2, &ret))
298		    return NULL;
299		timeptr->tm_mday = ret;
300		break;
301	    case 'H' :
302	    case 'k' :
303		if (parse_number(&buf, 2, &ret))
304		    return NULL;
305		timeptr->tm_hour = ret;
306		break;
307	    case 'I' :
308	    case 'l' :
309		if (parse_number(&buf, 2, &ret))
310		    return NULL;
311		if (ret == 12)
312		    timeptr->tm_hour = 0;
313		else
314		    timeptr->tm_hour = ret;
315		break;
316	    case 'j' :
317		if (parse_number(&buf, 3, &ret))
318		    return NULL;
319		if (ret == 0)
320		    return NULL;
321		timeptr->tm_yday = ret - 1;
322		break;
323	    case 'm' :
324		if (parse_number(&buf, 2, &ret))
325		    return NULL;
326		if (ret == 0)
327		    return NULL;
328		timeptr->tm_mon = ret - 1;
329		break;
330	    case 'M' :
331		if (parse_number(&buf, 2, &ret))
332		    return NULL;
333		timeptr->tm_min = ret;
334		break;
335	    case 'n' :
336		while (isspace ((unsigned char)*buf))
337		    buf++;
338		break;
339	    case 'p' :
340		ret = match_string (&buf, ampm);
341		if (ret < 0)
342		    return NULL;
343		if (timeptr->tm_hour == 0) {
344		    if (ret == 1)
345			timeptr->tm_hour = 12;
346		} else
347		    timeptr->tm_hour += 12;
348		break;
349	    case 'r' :		/* %I:%M:%S %p */
350		s = strptime (buf, "%I:%M:%S %p", timeptr);
351		if (s == NULL)
352		    return NULL;
353		buf = s;
354		break;
355	    case 'R' :		/* %H:%M */
356		s = strptime (buf, "%H:%M", timeptr);
357		if (s == NULL)
358		    return NULL;
359		buf = s;
360		break;
361	    case 'S' :
362		if (parse_number(&buf, 2, &ret))
363		    return NULL;
364		timeptr->tm_sec = ret;
365		break;
366	    case 't' :
367		while (isspace ((unsigned char)*buf))
368		    buf++;
369		break;
370	    case 'T' :		/* %H:%M:%S */
371	    case 'X' :
372		s = strptime (buf, "%H:%M:%S", timeptr);
373		if (s == NULL)
374		    return NULL;
375		buf = s;
376		break;
377	    case 'u' :
378		if (parse_number(&buf, 1, &ret))
379		    return NULL;
380		if (ret <= 0)
381		    return NULL;
382		timeptr->tm_wday = ret - 1;
383		break;
384	    case 'w' :
385		if (parse_number(&buf, 1, &ret))
386		    return NULL;
387		timeptr->tm_wday = ret;
388		break;
389	    case 'U' :
390		if (parse_number(&buf, 2, &ret))
391		    return NULL;
392		set_week_number_sun (timeptr, ret);
393		break;
394	    case 'V' :
395		if (parse_number(&buf, 2, &ret))
396		    return NULL;
397		set_week_number_mon4 (timeptr, ret);
398		break;
399	    case 'W' :
400		if (parse_number(&buf, 2, &ret))
401		    return NULL;
402		set_week_number_mon (timeptr, ret);
403		break;
404	    case 'x' :
405		s = strptime (buf, "%Y:%m:%d", timeptr);
406		if (s == NULL)
407		    return NULL;
408		buf = s;
409		break;
410	    case 'y' :
411		if (parse_number(&buf, 2, &ret))
412		    return NULL;
413		if (ret < 70)
414		    timeptr->tm_year = 100 + ret;
415		else
416		    timeptr->tm_year = ret;
417		break;
418	    case 'Y' :
419		if (parse_number(&buf, 4, &ret))
420		    return NULL;
421		timeptr->tm_year = ret - tm_year_base;
422		break;
423	    case 'Z' :
424		abort ();
425	    case '\0' :
426		--format;
427		/* FALLTHROUGH */
428	    case '%' :
429		if (*buf == '%')
430		    ++buf;
431		else
432		    return NULL;
433		break;
434	    default :
435		if (*buf == '%' || *++buf == c)
436		    ++buf;
437		else
438		    return NULL;
439		break;
440	    }
441	} else {
442	    if (*buf == c)
443		++buf;
444	    else
445		return NULL;
446	}
447    }
448    return rk_UNCONST(buf);
449}
450