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