453 lines
10 KiB
C
453 lines
10 KiB
C
#if !defined(lint) && defined(SCCSIDS)
|
|
static char sccsid[] = "@(#)strptime.c 1.1 92/07/30 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 > 99)
|
|
return (NULL);
|
|
}
|
|
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);
|
|
}
|