strptime.c revision 79664
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
55static const char rcsid[] =
56  "$FreeBSD: head/lib/libc/stdtime/strptime.c 79664 2001-07-13 13:59:24Z dd $";
57#endif
58
59#ifndef lint
60#ifndef NOID
61static char copyright[] =
62"@(#) Copyright (c) 1994 Powerdog Industries.  All rights reserved.";
63static 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
78static char * _strptime(const char *, const char *, struct tm *);
79
80static pthread_mutex_t	gotgmt_mutex = PTHREAD_MUTEX_INITIALIZER;
81static int got_GMT;
82
83#define asizeof(a)	(sizeof (a) / sizeof ((a)[0]))
84
85static 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;
113label:
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
522char *
523strptime(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