	Parsing date and time

	The date and time can occur in many formats, but the most
	common are:

	16 Feb 1994 14:39:35 -0500
	15 Dec 1993 18:00:37 GMT
	4 Dec 93 23:53:10 GMT
	Fri, 4 Feb 94 03:39:15 METDST
	Tue, 14 Dec 1993 21:29:02 GMT
	Wed, 2 Mar 1994 17:00:56 +0100
	Mon, 06 Dec 93 22:05:22 +1000 (AEST)
	Wed Mar  2 17:55:09 MET 1994
	Friday, 24-Sep-93 20:38:06 GMT
	Monday 13 April, 1992 09:30:00 PDT
	Monday, March 3, 12:59:00 CDT
	Monday May 24 23:01:34.34 CDT 1993
	Thu, 2 Dec 1993 12:53:49
	Wed, 15 Dec 1993 13:36:54 UNDEFINED
	3 Mar 94 12:46:21 GMT+0100

	The names of time zones are not standardized and the usage in
	the example dates above is ambiguous. The routine
	[[try_timezone]] that tries to compute the timezone from these
	names is therefore not always right.

<<*>>=
#include <config.h>
#include "equal.e"
#include "skip-sp.e"

/*
   Ignoring commas and hyphens:


   o-+-wday-+-+-date-month-+-+-H:M:S-zone-year-----+-garbage-o
     |      | |            | |		       	   |
     +------+ +-month-date-+ +-year-----H:M:S-zone-+


   zone = o-+-zone-+------+-o
            |      |      |
            |      +-diff-+
            |             |
            +-diff--------+
            |             |
            +-------------+


   H:M:S = o-hour-":"-min-":"-sec-+---------------+-o
                                  |               |
                                  +-"."-hundreths-+

*/

/*
This is from RFC822:

     5.  DATE AND TIME SPECIFICATION

     5.1.  SYNTAX

     date-time   =  [ day "," ] date time        ; dd mm yy
                                                 ;  hh:mm:ss zzz

     day         =  "Mon"  / "Tue" /  "Wed"  / "Thu"
                 /  "Fri"  / "Sat" /  "Sun"

     date        =  1*2DIGIT month 2DIGIT        ; day month year
                                                 ;  e.g. 20 Jun 82

     month       =  "Jan"  /  "Feb" /  "Mar"  /  "Apr"
                 /  "May"  /  "Jun" /  "Jul"  /  "Aug"
                 /  "Sep"  /  "Oct" /  "Nov"  /  "Dec"

     time        =  hour zone                    ; ANSI and Military

     hour        =  2DIGIT ":" 2DIGIT [":" 2DIGIT]
                                                 ; 00:00:00 - 23:59:59

     zone        =  "GMT"                ; Universal Time
*/

#define SKIP(s) while (isspace(*(s)) || *(s) == ',') (s)++

static void try_weekday(char **s)
{
    char *t = *s;

    switch (upcase[*t]) {
    case 'F':					/* Fri or Feb */
	t++;
	if (upcase[*t] != 'R') return;		/* Not a weekday */
	break;
    case 'M':					/* Mon, Mar or May */
	t++;
	if (upcase[*t] != 'O') return;		/* Not a weekday */
	break;
    case 'S':					/* Sat, Sun or Sep */
	t++;
	if (upcase[*t] != 'A' && upcase[*t] != 'U') return; /* Not a weekday */
	break;
    case 'T':					/* Tue or Thu */
	break;
    case 'W':					/* Wed */
	break;
    default:
	return;					/* Not a weekday */
    }
    while (isalpha(*t)) t++;
    SKIP(t);
    *s = t;
}

static Bool try_number(char **s, int *n)
{
    if (!isdigit(**s)) return FALSE;
    for (*n = 0; isdigit(**s); (*s)++)
	*n = 10 * *n + **s - '0';
    SKIP(*s);
    return TRUE;
}

