root/usr.bin/cvs/date.y
%{
/*      $OpenBSD: date.y,v 1.27 2023/03/08 04:43:10 guenther Exp $      */

/*
**  Originally written by Steven M. Bellovin <smb@research.att.com> while
**  at the University of North Carolina at Chapel Hill.  Later tweaked by
**  a couple of people on Usenet.  Completely overhauled by Rich $alz
**  <rsalz@bbn.com> and Jim Berets <jberets@bbn.com> in August, 1990;
**
**  This grammar has 10 shift/reduce conflicts.
**
**  This code is in the public domain and has no copyright.
*/
/* SUPPRESS 287 on yaccpar_sccsid *//* Unused static variable */
/* SUPPRESS 288 on yyerrlab *//* Label unused */

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

#include "cvs.h"

#define YEAR_EPOCH      1970
#define YEAR_TMORIGIN   1900
#define HOUR(x)         ((time_t)(x) * 60)
#define SECSPERDAY      (24L * 60L * 60L)


/* An entry in the lexical lookup table */
typedef struct _TABLE {
        char    *name;
        int     type;
        time_t  value;
} TABLE;


/*  Daylight-savings mode:  on, off, or not yet known. */
typedef enum _DSTMODE {
        DSTon, DSToff, DSTmaybe
} DSTMODE;

/*  Meridian:  am, pm, or 24-hour style. */
typedef enum _MERIDIAN {
        MERam, MERpm, MER24
} MERIDIAN;


/*
 *  Global variables.  We could get rid of most of these by using a good
 *  union as the yacc stack.  (This routine was originally written before
 *  yacc had the %union construct.)  Maybe someday; right now we only use
 *  the %union very rarely.
 */
static const char       *yyInput;
static DSTMODE  yyDSTmode;
static time_t   yyDayOrdinal;
static time_t   yyDayNumber;
static int      yyHaveDate;
static int      yyHaveDay;
static int      yyHaveRel;
static int      yyHaveTime;
static int      yyHaveZone;
static time_t   yyTimezone;
static time_t   yyDay;
static time_t   yyHour;
static time_t   yyMinutes;
static time_t   yyMonth;
static time_t   yySeconds;
static time_t   yyYear;
static MERIDIAN yyMeridian;
static time_t   yyRelMonth;
static time_t   yyRelSeconds;


static int      yyerror(const char *);
static int      yylex(void);
static int      yyparse(void);
static int      lookup(char *);

%}

%union {
        time_t          Number;
        enum _MERIDIAN  Meridian;
}

%token  tAGO tDAY tDAYZONE tID tMERIDIAN tMINUTE_UNIT tMONTH tMONTH_UNIT
%token  tSEC_UNIT tSNUMBER tUNUMBER tZONE tDST

%type   <Number>        tDAY tDAYZONE tMINUTE_UNIT tMONTH tMONTH_UNIT
%type   <Number>        tSEC_UNIT tSNUMBER tUNUMBER tZONE
%type   <Meridian>      tMERIDIAN o_merid

%%

spec    : /* NULL */
        | spec item
        ;

item    : time {
                yyHaveTime++;
        }
        | zone {
                yyHaveZone++;
        }
        | date {
                yyHaveDate++;
        }
        | day {
                yyHaveDay++;
        }
        | rel {
                yyHaveRel++;
        }
        | number
        ;

