/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 1997 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
/*	  All Rights Reserved	*/

#pragma ident	"%Z%%M%	%I%	%E% SMI"

#if !defined(lint) && defined(SCCSIDS)
static  char *sccsid = "%Z%%M% %I%	%E% SMI";
#endif

#include <ctype.h>
#include <locale.h>
#include <time.h>

static char	*strmatch(/*char *cp, char *string*/);
static char	*yearmatch(/*char *cp, char *format, struct tm *tm,
    int *hadyearp*/);
static char	*cvtnum(/*char *cp, int *nump*/);
static char	*skipnws(/*char *format*/);

extern char *getlocale_time();
#define NULL	0

char *
strptime(buf, format, tm)
	char *buf;
	char *format;
	struct tm *tm;
{
	register char *cp, *p;
	register int c, ch;
	register int i;
	register struct dtconv *dtcp;
	int hadyear;

	(void) getlocale_time();
	dtcp = localdtconv();	/* get locale's strings */

	cp = buf;
	while ((c = *format++) != '\0') {
		if (c == '%') {
			switch (*format++) {

			case '%':	/* Percent sign */
				if (*cp++ != '%')
					return (NULL);
				break;

			case 'a':	/* Abbreviated weekday name */
			case 'A':	/* Weekday name */
				for (i = 0; i < 7; i++) {
					if ((p = strmatch(cp,
					      dtcp->weekday_names[i],
					      *format)) != NULL
					    || (p = strmatch(cp,
					      dtcp->abbrev_weekday_names[i],
					      *format)) != NULL)
						goto match_wday;
				}
				return (NULL);	/* no match */

			match_wday:
				tm->tm_wday = i;
				cp = p;
				break;

			case 'h':
			case 'b':	/* Abbreviated month name */
			case 'B':	/* Month name */
				for (i = 0; i < 12; i++) {
					if ((p = strmatch(cp,
					      dtcp->month_names[i],
					      *format)) != NULL
					    || (p = strmatch(cp,
					      dtcp->abbrev_month_names[i],
					      *format)) != NULL)
						goto match_month;
				}
				return (NULL);	/* no match */

			match_month:
				tm->tm_mon = i;
				cp = p;
				break;

			case 'c':	/* date and time representation */
				cp = strptime(cp, "%x %X", tm);
				if (cp == NULL)
					return (NULL);
				break;

			case 'C':	/* long date and time representation */
				cp = strptime(cp, dtcp->ldate_format, tm);
				if (cp == NULL)
					return (NULL);
				break;

			case 'd':	/* Day of month, with leading zero */
			case 'e':       /* Day of month without leading zero */
				cp = cvtnum(cp, &tm->tm_mday);
				if (cp == NULL)
					return (NULL);	/* no digits */
				if (tm->tm_mday > 31)
					return (NULL);
				if ((c = *cp) == '\0'
				    || isspace((unsigned char)c))
					format = skipnws(format);
				break;

			case 'D':	/* Shorthand for %m/%d/%y */
				cp = strptime(cp, "%m/%d/%y", tm);
				if (cp == NULL)
					return (NULL);
				break;

			case 'H':	/* Hour (24 hour version) */
			case 'k':	/* Hour (24 hour version) */
				cp = cvtnum(cp, &tm->tm_hour);
				if (cp == NULL)
					return (NULL);	/* no digits */
				if (tm->tm_hour > 23)
					return (NULL);
				if ((c = *cp) == '\0'
				    || isspace((unsigned char)c))
					format = skipnws(format);
				break;

			case 'I':	/* Hour (12 hour version) */
			case 'l':	/* Hour (12 hour version) */
				cp = cvtnum(cp, &tm->tm_hour);
				if (cp == NULL)
					return (NULL);	/* no digits */
				if (tm->tm_hour == 12)
					tm->tm_hour = 0;
				else if (tm->tm_hour > 11)
					return (NULL);
				if ((c = *cp) == '\0'
				    || isspace((unsigned char)c))
					format = skipnws(format);
				break;

			case 'j':	/* Julian date */
				cp = cvtnum(cp, &tm->tm_yday);
				if (cp == NULL)
					return (NULL);	/* no digits */
				if (tm->tm_yday > 365)
					return (NULL);
				break;

			case 'm':	/* Month number */
				cp = cvtnum(cp, &tm->tm_mon);
				if (cp == NULL)
					return (NULL);	/* no digits */
				tm->tm_mon--;
				if (tm->tm_mon < 0 || tm->tm_mon > 11)
					return (NULL);
				if ((c = *cp) == '\0'
				    || isspace((unsigned char)c))
					format = skipnws(format);
				break;

			case 'M':	/* Minute */
				/*
				 * This is optional; if we're at the end of the
				 * string, or the next character is white
				 * space, don't try to match it.
				 */
				if ((c = *cp) != '\0'
				    && !isspace((unsigned char)c)) {
					cp = cvtnum(cp, &tm->tm_min);
					if (cp == NULL)
						return (NULL);	/* no digits */
					if (tm->tm_min > 59)
						return (NULL);
				}
				if ((c = *cp) == '\0'
				    || isspace((unsigned char)c))
					format = skipnws(format);
				break;

			case 'p':	/* AM or PM */
				if ((p = strmatch(cp, dtcp->am_string,
				    *format)) != NULL) {
					/*
					 * AM.
					 */
					if (tm->tm_hour == 12)
						tm->tm_hour = 0;
					cp = p;
				} else if ((p = strmatch(cp, dtcp->pm_string,
				    *format)) != NULL) {
					/*
					 * PM.
					 */
					if (tm->tm_hour > 12)
						return (NULL); /* error */
					else if (tm->tm_hour != 12)
						tm->tm_hour += 12;
					cp = p;
				}
				break;

			case 'r':	/* Shorthand for %I:%M:%S AM or PM */
				cp = strptime(cp, "%I:%M:%S %p", tm);
				if (cp == NULL)
					return (NULL);
				break;

			case 'R':	/* Time as %H:%M */
				cp = strptime(cp, "%H:%M", tm);
				if (cp == NULL)
					return (NULL);
				break;

			case 'S':	/* Seconds */
				/*
				 * This is optional; if we're at the end of the
				 * string, or the next character is white
				 * space, don't try to match it.
				 */
				if ((c = *cp) != '\0'
				    && !isspace((unsigned char)c)) {
					cp = cvtnum(cp, &tm->tm_sec);
					if (cp == NULL)
						return (NULL);	/* no digits */
					if (tm->tm_sec > 59)
						return (NULL);
				}
				if ((c = *cp) == '\0'
				    || isspace((unsigned char)c))
					format = skipnws(format);
				break;

			case 'T':	/* Shorthand for %H:%M:%S */
				cp = strptime(cp, "%H:%M:%S", tm);
				if (cp == NULL)
					return (NULL);
				break;

			case 'x':	/* Localized date format */
				cp = strptime(cp, dtcp->sdate_format, tm);
				if (cp == NULL)
					return (NULL);
				break;

			case 'X':	/* Localized time format */
				cp = strptime(cp, dtcp->time_format, tm);
				if (cp == NULL)
					return (NULL);
				break;

			case 'y':	/* Year in the form yy */
				cp = yearmatch(cp, format, tm, &hadyear);
				if (cp == NULL)
					return (NULL);
				if (hadyear) {
					if (tm->tm_year < 69) 
						tm->tm_year += 100;
				}
				return (cp);	/* match is complete */

			case 'Y':	/* Year in the form ccyy */
				cp = yearmatch(cp, format, tm, &hadyear);
				if (cp == NULL)
					return (NULL);
				if (hadyear) {
					tm->tm_year -= 1900;
					if (tm->tm_year < 0)
						return (NULL);
				}
				return (cp);	/* match is complete */

			default:
				return (NULL);	/* unknown conversion */
			}
		} else {
			if (isspace((unsigned char)c)) {
				while ((ch = *cp++) != '\0'
				    && isspace((unsigned char)ch))
					;
				cp--;
			} else {
				if (*cp++ != c)
					return (NULL);
			}
		}
	}
	return (cp);
}