static Bool try_month(char **s, int *n)
{
    char *t = *s;

    switch (upcase[*t]) {
    case 'A':					/* Apr or Aug */
	*n = upcase[*(++t)] == 'P' ? 3 : 7;	/* Assume Apr, resp. Aug */
	break;
    case 'D': *n = 11; break;			/* Assume Dec */
    case 'F':					/* Feb or Fri */
	if (upcase[*(++t)] != 'E') return FALSE; /* Error */
	*n = 1;					/* Assume Feb */
	break;
    case 'J':					/* Jan, Jun or Jul */
	t++;
	switch (upcase[*t]) {
	case 'A': *n = 0; break;		/* Assume Jan */
	case 'U':				/* Jun or Jul */
	    *n = upcase[*(++t)] == 'N' ? 5 : 6;	/* Assume Jun, resp. Jul */
	    break;
	default: return FALSE;			/* Error */
	}
	break;
    case 'M':					/* Mar, May or Mon */
	if (upcase[*(++t)] != 'A') return FALSE; /* Error */
	*n = upcase[*(++t)] == 'R' ? 2 : 4;	/* Assume Mar, resp. May */
	break;
    case 'N': *n = 10; break;			/* Assume Nov */
    case 'O': *n = 9; break;			/* Assume Oct */
    case 'S':					/* Sep, Sat or Sun */
	if (upcase[*(++t)] != 'E') return FALSE; /* Error */
	*n = 8;
	break;
    default: return FALSE;			/* Error */
    }
    while (isalpha(*t)) t++;
    SKIP(t);
    *s = t;
    return TRUE;
}

static void try_numeric_zone(char **s, int *zone)
{
    char *t = *s;

    if (*t == '-') {
	t++;
	if (isdigit(*t)) {
	    *zone = 60 * 60 * (*t - '0');
	    t++;
	    if (isdigit(*t)) {
		*zone = 10 * *zone + 60 * 60 * (*t - '0');
		t++;
		if (isdigit(*t)) {
		    *zone += 10 * 60 * (*t - '0');
		    t++;
		    if (isdigit(*t)) {
			*zone += 60 * (*t - '0');
			t++;
		    }
		}
	    }
	} else
	    *zone = 0;				/* A '-' and no digits? */
    } else if (*t == '+') {
	t++;
	if (isdigit(*t)) {
	    *zone = -60 * 60 * (*t - '0');
	    t++;
	    if (isdigit(*t)) {
		*zone = 10 * *zone - 60 * 60 * (*t - '0');
		t++;
		if (isdigit(*t)) {
		    *zone -= 10 * 60 * (*t - '0');
		    t++;
		    if (isdigit(*t)) {
			*zone -= 60 * (*t - '0');
			t++;
		    }
		}
	    }
	} else
	    *zone = 0;				/* A '+' and no digits? */
    } else
	*zone = 0;				/* No '-' and no '+' */
    SKIP(t);
    *s = t;
}

static void try_zone(char **s, int *zone)
{
    char *t;
    int diff;

    if (**s == '-' || **s == '+') {
	try_numeric_zone(s, zone);
    } else {
	t = *s;
	switch (upcase[*t]) {
	case 'U': *zone = 0; break;		/* Assume UTC */
	case 'A':
	    if (upcase[*(++t)] == 'S')
		*zone = 4 * 60 * 60;		/* Assume AST */
	    else
		*zone = 3 * 60 * 60;		/* Assume ADT */
	    break;
	case 'B': *zone = 0; break;		/* Assume BST */
	case 'C':
	    if (upcase[*(++t)] == 'S')
		*zone = 6 * 60 * 60;		/* Assume CST */
	    else
		*zone = 5 * 60 * 60;		/* Assume CDT */
	    break;
	case 'E':
	    if (upcase[*(++t)] == 'S')
		*zone = 5 * 60 * 60;		/* Assume EST */
	    else
		*zone = 4 * 60 * 60;		/* Assume EDT */
	    break;
	case 'G': *zone = 0; break;		/* Assume GMT */
	case 'M':
	    if (upcase[*(++t)] == 'S')
		*zone = 7 * 60 * 60;		/* Assume MST */
	    else if (upcase[*t] == 'D')
		*zone = 6 * 60 * 60;		/* Assume MDT */
	    else if (upcase[*t] != 'E')
		;				/* Unknown */
	    else if (upcase[*(++t)] == 'Z')
		*zone = -60 * 60;		/* Assume MEZ */
	    else if (upcase[*t] == 'S')
		*zone = -2 * 60 * 60;		/* Assume MESZ */
	    else if (upcase[*t] != 'T')
		;				/* Unknown */
	    else if (upcase[*(++t)] == 'D')
		*zone = -2 * 60 * 60;		/* Assume METDST */
	    else
		*zone = -60 * 60;		/* Assume MET */
	    break;
	case 'N':
	    if (upcase[*(++t)] == 'S')
		*zone = 3 * 60 * 60 + 30 * 60;	/* Assume NST */
	    else if (upcase[*(++t)] == 'D')
		*zone = 2 * 60 * 60 + 30 * 60;	/* Assume NDT */
	    else if (upcase[*t] != 'Z')
		;				/* Unknown */
	    if (upcase[*(++t)] == 'S')
		*zone = -12 * 60 * 60;		/* Assume NZST */
	    else
		*zone = -13 * 60 * 60;		/* Assume NZDT */
	    break;
	case 'P':
	    if (upcase[*(++t)] == 'W')
		*zone = 0;			/* Assume PWT */
	    else if (upcase[*t] == 'S')
		*zone = 8 * 60 * 60;		/* Assume PST */
	    else
		*zone = 7 * 60 * 60;		/* Assume PDT */
	    break;
	case 'S':
	    if (upcase[*(++t)] != 'A')
		;				/* Unknown */
	    else if (upcase[*(++t)] == 'S')
		*zone = -2 * 60 * 60;		/* Assume SAST */
	    else
		*zone = -3 * 60 * 60;		/* Assume SADT */
	    break;
	case 'W': *zone = 0; break;		/* Assume WET */
	case 'Y':
	    if (upcase[*(++t)] == 'S')
		*zone = 9 * 60 * 60;		/* Assume YST */
	    else
		*zone = 8 * 60 * 60;		/* Assume YDT */
	    break;
	default: return;			/* Unknown */
	}
	while (isalpha(*t)) t++;
	SKIP(t);
	if (*t == '-' || *t == '+') {
	    try_numeric_zone(&t, &diff);
	    *zone += diff;
	}
	SKIP(t);
	*s = t;
    }
}