time    : tUNUMBER tMERIDIAN {
                yyHour = $1;
                yyMinutes = 0;
                yySeconds = 0;
                yyMeridian = $2;
        }
        | tUNUMBER ':' tUNUMBER o_merid {
                yyHour = $1;
                yyMinutes = $3;
                yySeconds = 0;
                yyMeridian = $4;
        }
        | tUNUMBER ':' tUNUMBER tSNUMBER {
                yyHour = $1;
                yyMinutes = $3;
                yyMeridian = MER24;
                yyDSTmode = DSToff;
                yyTimezone = - ($4 % 100 + ($4 / 100) * 60);
        }
        | tUNUMBER ':' tUNUMBER ':' tUNUMBER o_merid {
                yyHour = $1;
                yyMinutes = $3;
                yySeconds = $5;
                yyMeridian = $6;
        }
        | tUNUMBER ':' tUNUMBER ':' tUNUMBER tSNUMBER {
                yyHour = $1;
                yyMinutes = $3;
                yySeconds = $5;
                yyMeridian = MER24;
                yyDSTmode = DSToff;
                yyTimezone = - ($6 % 100 + ($6 / 100) * 60);
        }
        ;

zone    : tZONE {
                yyTimezone = $1;
                yyDSTmode = DSToff;
        }
        | tDAYZONE {
                yyTimezone = $1;
                yyDSTmode = DSTon;
        }
        | tZONE tDST {
                yyTimezone = $1;
                yyDSTmode = DSTon;
        }
        ;

day     : tDAY {
                yyDayOrdinal = 1;
                yyDayNumber = $1;
        }
        | tDAY ',' {
                yyDayOrdinal = 1;
                yyDayNumber = $1;
        }
        | tUNUMBER tDAY {
                yyDayOrdinal = $1;
                yyDayNumber = $2;
        }
        ;

date    : tUNUMBER '/' tUNUMBER {
                yyMonth = $1;
                yyDay = $3;
        }
        | tUNUMBER '/' tUNUMBER '/' tUNUMBER {
                if ($1 >= 100) {
                        yyYear = $1;
                        yyMonth = $3;
                        yyDay = $5;
                } else {
                        yyMonth = $1;
                        yyDay = $3;
                        yyYear = $5;
                }
        }
        | tUNUMBER tSNUMBER tSNUMBER {
                /* ISO 8601 format.  yyyy-mm-dd.  */
                yyYear = $1;
                yyMonth = -$2;
                yyDay = -$3;
        }
        | tUNUMBER tMONTH tSNUMBER {
                /* e.g. 17-JUN-1992.  */
                yyDay = $1;
                yyMonth = $2;
                yyYear = -$3;
        }
        | tMONTH tUNUMBER {
                yyMonth = $1;
                yyDay = $2;
        }
        | tMONTH tUNUMBER ',' tUNUMBER {
                yyMonth = $1;
                yyDay = $2;
                yyYear = $4;
        }
        | tUNUMBER tMONTH {
                yyMonth = $2;
                yyDay = $1;
        }
        | tUNUMBER tMONTH tUNUMBER {
                yyMonth = $2;
                yyDay = $1;
                yyYear = $3;
        }
        ;

rel     : relunit tAGO {
                yyRelSeconds = -yyRelSeconds;
                yyRelMonth = -yyRelMonth;
        }
        | relunit
        ;

relunit : tUNUMBER tMINUTE_UNIT {
                yyRelSeconds += $1 * $2 * 60L;
        }
        | tSNUMBER tMINUTE_UNIT {
                yyRelSeconds += $1 * $2 * 60L;
        }
        | tMINUTE_UNIT {
                yyRelSeconds += $1 * 60L;
        }
        | tSNUMBER tSEC_UNIT {
                yyRelSeconds += $1;
        }
        | tUNUMBER tSEC_UNIT {
                yyRelSeconds += $1;
        }
        | tSEC_UNIT {
                yyRelSeconds++;
        }
        | tSNUMBER tMONTH_UNIT {
                yyRelMonth += $1 * $2;
        }
        | tUNUMBER tMONTH_UNIT {
                yyRelMonth += $1 * $2;
        }
        | tMONTH_UNIT {
                yyRelMonth += $1;
        }
        ;

