Imported Upstream version 1.8.1p2
[debian/sudo] / plugins / sudoers / getdate.y
diff --git a/plugins/sudoers/getdate.y b/plugins/sudoers/getdate.y
new file mode 100644 (file)
index 0000000..13483ff
--- /dev/null
@@ -0,0 +1,962 @@
+%{
+/*
+**  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 <config.h>
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <stdio.h>
+#ifdef STDC_HEADERS
+# include <stdlib.h>
+# include <stddef.h>
+#else
+# ifdef HAVE_STDLIB_H
+#  include <stdlib.h>
+# endif
+#endif /* STDC_HEADERS */
+#ifdef HAVE_STRING_H
+# if defined(HAVE_MEMORY_H) && !defined(STDC_HEADERS)
+#  include <memory.h>
+# endif
+# include <string.h>
+#endif /* HAVE_STRING_H */
+#ifdef HAVE_STRINGS_H
+# include <strings.h>
+#endif /* HAVE_STRINGS_H */
+#if TIME_WITH_SYS_TIME
+# include <time.h>
+#endif
+#include <ctype.h>
+
+#include "missing.h"
+
+
+#define EPOCH          1970
+#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 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(char *s);
+static int     yylex(void);
+static int     yyparse(void);
+
+%}
+
+%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 }
+};
+
+\f
+
+
+/* ARGSUSED */
+static int
+yyerror(s)
+    char       *s;
+{
+  return 0;
+}
+
+
+static time_t
+ToSeconds(Hours, Minutes, Seconds, Meridian)
+    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:
+       abort ();
+    }
+    /* 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(Month, Day, Year, Hours, Minutes, Seconds, Meridian, DSTmode)
+    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 < EPOCH)
+               Year += 100;
+    }
+    DaysInMonth[1] = Year % 4 == 0 && (Year % 100 != 0 || Year % 400 == 0)
+                   ? 29 : 28;
+    /* Checking for 2038 bogusly assumes that time_t is 32 bits.  But
+       I'm too lazy to try to check for time_t overflow in another way.  */
+    if (Year < EPOCH || 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 = 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(Start, Future)
+    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(Start, DayOrdinal, DayNumber)
+    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(Start, RelMonth)
+    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
+LookupWord(buff)
+    char               *buff;
+{
+    char               *p;
+    char               *q;
+    const TABLE                *tp;
+    int                        i;
+    int                        abbrev;
+
+    /* Make it lowercase. */
+    for (p = buff; *p; p++)
+       if (isupper((unsigned char)*p))
+           *p = tolower((unsigned char)*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;
+    }
+
+    /* See if we have an abbreviation for a month. */
+    if (strlen(buff) == 3)
+       abbrev = 1;
+    else if (strlen(buff) == 4 && buff[3] == '.') {
+       abbrev = 1;
+       buff[3] = '\0';
+    }
+    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. */
+    i = strlen(buff) - 1;
+    if (buff[i] == 's') {
+       buff[i] = '\0';
+       for (tp = UnitsTable; tp->name; tp++)
+           if (strcmp(buff, tp->name) == 0) {
+               yylval.Number = tp->value;
+               return tp->type;
+           }
+       buff[i] = '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 (buff[1] == '\0' && isalpha((unsigned char)*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()
+{
+    char               c;
+    char               *p;
+    char               buff[20];
+    int                        Count;
+    int                        sign;
+
+    for ( ; ; ) {
+       while (isspace((unsigned char)*yyInput))
+           yyInput++;
+
+       if (isdigit((unsigned char)(c = *yyInput)) || c == '-' || c == '+') {
+           if (c == '-' || c == '+') {
+               sign = c == '-' ? -1 : 1;
+               if (!isdigit((unsigned char)*++yyInput))
+                   /* skip the '-' sign */
+                   continue;
+           }
+           else
+               sign = 0;
+           for (yylval.Number = 0; isdigit((unsigned char)(c = *yyInput++)); )
+               yylval.Number = 10 * yylval.Number + c - '0';
+           yyInput--;
+           if (sign < 0)
+               yylval.Number = -yylval.Number;
+           return sign ? tSNUMBER : tUNUMBER;
+       }
+       if (isalpha((unsigned char)c)) {
+           for (p = buff; isalpha((unsigned char)(c = *yyInput++)) || c == '.'; )
+               if (p < &buff[sizeof buff - 1])
+                   *p++ = c;
+           *p = '\0';
+           yyInput--;
+           return LookupWord(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);
+    }
+}
+
+#define TM_YEAR_ORIGIN 1900
+
+/* Yield A - B, measured in seconds.  */
+static long
+difftm (a, b)
+     struct tm *a, *b;
+{
+  int ay = a->tm_year + (TM_YEAR_ORIGIN - 1);
+  int by = b->tm_year + (TM_YEAR_ORIGIN - 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));
+}
+
+time_t
+get_date(p)
+    char               *p;
+{
+    struct tm          *tm, *gmt, gmtbuf;
+    time_t             Start;
+    time_t             tod;
+    time_t             now;
+    time_t             timezone;
+
+    yyInput = p;
+    (void)time (&now);
+
+    gmt = gmtime (&now);
+    if (gmt != NULL)
+    {
+       /* Make a copy, in case localtime modifies *tm (I think
+          that comment now applies to *gmt, but I am too
+          lazy to dig into how gmtime and locatime allocate the
+          structures they return pointers to).  */
+       gmtbuf = *gmt;
+       gmt = &gmtbuf;
+    }
+
+    if (! (tm = localtime (&now)))
+       return -1;
+
+    if (gmt != NULL)
+       timezone = difftm (gmt, tm) / 60;
+    else
+       /* We are on a system like VMS, where the system clock is
+          in local time and the system has no concept of timezones.
+          Hopefully we can fake this out (for the case in which the
+          user specifies no timezone) by just saying the timezone
+          is zero.  */
+       timezone = 0;
+
+    if(tm->tm_isdst)
+       timezone += 60;
+
+    tm = localtime(&now);
+    yyYear = tm->tm_year + 1900;
+    yyMonth = tm->tm_mon + 1;
+    yyDay = tm->tm_mday;
+    yyTimezone = timezone;
+    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 = now;
+       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;
+    }
+
+    /* Have to do *something* with a legitimate -1 so it's distinguishable
+     * from the error return value.  (Alternately could set errno on error.) */
+    return Start == -1 ? 0 : Start;
+}
+
+
+#if    defined(TEST)
+
+/* ARGSUSED */
+int
+main(ac, av)
+    int                ac;
+    char       *av[];
+{
+    char       buff[128];
+    time_t     d;
+
+    (void)printf("Enter date, or blank line to exit.\n\t> ");
+    (void)fflush(stdout);
+    while (gets(buff) && buff[0]) {
+       d = get_date(buff);
+       if (d == -1)
+           (void)printf("Bad format - couldn't convert.\n");
+       else
+           (void)printf("%s", ctime(&d));
+       (void)printf("\t> ");
+       (void)fflush(stdout);
+    }
+    exit(0);
+    /* NOTREACHED */
+}
+#endif /* defined(TEST) */