strptime.c revision 249140
1155093Smarius/** strptime workaround (for oa macos leopard)
2155093Smarius  * This strptime follows the man strptime (2001-11-12)
3155093Smarius  *		conforming to SUSv2, POSIX.1-2001
4155093Smarius  *
5155093Smarius  * This very simple version of strptime has no:
6155093Smarius  * - E alternatives
7155093Smarius  * - O alternatives
8155093Smarius  * - Glibc additions
9155093Smarius  * - Does not process week numbers
10155093Smarius  * - Does not properly processes year day
11155093Smarius  *
12155093Smarius  * LICENSE
13155093Smarius  * Copyright (c) 2008, NLnet Labs, Matthijs Mekking
14155093Smarius  * All rights reserved.
15155093Smarius  *
16155093Smarius  * Redistribution and use in source and binary forms, with or without
17155093Smarius  * modification, are permitted provided that the following conditions are met:
18155093Smarius  * * Redistributions of source code must retain the above copyright notice,
19155093Smarius  *     this list of conditions and the following disclaimer.
20155093Smarius  * * Redistributions in binary form must reproduce the above copyright
21155093Smarius  *   notice, this list of conditions and the following disclaimer in the
22155093Smarius  *   documentation and/or other materials provided with the distribution.
23155093Smarius  * * Neither the name of NLnetLabs nor the names of its
24155093Smarius  *   contributors may be used to endorse or promote products derived from this
25155093Smarius  *   software without specific prior written permission.
26155093Smarius  *
27155093Smarius  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
28155093Smarius  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29155093Smarius  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30155093Smarius  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
31155093Smarius  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
32155093Smarius  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
33155093Smarius  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
34155093Smarius  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
35155093Smarius  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
36155093Smarius  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
37155093Smarius  * POSSIBILITY OF SUCH DAMAGE.
38155093Smarius **/
39155093Smarius
40155093Smarius#include "config.h"
41155093Smarius
42155093Smarius#ifndef HAVE_CONFIG_H
43155093Smarius#include <time.h>
44155093Smarius#endif
45155093Smarius
46155093Smarius#ifndef STRPTIME_WORKS
47155093Smarius
48155093Smarius#define TM_YEAR_BASE 1900
49155093Smarius
50155093Smarius#include <ctype.h>
51155093Smarius#include <string.h>
52155093Smarius
53155093Smariusstatic const char *abb_weekdays[] = {
54155093Smarius	"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", NULL
55155093Smarius};
56155093Smariusstatic const char *full_weekdays[] = {
57155093Smarius	"Sunday", "Monday", "Tuesday", "Wednesday",
58155093Smarius	"Thursday", "Friday", "Saturday", NULL
59155093Smarius};
60155093Smariusstatic const char *abb_months[] = {
61155093Smarius	"Jan", "Feb", "Mar", "Apr", "May", "Jun",
62155093Smarius	"Jul", "Aug", "Sep", "Oct", "Nov", "Dec", NULL
63155093Smarius};
64155093Smariusstatic const char *full_months[] = {
65155093Smarius	"January", "February", "March", "April", "May", "June",
66155093Smarius	"July", "August", "September", "October", "November", "December", NULL
67155093Smarius};
68155093Smariusstatic const char *ampm[] = {
69155093Smarius    "am", "pm", NULL
70155093Smarius};
71155093Smarius
72155093Smariusstatic int
73155093Smariusmatch_string(const char **buf, const char **strs)
74164933Smarius{
75155093Smarius	int i = 0;
76155093Smarius
77155093Smarius	for (i = 0; strs[i] != NULL; i++) {
78155093Smarius		int len = strlen(strs[i]);
79155093Smarius		if (strncasecmp (*buf, strs[i], len) == 0) {
80155093Smarius			*buf += len;
81155093Smarius			return i;
82155093Smarius		}
83155093Smarius	}
84155093Smarius	return -1;
85155093Smarius}
86155093Smarius
87155093Smariusstatic int
88158663Smariusstr2int(const char **buf, int max)
89158663Smarius{
90155093Smarius	int ret=0, count=0;
91155093Smarius
92155093Smarius	while (*buf[0] != '\0' && isdigit(*buf[0]) && count<max) {
93155093Smarius		ret = ret*10 + (*buf[0] - '0');
94155093Smarius		(*buf)++;
95155093Smarius		count++;
96155093Smarius	}
97155093Smarius
98164933Smarius	if (!count)
99155093Smarius		return -1;
100155093Smarius	return ret;
101155093Smarius}
102155093Smarius
103155093Smarius/** Converts the character string s to values which are stored in tm
104155093Smarius  * using the format specified by format
105155093Smarius **/
106155093Smariuschar *
107155093Smariusunbound_strptime(const char *s, const char *format, struct tm *tm)
108155093Smarius{
109155093Smarius	int c, ret;
110155093Smarius	int split_year = 0;
111155093Smarius
112155093Smarius	while ((c = *format) != '\0') {
113155093Smarius		/* whitespace, literal or format */
114155093Smarius		if (isspace(c)) { /* whitespace */
115155093Smarius			/** whitespace matches zero or more whitespace characters in the
116164933Smarius			  * input string.
117164933Smarius			 **/
118155093Smarius			while (isspace(*s))
119155093Smarius				s++;
120155093Smarius		}
121155093Smarius		else if (c == '%') { /* format */
122155093Smarius			format++;
123155093Smarius			c = *format;
124155093Smarius			switch (c) {
125155093Smarius				case '%': /* %% is converted to % */
126155093Smarius					if (*s != c) {
127155093Smarius						return NULL;
128155093Smarius					}
129207554Ssobomax					s++;
130207554Ssobomax					break;
131155093Smarius				case 'a': /* weekday name, abbreviated or full */
132155093Smarius				case 'A':
133155093Smarius					ret = match_string(&s, full_weekdays);
134155093Smarius					if (ret < 0)
135155093Smarius						ret = match_string(&s, abb_weekdays);
136155093Smarius					if (ret < 0) {
137155093Smarius						return NULL;
138155093Smarius					}
139155093Smarius					tm->tm_wday = ret;
140155093Smarius					break;
141155093Smarius				case 'b': /* month name, abbreviated or full */
142155093Smarius				case 'B':
143155093Smarius				case 'h':
144155093Smarius					ret = match_string(&s, full_months);
145155093Smarius					if (ret < 0)
146155093Smarius						ret = match_string(&s, abb_months);
147155093Smarius					if (ret < 0) {
148155093Smarius						return NULL;
149155093Smarius					}
150155093Smarius					tm->tm_mon = ret;
151155093Smarius					break;
152155093Smarius				case 'c': /* date and time representation */
153155093Smarius					if (!(s = unbound_strptime(s, "%x %X", tm))) {
154155093Smarius						return NULL;
155155093Smarius					}
156155093Smarius					break;
157155093Smarius				case 'C': /* century number */
158155093Smarius					ret = str2int(&s, 2);
159155093Smarius					if (ret < 0 || ret > 99) { /* must be in [00,99] */
160155093Smarius						return NULL;
161155093Smarius					}
162155093Smarius
163155093Smarius					if (split_year)	{
164155093Smarius						tm->tm_year = ret*100 + (tm->tm_year%100);
165155093Smarius					}
166155093Smarius					else {
167155093Smarius						tm->tm_year = ret*100 - TM_YEAR_BASE;
168155093Smarius						split_year = 1;
169155093Smarius					}
170155093Smarius					break;
171155093Smarius				case 'd': /* day of month */
172155093Smarius				case 'e':
173155093Smarius					ret = str2int(&s, 2);
174155093Smarius					if (ret < 1 || ret > 31) { /* must be in [01,31] */
175155093Smarius						return NULL;
176155093Smarius					}
177155093Smarius					tm->tm_mday = ret;
178155093Smarius					break;
179155093Smarius				case 'D': /* equivalent to %m/%d/%y */
180155093Smarius					if (!(s = unbound_strptime(s, "%m/%d/%y", tm))) {
181155093Smarius						return NULL;
182155093Smarius					}
183155093Smarius					break;
184155093Smarius				case 'H': /* hour */
185155093Smarius					ret = str2int(&s, 2);
186155093Smarius					if (ret < 0 || ret > 23) { /* must be in [00,23] */
187155093Smarius						return NULL;
188155093Smarius					}
189155093Smarius					tm->tm_hour = ret;
190155093Smarius					break;
191155093Smarius				case 'I': /* 12hr clock hour */
192155093Smarius					ret = str2int(&s, 2);
193155093Smarius					if (ret < 1 || ret > 12) { /* must be in [01,12] */
194155093Smarius						return NULL;
195155093Smarius					}
196155093Smarius					if (ret == 12) /* actually [0,11] */
197155093Smarius						ret = 0;
198155093Smarius					tm->tm_hour = ret;
199155093Smarius					break;
200155093Smarius				case 'j': /* day of year */
201155093Smarius					ret = str2int(&s, 2);
202155093Smarius					if (ret < 1 || ret > 366) { /* must be in [001,366] */
203155093Smarius						return NULL;
204155093Smarius					}
205155093Smarius					tm->tm_yday = ret;
206155093Smarius					break;
207155093Smarius				case 'm': /* month */
208155093Smarius					ret = str2int(&s, 2);
209155093Smarius					if (ret < 1 || ret > 12) { /* must be in [01,12] */
210155093Smarius						return NULL;
211164933Smarius					}
212155093Smarius					/* months go from 0-11 */
213155093Smarius					tm->tm_mon = (ret-1);
214155093Smarius					break;
215155093Smarius				case 'M': /* minute */
216155093Smarius					ret = str2int(&s, 2);
217155093Smarius					if (ret < 0 || ret > 59) { /* must be in [00,59] */
218155093Smarius						return NULL;
219155093Smarius					}
220155093Smarius					tm->tm_min = ret;
221155093Smarius					break;
222155093Smarius				case 'n': /* arbitrary whitespace */
223155093Smarius				case 't':
224155093Smarius					while (isspace(*s))
225155093Smarius						s++;
226155093Smarius					break;
227155093Smarius				case 'p': /* am pm */
228155093Smarius					ret = match_string(&s, ampm);
229155093Smarius					if (ret < 0) {
230155093Smarius						return NULL;
231155093Smarius					}
232155093Smarius					if (tm->tm_hour < 0 || tm->tm_hour > 11) { /* %I */
233155093Smarius						return NULL;
234155093Smarius					}
235155093Smarius
236155093Smarius					if (ret == 1) /* pm */
237155093Smarius						tm->tm_hour += 12;
238155093Smarius					break;
239155093Smarius				case 'r': /* equivalent of %I:%M:%S %p */
240155093Smarius					if (!(s = unbound_strptime(s, "%I:%M:%S %p", tm))) {
241155093Smarius						return NULL;
242155093Smarius					}
243155093Smarius					break;
244155093Smarius				case 'R': /* equivalent of %H:%M */
245155093Smarius					if (!(s = unbound_strptime(s, "%H:%M", tm))) {
246155093Smarius						return NULL;
247155093Smarius					}
248155093Smarius					break;
249155093Smarius				case 'S': /* seconds */
250155093Smarius					ret = str2int(&s, 2);
251155093Smarius					/* 60 may occur for leap seconds */
252155093Smarius					/* earlier 61 was also allowed */
253155093Smarius					if (ret < 0 || ret > 60) { /* must be in [00,60] */
254155093Smarius						return NULL;
255155093Smarius					}
256164933Smarius					tm->tm_sec = ret;
257164933Smarius					break;
258155093Smarius				case 'T': /* equivalent of %H:%M:%S */
259155093Smarius					if (!(s = unbound_strptime(s, "%H:%M:%S", tm))) {
260155093Smarius						return NULL;
261155093Smarius					}
262155093Smarius					break;
263155093Smarius				case 'U': /* week number, with the first Sun of Jan being w1 */
264155093Smarius					ret = str2int(&s, 2);
265155093Smarius					if (ret < 0 || ret > 53) { /* must be in [00,53] */
266155093Smarius						return NULL;
267155093Smarius					}
268155093Smarius					/** it is hard (and not necessary for nsd) to determine time
269155093Smarius					  * data from week number.
270155093Smarius					 **/
271155093Smarius					break;
272155093Smarius				case 'w': /* day of week */
273155093Smarius					ret = str2int(&s, 1);
274155093Smarius					if (ret < 0 || ret > 6) { /* must be in [0,6] */
275155093Smarius						return NULL;
276155093Smarius					}
277155093Smarius					tm->tm_wday = ret;
278155093Smarius					break;
279155093Smarius				case 'W': /* week number, with the first Mon of Jan being w1 */
280155093Smarius					ret = str2int(&s, 2);
281155093Smarius					if (ret < 0 || ret > 53) { /* must be in [00,53] */
282155093Smarius						return NULL;
283155093Smarius					}
284155093Smarius					/** it is hard (and not necessary for nsd) to determine time
285155093Smarius					  * data from week number.
286155093Smarius					 **/
287155093Smarius					break;
288155093Smarius				case 'x': /* date format */
289155093Smarius					if (!(s = unbound_strptime(s, "%m/%d/%y", tm))) {
290155093Smarius						return NULL;
291155093Smarius					}
292155093Smarius					break;
293155093Smarius				case 'X': /* time format */
294155093Smarius					if (!(s = unbound_strptime(s, "%H:%M:%S", tm))) {
295166139Smarius						return NULL;
296166139Smarius					}
297166139Smarius					break;
298166139Smarius				case 'y': /* last two digits of a year */
299155093Smarius					ret = str2int(&s, 2);
300155093Smarius					if (ret < 0 || ret > 99) { /* must be in [00,99] */
301155093Smarius						return NULL;
302155093Smarius					}
303155093Smarius					if (split_year) {
304155093Smarius						tm->tm_year = ((tm->tm_year/100) * 100) + ret;
305155093Smarius					}
306155093Smarius					else {
307155093Smarius						split_year = 1;
308155093Smarius
309155093Smarius						/** currently:
310155093Smarius						  * if in [0,68] we are in 21th century,
311155093Smarius						  * if in [69,99] we are in 20th century.
312155093Smarius						 **/
313155093Smarius						if (ret < 69) /* 2000 */
314155093Smarius							ret += 100;
315155093Smarius						tm->tm_year = ret;
316155093Smarius					}
317155093Smarius					break;
318155093Smarius				case 'Y': /* year */
319155093Smarius					ret = str2int(&s, 4);
320155093Smarius					if (ret < 0 || ret > 9999) {
321155093Smarius						return NULL;
322155093Smarius					}
323155093Smarius					tm->tm_year = ret - TM_YEAR_BASE;
324155093Smarius					break;
325155093Smarius				case '\0':
326155093Smarius				default: /* unsupported, cannot match format */
327164933Smarius					return NULL;
328164933Smarius					break;
329155093Smarius			}
330155093Smarius		}
331155093Smarius		else { /* literal */
332155093Smarius			/* if input cannot match format, return NULL */
333155093Smarius			if (*s != c)
334155093Smarius				return NULL;
335155093Smarius			s++;
336155093Smarius		}
337155093Smarius
338155093Smarius		format++;
339155093Smarius	}
340155093Smarius
341155093Smarius	/* return pointer to remainder of s */
342155093Smarius	return (char*) s;
343155093Smarius}
344155093Smarius
345155093Smarius#endif /* STRPTIME_WORKS */
346155093Smarius