/*
 * Try to match the beginning of the string pointed to by "cp" with the string
 * pointed to by "string".  The match is independent of the case of either
 * string.
 *
 * "termc" is the next character in the format string following the one for
 * which this match is being done.  If the match succeeds, make sure the next
 * character after the match is either '\0', or that it would match "termc".
 *
 * If both matches succeed, return a pointer to the next character after the
 * first match.  Otherwise, return NULL.
 */
static char *
strmatch(cp, string, termc)
	register char *cp;
	register char *string;
	char termc;
{
	register unsigned char c, strc;

	/*
	 * Match the beginning portion of "cp" with "string".
	 */
	while ((strc = *string++) != '\0') {
		c = *cp++;
		if (isupper(c))
			c = tolower(c);
		if (isupper(strc))
			strc = tolower(strc);
		if (c != strc)
			return (NULL);
	}

	if ((c = *cp) != '\0') {
		if (isspace((unsigned char)termc)) {
			if (!isspace(c))
				return (NULL);
		} else {
			if (c != (unsigned char)termc)
				return (NULL);
		}
	}
	return (cp);
}

/*
 * Try to match a %y or %Y specification.
 * If it matches, try matching the rest of the format.  If it succeeds, just
 * return.  Otherwise, try backing the scan up, ignoring the %y/%Y and any
 * following non-white-space string.  If that succeeds, just return.  (This
 * permits a missing year to be detected if it's at the beginning of a date, as
 * well as if it's at the end of a date, so that formats such as "%Y/%m/%d" can
 * match "3/14" and default the year.)
 *
 * Set "*hadyearp" to indicate whether a year was specified or not.
 */
