xref: /freebsd/lib/libc/stdtime/strptime.c (revision 17d6c636720d00f77e5d098daf4c278f89d84f7b)
1 /*
2  * Powerdog Industries kindly requests feedback from anyone modifying
3  * this function:
4  *
5  * Date: Thu, 05 Jun 1997 23:17:17 -0400
6  * From: Kevin Ruddy <kevin.ruddy@powerdog.com>
7  * To: James FitzGibbon <james@nexis.net>
8  * Subject: Re: Use of your strptime(3) code (fwd)
9  *
10  * The reason for the "no mod" clause was so that modifications would
11  * come back and we could integrate them and reissue so that a wider
12  * audience could use it (thereby spreading the wealth).  This has
13  * made it possible to get strptime to work on many operating systems.
14  * I'm not sure why that's "plain unacceptable" to the FreeBSD team.
15  *
16  * Anyway, you can change it to "with or without modification" as
17  * you see fit.  Enjoy.
18  *
19  * Kevin Ruddy
20  * Powerdog Industries, Inc.
21  */
22 /*
23  * Copyright (c) 1994 Powerdog Industries.  All rights reserved.
24  *
25  * Redistribution and use in source and binary forms, with or without
26  * modification, are permitted provided that the following conditions
27  * are met:
28  * 1. Redistributions of source code must retain the above copyright
29  *    notice, this list of conditions and the following disclaimer.
30  * 2. Redistributions in binary form must reproduce the above copyright
31  *    notice, this list of conditions and the following disclaimer
32  *    in the documentation and/or other materials provided with the
33  *    distribution.
34  * 3. All advertising materials mentioning features or use of this
35  *    software must display the following acknowledgement:
36  *      This product includes software developed by Powerdog Industries.
37  * 4. The name of Powerdog Industries may not be used to endorse or
38  *    promote products derived from this software without specific prior
39  *    written permission.
40  *
41  * THIS SOFTWARE IS PROVIDED BY POWERDOG INDUSTRIES ``AS IS'' AND ANY
42  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
43  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
44  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE POWERDOG INDUSTRIES BE
45  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
46  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
47  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
48  * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
49  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
50  * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
51  * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
52  */
53 
54 #ifdef LIBC_RCS
55 static const char rcsid[] =
56   "$FreeBSD$";
57 #endif
58 
59 #ifndef lint
60 #ifndef NOID
61 static char copyright[] =
62 "@(#) Copyright (c) 1994 Powerdog Industries.  All rights reserved.";
63 static char sccsid[] = "@(#)strptime.c	0.1 (Powerdog) 94/03/27";
64 #endif /* !defined NOID */
65 #endif /* not lint */
66 
67 #include "namespace.h"
68 #include <time.h>
69 #include <ctype.h>
70 #include <limits.h>
71 #include <stdlib.h>
72 #include <string.h>
73 #include <pthread.h>
74 #include "un-namespace.h"
75 #include "libc_private.h"
76 #include "timelocal.h"
77 
78 static char * _strptime(const char *, const char *, struct tm *);
79 
80 static pthread_mutex_t	gotgmt_mutex = PTHREAD_MUTEX_INITIALIZER;
81 static int got_GMT;
82 
83 #define asizeof(a)	(sizeof (a) / sizeof ((a)[0]))
84 
85 static char *
86 _strptime(const char *buf, const char *fmt, struct tm *tm)
87 {
88 	char	c;
89 	const char *ptr;
90 	int	i,
91 		len;
92 	int Ealternative, Oalternative;
93 	struct lc_time_T *tptr = __get_current_time_locale();
94 
95 	ptr = fmt;
96 	while (*ptr != 0) {
97 		if (*buf == 0)
98 			break;
99 
100 		c = *ptr++;
101 
102 		if (c != '%') {
103 			if (isspace((unsigned char)c))
104 				while (*buf != 0 && isspace((unsigned char)*buf))
105 					buf++;
106 			else if (c != *buf++)
107 				return 0;
108 			continue;
109 		}
110 
111 		Ealternative = 0;
112 		Oalternative = 0;
113 label:
114 		c = *ptr++;
115 		switch (c) {
116 		case 0:
117 		case '%':
118 			if (*buf++ != '%')
119 				return 0;
120 			break;
121 
122 		case '+':
123 			buf = _strptime(buf, tptr->date_fmt, tm);
124 			if (buf == 0)
125 				return 0;
126 			break;
127 
128 		case 'C':
129 			if (!isdigit((unsigned char)*buf))
130 				return 0;
131 
132 			/* XXX This will break for 3-digit centuries. */
133 			len = 2;
134 			for (i = 0; len && *buf != 0 && isdigit((unsigned char)*buf); buf++) {
135 				i *= 10;
136 				i += *buf - '0';
137 				len--;
138 			}
139 			if (i < 19)
140 				return 0;
141 
142 			tm->tm_year = i * 100 - 1900;
143 			break;
144 
145 		case 'c':
146 			buf = _strptime(buf, tptr->c_fmt, tm);
147 			if (buf == 0)
148 				return 0;
149 			break;
150 
151 		case 'D':
152 			buf = _strptime(buf, "%m/%d/%y", tm);
153 			if (buf == 0)
154 				return 0;
155 			break;
156 
157 		case 'E':
158 			if (Ealternative || Oalternative)
159 				break;
160 			Ealternative++;
161 			goto label;
162 
163 		case 'O':
164 			if (Ealternative || Oalternative)
165 				break;
166 			Oalternative++;
167 			goto label;
168 
169 		case 'F':
170 			buf = _strptime(buf, "%Y-%m-%d", tm);
171 			if (buf == 0)
172 				return 0;
173 			break;
174 
175 		case 'R':
176 			buf = _strptime(buf, "%H:%M", tm);
177 			if (buf == 0)
178 				return 0;
179 			break;
180 
181 		case 'r':
182 			buf = _strptime(buf, tptr->ampm_fmt, tm);
183 			if (buf == 0)
184 				return 0;
185 			break;
186 
187 		case 'T':
188 			buf = _strptime(buf, "%H:%M:%S", tm);
189 			if (buf == 0)
190 				return 0;
191 			break;
192 
193 		case 'X':
194 			buf = _strptime(buf, tptr->X_fmt, tm);
195 			if (buf == 0)
196 				return 0;
197 			break;
198 
199 		case 'x':
200 			buf = _strptime(buf, tptr->x_fmt, tm);
201 			if (buf == 0)
202 				return 0;
203 			break;
204 
205 		case 'j':
206 			if (!isdigit((unsigned char)*buf))
207 				return 0;
208 
209 			len = 3;
210 			for (i = 0; len && *buf != 0 && isdigit((unsigned char)*buf); buf++) {
211 				i *= 10;
212 				i += *buf - '0';
213 				len--;
214 			}
215 			if (i < 1 || i > 366)
216 				return 0;
217 
218 			tm->tm_yday = i - 1;
219 			break;
220 
221 		case 'M':
222 		case 'S':
223 			if (*buf == 0 || isspace((unsigned char)*buf))
224 				break;
225 
226 			if (!isdigit((unsigned char)*buf))
227 				return 0;
228 
229 			len = 2;
230 			for (i = 0; len && *buf != 0 && isdigit((unsigned char)*buf); buf++) {
231 				i *= 10;
232 				i += *buf - '0';
233 				len--;
234 			}
235 
236 			if (c == 'M') {
237 				if (i > 59)
238 					return 0;
239 				tm->tm_min = i;
240 			} else {
241 				if (i > 60)
242 					return 0;
243 				tm->tm_sec = i;
244 			}
245 
246 			if (*buf != 0 && isspace((unsigned char)*buf))
247 				while (*ptr != 0 && !isspace((unsigned char)*ptr))
248 					ptr++;
249 			break;
250 
251 		case 'H':
252 		case 'I':
253 		case 'k':
254 		case 'l':
255 			/*
256 			 * Of these, %l is the only specifier explicitly
257 			 * documented as not being zero-padded.  However,
258 			 * there is no harm in allowing zero-padding.
259 			 *
260 			 * XXX The %l specifier may gobble one too many
261 			 * digits if used incorrectly.
262 			 */
263 			if (!isdigit((unsigned char)*buf))
264 				return 0;
265 
266 			len = 2;
267 			for (i = 0; len && *buf != 0 && isdigit((unsigned char)*buf); buf++) {
268 				i *= 10;
269 				i += *buf - '0';
270 				len--;
271 			}
272 			if (c == 'H' || c == 'k') {
273 				if (i > 23)
274 					return 0;
275 			} else if (i > 12)
276 				return 0;
277 
278 			tm->tm_hour = i;
279 
280 			if (*buf != 0 && isspace((unsigned char)*buf))
281 				while (*ptr != 0 && !isspace((unsigned char)*ptr))
282 					ptr++;
283 			break;
284 
285 		case 'p':
286 			/*
287 			 * XXX This is bogus if parsed before hour-related
288 			 * specifiers.
289 			 */
290 			len = strlen(tptr->am);
291 			if (strncasecmp(buf, tptr->am, len) == 0) {
292 				if (tm->tm_hour > 12)
293 					return 0;
294 				if (tm->tm_hour == 12)
295 					tm->tm_hour = 0;
296 				buf += len;
297 				break;
298 			}
299 
300 			len = strlen(tptr->pm);
301 			if (strncasecmp(buf, tptr->pm, len) == 0) {
302 				if (tm->tm_hour > 12)
303 					return 0;
304 				if (tm->tm_hour != 12)
305 					tm->tm_hour += 12;
306 				buf += len;
307 				break;
308 			}
309 
310 			return 0;
311 
312 		case 'A':
313 		case 'a':
314 			for (i = 0; i < asizeof(tptr->weekday); i++) {
315 				len = strlen(tptr->weekday[i]);
316 				if (strncasecmp(buf, tptr->weekday[i],
317 						len) == 0)
318 					break;
319 				len = strlen(tptr->wday[i]);
320 				if (strncasecmp(buf, tptr->wday[i],
321 						len) == 0)
322 					break;
323 			}
324 			if (i == asizeof(tptr->weekday))
325 				return 0;
326 
327 			tm->tm_wday = i;
328 			buf += len;
329 			break;
330 
331 		case 'U':
332 		case 'W':
333 			/*
334 			 * XXX This is bogus, as we can not assume any valid
335 			 * information present in the tm structure at this
336 			 * point to calculate a real value, so just check the
337 			 * range for now.
338 			 */
339 			if (!isdigit((unsigned char)*buf))
340 				return 0;
341 
342 			len = 2;
343 			for (i = 0; len && *buf != 0 && isdigit((unsigned char)*buf); buf++) {
344 				i *= 10;
345 				i += *buf - '0';
346 				len--;
347 			}
348 			if (i > 53)
349 				return 0;
350 
351 			if (*buf != 0 && isspace((unsigned char)*buf))
352 				while (*ptr != 0 && !isspace((unsigned char)*ptr))
353 					ptr++;
354 			break;
355 
356 		case 'w':
357 			if (!isdigit((unsigned char)*buf))
358 				return 0;
359 
360 			i = *buf - '0';
361 			if (i > 6)
362 				return 0;
363 
364 			tm->tm_wday = i;
365 
366 			if (*buf != 0 && isspace((unsigned char)*buf))
367 				while (*ptr != 0 && !isspace((unsigned char)*ptr))
368 					ptr++;
369 			break;
370 
371 		case 'd':
372 		case 'e':
373 			/*
374 			 * The %e specifier is explicitly documented as not
375 			 * being zero-padded but there is no harm in allowing
376 			 * such padding.
377 			 *
378 			 * XXX The %e specifier may gobble one too many
379 			 * digits if used incorrectly.
380 			 */
381 			if (!isdigit((unsigned char)*buf))
382 				return 0;
383 
384 			len = 2;
385 			for (i = 0; len && *buf != 0 && isdigit((unsigned char)*buf); buf++) {
386 				i *= 10;
387 				i += *buf - '0';
388 				len--;
389 			}
390 			if (i > 31)
391 				return 0;
392 
393 			tm->tm_mday = i;
394 
395 			if (*buf != 0 && isspace((unsigned char)*buf))
396 				while (*ptr != 0 && !isspace((unsigned char)*ptr))
397 					ptr++;
398 			break;
399 
400 		case 'B':
401 		case 'b':
402 		case 'h':
403 			for (i = 0; i < asizeof(tptr->month); i++) {
404 				if (Oalternative) {
405 					if (c == 'B') {
406 						len = strlen(tptr->alt_month[i]);
407 						if (strncasecmp(buf,
408 								tptr->alt_month[i],
409 								len) == 0)
410 							break;
411 					}
412 				} else {
413 					len = strlen(tptr->month[i]);
414 					if (strncasecmp(buf, tptr->month[i],
415 							len) == 0)
416 						break;
417 					len = strlen(tptr->mon[i]);
418 					if (strncasecmp(buf, tptr->mon[i],
419 							len) == 0)
420 						break;
421 				}
422 			}
423 			if (i == asizeof(tptr->month))
424 				return 0;
425 
426 			tm->tm_mon = i;
427 			buf += len;
428 			break;
429 
430 		case 'm':
431 			if (!isdigit((unsigned char)*buf))
432 				return 0;
433 
434 			len = 2;
435 			for (i = 0; len && *buf != 0 && isdigit((unsigned char)*buf); buf++) {
436 				i *= 10;
437 				i += *buf - '0';
438 				len--;
439 			}
440 			if (i < 1 || i > 12)
441 				return 0;
442 
443 			tm->tm_mon = i - 1;
444 
445 			if (*buf != 0 && isspace((unsigned char)*buf))
446 				while (*ptr != 0 && !isspace((unsigned char)*ptr))
447 					ptr++;
448 			break;
449 
450 		case 's':
451 			{
452 			char *cp;
453 			time_t t;
454 
455 			t = strtol(buf, &cp, 10);
456 			if (t == LONG_MAX)
457 				return 0;
458 			buf = cp;
459 			gmtime_r(&t, tm);
460 			got_GMT = 1;
461 			}
462 			break;
463 
464 		case 'Y':
465 		case 'y':
466 			if (*buf == 0 || isspace((unsigned char)*buf))
467 				break;
468 
469 			if (!isdigit((unsigned char)*buf))
470 				return 0;
471 
472 			len = (c == 'Y') ? 4 : 2;
473 			for (i = 0; len && *buf != 0 && isdigit((unsigned char)*buf); buf++) {
474 				i *= 10;
475 				i += *buf - '0';
476 				len--;
477 			}
478 			if (c == 'Y')
479 				i -= 1900;
480 			if (c == 'y' && i < 69)
481 				i += 100;
482 			if (i < 0)
483 				return 0;
484 
485 			tm->tm_year = i;
486 
487 			if (*buf != 0 && isspace((unsigned char)*buf))
488 				while (*ptr != 0 && !isspace((unsigned char)*ptr))
489 					ptr++;
490 			break;
491 
492 		case 'Z':
493 			{
494 			const char *cp;
495 			char *zonestr;
496 
497 			for (cp = buf; *cp && isupper((unsigned char)*cp); ++cp) {/*empty*/}
498 			if (cp - buf) {
499 				zonestr = alloca(cp - buf + 1);
500 				strncpy(zonestr, buf, cp - buf);
501 				zonestr[cp - buf] = '\0';
502 				tzset();
503 				if (0 == strcmp(zonestr, "GMT")) {
504 				    got_GMT = 1;
505 				} else if (0 == strcmp(zonestr, tzname[0])) {
506 				    tm->tm_isdst = 0;
507 				} else if (0 == strcmp(zonestr, tzname[1])) {
508 				    tm->tm_isdst = 1;
509 				} else {
510 				    return 0;
511 				}
512 				buf += cp - buf;
513 			}
514 			}
515 			break;
516 		}
517 	}
518 	return (char *)buf;
519 }
520 
521 
522 char *
523 strptime(const char *buf, const char *fmt, struct tm *tm)
524 {
525 	char *ret;
526 
527 	if (__isthreaded)
528 		_pthread_mutex_lock(&gotgmt_mutex);
529 
530 	got_GMT = 0;
531 	ret = _strptime(buf, fmt, tm);
532 	if (ret && got_GMT) {
533 		time_t t = timegm(tm);
534 		localtime_r(&t, tm);
535 		got_GMT = 0;
536 	}
537 
538 	if (__isthreaded)
539 		_pthread_mutex_unlock(&gotgmt_mutex);
540 
541 	return ret;
542 }
543