number  : tUNUMBER {
                if (yyHaveTime && yyHaveDate && !yyHaveRel)
                        yyYear = $1;
                else {
                        if ($1 > 10000) {
                                yyHaveDate++;
                                yyDay= ($1)%100;
                                yyMonth= ($1/100)%100;
                                yyYear = $1/10000;
                        } else {
                                yyHaveTime++;
                                if ($1 < 100) {
                                        yyHour = $1;
                                        yyMinutes = 0;
                                } else {
                                        yyHour = $1 / 100;
                                        yyMinutes = $1 % 100;
                                }
                                yySeconds = 0;
                                yyMeridian = MER24;
                        }
                }
        }
        ;

o_merid : /* NULL */ {
                $$ = MER24;
        }
        | tMERIDIAN {
                $$ = $1;
        }
        ;

%%

/* Month and day table. */
static TABLE const MonthDayTable[] = {
        { "january",    tMONTH, 1 },
        { "february",   tMONTH, 2 },
        { "march",      tMONTH, 3 },
        { "april",      tMONTH, 4 },
        { "may",        tMONTH, 5 },
        { "june",       tMONTH, 6 },
        { "july",       tMONTH, 7 },
        { "august",     tMONTH, 8 },
        { "september",  tMONTH, 9 },
        { "sept",       tMONTH, 9 },
        { "october",    tMONTH, 10 },
        { "november",   tMONTH, 11 },
        { "december",   tMONTH, 12 },
        { "sunday",     tDAY,   0 },
        { "monday",     tDAY,   1 },
        { "tuesday",    tDAY,   2 },
        { "tues",       tDAY,   2 },
        { "wednesday",  tDAY,   3 },
        { "wednes",     tDAY,   3 },
        { "thursday",   tDAY,   4 },
        { "thur",       tDAY,   4 },
        { "thurs",      tDAY,   4 },
        { "friday",     tDAY,   5 },
        { "saturday",   tDAY,   6 },
        { NULL }
};

/* Time units table. */
static TABLE const UnitsTable[] = {
        { "year",       tMONTH_UNIT,    12 },
        { "month",      tMONTH_UNIT,    1 },
        { "fortnight",  tMINUTE_UNIT,   14 * 24 * 60 },
        { "week",       tMINUTE_UNIT,   7 * 24 * 60 },
        { "day",        tMINUTE_UNIT,   1 * 24 * 60 },
        { "hour",       tMINUTE_UNIT,   60 },
        { "minute",     tMINUTE_UNIT,   1 },
        { "min",        tMINUTE_UNIT,   1 },
        { "second",     tSEC_UNIT,      1 },
        { "sec",        tSEC_UNIT,      1 },
        { NULL }
};

/* Assorted relative-time words. */
static TABLE const OtherTable[] = {
        { "tomorrow",   tMINUTE_UNIT,   1 * 24 * 60 },
        { "yesterday",  tMINUTE_UNIT,   -1 * 24 * 60 },
        { "today",      tMINUTE_UNIT,   0 },
        { "now",        tMINUTE_UNIT,   0 },
        { "last",       tUNUMBER,       -1 },
        { "this",       tMINUTE_UNIT,   0 },
        { "next",       tUNUMBER,       2 },
        { "first",      tUNUMBER,       1 },
/*  { "second",         tUNUMBER,       2 }, */
        { "third",      tUNUMBER,       3 },
        { "fourth",     tUNUMBER,       4 },
        { "fifth",      tUNUMBER,       5 },
        { "sixth",      tUNUMBER,       6 },
        { "seventh",    tUNUMBER,       7 },
        { "eighth",     tUNUMBER,       8 },
        { "ninth",      tUNUMBER,       9 },
        { "tenth",      tUNUMBER,       10 },
        { "eleventh",   tUNUMBER,       11 },
        { "twelfth",    tUNUMBER,       12 },
        { "ago",        tAGO,   1 },
        { NULL }
};