static Bool try_time(char **s, int *hour, int *min, int *sec)
{
    char *t = *s;

    if (!isdigit(*t)) return FALSE;
    for (*hour = 0; isdigit(*t); t++)		/* Hours */
	*hour = 10 * *hour + *t - '0';
    if (*hour > 24 || *t != ':') return FALSE;
    t++;
    if (!isdigit(*t)) return FALSE;
    for (*min = 0; isdigit(*t); t++)		/* Minutes */
	*min = 10 * *min + *t - '0';
    if (*min > 59 || *t != ':') return FALSE;
    t++;
    if (!isdigit(*t)) return FALSE;
    for (*sec = 0; isdigit(*t); t++)		/* Seconds */
	*sec = 10 * *sec + *t - '0';
    if (*sec > 59) return FALSE;
    if (*t == '.')
	do t++; while (isdigit(*t));		/* Skip hundreths */
    SKIP(t);
    *s = t;
    return TRUE;
}

#define CENTURY_CUTOFF 70

EXPORT Bool parse_date(char *s, time_t *t)
{
    struct tm h, *timeptr;
    int n, zone = 0;

    *t = time(NULL);
    timeptr = gmtime(t);
    h = *timeptr;

    SKIP(s);
    try_weekday(&s);
    if (isdigit(*s)) {
	(void) try_number(&s, &h.tm_mday);
	if (*s == '-') { s++; SKIP(s); }
	if (!try_month(&s, &h.tm_mon)) return FALSE;
	if (*s == '-') { s++; SKIP(s); }
    } else {
	if (!try_month(&s, &h.tm_mon)) return FALSE;
	if (!try_number(&s, &h.tm_mday)) return FALSE;
    }
    if (try_time(&s, &h.tm_hour, &h.tm_min, &h.tm_sec)) {
	try_zone(&s, &zone);
	if (!try_number(&s, &n)) return FALSE;
	if (n > 1900) h.tm_year = n - 1900;
	else if (n < CENTURY_CUTOFF) h.tm_year = 100 + n; /* 21st century? */
	else h.tm_year = n;
    } else {
	if (!try_number(&s, &n)) return FALSE;
	if (n > 1900) h.tm_year = n - 1900;
	else if (n < CENTURY_CUTOFF) h.tm_year = 100 + n; /* 21st century? */
	else h.tm_year = n;
	if (!try_time(&s, &h.tm_hour, &h.tm_min, &h.tm_sec)) return FALSE;
	try_zone(&s, &zone);
    }
    tzset();
    h.tm_sec += zone;				/* Convert to GMT */
    h.tm_sec -= timezone;			/* Convert to local time */
    *t = mktime(&h);				/* Compute secs since epoch */
    return TRUE;
}
