strptime.c revision 111010
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#include <sys/cdefs.h>
55#ifndef lint
56#ifndef NOID
57static char copyright[] __unused =
58"@(#) Copyright (c) 1994 Powerdog Industries.  All rights reserved.";
59static char sccsid[] __unused = "@(#)strptime.c	0.1 (Powerdog) 94/03/27";
60#endif /* !defined NOID */
61#endif /* not lint */
62__FBSDID("$FreeBSD: head/lib/libc/stdtime/strptime.c 111010 2003-02-16 17:29:11Z nectar $");
63
64#include "namespace.h"
65#include <time.h>
66#include <ctype.h>
67#include <limits.h>
68#include <stdlib.h>
69#include <string.h>
70#include <pthread.h>
71#include "un-namespace.h"
72#include "libc_private.h"
73#include "timelocal.h"
74
75static char * _strptime(const char *, const char *, struct tm *);
76
77static pthread_mutex_t	gotgmt_mutex = PTHREAD_MUTEX_INITIALIZER;
78static int got_GMT;
79
80#define asizeof(a)	(sizeof (a) / sizeof ((a)[0]))
81
82static char *
83_strptime(const char *buf, const char *fmt, struct tm *tm)
84{
85	char	c;
86	const char *ptr;
87	int	i,
88		len;
89	int Ealternative, Oalternative;
90	struct lc_time_T *tptr = __get_current_time_locale();
91
92	ptr = fmt;
93	while (*ptr != 0) {
94		if (*buf == 0)
95			break;
96
97		c = *ptr++;
98
99		if (c != '%') {
100			if (isspace((unsigned char)c))
101				while (*buf != 0 && isspace((unsigned char)*buf))
102					buf++;
103			else if (c != *buf++)
104				return 0;
105			continue;
106		}
107
108		Ealternative = 0;
109		Oalternative = 0;
110label:
111		c = *ptr++;
112		switch (c) {
113		case 0:
114		case '%':
115			if (*buf++ != '%')
116				return 0;
117			break;
118
119		case '+':
120			buf = _strptime(buf, tptr->date_fmt, tm);
121			if (buf == 0)
122				return 0;
123			break;
124
125		case 'C':
126			if (!isdigit((unsigned char)*buf))
127				return 0;
128
129			/* XXX This will break for 3-digit centuries. */
130			len = 2;
131			for (i = 0; len && *buf != 0 && isdigit((unsigned char)*buf); buf++) {
132				i *= 10;
133				i += *buf - '0';
134				len--;
135			}
136			if (i < 19)
137				return 0;
138
139			tm->tm_year = i * 100 - 1900;
140			break;
141
142		case 'c':
143			buf = _strptime(buf, tptr->c_fmt, tm);
144			if (buf == 0)
145				return 0;
146			break;
147
148		case 'D':
149			buf = _strptime(buf, "%m/%d/%y", tm);
150			if (buf == 0)
151				return 0;
152			break;
153
154		case 'E':
155			if (Ealternative || Oalternative)
156				break;
157			Ealternative++;
158			goto label;
159
160		case 'O':
161			if (Ealternative || Oalternative)
162				break;
163			Oalternative++;
164			goto label;
165
166		case 'F':
167			buf = _strptime(buf, "%Y-%m-%d", tm);
168			if (buf == 0)
169				return 0;
170			break;
171
172		case 'R':
173			buf = _strptime(buf, "%H:%M", tm);
174			if (buf == 0)
175				return 0;
176			break;
177
178		case 'r':
179			buf = _strptime(buf, tptr->ampm_fmt, tm);
180			if (buf == 0)
181				return 0;
182			break;
183
184		case 'T':
185			buf = _strptime(buf, "%H:%M:%S", tm);
186			if (buf == 0)
187				return 0;
188			break;
189
190		case 'X':
191			buf = _strptime(buf, tptr->X_fmt, tm);
192			if (buf == 0)
193				return 0;
194			break;
195
196		case 'x':
197			buf = _strptime(buf, tptr->x_fmt, tm);
198			if (buf == 0)
199				return 0;
200			break;
201
202		case 'j':
203			if (!isdigit((unsigned char)*buf))
204				return 0;
205
206			len = 3;
207			for (i = 0; len && *buf != 0 && isdigit((unsigned char)*buf); buf++) {
208				i *= 10;
209				i += *buf - '0';
210				len--;
211			}
212			if (i < 1 || i > 366)
213				return 0;
214
215			tm->tm_yday = i - 1;
216			break;
217
218		case 'M':
219		case 'S':
220			if (*buf == 0 || isspace((unsigned char)*buf))
221				break;
222
223			if (!isdigit((unsigned char)*buf))
224				return 0;
225
226			len = 2;
227			for (i = 0; len && *buf != 0 && isdigit((unsigned char)*buf); buf++) {
228				i *= 10;
229				i += *buf - '0';
230				len--;
231			}
232
233			if (c == 'M') {
234				if (i > 59)
235					return 0;
236				tm->tm_min = i;
237			} else {
238				if (i > 60)
239					return 0;
240				tm->tm_sec = i;
241			}
242
243			if (*buf != 0 && isspace((unsigned char)*buf))
244				while (*ptr != 0 && !isspace((unsigned char)*ptr))
245					ptr++;
246			break;
247
248		case 'H':
249		case 'I':
250		case 'k':
251		case 'l':
252			/*
253			 * Of these, %l is the only specifier explicitly
254			 * documented as not being zero-padded.  However,
255			 * there is no harm in allowing zero-padding.
256			 *
257			 * XXX The %l specifier may gobble one too many
258			 * digits if used incorrectly.
259			 */
260			if (!isdigit((unsigned char)*buf))
261				return 0;
262
263			len = 2;
264			for (i = 0; len && *buf != 0 && isdigit((unsigned char)*buf); buf++) {
265				i *= 10;
266				i += *buf - '0';
267				len--;
268			}
269			if (c == 'H' || c == 'k') {
270				if (i > 23)
271					return 0;
272			} else if (i > 12)
273				return 0;
274
275			tm->tm_hour = i;
276
277			if (*buf != 0 && isspace((unsigned char)*buf))
278				while (*ptr != 0 && !isspace((unsigned char)*ptr))
279					ptr++;
280			break;
281
282		case 'p':
283			/*
284			 * XXX This is bogus if parsed before hour-related
285			 * specifiers.
286			 */
287			len = strlen(tptr->am);
288			if (strncasecmp(buf, tptr->am, len) == 0) {
289				if (tm->tm_hour > 12)
290					return 0;
291				if (tm->tm_hour == 12)
292					tm->tm_hour = 0;
293				buf += len;
294				break;
295			}
296
297			len = strlen(tptr->pm);
298			if (strncasecmp(buf, tptr->pm, len) == 0) {
299				if (tm->tm_hour > 12)
300					return 0;
301				if (tm->tm_hour != 12)
302					tm->tm_hour += 12;
303				buf += len;
304				break;
305			}
306
307			return 0;
308
309		case 'A':
310		case 'a':
311			for (i = 0; i < asizeof(tptr->weekday); i++) {
312				len = strlen(tptr->weekday[i]);
313				if (strncasecmp(buf, tptr->weekday[i],
314						len) == 0)
315					break;
316				len = strlen(tptr->wday[i]);
317				if (strncasecmp(buf, tptr->wday[i],
318						len) == 0)
319					break;
320			}
321			if (i == asizeof(tptr->weekday))
322				return 0;
323
324			tm->tm_wday = i;
325			buf += len;
326			break;
327
328		case 'U':
329		case 'W':
330			/*
331			 * XXX This is bogus, as we can not assume any valid
332			 * information present in the tm structure at this
333			 * point to calculate a real value, so just check the
334			 * range for now.
335			 */
336			if (!isdigit((unsigned char)*buf))
337				return 0;
338
339			len = 2;
340			for (i = 0; len && *buf != 0 && isdigit((unsigned char)*buf); buf++) {
341				i *= 10;
342				i += *buf - '0';
343				len--;
344			}
345			if (i > 53)
346				return 0;
347
348			if (*buf != 0 && isspace((unsigned char)*buf))
349				while (*ptr != 0 && !isspace((unsigned char)*ptr))
350					ptr++;
351			break;
352
353		case 'w':
354			if (!isdigit((unsigned char)*buf))
355				return 0;
356
357			i = *buf - '0';
358			if (i > 6)
359				return 0;
360
361			tm->tm_wday = i;
362
363			if (*buf != 0 && isspace((unsigned char)*buf))
364				while (*ptr != 0 && !isspace((unsigned char)*ptr))
365					ptr++;
366			break;
367
368		case 'd':
369		case 'e':
370			/*
371			 * The %e specifier is explicitly documented as not
372			 * being zero-padded but there is no harm in allowing
373			 * such padding.
374			 *
375			 * XXX The %e specifier may gobble one too many
376			 * digits if used incorrectly.
377			 */
378			if (!isdigit((unsigned char)*buf))
379				return 0;
380
381			len = 2;
382			for (i = 0; len && *buf != 0 && isdigit((unsigned char)*buf); buf++) {
383				i *= 10;
384				i += *buf - '0';
385				len--;
386			}
387			if (i > 31)
388				return 0;
389
390			tm->tm_mday = i;
391
392			if (*buf != 0 && isspace((unsigned char)*buf))
393				while (*ptr != 0 && !isspace((unsigned char)*ptr))
394					ptr++;
395			break;
396
397		case 'B':
398		case 'b':
399		case 'h':
400			for (i = 0; i < asizeof(tptr->month); i++) {
401				if (Oalternative) {
402					if (c == 'B') {
403						len = strlen(tptr->alt_month[i]);
404						if (strncasecmp(buf,
405								tptr->alt_month[i],
406								len) == 0)
407							break;
408					}
409				} else {
410					len = strlen(tptr->month[i]);
411					if (strncasecmp(buf, tptr->month[i],
412							len) == 0)
413						break;
414					len = strlen(tptr->mon[i]);
415					if (strncasecmp(buf, tptr->mon[i],
416							len) == 0)
417						break;
418				}
419			}
420			if (i == asizeof(tptr->month))
421				return 0;
422
423			tm->tm_mon = i;
424			buf += len;
425			break;
426
427		case 'm':
428			if (!isdigit((unsigned char)*buf))
429				return 0;
430
431			len = 2;
432			for (i = 0; len && *buf != 0 && isdigit((unsigned char)*buf); buf++) {
433				i *= 10;
434				i += *buf - '0';
435				len--;
436			}
437			if (i < 1 || i > 12)
438				return 0;
439
440			tm->tm_mon = i - 1;
441
442			if (*buf != 0 && isspace((unsigned char)*buf))
443				while (*ptr != 0 && !isspace((unsigned char)*ptr))
444					ptr++;
445			break;
446
447		case 's':
448			{
449			char *cp;
450			time_t t;
451
452			t = strtol(buf, &cp, 10);
453			if (t == LONG_MAX)
454				return 0;
455			buf = cp;
456			gmtime_r(&t, tm);
457			got_GMT = 1;
458			}
459			break;
460
461		case 'Y':
462		case 'y':
463			if (*buf == 0 || isspace((unsigned char)*buf))
464				break;
465
466			if (!isdigit((unsigned char)*buf))
467				return 0;
468
469			len = (c == 'Y') ? 4 : 2;
470			for (i = 0; len && *buf != 0 && isdigit((unsigned char)*buf); buf++) {
471				i *= 10;
472				i += *buf - '0';
473				len--;
474			}
475			if (c == 'Y')
476				i -= 1900;
477			if (c == 'y' && i < 69)
478				i += 100;
479			if (i < 0)
480				return 0;
481
482			tm->tm_year = i;
483
484			if (*buf != 0 && isspace((unsigned char)*buf))
485				while (*ptr != 0 && !isspace((unsigned char)*ptr))
486					ptr++;
487			break;
488
489		case 'Z':
490			{
491			const char *cp;
492			char *zonestr;
493
494			for (cp = buf; *cp && isupper((unsigned char)*cp); ++cp) {/*empty*/}
495			if (cp - buf) {
496				zonestr = alloca(cp - buf + 1);
497				strncpy(zonestr, buf, cp - buf);
498				zonestr[cp - buf] = '\0';
499				tzset();
500				if (0 == strcmp(zonestr, "GMT")) {
501				    got_GMT = 1;
502				} else if (0 == strcmp(zonestr, tzname[0])) {
503				    tm->tm_isdst = 0;
504				} else if (0 == strcmp(zonestr, tzname[1])) {
505				    tm->tm_isdst = 1;
506				} else {
507				    return 0;
508				}
509				buf += cp - buf;
510			}
511			}
512			break;
513		}
514	}
515	return (char *)buf;
516}
517
518
519char *
520strptime(const char * __restrict buf, const char * __restrict fmt,
521    struct tm * __restrict tm)
522{
523	char *ret;
524
525	if (__isthreaded)
526		_pthread_mutex_lock(&gotgmt_mutex);
527
528	got_GMT = 0;
529	ret = _strptime(buf, fmt, tm);
530	if (ret && got_GMT) {
531		time_t t = timegm(tm);
532		localtime_r(&t, tm);
533		got_GMT = 0;
534	}
535
536	if (__isthreaded)
537		_pthread_mutex_unlock(&gotgmt_mutex);
538
539	return ret;
540}
541