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)
33static  char *sccsid = "%Z%%M% %I%	%E% SMI";
34#endif
35
36#include <ctype.h>
37#include <locale.h>
38#include <time.h>
39
40static char	*strmatch(/*char *cp, char *string*/);
41static char	*yearmatch(/*char *cp, char *format, struct tm *tm,
42    int *hadyearp*/);
43static char	*cvtnum(/*char *cp, int *nump*/);
44static char	*skipnws(/*char *format*/);
45
46extern char *getlocale_time();
47#define NULL	0
48
49char *
50strptime(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 */
328static char *
329strmatch(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 */
372static char *
373yearmatch(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 */
433static char *
434cvtnum(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 */
459static char *
460skipnws(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