/* The timezone table. */
/* Some of these are commented out because a time_t can't store a float. */
static TABLE const TimezoneTable[] = {
        { "gmt",        tZONE,     HOUR( 0) },  /* Greenwich Mean */
        { "ut",         tZONE,     HOUR( 0) },  /* Universal (Coordinated) */
        { "utc",        tZONE,     HOUR( 0) },
        { "wet",        tZONE,     HOUR( 0) },  /* Western European */
        { "bst",        tDAYZONE,  HOUR( 0) },  /* British Summer */
        { "wat",        tZONE,     HOUR( 1) },  /* West Africa */
        { "at",         tZONE,     HOUR( 2) },  /* Azores */
#if     0
        /* For completeness.  BST is also British Summer, and GST is
         * also Guam Standard. */
        { "bst",        tZONE,     HOUR( 3) },  /* Brazil Standard */
        { "gst",        tZONE,     HOUR( 3) },  /* Greenland Standard */
#endif
#if 0
        { "nft",        tZONE,     HOUR(3.5) }, /* Newfoundland */
        { "nst",        tZONE,     HOUR(3.5) }, /* Newfoundland Standard */
        { "ndt",        tDAYZONE,  HOUR(3.5) }, /* Newfoundland Daylight */
#endif
        { "ast",        tZONE,     HOUR( 4) },  /* Atlantic Standard */
        { "adt",        tDAYZONE,  HOUR( 4) },  /* Atlantic Daylight */
        { "est",        tZONE,     HOUR( 5) },  /* Eastern Standard */
        { "edt",        tDAYZONE,  HOUR( 5) },  /* Eastern Daylight */
        { "cst",        tZONE,     HOUR( 6) },  /* Central Standard */
        { "cdt",        tDAYZONE,  HOUR( 6) },  /* Central Daylight */
        { "mst",        tZONE,     HOUR( 7) },  /* Mountain Standard */
        { "mdt",        tDAYZONE,  HOUR( 7) },  /* Mountain Daylight */
        { "pst",        tZONE,     HOUR( 8) },  /* Pacific Standard */
        { "pdt",        tDAYZONE,  HOUR( 8) },  /* Pacific Daylight */
        { "yst",        tZONE,     HOUR( 9) },  /* Yukon Standard */
        { "ydt",        tDAYZONE,  HOUR( 9) },  /* Yukon Daylight */
        { "hst",        tZONE,     HOUR(10) },  /* Hawaii Standard */
        { "hdt",        tDAYZONE,  HOUR(10) },  /* Hawaii Daylight */
        { "cat",        tZONE,     HOUR(10) },  /* Central Alaska */
        { "ahst",       tZONE,     HOUR(10) },  /* Alaska-Hawaii Standard */
        { "nt",         tZONE,     HOUR(11) },  /* Nome */
        { "idlw",       tZONE,     HOUR(12) },  /* International Date Line West */
        { "cet",        tZONE,     -HOUR(1) },  /* Central European */
        { "met",        tZONE,     -HOUR(1) },  /* Middle European */
        { "mewt",       tZONE,     -HOUR(1) },  /* Middle European Winter */
        { "mest",       tDAYZONE,  -HOUR(1) },  /* Middle European Summer */
        { "swt",        tZONE,     -HOUR(1) },  /* Swedish Winter */
        { "sst",        tDAYZONE,  -HOUR(1) },  /* Swedish Summer */
        { "fwt",        tZONE,     -HOUR(1) },  /* French Winter */
        { "fst",        tDAYZONE,  -HOUR(1) },  /* French Summer */
        { "eet",        tZONE,     -HOUR(2) },  /* Eastern Europe, USSR Zone 1 */
        { "bt",         tZONE,     -HOUR(3) },  /* Baghdad, USSR Zone 2 */
#if 0
        { "it",         tZONE,     -HOUR(3.5) },/* Iran */
#endif
        { "zp4",        tZONE,     -HOUR(4) },  /* USSR Zone 3 */
        { "zp5",        tZONE,     -HOUR(5) },  /* USSR Zone 4 */
#if 0
        { "ist",        tZONE,     -HOUR(5.5) },/* Indian Standard */
#endif
        { "zp6",        tZONE,     -HOUR(6) },  /* USSR Zone 5 */
#if     0
        /* For completeness.  NST is also Newfoundland Stanard, and SST is
         * also Swedish Summer. */
        { "nst",        tZONE,     -HOUR(6.5) },/* North Sumatra */
        { "sst",        tZONE,     -HOUR(7) },  /* South Sumatra, USSR Zone 6 */
#endif  /* 0 */
        { "wast",       tZONE,     -HOUR(7) },  /* West Australian Standard */
        { "wadt",       tDAYZONE,  -HOUR(7) },  /* West Australian Daylight */
#if 0
        { "jt",         tZONE,     -HOUR(7.5) },/* Java (3pm in Cronusland!) */
#endif
        { "cct",        tZONE,     -HOUR(8) },  /* China Coast, USSR Zone 7 */
        { "jst",        tZONE,     -HOUR(9) },  /* Japan Standard, USSR Zone 8 */
#if 0
        { "cast",       tZONE,     -HOUR(9.5) },/* Central Australian Standard */
        { "cadt",       tDAYZONE,  -HOUR(9.5) },/* Central Australian Daylight */
#endif
        { "east",       tZONE,     -HOUR(10) }, /* Eastern Australian Standard */
        { "eadt",       tDAYZONE,  -HOUR(10) }, /* Eastern Australian Daylight */
        { "gst",        tZONE,     -HOUR(10) }, /* Guam Standard, USSR Zone 9 */
        { "nzt",        tZONE,     -HOUR(12) }, /* New Zealand */
        { "nzst",       tZONE,     -HOUR(12) }, /* New Zealand Standard */
        { "nzdt",       tDAYZONE,  -HOUR(12) }, /* New Zealand Daylight */
        { "idle",       tZONE,     -HOUR(12) }, /* International Date Line East */
        {  NULL  }
};

