xref: /freebsd/lib/libc/stdtime/strptime.c (revision d1ba25f456132eabc6f1244e4bbbf3d19e8f3a31)
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 <time.h>
68 #include <ctype.h>
69 #include <string.h>
70 #ifdef	_THREAD_SAFE
71 #include <pthread.h>
72 #include "pthread_private.h"
73 #endif
74 #include "timelocal.h"
75 
76 static char * _strptime(const char *, const char *, struct tm *);
77 
78 #ifdef	_THREAD_SAFE
79 static struct pthread_mutex	_gotgmt_mutexd = PTHREAD_MUTEX_STATIC_INITIALIZER;
80 static pthread_mutex_t		gotgmt_mutex   = &_gotgmt_mutexd;
81 #endif
82 static int got_GMT;
83 
84 #define asizeof(a)	(sizeof (a) / sizeof ((a)[0]))
85 
86 static char *
87 _strptime(const char *buf, const char *fmt, struct tm *tm)
88 {
89 	char	c;
90 	const char *ptr;
91 	int	i,
92 		len;
93 	int Ealternative, Oalternative;
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, Locale->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 			/* NOTE: c_fmt is intentionally ignored */
147 			buf = _strptime(buf, "%a %Ef %T %Y", tm);
148 			if (buf == 0)
149 				return 0;
150 			break;
151 
152 		case 'D':
153 			buf = _strptime(buf, "%m/%d/%y", tm);
154 			if (buf == 0)
155 				return 0;
156 			break;
157 
158 		case 'E':
159 			if (Ealternative || Oalternative)
160 				break;
161 			Ealternative++;
162 			goto label;
163 
164 		case 'O':
165 			if (Ealternative || Oalternative)
166 				break;
167 			Oalternative++;
168 			goto label;
169 
170 		case 'F':
171 		case 'f':
172 			if (!Ealternative)
173 				break;
174 			buf = _strptime(buf, (c == 'f') ? Locale->Ef_fmt : Locale->EF_fmt, tm);
175 			if (buf == 0)
176 				return 0;
177 			break;
178 
179 		case 'R':
180 			buf = _strptime(buf, "%H:%M", tm);
181 			if (buf == 0)
182 				return 0;
183 			break;
184 
185 		case 'r':
186 			buf = _strptime(buf, "%I:%M:%S %p", tm);
187 			if (buf == 0)
188 				return 0;
189 			break;
190 
191 		case 'T':
192 			buf = _strptime(buf, "%H:%M:%S", tm);
193 			if (buf == 0)
194 				return 0;
195 			break;
196 
197 		case 'X':
198 			buf = _strptime(buf, Locale->X_fmt, tm);
199 			if (buf == 0)
200 				return 0;
201 			break;
202 
203 		case 'x':
204 			buf = _strptime(buf, Locale->x_fmt, tm);
205 			if (buf == 0)
206 				return 0;
207 			break;
208 
209 		case 'j':
210 			if (!isdigit((unsigned char)*buf))
211 				return 0;
212 
213 			len = 3;
214 			for (i = 0; len && *buf != 0 && isdigit((unsigned char)*buf); buf++) {
215 				i *= 10;
216 				i += *buf - '0';
217 				len--;
218 			}
219 			if (i < 1 || i > 366)
220 				return 0;
221 
222 			tm->tm_yday = i - 1;
223 			break;
224 
225 		case 'M':
226 		case 'S':
227 			if (*buf == 0 || isspace((unsigned char)*buf))
228 				break;
229 
230 			if (!isdigit((unsigned char)*buf))
231 				return 0;
232 
233 			len = 2;
234 			for (i = 0; len && *buf != 0 && isdigit((unsigned char)*buf); buf++) {
235 				i *= 10;
236 				i += *buf - '0';
237 				len--;
238 			}
239 
240 			if (c == 'M') {
241 				if (i > 59)
242 					return 0;
243 				tm->tm_min = i;
244 			} else {
245 				if (i > 60)
246 					return 0;
247 				tm->tm_sec = i;
248 			}
249 
250 			if (*buf != 0 && isspace((unsigned char)*buf))
251 				while (*ptr != 0 && !isspace((unsigned char)*ptr))
252 					ptr++;
253 			break;
254 
255 		case 'H':
256 		case 'I':
257 		case 'k':
258 		case 'l':
259 			/*
260 			 * Of these, %l is the only specifier explicitly
261 			 * documented as not being zero-padded.  However,
262 			 * there is no harm in allowing zero-padding.
263 			 *
264 			 * XXX The %l specifier may gobble one too many
265 			 * digits if used incorrectly.
266 			 */
267 			if (!isdigit((unsigned char)*buf))
268 				return 0;
269 
270 			len = 2;
271 			for (i = 0; len && *buf != 0 && isdigit((unsigned char)*buf); buf++) {
272 				i *= 10;
273 				i += *buf - '0';
274 				len--;
275 			}
276 			if (c == 'H' || c == 'k') {
277 				if (i > 23)
278 					return 0;
279 			} else if (i > 12)
280 				return 0;
281 
282 			tm->tm_hour = i;
283 
284 			if (*buf != 0 && isspace((unsigned char)*buf))
285 				while (*ptr != 0 && !isspace((unsigned char)*ptr))
286 					ptr++;
287 			break;
288 
289 		case 'p':
290 			/*
291 			 * XXX This is bogus if parsed before hour-related
292 			 * specifiers.
293 			 */
294 			len = strlen(Locale->am);
295 			if (strncasecmp(buf, Locale->am, len) == 0) {
296 				if (tm->tm_hour > 12)
297 					return 0;
298 				if (tm->tm_hour == 12)
299 					tm->tm_hour = 0;
300 				buf += len;
301 				break;
302 			}
303 
304 			len = strlen(Locale->pm);
305 			if (strncasecmp(buf, Locale->pm, len) == 0) {
306 				if (tm->tm_hour > 12)
307 					return 0;
308 				if (tm->tm_hour != 12)
309 					tm->tm_hour += 12;
310 				buf += len;
311 				break;
312 			}
313 
314 			return 0;
315 
316 		case 'A':
317 		case 'a':
318 			for (i = 0; i < asizeof(Locale->weekday); i++) {
319 				if (c == 'A') {
320 					len = strlen(Locale->weekday[i]);
321 					if (strncasecmp(buf,
322 							Locale->weekday[i],
323 							len) == 0)
324 						break;
325 				} else {
326 					len = strlen(Locale->wday[i]);
327 					if (strncasecmp(buf,
328 							Locale->wday[i],
329 							len) == 0)
330 						break;
331 				}
332 			}
333 			if (i == asizeof(Locale->weekday))
334 				return 0;
335 
336 			tm->tm_wday = i;
337 			buf += len;
338 			break;
339 
340 		case 'U':
341 		case 'W':
342 			/*
343 			 * XXX This is bogus, as we can not assume any valid
344 			 * information present in the tm structure at this
345 			 * point to calculate a real value, so just check the
346 			 * range for now.
347 			 */
348 			if (!isdigit((unsigned char)*buf))
349 				return 0;
350 
351 			len = 2;
352 			for (i = 0; len && *buf != 0 && isdigit((unsigned char)*buf); buf++) {
353 				i *= 10;
354 				i += *buf - '0';
355 				len--;
356 			}
357 			if (i > 53)
358 				return 0;
359 
360 			if (*buf != 0 && isspace((unsigned char)*buf))
361 				while (*ptr != 0 && !isspace((unsigned char)*ptr))
362 					ptr++;
363 			break;
364 
365 		case 'w':
366 			if (!isdigit((unsigned char)*buf))
367 				return 0;
368 
369 			i = *buf - '0';
370 			if (i > 6)
371 				return 0;
372 
373 			tm->tm_wday = i;
374 
375 			if (*buf != 0 && isspace((unsigned char)*buf))
376 				while (*ptr != 0 && !isspace((unsigned char)*ptr))
377 					ptr++;
378 			break;
379 
380 		case 'd':
381 		case 'e':
382 			/*
383 			 * The %e specifier is explicitly documented as not
384 			 * being zero-padded but there is no harm in allowing
385 			 * such padding.
386 			 *
387 			 * XXX The %e specifier may gobble one too many
388 			 * digits if used incorrectly.
389 			 */
390 			if (!isdigit((unsigned char)*buf))
391 				return 0;
392 
393 			len = 2;
394 			for (i = 0; len && *buf != 0 && isdigit((unsigned char)*buf); buf++) {
395 				i *= 10;
396 				i += *buf - '0';
397 				len--;
398 			}
399 			if (i > 31)
400 				return 0;
401 
402 			tm->tm_mday = i;
403 
404 			if (*buf != 0 && isspace((unsigned char)*buf))
405 				while (*ptr != 0 && !isspace((unsigned char)*ptr))
406 					ptr++;
407 			break;
408 
409 		case 'B':
410 		case 'b':
411 		case 'h':
412 			for (i = 0; i < asizeof(Locale->month); i++) {
413 				if (Oalternative) {
414 					if (c == 'B') {
415 						len = strlen(Locale->alt_month[i]);
416 						if (strncasecmp(buf,
417 								Locale->alt_month[i],
418 								len) == 0)
419 							break;
420 					}
421 				} else {
422 					if (c == 'B') {
423 						len = strlen(Locale->month[i]);
424 						if (strncasecmp(buf,
425 								Locale->month[i],
426 								len) == 0)
427 							break;
428 					} else {
429 						len = strlen(Locale->mon[i]);
430 						if (strncasecmp(buf,
431 								Locale->mon[i],
432 								len) == 0)
433 							break;
434 					}
435 				}
436 			}
437 			if (i == asizeof(Locale->month))
438 				return 0;
439 
440 			tm->tm_mon = i;
441 			buf += len;
442 			break;
443 
444 		case 'm':
445 			if (!isdigit((unsigned char)*buf))
446 				return 0;
447 
448 			len = 2;
449 			for (i = 0; len && *buf != 0 && isdigit((unsigned char)*buf); buf++) {
450 				i *= 10;
451 				i += *buf - '0';
452 				len--;
453 			}
454 			if (i < 1 || i > 12)
455 				return 0;
456 
457 			tm->tm_mon = i - 1;
458 
459 			if (*buf != 0 && isspace((unsigned char)*buf))
460 				while (*ptr != 0 && !isspace((unsigned char)*ptr))
461 					ptr++;
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 #ifdef	_THREAD_SAFE
528 	pthread_mutex_lock(&gotgmt_mutex);
529 #endif
530 
531 	got_GMT = 0;
532 	ret = _strptime(buf, fmt, tm);
533 	if (ret && got_GMT) {
534 		time_t t = timegm(tm);
535 	    localtime_r(&t, tm);
536 		got_GMT = 0;
537 	}
538 
539 #ifdef	_THREAD_SAFE
540 	pthread_mutex_unlock(&gotgmt_mutex);
541 #endif
542 
543 	return ret;
544 }
545