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