xref: /titanic_50/usr/src/lib/libbc/libc/gen/common/strptime.c (revision abc79d9dd51e98eafb6fc25b4a0b4f66bef40b00)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 1997 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 /*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
28 /*	  All Rights Reserved	*/
29 
30 #pragma ident	"%Z%%M%	%I%	%E% SMI"
31 
32 #if !defined(lint) && defined(SCCSIDS)
33 static  char *sccsid = "%Z%%M% %I%	%E% SMI";
34 #endif
35 
36 #include <ctype.h>
37 #include <locale.h>
38 #include <time.h>
39 
40 static char	*strmatch(/*char *cp, char *string*/);
41 static char	*yearmatch(/*char *cp, char *format, struct tm *tm,
42     int *hadyearp*/);
43 static char	*cvtnum(/*char *cp, int *nump*/);
44 static char	*skipnws(/*char *format*/);
45 
46 extern char *getlocale_time();
47 #define NULL	0
48 
49 char *
50 strptime(buf, format, tm)
51 	char *buf;
52 	char *format;
53 	struct tm *tm;
54 {
55 	register char *cp, *p;
56 	register int c, ch;
57 	register int i;
58 	register struct dtconv *dtcp;
59 	int hadyear;
60 
61 	(void) getlocale_time();
62 	dtcp = localdtconv();	/* get locale's strings */
63 
64 	cp = buf;
65 	while ((c = *format++) != '\0') {
66 		if (c == '%') {
67 			switch (*format++) {
68 
69 			case '%':	/* Percent sign */
70 				if (*cp++ != '%')
71 					return (NULL);
72 				break;
73 
74 			case 'a':	/* Abbreviated weekday name */
75 			case 'A':	/* Weekday name */
76 				for (i = 0; i < 7; i++) {
77 					if ((p = strmatch(cp,
78 					      dtcp->weekday_names[i],
79 					      *format)) != NULL
80 					    || (p = strmatch(cp,
81 					      dtcp->abbrev_weekday_names[i],
82 					      *format)) != NULL)
83 						goto match_wday;
84 				}
85 				return (NULL);	/* no match */
86 
87 			match_wday:
88 				tm->tm_wday = i;
89 				cp = p;
90 				break;
91 
92 			case 'h':
93 			case 'b':	/* Abbreviated month name */
94 			case 'B':	/* Month name */
95 				for (i = 0; i < 12; i++) {
96 					if ((p = strmatch(cp,
97 					      dtcp->month_names[i],
98 					      *format)) != NULL
99 					    || (p = strmatch(cp,
100 					      dtcp->abbrev_month_names[i],
101 					      *format)) != NULL)
102 						goto match_month;
103 				}
104 				return (NULL);	/* no match */
105 
106 			match_month:
107 				tm->tm_mon = i;
108 				cp = p;
109 				break;
110 
111 			case 'c':	/* date and time representation */
112 				cp = strptime(cp, "%x %X", tm);
113 				if (cp == NULL)
114 					return (NULL);
115 				break;
116 
117 			case 'C':	/* long date and time representation */
118 				cp = strptime(cp, dtcp->ldate_format, tm);
119 				if (cp == NULL)
120 					return (NULL);
121 				break;
122 
123 			case 'd':	/* Day of month, with leading zero */
124 			case 'e':       /* Day of month without leading zero */
125 				cp = cvtnum(cp, &tm->tm_mday);
126 				if (cp == NULL)
127 					return (NULL);	/* no digits */
128 				if (tm->tm_mday > 31)
129 					return (NULL);
130 				if ((c = *cp) == '\0'
131 				    || isspace((unsigned char)c))
132 					format = skipnws(format);
133 				break;
134 
135 			case 'D':	/* Shorthand for %m/%d/%y */
136 				cp = strptime(cp, "%m/%d/%y", tm);
137 				if (cp == NULL)
138 					return (NULL);
139 				break;
140 
141 			case 'H':	/* Hour (24 hour version) */
142 			case 'k':	/* Hour (24 hour version) */
143 				cp = cvtnum(cp, &tm->tm_hour);
144 				if (cp == NULL)
145 					return (NULL);	/* no digits */
146 				if (tm->tm_hour > 23)
147 					return (NULL);
148 				if ((c = *cp) == '\0'
149 				    || isspace((unsigned char)c))
150 					format = skipnws(format);
151 				break;
152 
153 			case 'I':	/* Hour (12 hour version) */
154 			case 'l':	/* Hour (12 hour version) */
155 				cp = cvtnum(cp, &tm->tm_hour);
156 				if (cp == NULL)
157 					return (NULL);	/* no digits */
158 				if (tm->tm_hour == 12)
159 					tm->tm_hour = 0;
160 				else if (tm->tm_hour > 11)
161 					return (NULL);
162 				if ((c = *cp) == '\0'
163 				    || isspace((unsigned char)c))
164 					format = skipnws(format);
165 				break;
166 
167 			case 'j':	/* Julian date */
168 				cp = cvtnum(cp, &tm->tm_yday);
169 				if (cp == NULL)
170 					return (NULL);	/* no digits */
171 				if (tm->tm_yday > 365)
172 					return (NULL);
173 				break;
174 
175 			case 'm':	/* Month number */
176 				cp = cvtnum(cp, &tm->tm_mon);
177 				if (cp == NULL)
178 					return (NULL);	/* no digits */
179 				tm->tm_mon--;
180 				if (tm->tm_mon < 0 || tm->tm_mon > 11)
181 					return (NULL);
182 				if ((c = *cp) == '\0'
183 				    || isspace((unsigned char)c))
184 					format = skipnws(format);
185 				break;
186 
187 			case 'M':	/* Minute */
188 				/*
189 				 * This is optional; if we're at the end of the
190 				 * string, or the next character is white
191 				 * space, don't try to match it.
192 				 */
193 				if ((c = *cp) != '\0'
194 				    && !isspace((unsigned char)c)) {
195 					cp = cvtnum(cp, &tm->tm_min);
196 					if (cp == NULL)
197 						return (NULL);	/* no digits */
198 					if (tm->tm_min > 59)
199 						return (NULL);
200 				}
201 				if ((c = *cp) == '\0'
202 				    || isspace((unsigned char)c))
203 					format = skipnws(format);
204 				break;
205 
206 			case 'p':	/* AM or PM */
207 				if ((p = strmatch(cp, dtcp->am_string,
208 				    *format)) != NULL) {
209 					/*
210 					 * AM.
211 					 */
212 					if (tm->tm_hour == 12)
213 						tm->tm_hour = 0;
214 					cp = p;
215 				} else if ((p = strmatch(cp, dtcp->pm_string,
216 				    *format)) != NULL) {
217 					/*
218 					 * PM.
219 					 */
220 					if (tm->tm_hour > 12)
221 						return (NULL); /* error */
222 					else if (tm->tm_hour != 12)
223 						tm->tm_hour += 12;
224 					cp = p;
225 				}
226 				break;
227 
228 			case 'r':	/* Shorthand for %I:%M:%S AM or PM */
229 				cp = strptime(cp, "%I:%M:%S %p", tm);
230 				if (cp == NULL)
231 					return (NULL);
232 				break;
233 
234 			case 'R':	/* Time as %H:%M */
235 				cp = strptime(cp, "%H:%M", tm);
236 				if (cp == NULL)
237 					return (NULL);
238 				break;
239 
240 			case 'S':	/* Seconds */
241 				/*
242 				 * This is optional; if we're at the end of the
243 				 * string, or the next character is white
244 				 * space, don't try to match it.
245 				 */
246 				if ((c = *cp) != '\0'
247 				    && !isspace((unsigned char)c)) {
248 					cp = cvtnum(cp, &tm->tm_sec);
249 					if (cp == NULL)
250 						return (NULL);	/* no digits */
251 					if (tm->tm_sec > 59)
252 						return (NULL);
253 				}
254 				if ((c = *cp) == '\0'
255 				    || isspace((unsigned char)c))
256 					format = skipnws(format);
257 				break;
258 
259 			case 'T':	/* Shorthand for %H:%M:%S */
260 				cp = strptime(cp, "%H:%M:%S", tm);
261 				if (cp == NULL)
262 					return (NULL);
263 				break;
264 
265 			case 'x':	/* Localized date format */
266 				cp = strptime(cp, dtcp->sdate_format, tm);
267 				if (cp == NULL)
268 					return (NULL);
269 				break;
270 
271 			case 'X':	/* Localized time format */
272 				cp = strptime(cp, dtcp->time_format, tm);
273 				if (cp == NULL)
274 					return (NULL);
275 				break;
276 
277 			case 'y':	/* Year in the form yy */
278 				cp = yearmatch(cp, format, tm, &hadyear);
279 				if (cp == NULL)
280 					return (NULL);
281 				if (hadyear) {
282 					if (tm->tm_year < 69)
283 						tm->tm_year += 100;
284 				}
285 				return (cp);	/* match is complete */
286 
287 			case 'Y':	/* Year in the form ccyy */
288 				cp = yearmatch(cp, format, tm, &hadyear);
289 				if (cp == NULL)
290 					return (NULL);
291 				if (hadyear) {
292 					tm->tm_year -= 1900;
293 					if (tm->tm_year < 0)
294 						return (NULL);
295 				}
296 				return (cp);	/* match is complete */
297 
298 			default:
299 				return (NULL);	/* unknown conversion */
300 			}
301 		} else {
302 			if (isspace((unsigned char)c)) {
303 				while ((ch = *cp++) != '\0'
304 				    && isspace((unsigned char)ch))
305 					;
306 				cp--;
307 			} else {
308 				if (*cp++ != c)
309 					return (NULL);
310 			}
311 		}
312 	}
313 	return (cp);
314 }
315 
316 /*
317  * Try to match the beginning of the string pointed to by "cp" with the string
318  * pointed to by "string".  The match is independent of the case of either
319  * string.
320  *
321  * "termc" is the next character in the format string following the one for
322  * which this match is being done.  If the match succeeds, make sure the next
323  * character after the match is either '\0', or that it would match "termc".
324  *
325  * If both matches succeed, return a pointer to the next character after the
326  * first match.  Otherwise, return NULL.
327  */
328 static char *
329 strmatch(cp, string, termc)
330 	register char *cp;
331 	register char *string;
332 	char termc;
333 {
334 	register unsigned char c, strc;
335 
336 	/*
337 	 * Match the beginning portion of "cp" with "string".
338 	 */
339 	while ((strc = *string++) != '\0') {
340 		c = *cp++;
341 		if (isupper(c))
342 			c = tolower(c);
343 		if (isupper(strc))
344 			strc = tolower(strc);
345 		if (c != strc)
346 			return (NULL);
347 	}
348 
349 	if ((c = *cp) != '\0') {
350 		if (isspace((unsigned char)termc)) {
351 			if (!isspace(c))
352 				return (NULL);
353 		} else {
354 			if (c != (unsigned char)termc)
355 				return (NULL);
356 		}
357 	}
358 	return (cp);
359 }
360 
361 /*
362  * Try to match a %y or %Y specification.
363  * If it matches, try matching the rest of the format.  If it succeeds, just
364  * return.  Otherwise, try backing the scan up, ignoring the %y/%Y and any
365  * following non-white-space string.  If that succeeds, just return.  (This
366  * permits a missing year to be detected if it's at the beginning of a date, as
367  * well as if it's at the end of a date, so that formats such as "%Y/%m/%d" can
368  * match "3/14" and default the year.)
369  *
370  * Set "*hadyearp" to indicate whether a year was specified or not.
371  */
372 static char *
373 yearmatch(cp, format, tm, hadyearp)
374 	register char *cp;
375 	char *format;
376 	struct tm *tm;
377 	int *hadyearp;
378 {
379 	register int c;
380 	char *savecp;
381 	int saveyear;
382 
383 	/*
384 	 * This is optional; if we're at the end of the
385 	 * string, or the next character is white
386 	 * space, don't try to match it.
387 	 */
388 	if ((c = *cp) != '\0' && !isspace((unsigned char)c)) {
389 		savecp = cp;
390 		saveyear = tm->tm_year;
391 		cp = cvtnum(cp, &tm->tm_year);
392 		if (cp == NULL)
393 			return (NULL);	/* no digits */
394 		if ((c = *cp) == '\0'
395 		    || isspace((unsigned char)c))
396 			format = skipnws(format);
397 
398 		/*
399 		 * Year can also be optional if it's at
400 		 * the *beginning* of a date.  We check
401 		 * this by trying to parse the rest of
402 		 * the date here.  If we succeed, OK;
403 		 * otherwise, we skip over the %y and
404 		 * try again.
405 		 */
406 		cp = strptime(cp, format, tm);
407 		if (cp != NULL)
408 			*hadyearp = 1;
409 		else {
410 			*hadyearp = 0;
411 			cp = savecp;
412 			format = skipnws(format);
413 			tm->tm_year = saveyear;
414 			cp = strptime(cp, format, tm);
415 		}
416 	} else {
417 		*hadyearp = 0;
418 		if ((c = *cp) == '\0'
419 		    || isspace((unsigned char)c))
420 			format = skipnws(format);
421 		cp = strptime(cp, format, tm);
422 	}
423 
424 	return (cp);
425 }
426 
427 /*
428  * Try to match a (decimal) number in the string pointed to by "cp".
429  * If the match succeeds, store the result in the "int" pointed to by "nump"
430  * and return a pointer to the character following the number in the string.
431  * If it fails, return NULL.
432  */
433 static char *
434 cvtnum(cp, nump)
435 	register char *cp;
436 	int *nump;
437 {
438 	register int c;
439 	register int i;
440 
441 	c = (unsigned char)*cp++;
442 	if (!isdigit(c))
443 		return (NULL);	/* no digits */
444 	i = 0;
445 	do {
446 		i = i*10 + c - '0';
447 		c = (unsigned char)*cp++;
448 	} while (isdigit(c));
449 	*nump = i;
450 	return (cp - 1);
451 }
452 
453 /*
454  * If a format item (such as %H, hours) is followed by a non-white-space
455  * character other than "%", and the part of the string that matched the format
456  * item is followed by white space, the string of non-white-space,
457  * non-format-item characters following that format item may be omitted.
458  */
459 static char *
460 skipnws(format)
461 	register char *format;
462 {
463 	register char c;
464 
465 	/*
466 	 * Skip over non-white-space, non-digit characters.  "%" is special.
467 	 */
468 	while ((c = *format) != '\0' && !isspace((unsigned char)c)) {
469 		if (c == '%') {
470 			/*
471 			 * This is a format item.  If it's %%, skip it as
472 			 * that's a non-white space, non-digit character.
473 			 */
474 			if (*(format + 1) == '%')
475 				format++;	/* skip % */
476 			else
477 				break;
478 		}
479 		format++;
480 	}
481 
482 	return (format);
483 }
484