static char *
yearmatch(cp, format, tm, hadyearp)
	register char *cp;
	char *format;
	struct tm *tm;
	int *hadyearp;
{
	register int c;
	char *savecp;
	int saveyear;

	/*
	 * This is optional; if we're at the end of the
	 * string, or the next character is white
	 * space, don't try to match it.
	 */
	if ((c = *cp) != '\0' && !isspace((unsigned char)c)) {
		savecp = cp;
		saveyear = tm->tm_year;
		cp = cvtnum(cp, &tm->tm_year);
		if (cp == NULL)
			return (NULL);	/* no digits */
		if ((c = *cp) == '\0'
		    || isspace((unsigned char)c))
			format = skipnws(format);

		/*
		 * Year can also be optional if it's at
		 * the *beginning* of a date.  We check
		 * this by trying to parse the rest of
		 * the date here.  If we succeed, OK;
		 * otherwise, we skip over the %y and
		 * try again.
		 */
		cp = strptime(cp, format, tm);
		if (cp != NULL)
			*hadyearp = 1;
		else {
			*hadyearp = 0;
			cp = savecp;
			format = skipnws(format);
			tm->tm_year = saveyear;
			cp = strptime(cp, format, tm);
		}
	} else {
		*hadyearp = 0;
		if ((c = *cp) == '\0'
		    || isspace((unsigned char)c))
			format = skipnws(format);
		cp = strptime(cp, format, tm);
	}

	return (cp);
}

/*
 * Try to match a (decimal) number in the string pointed to by "cp".
 * If the match succeeds, store the result in the "int" pointed to by "nump"
 * and return a pointer to the character following the number in the string.
 * If it fails, return NULL.
 */
static char *
cvtnum(cp, nump)
	register char *cp;
	int *nump;
{
	register int c;
	register int i;

	c = (unsigned char)*cp++;
	if (!isdigit(c))
		return (NULL);	/* no digits */
	i = 0;
	do {
		i = i*10 + c - '0';
		c = (unsigned char)*cp++;
	} while (isdigit(c));
	*nump = i;
	return (cp - 1);
}

/*
 * If a format item (such as %H, hours) is followed by a non-white-space
 * character other than "%", and the part of the string that matched the format
 * item is followed by white space, the string of non-white-space,
 * non-format-item characters following that format item may be omitted.
 */
static char *
skipnws(format)
	register char *format;
{
	register char c;

	/*
	 * Skip over non-white-space, non-digit characters.  "%" is special.
	 */
	while ((c = *format) != '\0' && !isspace((unsigned char)c)) {
		if (c == '%') {
			/*
			 * This is a format item.  If it's %%, skip it as
			 * that's a non-white space, non-digit character.
			 */
			if (*(format + 1) == '%')
				format++;	/* skip % */
			else
				break;
		}
		format++;
	}

	return (format);
}