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