/* Military timezone table. */
static TABLE const MilitaryTable[] = {
        { "a",  tZONE,  HOUR(  1) },
        { "b",  tZONE,  HOUR(  2) },
        { "c",  tZONE,  HOUR(  3) },
        { "d",  tZONE,  HOUR(  4) },
        { "e",  tZONE,  HOUR(  5) },
        { "f",  tZONE,  HOUR(  6) },
        { "g",  tZONE,  HOUR(  7) },
        { "h",  tZONE,  HOUR(  8) },
        { "i",  tZONE,  HOUR(  9) },
        { "k",  tZONE,  HOUR( 10) },
        { "l",  tZONE,  HOUR( 11) },
        { "m",  tZONE,  HOUR( 12) },
        { "n",  tZONE,  HOUR(- 1) },
        { "o",  tZONE,  HOUR(- 2) },
        { "p",  tZONE,  HOUR(- 3) },
        { "q",  tZONE,  HOUR(- 4) },
        { "r",  tZONE,  HOUR(- 5) },
        { "s",  tZONE,  HOUR(- 6) },
        { "t",  tZONE,  HOUR(- 7) },
        { "u",  tZONE,  HOUR(- 8) },
        { "v",  tZONE,  HOUR(- 9) },
        { "w",  tZONE,  HOUR(-10) },
        { "x",  tZONE,  HOUR(-11) },
        { "y",  tZONE,  HOUR(-12) },
        { "z",  tZONE,  HOUR(  0) },
        { NULL }
};


static int
yyerror(const char *s)
{
#if !defined(TEST)
        char *str;

        (void)xasprintf(&str, "parsing date string: %s", s);
        cvs_log(LP_ERR, "%s", str);
        free(str);
#endif
        return (0);
}


