#line 28 "date.c-nw"
#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;
}