xref: /freebsd/crypto/heimdal/lib/roken/strptime.c (revision a0ee8cc6)
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 
40 static const char *abb_weekdays[] = {
41     "Sun",
42     "Mon",
43     "Tue",
44     "Wed",
45     "Thu",
46     "Fri",
47     "Sat",
48     NULL
49 };
50 
51 static const char *full_weekdays[] = {
52     "Sunday",
53     "Monday",
54     "Tuesday",
55     "Wednesday",
56     "Thursday",
57     "Friday",
58     "Saturday",
59     NULL
60 };
61 
62 static 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 
78 static 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 
94 static 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 
105 static int
106 match_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 
127 static int
128 parse_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 
158 const int tm_year_base = 1900;
159 
160 /*
161  * Return TRUE iff `year' was a leap year.
162  */
163 
164 static int
165 is_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 
174 static int
175 first_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 
188 static void
189 set_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 
204 static void
205 set_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 
220 static void
221 set_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 
240 ROKEN_LIB_FUNCTION char * ROKEN_LIB_CALL
241 strptime (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