static time_t
ToSeconds(time_t Hours, time_t Minutes, time_t  Seconds, MERIDIAN Meridian)
{
        if (Minutes < 0 || Minutes > 59 || Seconds < 0 || Seconds > 59)
                return (-1);

        switch (Meridian) {
        case MER24:
                if (Hours < 0 || Hours > 23)
                        return (-1);
                return (Hours * 60L + Minutes) * 60L + Seconds;
        case MERam:
                if (Hours < 1 || Hours > 12)
                        return (-1);
                if (Hours == 12)
                        Hours = 0;
                return (Hours * 60L + Minutes) * 60L + Seconds;
        case MERpm:
                if (Hours < 1 || Hours > 12)
                        return (-1);
                if (Hours == 12)
                        Hours = 0;
                return ((Hours + 12) * 60L + Minutes) * 60L + Seconds;
        default:
                return (-1);
        }
        /* NOTREACHED */
}


/* Year is either
 * A negative number, which means to use its absolute value (why?)
 * A number from 0 to 99, which means a year from 1900 to 1999, or
 * The actual year (>=100).
 */
static time_t
Convert(time_t Month, time_t Day, time_t Year, time_t Hours, time_t Minutes,
    time_t Seconds, MERIDIAN Meridian, DSTMODE DSTmode)
{
        static int DaysInMonth[12] = {
                31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
        };
        time_t  tod;
        time_t  julian;
        int     i;

        if (Year < 0)
                Year = -Year;
        if (Year < 69)
                Year += 2000;
        else if (Year < 100) {
                Year += 1900;
                if (Year < YEAR_EPOCH)
                        Year += 100;
        }
        DaysInMonth[1] = Year % 4 == 0 && (Year % 100 != 0 || Year % 400 == 0)
            ? 29 : 28;
        /* XXX Sloppily check for 2038 if time_t is 32 bits */
        if (Year < YEAR_EPOCH ||
            (sizeof(time_t) == sizeof(int) && Year > 2038) ||
            Month < 1 || Month > 12 ||
            /* Lint fluff:  "conversion from long may lose accuracy" */
             Day < 1 || Day > DaysInMonth[(int)--Month])
                return (-1);

        for (julian = Day - 1, i = 0; i < Month; i++)
                julian += DaysInMonth[i];

        for (i = YEAR_EPOCH; i < Year; i++)
                julian += 365 + (i % 4 == 0);
        julian *= SECSPERDAY;
        julian += yyTimezone * 60L;

        if ((tod = ToSeconds(Hours, Minutes, Seconds, Meridian)) < 0)
                return (-1);
        julian += tod;
        if ((DSTmode == DSTon) ||
            (DSTmode == DSTmaybe && localtime(&julian)->tm_isdst))
        julian -= 60 * 60;
        return (julian);
}


static time_t
DSTcorrect(time_t Start, time_t Future)
{
        time_t  StartDay;
        time_t  FutureDay;

        StartDay = (localtime(&Start)->tm_hour + 1) % 24;
        FutureDay = (localtime(&Future)->tm_hour + 1) % 24;
        return (Future - Start) + (StartDay - FutureDay) * 60L * 60L;
}


static time_t
RelativeDate(time_t Start, time_t DayOrdinal, time_t DayNumber)
{
        struct tm       *tm;
        time_t  now;

        now = Start;
        tm = localtime(&now);
        now += SECSPERDAY * ((DayNumber - tm->tm_wday + 7) % 7);
        now += 7 * SECSPERDAY * (DayOrdinal <= 0 ? DayOrdinal : DayOrdinal - 1);
        return DSTcorrect(Start, now);
}


static time_t
RelativeMonth(time_t Start, time_t RelMonth)
{
        struct tm       *tm;
        time_t  Month;
        time_t  Year;

        if (RelMonth == 0)
                return (0);
        tm = localtime(&Start);
        Month = 12 * (tm->tm_year + 1900) + tm->tm_mon + RelMonth;
        Year = Month / 12;
        Month = Month % 12 + 1;
        return DSTcorrect(Start,
            Convert(Month, (time_t)tm->tm_mday, Year,
            (time_t)tm->tm_hour, (time_t)tm->tm_min, (time_t)tm->tm_sec,
            MER24, DSTmaybe));
}


