xref: /netbsd-src/external/bsd/unbound/dist/compat/strptime.c (revision 3b6c3722d8f990f9a667d638078aee8ccdc3c0f3)
1*3b6c3722Schristos /** strptime workaround (for oa macos leopard)
2*3b6c3722Schristos   * This strptime follows the man strptime (2001-11-12)
3*3b6c3722Schristos   *		conforming to SUSv2, POSIX.1-2001
4*3b6c3722Schristos   *
5*3b6c3722Schristos   * This very simple version of strptime has no:
6*3b6c3722Schristos   * - E alternatives
7*3b6c3722Schristos   * - O alternatives
8*3b6c3722Schristos   * - Glibc additions
9*3b6c3722Schristos   * - Does not process week numbers
10*3b6c3722Schristos   * - Does not properly processes year day
11*3b6c3722Schristos   *
12*3b6c3722Schristos   * LICENSE
13*3b6c3722Schristos   * Copyright (c) 2008, NLnet Labs, Matthijs Mekking
14*3b6c3722Schristos   * All rights reserved.
15*3b6c3722Schristos   *
16*3b6c3722Schristos   * Redistribution and use in source and binary forms, with or without
17*3b6c3722Schristos   * modification, are permitted provided that the following conditions are met:
18*3b6c3722Schristos   * * Redistributions of source code must retain the above copyright notice,
19*3b6c3722Schristos   *     this list of conditions and the following disclaimer.
20*3b6c3722Schristos   * * Redistributions in binary form must reproduce the above copyright
21*3b6c3722Schristos   *   notice, this list of conditions and the following disclaimer in the
22*3b6c3722Schristos   *   documentation and/or other materials provided with the distribution.
23*3b6c3722Schristos   * * Neither the name of NLnetLabs nor the names of its
24*3b6c3722Schristos   *   contributors may be used to endorse or promote products derived from this
25*3b6c3722Schristos   *   software without specific prior written permission.
26*3b6c3722Schristos   *
27*3b6c3722Schristos   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
28*3b6c3722Schristos   * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29*3b6c3722Schristos   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30*3b6c3722Schristos   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
31*3b6c3722Schristos   * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
32*3b6c3722Schristos   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
33*3b6c3722Schristos   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
34*3b6c3722Schristos   * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
35*3b6c3722Schristos   * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
36*3b6c3722Schristos   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
37*3b6c3722Schristos   * POSSIBILITY OF SUCH DAMAGE.
38*3b6c3722Schristos  **/
39*3b6c3722Schristos 
40*3b6c3722Schristos #include "config.h"
41*3b6c3722Schristos 
42*3b6c3722Schristos #ifndef HAVE_CONFIG_H
43*3b6c3722Schristos #include <time.h>
44*3b6c3722Schristos #endif
45*3b6c3722Schristos 
46*3b6c3722Schristos #ifndef STRPTIME_WORKS
47*3b6c3722Schristos 
48*3b6c3722Schristos #define TM_YEAR_BASE 1900
49*3b6c3722Schristos 
50*3b6c3722Schristos #include <ctype.h>
51*3b6c3722Schristos #include <string.h>
52*3b6c3722Schristos 
53*3b6c3722Schristos static const char *abb_weekdays[] = {
54*3b6c3722Schristos 	"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", NULL
55*3b6c3722Schristos };
56*3b6c3722Schristos static const char *full_weekdays[] = {
57*3b6c3722Schristos 	"Sunday", "Monday", "Tuesday", "Wednesday",
58*3b6c3722Schristos 	"Thursday", "Friday", "Saturday", NULL
59*3b6c3722Schristos };
60*3b6c3722Schristos static const char *abb_months[] = {
61*3b6c3722Schristos 	"Jan", "Feb", "Mar", "Apr", "May", "Jun",
62*3b6c3722Schristos 	"Jul", "Aug", "Sep", "Oct", "Nov", "Dec", NULL
63*3b6c3722Schristos };
64*3b6c3722Schristos static const char *full_months[] = {
65*3b6c3722Schristos 	"January", "February", "March", "April", "May", "June",
66*3b6c3722Schristos 	"July", "August", "September", "October", "November", "December", NULL
67*3b6c3722Schristos };
68*3b6c3722Schristos static const char *ampm[] = {
69*3b6c3722Schristos     "am", "pm", NULL
70*3b6c3722Schristos };
71*3b6c3722Schristos 
72*3b6c3722Schristos static int
match_string(const char ** buf,const char ** strs)73*3b6c3722Schristos match_string(const char **buf, const char **strs)
74*3b6c3722Schristos {
75*3b6c3722Schristos 	int i = 0;
76*3b6c3722Schristos 
77*3b6c3722Schristos 	for (i = 0; strs[i] != NULL; i++) {
78*3b6c3722Schristos 		int len = strlen(strs[i]);
79*3b6c3722Schristos 		if (strncasecmp (*buf, strs[i], len) == 0) {
80*3b6c3722Schristos 			*buf += len;
81*3b6c3722Schristos 			return i;
82*3b6c3722Schristos 		}
83*3b6c3722Schristos 	}
84*3b6c3722Schristos 	return -1;
85*3b6c3722Schristos }
86*3b6c3722Schristos 
87*3b6c3722Schristos static int
str2int(const char ** buf,int max)88*3b6c3722Schristos str2int(const char **buf, int max)
89*3b6c3722Schristos {
90*3b6c3722Schristos 	int ret=0, count=0;
91*3b6c3722Schristos 
92*3b6c3722Schristos 	while (*buf[0] != '\0' && isdigit((unsigned char)*buf[0]) && count<max) {
93*3b6c3722Schristos 		ret = ret*10 + (*buf[0] - '0');
94*3b6c3722Schristos 		(*buf)++;
95*3b6c3722Schristos 		count++;
96*3b6c3722Schristos 	}
97*3b6c3722Schristos 
98*3b6c3722Schristos 	if (!count)
99*3b6c3722Schristos 		return -1;
100*3b6c3722Schristos 	return ret;
101*3b6c3722Schristos }
102*3b6c3722Schristos 
103*3b6c3722Schristos /** Converts the character string s to values which are stored in tm
104*3b6c3722Schristos   * using the format specified by format
105*3b6c3722Schristos  **/
106*3b6c3722Schristos char *
unbound_strptime(const char * s,const char * format,struct tm * tm)107*3b6c3722Schristos unbound_strptime(const char *s, const char *format, struct tm *tm)
108*3b6c3722Schristos {
109*3b6c3722Schristos 	int c, ret;
110*3b6c3722Schristos 	int split_year = 0;
111*3b6c3722Schristos 
112*3b6c3722Schristos 	while ((c = *format) != '\0') {
113*3b6c3722Schristos 		/* whitespace, literal or format */
114*3b6c3722Schristos 		if (isspace((unsigned char)c)) { /* whitespace */
115*3b6c3722Schristos 			/** whitespace matches zero or more whitespace characters in the
116*3b6c3722Schristos 			  * input string.
117*3b6c3722Schristos 			 **/
118*3b6c3722Schristos 			while (isspace((unsigned char)*s))
119*3b6c3722Schristos 				s++;
120*3b6c3722Schristos 		}
121*3b6c3722Schristos 		else if (c == '%') { /* format */
122*3b6c3722Schristos 			format++;
123*3b6c3722Schristos 			c = *format;
124*3b6c3722Schristos 			switch (c) {
125*3b6c3722Schristos 				case '%': /* %% is converted to % */
126*3b6c3722Schristos 					if (*s != c) {
127*3b6c3722Schristos 						return NULL;
128*3b6c3722Schristos 					}
129*3b6c3722Schristos 					s++;
130*3b6c3722Schristos 					break;
131*3b6c3722Schristos 				case 'a': /* weekday name, abbreviated or full */
132*3b6c3722Schristos 				case 'A':
133*3b6c3722Schristos 					ret = match_string(&s, full_weekdays);
134*3b6c3722Schristos 					if (ret < 0)
135*3b6c3722Schristos 						ret = match_string(&s, abb_weekdays);
136*3b6c3722Schristos 					if (ret < 0) {
137*3b6c3722Schristos 						return NULL;
138*3b6c3722Schristos 					}
139*3b6c3722Schristos 					tm->tm_wday = ret;
140*3b6c3722Schristos 					break;
141*3b6c3722Schristos 				case 'b': /* month name, abbreviated or full */
142*3b6c3722Schristos 				case 'B':
143*3b6c3722Schristos 				case 'h':
144*3b6c3722Schristos 					ret = match_string(&s, full_months);
145*3b6c3722Schristos 					if (ret < 0)
146*3b6c3722Schristos 						ret = match_string(&s, abb_months);
147*3b6c3722Schristos 					if (ret < 0) {
148*3b6c3722Schristos 						return NULL;
149*3b6c3722Schristos 					}
150*3b6c3722Schristos 					tm->tm_mon = ret;
151*3b6c3722Schristos 					break;
152*3b6c3722Schristos 				case 'c': /* date and time representation */
153*3b6c3722Schristos 					if (!(s = unbound_strptime(s, "%x %X", tm))) {
154*3b6c3722Schristos 						return NULL;
155*3b6c3722Schristos 					}
156*3b6c3722Schristos 					break;
157*3b6c3722Schristos 				case 'C': /* century number */
158*3b6c3722Schristos 					ret = str2int(&s, 2);
159*3b6c3722Schristos 					if (ret < 0 || ret > 99) { /* must be in [00,99] */
160*3b6c3722Schristos 						return NULL;
161*3b6c3722Schristos 					}
162*3b6c3722Schristos 
163*3b6c3722Schristos 					if (split_year)	{
164*3b6c3722Schristos 						tm->tm_year = ret*100 + (tm->tm_year%100);
165*3b6c3722Schristos 					}
166*3b6c3722Schristos 					else {
167*3b6c3722Schristos 						tm->tm_year = ret*100 - TM_YEAR_BASE;
168*3b6c3722Schristos 						split_year = 1;
169*3b6c3722Schristos 					}
170*3b6c3722Schristos 					break;
171*3b6c3722Schristos 				case 'd': /* day of month */
172*3b6c3722Schristos 				case 'e':
173*3b6c3722Schristos 					ret = str2int(&s, 2);
174*3b6c3722Schristos 					if (ret < 1 || ret > 31) { /* must be in [01,31] */
175*3b6c3722Schristos 						return NULL;
176*3b6c3722Schristos 					}
177*3b6c3722Schristos 					tm->tm_mday = ret;
178*3b6c3722Schristos 					break;
179*3b6c3722Schristos 				case 'D': /* equivalent to %m/%d/%y */
180*3b6c3722Schristos 					if (!(s = unbound_strptime(s, "%m/%d/%y", tm))) {
181*3b6c3722Schristos 						return NULL;
182*3b6c3722Schristos 					}
183*3b6c3722Schristos 					break;
184*3b6c3722Schristos 				case 'H': /* hour */
185*3b6c3722Schristos 					ret = str2int(&s, 2);
186*3b6c3722Schristos 					if (ret < 0 || ret > 23) { /* must be in [00,23] */
187*3b6c3722Schristos 						return NULL;
188*3b6c3722Schristos 					}
189*3b6c3722Schristos 					tm->tm_hour = ret;
190*3b6c3722Schristos 					break;
191*3b6c3722Schristos 				case 'I': /* 12hr clock hour */
192*3b6c3722Schristos 					ret = str2int(&s, 2);
193*3b6c3722Schristos 					if (ret < 1 || ret > 12) { /* must be in [01,12] */
194*3b6c3722Schristos 						return NULL;
195*3b6c3722Schristos 					}
196*3b6c3722Schristos 					if (ret == 12) /* actually [0,11] */
197*3b6c3722Schristos 						ret = 0;
198*3b6c3722Schristos 					tm->tm_hour = ret;
199*3b6c3722Schristos 					break;
200*3b6c3722Schristos 				case 'j': /* day of year */
201*3b6c3722Schristos 					ret = str2int(&s, 2);
202*3b6c3722Schristos 					if (ret < 1 || ret > 366) { /* must be in [001,366] */
203*3b6c3722Schristos 						return NULL;
204*3b6c3722Schristos 					}
205*3b6c3722Schristos 					tm->tm_yday = ret;
206*3b6c3722Schristos 					break;
207*3b6c3722Schristos 				case 'm': /* month */
208*3b6c3722Schristos 					ret = str2int(&s, 2);
209*3b6c3722Schristos 					if (ret < 1 || ret > 12) { /* must be in [01,12] */
210*3b6c3722Schristos 						return NULL;
211*3b6c3722Schristos 					}
212*3b6c3722Schristos 					/* months go from 0-11 */
213*3b6c3722Schristos 					tm->tm_mon = (ret-1);
214*3b6c3722Schristos 					break;
215*3b6c3722Schristos 				case 'M': /* minute */
216*3b6c3722Schristos 					ret = str2int(&s, 2);
217*3b6c3722Schristos 					if (ret < 0 || ret > 59) { /* must be in [00,59] */
218*3b6c3722Schristos 						return NULL;
219*3b6c3722Schristos 					}
220*3b6c3722Schristos 					tm->tm_min = ret;
221*3b6c3722Schristos 					break;
222*3b6c3722Schristos 				case 'n': /* arbitrary whitespace */
223*3b6c3722Schristos 				case 't':
224*3b6c3722Schristos 					while (isspace((unsigned char)*s))
225*3b6c3722Schristos 						s++;
226*3b6c3722Schristos 					break;
227*3b6c3722Schristos 				case 'p': /* am pm */
228*3b6c3722Schristos 					ret = match_string(&s, ampm);
229*3b6c3722Schristos 					if (ret < 0) {
230*3b6c3722Schristos 						return NULL;
231*3b6c3722Schristos 					}
232*3b6c3722Schristos 					if (tm->tm_hour < 0 || tm->tm_hour > 11) { /* %I */
233*3b6c3722Schristos 						return NULL;
234*3b6c3722Schristos 					}
235*3b6c3722Schristos 
236*3b6c3722Schristos 					if (ret == 1) /* pm */
237*3b6c3722Schristos 						tm->tm_hour += 12;
238*3b6c3722Schristos 					break;
239*3b6c3722Schristos 				case 'r': /* equivalent of %I:%M:%S %p */
240*3b6c3722Schristos 					if (!(s = unbound_strptime(s, "%I:%M:%S %p", tm))) {
241*3b6c3722Schristos 						return NULL;
242*3b6c3722Schristos 					}
243*3b6c3722Schristos 					break;
244*3b6c3722Schristos 				case 'R': /* equivalent of %H:%M */
245*3b6c3722Schristos 					if (!(s = unbound_strptime(s, "%H:%M", tm))) {
246*3b6c3722Schristos 						return NULL;
247*3b6c3722Schristos 					}
248*3b6c3722Schristos 					break;
249*3b6c3722Schristos 				case 'S': /* seconds */
250*3b6c3722Schristos 					ret = str2int(&s, 2);
251*3b6c3722Schristos 					/* 60 may occur for leap seconds */
252*3b6c3722Schristos 					/* earlier 61 was also allowed */
253*3b6c3722Schristos 					if (ret < 0 || ret > 60) { /* must be in [00,60] */
254*3b6c3722Schristos 						return NULL;
255*3b6c3722Schristos 					}
256*3b6c3722Schristos 					tm->tm_sec = ret;
257*3b6c3722Schristos 					break;
258*3b6c3722Schristos 				case 'T': /* equivalent of %H:%M:%S */
259*3b6c3722Schristos 					if (!(s = unbound_strptime(s, "%H:%M:%S", tm))) {
260*3b6c3722Schristos 						return NULL;
261*3b6c3722Schristos 					}
262*3b6c3722Schristos 					break;
263*3b6c3722Schristos 				case 'U': /* week number, with the first Sun of Jan being w1 */
264*3b6c3722Schristos 					ret = str2int(&s, 2);
265*3b6c3722Schristos 					if (ret < 0 || ret > 53) { /* must be in [00,53] */
266*3b6c3722Schristos 						return NULL;
267*3b6c3722Schristos 					}
268*3b6c3722Schristos 					/** it is hard (and not necessary for nsd) to determine time
269*3b6c3722Schristos 					  * data from week number.
270*3b6c3722Schristos 					 **/
271*3b6c3722Schristos 					break;
272*3b6c3722Schristos 				case 'w': /* day of week */
273*3b6c3722Schristos 					ret = str2int(&s, 1);
274*3b6c3722Schristos 					if (ret < 0 || ret > 6) { /* must be in [0,6] */
275*3b6c3722Schristos 						return NULL;
276*3b6c3722Schristos 					}
277*3b6c3722Schristos 					tm->tm_wday = ret;
278*3b6c3722Schristos 					break;
279*3b6c3722Schristos 				case 'W': /* week number, with the first Mon of Jan being w1 */
280*3b6c3722Schristos 					ret = str2int(&s, 2);
281*3b6c3722Schristos 					if (ret < 0 || ret > 53) { /* must be in [00,53] */
282*3b6c3722Schristos 						return NULL;
283*3b6c3722Schristos 					}
284*3b6c3722Schristos 					/** it is hard (and not necessary for nsd) to determine time
285*3b6c3722Schristos 					  * data from week number.
286*3b6c3722Schristos 					 **/
287*3b6c3722Schristos 					break;
288*3b6c3722Schristos 				case 'x': /* date format */
289*3b6c3722Schristos 					if (!(s = unbound_strptime(s, "%m/%d/%y", tm))) {
290*3b6c3722Schristos 						return NULL;
291*3b6c3722Schristos 					}
292*3b6c3722Schristos 					break;
293*3b6c3722Schristos 				case 'X': /* time format */
294*3b6c3722Schristos 					if (!(s = unbound_strptime(s, "%H:%M:%S", tm))) {
295*3b6c3722Schristos 						return NULL;
296*3b6c3722Schristos 					}
297*3b6c3722Schristos 					break;
298*3b6c3722Schristos 				case 'y': /* last two digits of a year */
299*3b6c3722Schristos 					ret = str2int(&s, 2);
300*3b6c3722Schristos 					if (ret < 0 || ret > 99) { /* must be in [00,99] */
301*3b6c3722Schristos 						return NULL;
302*3b6c3722Schristos 					}
303*3b6c3722Schristos 					if (split_year) {
304*3b6c3722Schristos 						tm->tm_year = ((tm->tm_year/100) * 100) + ret;
305*3b6c3722Schristos 					}
306*3b6c3722Schristos 					else {
307*3b6c3722Schristos 						split_year = 1;
308*3b6c3722Schristos 
309*3b6c3722Schristos 						/** currently:
310*3b6c3722Schristos 						  * if in [0,68] we are in 21th century,
311*3b6c3722Schristos 						  * if in [69,99] we are in 20th century.
312*3b6c3722Schristos 						 **/
313*3b6c3722Schristos 						if (ret < 69) /* 2000 */
314*3b6c3722Schristos 							ret += 100;
315*3b6c3722Schristos 						tm->tm_year = ret;
316*3b6c3722Schristos 					}
317*3b6c3722Schristos 					break;
318*3b6c3722Schristos 				case 'Y': /* year */
319*3b6c3722Schristos 					ret = str2int(&s, 4);
320*3b6c3722Schristos 					if (ret < 0 || ret > 9999) {
321*3b6c3722Schristos 						return NULL;
322*3b6c3722Schristos 					}
323*3b6c3722Schristos 					tm->tm_year = ret - TM_YEAR_BASE;
324*3b6c3722Schristos 					break;
325*3b6c3722Schristos 				case '\0':
326*3b6c3722Schristos 				default: /* unsupported, cannot match format */
327*3b6c3722Schristos 					return NULL;
328*3b6c3722Schristos 					break;
329*3b6c3722Schristos 			}
330*3b6c3722Schristos 		}
331*3b6c3722Schristos 		else { /* literal */
332*3b6c3722Schristos 			/* if input cannot match format, return NULL */
333*3b6c3722Schristos 			if (*s != c)
334*3b6c3722Schristos 				return NULL;
335*3b6c3722Schristos 			s++;
336*3b6c3722Schristos 		}
337*3b6c3722Schristos 
338*3b6c3722Schristos 		format++;
339*3b6c3722Schristos 	}
340*3b6c3722Schristos 
341*3b6c3722Schristos 	/* return pointer to remainder of s */
342*3b6c3722Schristos 	return (char*) s;
343*3b6c3722Schristos }
344*3b6c3722Schristos 
345*3b6c3722Schristos #endif /* STRPTIME_WORKS */
346