strptime.c revision 55682
1/*
2 * Copyright (c) 1999 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#ifdef HAVE_CONFIG_H
34#include <config.h>
35#endif
36#include <ctype.h>
37#include "roken.h"
38
39RCSID("$Id: strptime.c,v 1.2 1999/11/12 15:29:55 assar Exp $");
40
41static const char *abb_weekdays[] = {
42    "Sun",
43    "Mon",
44    "Tue",
45    "Wed",
46    "Thu",
47    "Fri",
48    "Sat",
49    NULL
50};
51
52static const char *full_weekdays[] = {
53    "Sunday",
54    "Monday",
55    "Tuesday",
56    "Wednesday",
57    "Thursday",
58    "Friday",
59    "Saturday",
60    NULL
61};
62
63static const char *abb_month[] = {
64    "Jan",
65    "Feb",
66    "Mar",
67    "Apr",
68    "May",
69    "Jun",
70    "Jul",
71    "Aug",
72    "Sep",
73    "Oct",
74    "Nov",
75    "Dec",
76    NULL
77};
78
79static const char *full_month[] = {
80    "January",
81    "February",
82    "Mars",
83    "April",
84    "May",
85    "June",
86    "July",
87    "August",
88    "September",
89    "October",
90    "November",
91    "December",
92    NULL,
93};
94
95static const char *ampm[] = {
96    "am",
97    "pm",
98    NULL
99};
100
101/*
102 * Try to match `*buf' to one of the strings in `strs'.  Return the
103 * index of the matching string (or -1 if none).  Also advance buf.
104 */
105
106static int
107match_string (const char **buf, const char **strs)
108{
109    int i = 0;
110
111    for (i = 0; strs[i] != NULL; ++i) {
112	int len = strlen (strs[i]);
113
114	if (strncasecmp (*buf, strs[i], len) == 0) {
115	    *buf += len;
116	    return i;
117	}
118    }
119    return -1;
120}
121
122/*
123 * tm_year is relative this year */
124
125const int tm_year_base = 1900;
126
127/*
128 * Return TRUE iff `year' was a leap year.
129 */
130
131static int
132is_leap_year (int year)
133{
134    return (year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0);
135}
136
137/*
138 * Return the weekday [0,6] (0 = Sunday) of the first day of `year'
139 */
140
141static int
142first_day (int year)
143{
144    int ret = 4;
145
146    for (; year > 1970; --year)
147	ret = (ret + 365 + is_leap_year (year) ? 1 : 0) % 7;
148    return ret;
149}
150
151/*
152 * Set `timeptr' given `wnum' (week number [0, 53])
153 */
154
155static void
156set_week_number_sun (struct tm *timeptr, int wnum)
157{
158    int fday = first_day (timeptr->tm_year + tm_year_base);
159
160    timeptr->tm_yday = wnum * 7 + timeptr->tm_wday - fday;
161    if (timeptr->tm_yday < 0) {
162	timeptr->tm_wday = fday;
163	timeptr->tm_yday = 0;
164    }
165}
166
167/*
168 * Set `timeptr' given `wnum' (week number [0, 53])
169 */
170
171static void
172set_week_number_mon (struct tm *timeptr, int wnum)
173{
174    int fday = (first_day (timeptr->tm_year + tm_year_base) + 6) % 7;
175
176    timeptr->tm_yday = wnum * 7 + (timeptr->tm_wday + 6) % 7 - fday;
177    if (timeptr->tm_yday < 0) {
178	timeptr->tm_wday = (fday + 1) % 7;
179	timeptr->tm_yday = 0;
180    }
181}
182
183/*
184 * Set `timeptr' given `wnum' (week number [0, 53])
185 */
186
187static void
188set_week_number_mon4 (struct tm *timeptr, int wnum)
189{
190    int fday = (first_day (timeptr->tm_year + tm_year_base) + 6) % 7;
191    int offset = 0;
192
193    if (fday < 4)
194	offset += 7;
195
196    timeptr->tm_yday = offset + (wnum - 1) * 7 + timeptr->tm_wday - fday;
197    if (timeptr->tm_yday < 0) {
198	timeptr->tm_wday = fday;
199	timeptr->tm_yday = 0;
200    }
201}
202
203/*
204 *
205 */
206
207char *
208strptime (const char *buf, const char *format, struct tm *timeptr)
209{
210    char c;
211
212    for (; (c = *format) != '\0'; ++format) {
213	char *s;
214	int ret;
215
216	if (isspace (c)) {
217	    while (isspace (*buf))
218		++buf;
219	} else if (c == '%' && format[1] != '\0') {
220	    c = *++format;
221	    if (c == 'E' || c == 'O')
222		c = *++format;
223	    switch (c) {
224	    case 'A' :
225		ret = match_string (&buf, full_weekdays);
226		if (ret < 0)
227		    return NULL;
228		timeptr->tm_wday = ret;
229		break;
230	    case 'a' :
231		ret = match_string (&buf, abb_weekdays);
232		if (ret < 0)
233		    return NULL;
234		timeptr->tm_wday = ret;
235		break;
236	    case 'B' :
237		ret = match_string (&buf, full_month);
238		if (ret < 0)
239		    return NULL;
240		timeptr->tm_mon = ret;
241		break;
242	    case 'b' :
243	    case 'h' :
244		ret = match_string (&buf, abb_month);
245		if (ret < 0)
246		    return NULL;
247		timeptr->tm_mon = ret;
248		break;
249	    case 'C' :
250		ret = strtol (buf, &s, 10);
251		if (s == buf)
252		    return NULL;
253		timeptr->tm_year = (ret * 100) - tm_year_base;
254		buf = s;
255		break;
256	    case 'c' :
257		abort ();
258	    case 'D' :		/* %m/%d/%y */
259		s = strptime (buf, "%m/%d/%y", timeptr);
260		if (s == NULL)
261		    return NULL;
262		buf = s;
263		break;
264	    case 'd' :
265	    case 'e' :
266		ret = strtol (buf, &s, 10);
267		if (s == buf)
268		    return NULL;
269		timeptr->tm_mday = ret;
270		buf = s;
271		break;
272	    case 'H' :
273	    case 'k' :
274		ret = strtol (buf, &s, 10);
275		if (s == buf)
276		    return NULL;
277		timeptr->tm_hour = ret;
278		buf = s;
279		break;
280	    case 'I' :
281	    case 'l' :
282		ret = strtol (buf, &s, 10);
283		if (s == buf)
284		    return NULL;
285		if (ret == 12)
286		    timeptr->tm_hour = 0;
287		else
288		    timeptr->tm_hour = ret;
289		buf = s;
290		break;
291	    case 'j' :
292		ret = strtol (buf, &s, 10);
293		if (s == buf)
294		    return NULL;
295		timeptr->tm_yday = ret - 1;
296		buf = s;
297		break;
298	    case 'm' :
299		ret = strtol (buf, &s, 10);
300		if (s == buf)
301		    return NULL;
302		timeptr->tm_mon = ret - 1;
303		buf = s;
304		break;
305	    case 'M' :
306		ret = strtol (buf, &s, 10);
307		if (s == buf)
308		    return NULL;
309		timeptr->tm_min = ret;
310		buf = s;
311		break;
312	    case 'n' :
313		if (*buf == '\n')
314		    ++buf;
315		else
316		    return NULL;
317		break;
318	    case 'p' :
319		ret = match_string (&buf, ampm);
320		if (ret < 0)
321		    return NULL;
322		if (timeptr->tm_hour == 0) {
323		    if (ret == 1)
324			timeptr->tm_hour = 12;
325		} else
326		    timeptr->tm_hour += 12;
327		break;
328	    case 'r' :		/* %I:%M:%S %p */
329		s = strptime (buf, "%I:%M:%S %p", timeptr);
330		if (s == NULL)
331		    return NULL;
332		buf = s;
333		break;
334	    case 'R' :		/* %H:%M */
335		s = strptime (buf, "%H:%M", timeptr);
336		if (s == NULL)
337		    return NULL;
338		buf = s;
339		break;
340	    case 'S' :
341		ret = strtol (buf, &s, 10);
342		if (s == buf)
343		    return NULL;
344		timeptr->tm_sec = ret;
345		buf = s;
346		break;
347	    case 't' :
348		if (*buf == '\t')
349		    ++buf;
350		else
351		    return NULL;
352		break;
353	    case 'T' :		/* %H:%M:%S */
354	    case 'X' :
355		s = strptime (buf, "%H:%M:%S", timeptr);
356		if (s == NULL)
357		    return NULL;
358		buf = s;
359		break;
360	    case 'u' :
361		ret = strtol (buf, &s, 10);
362		if (s == buf)
363		    return NULL;
364		timeptr->tm_wday = ret - 1;
365		buf = s;
366		break;
367	    case 'w' :
368		ret = strtol (buf, &s, 10);
369		if (s == buf)
370		    return NULL;
371		timeptr->tm_wday = ret;
372		buf = s;
373		break;
374	    case 'U' :
375		ret = strtol (buf, &s, 10);
376		if (s == buf)
377		    return NULL;
378		set_week_number_sun (timeptr, ret);
379		buf = s;
380		break;
381	    case 'V' :
382		ret = strtol (buf, &s, 10);
383		if (s == buf)
384		    return NULL;
385		set_week_number_mon4 (timeptr, ret);
386		buf = s;
387		break;
388	    case 'W' :
389		ret = strtol (buf, &s, 10);
390		if (s == buf)
391		    return NULL;
392		set_week_number_mon (timeptr, ret);
393		buf = s;
394		break;
395	    case 'x' :
396		s = strptime (buf, "%Y:%m:%d", timeptr);
397		if (s == NULL)
398		    return NULL;
399		buf = s;
400		break;
401	    case 'y' :
402		ret = strtol (buf, &s, 10);
403		if (s == buf)
404		    return NULL;
405		if (ret < 70)
406		    timeptr->tm_year = 100 + ret;
407		else
408		    timeptr->tm_year = ret;
409		buf = s;
410		break;
411	    case 'Y' :
412		ret = strtol (buf, &s, 10);
413		if (s == buf)
414		    return NULL;
415		timeptr->tm_year = ret - tm_year_base;
416		buf = s;
417		break;
418	    case 'Z' :
419		abort ();
420	    case '\0' :
421		--format;
422		/* FALLTHROUGH */
423	    case '%' :
424		if (*buf == '%')
425		    ++buf;
426		else
427		    return NULL;
428		break;
429	    default :
430		if (*buf == '%' || *++buf == c)
431		    ++buf;
432		else
433		    return NULL;
434		break;
435	    }
436	} else {
437	    if (*buf == c)
438		++buf;
439	    else
440		return NULL;
441	}
442    }
443    return (char *)buf;
444}
445