static int
lookup(char *buff)
{
        size_t          len;
        char            *p, *q;
        int             i, abbrev;
        const TABLE     *tp;

        /* Make it lowercase. */
        for (p = buff; *p; p++)
                if (isupper(*p))
                        *p = tolower(*p);

        if (strcmp(buff, "am") == 0 || strcmp(buff, "a.m.") == 0) {
                yylval.Meridian = MERam;
                return (tMERIDIAN);
        }
        if (strcmp(buff, "pm") == 0 || strcmp(buff, "p.m.") == 0) {
                yylval.Meridian = MERpm;
                return (tMERIDIAN);
        }

        len = strlen(buff);
        /* See if we have an abbreviation for a month. */
        if (len == 3)
                abbrev = 1;
        else if (len == 4 && buff[3] == '.') {
                abbrev = 1;
                buff[3] = '\0';
                --len;
        } else
                abbrev = 0;

        for (tp = MonthDayTable; tp->name; tp++) {
                if (abbrev) {
                        if (strncmp(buff, tp->name, 3) == 0) {
                                yylval.Number = tp->value;
                                return (tp->type);
                        }
                } else if (strcmp(buff, tp->name) == 0) {
                        yylval.Number = tp->value;
                        return (tp->type);
                }
        }

        for (tp = TimezoneTable; tp->name; tp++)
                if (strcmp(buff, tp->name) == 0) {
                        yylval.Number = tp->value;
                        return (tp->type);
                }

        if (strcmp(buff, "dst") == 0)
                return (tDST);

        for (tp = UnitsTable; tp->name; tp++)
                if (strcmp(buff, tp->name) == 0) {
                        yylval.Number = tp->value;
                        return (tp->type);
                }

        /* Strip off any plural and try the units table again. */
        if (len != 0 && buff[len - 1] == 's') {
                buff[len - 1] = '\0';
                for (tp = UnitsTable; tp->name; tp++)
                        if (strcmp(buff, tp->name) == 0) {
                                yylval.Number = tp->value;
                                return (tp->type);
                        }
                buff[len - 1] = 's';    /* Put back for "this" in OtherTable. */
        }

        for (tp = OtherTable; tp->name; tp++)
                if (strcmp(buff, tp->name) == 0) {
                        yylval.Number = tp->value;
                        return (tp->type);
                }

        /* Military timezones. */
        if (len == 1 && isalpha(*buff)) {
                for (tp = MilitaryTable; tp->name; tp++)
                        if (strcmp(buff, tp->name) == 0) {
                                yylval.Number = tp->value;
                                return (tp->type);
                        }
        }

        /* Drop out any periods and try the timezone table again. */
        for (i = 0, p = q = buff; *q; q++)
                if (*q != '.')
                        *p++ = *q;
                else
                        i++;
        *p = '\0';
        if (i)
                for (tp = TimezoneTable; tp->name; tp++)
                        if (strcmp(buff, tp->name) == 0) {
                                yylval.Number = tp->value;
                                return (tp->type);
                        }

        return (tID);
}


static int
yylex(void)
{
        char    c, *p, buff[20];
        int     count, sign;

        for (;;) {
                while (isspace(*yyInput))
                        yyInput++;

                if (isdigit(c = *yyInput) || c == '-' || c == '+') {
                        if (c == '-' || c == '+') {
                                sign = c == '-' ? -1 : 1;
                                if (!isdigit(*++yyInput))
                                        /* skip the '-' sign */
                                        continue;
                        }
                        else
                                sign = 0;

                        for (yylval.Number = 0; isdigit(c = *yyInput++); )
                                yylval.Number = 10 * yylval.Number + c - '0';
                        yyInput--;
                        if (sign < 0)
                                yylval.Number = -yylval.Number;
                        return sign ? tSNUMBER : tUNUMBER;
                }

                if (isalpha(c)) {
                        for (p = buff; isalpha(c = *yyInput++) || c == '.'; )
                                if (p < &buff[sizeof buff - 1])
                                        *p++ = c;
                        *p = '\0';
                        yyInput--;
                        return lookup(buff);
                }
                if (c != '(')
                        return *yyInput++;

                count = 0;
                do {
                        c = *yyInput++;
                        if (c == '\0')
                                return (c);
                        if (c == '(')
                                count++;
                        else if (c == ')')
                                count--;
                } while (count > 0);
        }
}

/* Yield A - B, measured in seconds.  */
static long
difftm(struct tm *a, struct tm *b)
{
        int ay = a->tm_year + (YEAR_TMORIGIN - 1);
        int by = b->tm_year + (YEAR_TMORIGIN - 1);
        int days = (
            /* difference in day of year */
            a->tm_yday - b->tm_yday
            /* + intervening leap days */
            +  ((ay >> 2) - (by >> 2))
            -  (ay/100 - by/100)
            +  ((ay/100 >> 2) - (by/100 >> 2))
            /* + difference in years * 365 */
            +  (long)(ay-by) * 365);
        return (60 * (60 * (24 * days + (a->tm_hour - b->tm_hour))
            + (a->tm_min - b->tm_min)) + (a->tm_sec - b->tm_sec));
}

/*
 * date_parse()
 *
 * Returns the number of seconds since the Epoch corresponding to the date.
 */
time_t
date_parse(const char *p)
{
        struct tm       gmt, tm;
        time_t          Start, tod, nowtime, tz;

        yyInput = p;

        if (time(&nowtime) == -1 || !gmtime_r(&nowtime, &gmt) ||
            !localtime_r(&nowtime, &tm))
                return -1;

        tz = difftm(&gmt, &tm) / 60;

        if (tm.tm_isdst)
                tz += 60;

        yyYear = tm.tm_year + 1900;
        yyMonth = tm.tm_mon + 1;
        yyDay = tm.tm_mday;
        yyTimezone = tz;
        yyDSTmode = DSTmaybe;
        yyHour = 0;
        yyMinutes = 0;
        yySeconds = 0;
        yyMeridian = MER24;
        yyRelSeconds = 0;
        yyRelMonth = 0;
        yyHaveDate = 0;
        yyHaveDay = 0;
        yyHaveRel = 0;
        yyHaveTime = 0;
        yyHaveZone = 0;

        if (yyparse() || yyHaveTime > 1 || yyHaveZone > 1 ||
            yyHaveDate > 1 || yyHaveDay > 1)
                return (-1);

        if (yyHaveDate || yyHaveTime || yyHaveDay) {
                Start = Convert(yyMonth, yyDay, yyYear, yyHour, yyMinutes,
                    yySeconds, yyMeridian, yyDSTmode);
                if (Start < 0)
                        return (-1);
        } else {
                Start = nowtime;
                if (!yyHaveRel)
                        Start -= ((tm.tm_hour * 60L + tm.tm_min) * 60L) +
                            tm.tm_sec;
        }

        Start += yyRelSeconds;
        Start += RelativeMonth(Start, yyRelMonth);

        if (yyHaveDay && !yyHaveDate) {
                tod = RelativeDate(Start, yyDayOrdinal, yyDayNumber);
                Start += tod;
        }

        return Start;
}

#if defined(TEST)
int
main(int argc, char **argv)
{
        char    buff[128];
        time_t  d;

        (void)printf("Enter date, or blank line to exit.\n\t> ");
        (void)fflush(stdout);
        while (fgets(buff, sizeof(buff), stdin) && buff[0]) {
                d = date_parse(buff);
                if (d == -1)
                        (void)printf("Bad format - couldn't convert.\n");
                else
                        (void)printf("%s", ctime(&d));
                (void)printf("\t> ");
                (void)fflush(stdout);
        }

        return (0);
}
#endif  /* defined(TEST) */