Home | History | Annotate | Line # | Download | only in libutil
parsedate.y revision 1.6
      1 %{
      2 /*
      3 **  Originally written by Steven M. Bellovin <smb (at) research.att.com> while
      4 **  at the University of North Carolina at Chapel Hill.  Later tweaked by
      5 **  a couple of people on Usenet.  Completely overhauled by Rich $alz
      6 **  <rsalz (at) bbn.com> and Jim Berets <jberets (at) bbn.com> in August, 1990;
      7 **
      8 **  This grammar has 10 shift/reduce conflicts.
      9 **
     10 **  This code is in the public domain and has no copyright.
     11 */
     12 /* SUPPRESS 287 on yaccpar_sccsid *//* Unused static variable */
     13 /* SUPPRESS 288 on yyerrlab *//* Label unused */
     14 
     15 #include <stdio.h>
     16 #include <ctype.h>
     17 #include <string.h>
     18 #include <time.h>
     19 #include <util.h>
     20 #include <stdlib.h>
     21 
     22 /* NOTES on rebuilding parsedate.c (particularly for inclusion in CVS
     23    releases):
     24 
     25    We don't want to mess with all the portability hassles of alloca.
     26    In particular, most (all?) versions of bison will use alloca in
     27    their parser.  If bison works on your system (e.g. it should work
     28    with gcc), then go ahead and use it, but the more general solution
     29    is to use byacc instead of bison, which should generate a portable
     30    parser.  I played with adding "#define alloca dont_use_alloca", to
     31    give an error if the parser generator uses alloca (and thus detect
     32    unportable parsedate.c's), but that seems to cause as many problems
     33    as it solves.  */
     34 
     35 #define EPOCH		1970
     36 #define HOUR(x)		((time_t)(x) * 60)
     37 #define SECSPERDAY	(24L * 60L * 60L)
     38 
     39 
     40 /*
     41 **  An entry in the lexical lookup table.
     42 */
     43 typedef struct _TABLE {
     44     const char	*name;
     45     int		type;
     46     time_t	value;
     47 } TABLE;
     48 
     49 
     50 /*
     51 **  Daylight-savings mode:  on, off, or not yet known.
     52 */
     53 typedef enum _DSTMODE {
     54     DSTon, DSToff, DSTmaybe
     55 } DSTMODE;
     56 
     57 /*
     58 **  Meridian:  am, pm, or 24-hour style.
     59 */
     60 typedef enum _MERIDIAN {
     61     MERam, MERpm, MER24
     62 } MERIDIAN;
     63 
     64 
     65 /*
     66 **  Global variables.  We could get rid of most of these by using a good
     67 **  union as the yacc stack.  (This routine was originally written before
     68 **  yacc had the %union construct.)  Maybe someday; right now we only use
     69 **  the %union very rarely.
     70 */
     71 static const char *yyInput;
     72 static DSTMODE	yyDSTmode;
     73 static time_t	yyDayOrdinal;
     74 static time_t	yyDayNumber;
     75 static int	yyHaveDate;
     76 static int	yyHaveDay;
     77 static int	yyHaveRel;
     78 static int	yyHaveTime;
     79 static int	yyHaveZone;
     80 static time_t	yyTimezone;
     81 static time_t	yyDay;
     82 static time_t	yyHour;
     83 static time_t	yyMinutes;
     84 static time_t	yyMonth;
     85 static time_t	yySeconds;
     86 static time_t	yyYear;
     87 static MERIDIAN	yyMeridian;
     88 static time_t	yyRelMonth;
     89 static time_t	yyRelSeconds;
     90 
     91 %}
     92 
     93 %union {
     94     time_t		Number;
     95     enum _MERIDIAN	Meridian;
     96 }
     97 
     98 %token	tAGO tDAY tDAYZONE tID tMERIDIAN tMINUTE_UNIT tMONTH tMONTH_UNIT
     99 %token	tSEC_UNIT tSNUMBER tUNUMBER tZONE tDST AT_SIGN
    100 
    101 %type	<Number>	tDAY tDAYZONE tMINUTE_UNIT tMONTH tMONTH_UNIT
    102 %type	<Number>	tSEC_UNIT tSNUMBER tUNUMBER tZONE
    103 %type	<Meridian>	tMERIDIAN o_merid
    104 
    105 %%
    106 
    107 spec	: /* NULL */
    108 	| spec item
    109 	;
    110 
    111 item	: time {
    112 	    yyHaveTime++;
    113 	}
    114 	| zone {
    115 	    yyHaveZone++;
    116 	}
    117 	| date {
    118 	    yyHaveDate++;
    119 	}
    120 	| day {
    121 	    yyHaveDay++;
    122 	}
    123 	| rel {
    124 	    yyHaveRel++;
    125 	}
    126 	| cvsstamp {
    127 	    yyHaveTime++;
    128 	    yyHaveDate++;
    129 	    yyHaveZone++;
    130 	}
    131 	| epochdate {
    132 	    yyHaveTime++;
    133 	    yyHaveDate++;
    134 	    yyHaveZone++;
    135 	}
    136 	| number
    137 	;
    138 
    139 cvsstamp: tUNUMBER '.' tUNUMBER '.' tUNUMBER '.' tUNUMBER '.' tUNUMBER '.' tUNUMBER {
    140 	    yyYear = $1;
    141 	    if (yyYear < 100) yyYear += 1900;
    142 	    yyMonth = $3;
    143 	    yyDay = $5;
    144 	    yyHour = $7;
    145 	    yyMinutes = $9;
    146 	    yySeconds = $11;
    147 	    yyDSTmode = DSToff;
    148 	    yyTimezone = 0;
    149 	}
    150 	;
    151 
    152 epochdate: AT_SIGN tUNUMBER {
    153             time_t    when = $2;
    154             struct tm tmbuf;
    155             if (gmtime_r(&when, &tmbuf) != NULL) {
    156 		yyYear = tmbuf.tm_year + 1900;
    157 		yyMonth = tmbuf.tm_mon + 1;
    158 		yyDay = tmbuf.tm_mday;
    159 
    160 		yyHour = tmbuf.tm_hour;
    161 		yyMinutes = tmbuf.tm_min;
    162 		yySeconds = tmbuf.tm_sec;
    163 	    } else {
    164 		yyYear = 1970;
    165 		yyMonth = 1;
    166 		yyDay = 1;
    167 
    168 		yyHour = 0;
    169 		yyMinutes = 0;
    170 		yySeconds = 0;
    171 	    }
    172 	    yyDSTmode = DSToff;
    173 	    yyTimezone = 0;
    174 	}
    175 	;
    176 
    177 time	: tUNUMBER tMERIDIAN {
    178 	    yyHour = $1;
    179 	    yyMinutes = 0;
    180 	    yySeconds = 0;
    181 	    yyMeridian = $2;
    182 	}
    183 	| tUNUMBER ':' tUNUMBER o_merid {
    184 	    yyHour = $1;
    185 	    yyMinutes = $3;
    186 	    yySeconds = 0;
    187 	    yyMeridian = $4;
    188 	}
    189 	| tUNUMBER ':' tUNUMBER tSNUMBER {
    190 	    yyHour = $1;
    191 	    yyMinutes = $3;
    192 	    yyMeridian = MER24;
    193 	    yyDSTmode = DSToff;
    194 	    yyTimezone = - ($4 % 100 + ($4 / 100) * 60);
    195 	}
    196 	| tUNUMBER ':' tUNUMBER ':' tUNUMBER o_merid {
    197 	    yyHour = $1;
    198 	    yyMinutes = $3;
    199 	    yySeconds = $5;
    200 	    yyMeridian = $6;
    201 	}
    202 	| tUNUMBER ':' tUNUMBER ':' tUNUMBER tSNUMBER {
    203 	    yyHour = $1;
    204 	    yyMinutes = $3;
    205 	    yySeconds = $5;
    206 	    yyMeridian = MER24;
    207 	    yyDSTmode = DSToff;
    208 	    yyTimezone = - ($6 % 100 + ($6 / 100) * 60);
    209 	}
    210 	;
    211 
    212 zone	: tZONE {
    213 	    yyTimezone = $1;
    214 	    yyDSTmode = DSToff;
    215 	}
    216 	| tDAYZONE {
    217 	    yyTimezone = $1;
    218 	    yyDSTmode = DSTon;
    219 	}
    220 	|
    221 	  tZONE tDST {
    222 	    yyTimezone = $1;
    223 	    yyDSTmode = DSTon;
    224 	}
    225 	;
    226 
    227 day	: tDAY {
    228 	    yyDayOrdinal = 1;
    229 	    yyDayNumber = $1;
    230 	}
    231 	| tDAY ',' {
    232 	    yyDayOrdinal = 1;
    233 	    yyDayNumber = $1;
    234 	}
    235 	| tUNUMBER tDAY {
    236 	    yyDayOrdinal = $1;
    237 	    yyDayNumber = $2;
    238 	}
    239 	;
    240 
    241 date	: tUNUMBER '/' tUNUMBER {
    242 	    yyMonth = $1;
    243 	    yyDay = $3;
    244 	}
    245 	| tUNUMBER '/' tUNUMBER '/' tUNUMBER {
    246 	    if ($1 >= 100) {
    247 		yyYear = $1;
    248 		yyMonth = $3;
    249 		yyDay = $5;
    250 	    } else {
    251 		yyMonth = $1;
    252 		yyDay = $3;
    253 		yyYear = $5;
    254 	    }
    255 	}
    256 	| tUNUMBER tSNUMBER tSNUMBER {
    257 	    /* ISO 8601 format.  yyyy-mm-dd.  */
    258 	    yyYear = $1;
    259 	    yyMonth = -$2;
    260 	    yyDay = -$3;
    261 	}
    262 	| tUNUMBER tMONTH tSNUMBER {
    263 	    /* e.g. 17-JUN-1992.  */
    264 	    yyDay = $1;
    265 	    yyMonth = $2;
    266 	    yyYear = -$3;
    267 	}
    268 	| tMONTH tUNUMBER {
    269 	    yyMonth = $1;
    270 	    yyDay = $2;
    271 	}
    272 	| tMONTH tUNUMBER ',' tUNUMBER {
    273 	    yyMonth = $1;
    274 	    yyDay = $2;
    275 	    yyYear = $4;
    276 	}
    277 	| tUNUMBER tMONTH {
    278 	    yyMonth = $2;
    279 	    yyDay = $1;
    280 	}
    281 	| tUNUMBER tMONTH tUNUMBER {
    282 	    yyMonth = $2;
    283 	    yyDay = $1;
    284 	    yyYear = $3;
    285 	}
    286 	;
    287 
    288 rel	: relunit tAGO {
    289 	    yyRelSeconds = -yyRelSeconds;
    290 	    yyRelMonth = -yyRelMonth;
    291 	}
    292 	| relunit
    293 	;
    294 
    295 relunit	: tUNUMBER tMINUTE_UNIT {
    296 	    yyRelSeconds += $1 * $2 * 60L;
    297 	}
    298 	| tSNUMBER tMINUTE_UNIT {
    299 	    yyRelSeconds += $1 * $2 * 60L;
    300 	}
    301 	| tMINUTE_UNIT {
    302 	    yyRelSeconds += $1 * 60L;
    303 	}
    304 	| tSNUMBER tSEC_UNIT {
    305 	    yyRelSeconds += $1;
    306 	}
    307 	| tUNUMBER tSEC_UNIT {
    308 	    yyRelSeconds += $1;
    309 	}
    310 	| tSEC_UNIT {
    311 	    yyRelSeconds++;
    312 	}
    313 	| tSNUMBER tMONTH_UNIT {
    314 	    yyRelMonth += $1 * $2;
    315 	}
    316 	| tUNUMBER tMONTH_UNIT {
    317 	    yyRelMonth += $1 * $2;
    318 	}
    319 	| tMONTH_UNIT {
    320 	    yyRelMonth += $1;
    321 	}
    322 	;
    323 
    324 number	: tUNUMBER {
    325 	    if (yyHaveTime && yyHaveDate && !yyHaveRel)
    326 		yyYear = $1;
    327 	    else {
    328 		if($1>10000) {
    329 		    yyHaveDate++;
    330 		    yyDay= ($1)%100;
    331 		    yyMonth= ($1/100)%100;
    332 		    yyYear = $1/10000;
    333 		}
    334 		else {
    335 		    yyHaveTime++;
    336 		    if ($1 < 100) {
    337 			yyHour = $1;
    338 			yyMinutes = 0;
    339 		    }
    340 		    else {
    341 		    	yyHour = $1 / 100;
    342 		    	yyMinutes = $1 % 100;
    343 		    }
    344 		    yySeconds = 0;
    345 		    yyMeridian = MER24;
    346 	        }
    347 	    }
    348 	}
    349 	;
    350 
    351 o_merid	: /* NULL */ {
    352 	    $$ = MER24;
    353 	}
    354 	| tMERIDIAN {
    355 	    $$ = $1;
    356 	}
    357 	;
    358 
    359 %%
    360 
    361 /* Month and day table. */
    362 static TABLE const MonthDayTable[] = {
    363     { "january",	tMONTH,  1 },
    364     { "february",	tMONTH,  2 },
    365     { "march",		tMONTH,  3 },
    366     { "april",		tMONTH,  4 },
    367     { "may",		tMONTH,  5 },
    368     { "june",		tMONTH,  6 },
    369     { "july",		tMONTH,  7 },
    370     { "august",		tMONTH,  8 },
    371     { "september",	tMONTH,  9 },
    372     { "sept",		tMONTH,  9 },
    373     { "october",	tMONTH, 10 },
    374     { "november",	tMONTH, 11 },
    375     { "december",	tMONTH, 12 },
    376     { "sunday",		tDAY, 0 },
    377     { "monday",		tDAY, 1 },
    378     { "tuesday",	tDAY, 2 },
    379     { "tues",		tDAY, 2 },
    380     { "wednesday",	tDAY, 3 },
    381     { "wednes",		tDAY, 3 },
    382     { "thursday",	tDAY, 4 },
    383     { "thur",		tDAY, 4 },
    384     { "thurs",		tDAY, 4 },
    385     { "friday",		tDAY, 5 },
    386     { "saturday",	tDAY, 6 },
    387     { NULL,		0,    0 }
    388 };
    389 
    390 /* Time units table. */
    391 static TABLE const UnitsTable[] = {
    392     { "year",		tMONTH_UNIT,	12 },
    393     { "month",		tMONTH_UNIT,	1 },
    394     { "fortnight",	tMINUTE_UNIT,	14 * 24 * 60 },
    395     { "week",		tMINUTE_UNIT,	7 * 24 * 60 },
    396     { "day",		tMINUTE_UNIT,	1 * 24 * 60 },
    397     { "hour",		tMINUTE_UNIT,	60 },
    398     { "minute",		tMINUTE_UNIT,	1 },
    399     { "min",		tMINUTE_UNIT,	1 },
    400     { "second",		tSEC_UNIT,	1 },
    401     { "sec",		tSEC_UNIT,	1 },
    402     { NULL,		0,		0 }
    403 };
    404 
    405 /* Assorted relative-time words. */
    406 static TABLE const OtherTable[] = {
    407     { "tomorrow",	tMINUTE_UNIT,	1 * 24 * 60 },
    408     { "yesterday",	tMINUTE_UNIT,	-1 * 24 * 60 },
    409     { "today",		tMINUTE_UNIT,	0 },
    410     { "now",		tMINUTE_UNIT,	0 },
    411     { "last",		tUNUMBER,	-1 },
    412     { "this",		tMINUTE_UNIT,	0 },
    413     { "next",		tUNUMBER,	2 },
    414     { "first",		tUNUMBER,	1 },
    415 /*  { "second",		tUNUMBER,	2 }, */
    416     { "third",		tUNUMBER,	3 },
    417     { "fourth",		tUNUMBER,	4 },
    418     { "fifth",		tUNUMBER,	5 },
    419     { "sixth",		tUNUMBER,	6 },
    420     { "seventh",	tUNUMBER,	7 },
    421     { "eighth",		tUNUMBER,	8 },
    422     { "ninth",		tUNUMBER,	9 },
    423     { "tenth",		tUNUMBER,	10 },
    424     { "eleventh",	tUNUMBER,	11 },
    425     { "twelfth",	tUNUMBER,	12 },
    426     { "ago",		tAGO,	1 },
    427     { NULL,		0,	0 }
    428 };
    429 
    430 /* The timezone table. */
    431 /* Some of these are commented out because a time_t can't store a float. */
    432 static TABLE const TimezoneTable[] = {
    433     { "gmt",	tZONE,     HOUR( 0) },	/* Greenwich Mean */
    434     { "ut",	tZONE,     HOUR( 0) },	/* Universal (Coordinated) */
    435     { "utc",	tZONE,     HOUR( 0) },
    436     { "wet",	tZONE,     HOUR( 0) },	/* Western European */
    437     { "bst",	tDAYZONE,  HOUR( 0) },	/* British Summer */
    438     { "wat",	tZONE,     HOUR( 1) },	/* West Africa */
    439     { "at",	tZONE,     HOUR( 2) },	/* Azores */
    440 #if	0
    441     /* For completeness.  BST is also British Summer, and GST is
    442      * also Guam Standard. */
    443     { "bst",	tZONE,     HOUR( 3) },	/* Brazil Standard */
    444     { "gst",	tZONE,     HOUR( 3) },	/* Greenland Standard */
    445 #endif
    446 #if 0
    447     { "nft",	tZONE,     HOUR(3.5) },	/* Newfoundland */
    448     { "nst",	tZONE,     HOUR(3.5) },	/* Newfoundland Standard */
    449     { "ndt",	tDAYZONE,  HOUR(3.5) },	/* Newfoundland Daylight */
    450 #endif
    451     { "ast",	tZONE,     HOUR( 4) },	/* Atlantic Standard */
    452     { "adt",	tDAYZONE,  HOUR( 4) },	/* Atlantic Daylight */
    453     { "est",	tZONE,     HOUR( 5) },	/* Eastern Standard */
    454     { "edt",	tDAYZONE,  HOUR( 5) },	/* Eastern Daylight */
    455     { "cst",	tZONE,     HOUR( 6) },	/* Central Standard */
    456     { "cdt",	tDAYZONE,  HOUR( 6) },	/* Central Daylight */
    457     { "mst",	tZONE,     HOUR( 7) },	/* Mountain Standard */
    458     { "mdt",	tDAYZONE,  HOUR( 7) },	/* Mountain Daylight */
    459     { "pst",	tZONE,     HOUR( 8) },	/* Pacific Standard */
    460     { "pdt",	tDAYZONE,  HOUR( 8) },	/* Pacific Daylight */
    461     { "yst",	tZONE,     HOUR( 9) },	/* Yukon Standard */
    462     { "ydt",	tDAYZONE,  HOUR( 9) },	/* Yukon Daylight */
    463     { "hst",	tZONE,     HOUR(10) },	/* Hawaii Standard */
    464     { "hdt",	tDAYZONE,  HOUR(10) },	/* Hawaii Daylight */
    465     { "cat",	tZONE,     HOUR(10) },	/* Central Alaska */
    466     { "ahst",	tZONE,     HOUR(10) },	/* Alaska-Hawaii Standard */
    467     { "nt",	tZONE,     HOUR(11) },	/* Nome */
    468     { "idlw",	tZONE,     HOUR(12) },	/* International Date Line West */
    469     { "cet",	tZONE,     -HOUR(1) },	/* Central European */
    470     { "met",	tZONE,     -HOUR(1) },	/* Middle European */
    471     { "mewt",	tZONE,     -HOUR(1) },	/* Middle European Winter */
    472     { "mest",	tDAYZONE,  -HOUR(1) },	/* Middle European Summer */
    473     { "swt",	tZONE,     -HOUR(1) },	/* Swedish Winter */
    474     { "sst",	tDAYZONE,  -HOUR(1) },	/* Swedish Summer */
    475     { "fwt",	tZONE,     -HOUR(1) },	/* French Winter */
    476     { "fst",	tDAYZONE,  -HOUR(1) },	/* French Summer */
    477     { "eet",	tZONE,     -HOUR(2) },	/* Eastern Europe, USSR Zone 1 */
    478     { "bt",	tZONE,     -HOUR(3) },	/* Baghdad, USSR Zone 2 */
    479 #if 0
    480     { "it",	tZONE,     -HOUR(3.5) },/* Iran */
    481 #endif
    482     { "zp4",	tZONE,     -HOUR(4) },	/* USSR Zone 3 */
    483     { "zp5",	tZONE,     -HOUR(5) },	/* USSR Zone 4 */
    484 #if 0
    485     { "ist",	tZONE,     -HOUR(5.5) },/* Indian Standard */
    486 #endif
    487     { "zp6",	tZONE,     -HOUR(6) },	/* USSR Zone 5 */
    488 #if	0
    489     /* For completeness.  NST is also Newfoundland Stanard, and SST is
    490      * also Swedish Summer. */
    491     { "nst",	tZONE,     -HOUR(6.5) },/* North Sumatra */
    492     { "sst",	tZONE,     -HOUR(7) },	/* South Sumatra, USSR Zone 6 */
    493 #endif	/* 0 */
    494     { "wast",	tZONE,     -HOUR(7) },	/* West Australian Standard */
    495     { "wadt",	tDAYZONE,  -HOUR(7) },	/* West Australian Daylight */
    496 #if 0
    497     { "jt",	tZONE,     -HOUR(7.5) },/* Java (3pm in Cronusland!) */
    498 #endif
    499     { "cct",	tZONE,     -HOUR(8) },	/* China Coast, USSR Zone 7 */
    500     { "jst",	tZONE,     -HOUR(9) },	/* Japan Standard, USSR Zone 8 */
    501 #if 0
    502     { "cast",	tZONE,     -HOUR(9.5) },/* Central Australian Standard */
    503     { "cadt",	tDAYZONE,  -HOUR(9.5) },/* Central Australian Daylight */
    504 #endif
    505     { "east",	tZONE,     -HOUR(10) },	/* Eastern Australian Standard */
    506     { "eadt",	tDAYZONE,  -HOUR(10) },	/* Eastern Australian Daylight */
    507     { "gst",	tZONE,     -HOUR(10) },	/* Guam Standard, USSR Zone 9 */
    508     { "nzt",	tZONE,     -HOUR(12) },	/* New Zealand */
    509     { "nzst",	tZONE,     -HOUR(12) },	/* New Zealand Standard */
    510     { "nzdt",	tDAYZONE,  -HOUR(12) },	/* New Zealand Daylight */
    511     { "idle",	tZONE,     -HOUR(12) },	/* International Date Line East */
    512     {  NULL,	0,	    0 }
    513 };
    514 
    515 /* Military timezone table. */
    516 static TABLE const MilitaryTable[] = {
    517     { "a",	tZONE,	HOUR(  1) },
    518     { "b",	tZONE,	HOUR(  2) },
    519     { "c",	tZONE,	HOUR(  3) },
    520     { "d",	tZONE,	HOUR(  4) },
    521     { "e",	tZONE,	HOUR(  5) },
    522     { "f",	tZONE,	HOUR(  6) },
    523     { "g",	tZONE,	HOUR(  7) },
    524     { "h",	tZONE,	HOUR(  8) },
    525     { "i",	tZONE,	HOUR(  9) },
    526     { "k",	tZONE,	HOUR( 10) },
    527     { "l",	tZONE,	HOUR( 11) },
    528     { "m",	tZONE,	HOUR( 12) },
    529     { "n",	tZONE,	HOUR(- 1) },
    530     { "o",	tZONE,	HOUR(- 2) },
    531     { "p",	tZONE,	HOUR(- 3) },
    532     { "q",	tZONE,	HOUR(- 4) },
    533     { "r",	tZONE,	HOUR(- 5) },
    534     { "s",	tZONE,	HOUR(- 6) },
    535     { "t",	tZONE,	HOUR(- 7) },
    536     { "u",	tZONE,	HOUR(- 8) },
    537     { "v",	tZONE,	HOUR(- 9) },
    538     { "w",	tZONE,	HOUR(-10) },
    539     { "x",	tZONE,	HOUR(-11) },
    540     { "y",	tZONE,	HOUR(-12) },
    541     { "z",	tZONE,	HOUR(  0) },
    542     { NULL,	0,	0 }
    543 };
    544 
    545 
    546 
    548 
    549 /* ARGSUSED */
    550 static int
    551 yyerror(const char *s __unused)
    552 {
    553   return 0;
    554 }
    555 
    556 
    557 static time_t
    558 ToSeconds(
    559     time_t	Hours,
    560     time_t	Minutes,
    561     time_t	Seconds,
    562     MERIDIAN	Meridian
    563 )
    564 {
    565     if (Minutes < 0 || Minutes > 59 || Seconds < 0 || Seconds > 59)
    566 	return -1;
    567     switch (Meridian) {
    568     case MER24:
    569 	if (Hours < 0 || Hours > 23)
    570 	    return -1;
    571 	return (Hours * 60L + Minutes) * 60L + Seconds;
    572     case MERam:
    573 	if (Hours < 1 || Hours > 12)
    574 	    return -1;
    575 	if (Hours == 12)
    576 	    Hours = 0;
    577 	return (Hours * 60L + Minutes) * 60L + Seconds;
    578     case MERpm:
    579 	if (Hours < 1 || Hours > 12)
    580 	    return -1;
    581 	if (Hours == 12)
    582 	    Hours = 0;
    583 	return ((Hours + 12) * 60L + Minutes) * 60L + Seconds;
    584     default:
    585 	abort ();
    586     }
    587     /* NOTREACHED */
    588 }
    589 
    590 static int
    591 isLeap(int year)
    592 {
    593     return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
    594 }
    595 
    596 
    597 /* Year is either
    598    * A negative number, which means to use its absolute value (why?)
    599    * A number from 0 to 99, which means a year from 1900 to 1999, or
    600    * The actual year (>=100).  */
    601 static time_t
    602 Convert(
    603     time_t	Month,
    604     time_t	Day,
    605     time_t	Year,
    606     time_t	Hours,
    607     time_t	Minutes,
    608     time_t	Seconds,
    609     MERIDIAN	Meridian,
    610     DSTMODE	DSTmode
    611 )
    612 {
    613     static int DaysInMonth[12] = {
    614 	31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
    615     };
    616     time_t	tod;
    617     time_t	Julian, oJulian;
    618     int		i;
    619 
    620     if (Year < 0)
    621 	Year = -Year;
    622     if (Year < 69)
    623 	Year += 2000;
    624     else if (Year < 100)
    625 	Year += 1900;
    626     DaysInMonth[1] = isLeap(Year) ? 29 : 28;
    627     if (Year < EPOCH || Month < 1 || Month > 12
    628      /* Lint fluff:  "conversion from long may lose accuracy" */
    629      || Day < 1 || Day > DaysInMonth[(int)--Month])
    630 	/* FIXME:
    631 	 * It would be nice to set a global error string here.
    632 	 * "February 30 is not a valid date" is much more informative than
    633 	 * "Can't parse date/time: 100 months" when the user input was
    634 	 * "100 months" and addition resolved that to February 30, for
    635 	 * example.  See rcs2-7 in src/sanity.sh for more. */
    636 	return -1;
    637 
    638     for (Julian = Day - 1, i = 0; i < Month; i++)
    639 	Julian += DaysInMonth[i];
    640 
    641     oJulian = Julian;
    642     for (i = EPOCH; i < Year; i++) {
    643 	Julian += 365 + isLeap(i);
    644 	if (oJulian > Julian)
    645 	    return -1;
    646 	oJulian = Julian;
    647     }
    648 
    649     Julian *= SECSPERDAY;
    650     if (oJulian > Julian)
    651 	return -1;
    652     oJulian = Julian;
    653 
    654     Julian += yyTimezone * 60L;
    655     if (oJulian > Julian)
    656 	return -1;
    657     oJulian = Julian;
    658 
    659     if ((tod = ToSeconds(Hours, Minutes, Seconds, Meridian)) < 0)
    660 	return -1;
    661 
    662     Julian += tod;
    663     if (oJulian > Julian)
    664 	return -1;
    665 
    666     if (DSTmode == DSTon || (DSTmode == DSTmaybe)) {
    667 	struct tm  *tm;
    668 	if ((tm = localtime(&Julian)) == NULL)
    669 	    return -1;
    670 	if (tm->tm_isdst)
    671 	    Julian -= 60 * 60;
    672     }
    673     return Julian;
    674 }
    675 
    676 
    677 static time_t
    678 DSTcorrect(
    679     time_t	Start,
    680     time_t	Future
    681 )
    682 {
    683     time_t	StartDay;
    684     time_t	FutureDay;
    685     struct tm  *tm;
    686 
    687     if ((tm = localtime(&Start)) == NULL)
    688 	return -1;
    689     StartDay = (tm->tm_hour + 1) % 24;
    690 
    691     if ((tm = localtime(&Future)) == NULL)
    692 	return -1;
    693     FutureDay = (tm->tm_hour + 1) % 24;
    694 
    695     return (Future - Start) + (StartDay - FutureDay) * 60L * 60L;
    696 }
    697 
    698 
    699 static time_t
    700 RelativeDate(
    701     time_t	Start,
    702     time_t	DayOrdinal,
    703     time_t	DayNumber
    704 )
    705 {
    706     struct tm	*tm;
    707     time_t	now;
    708 
    709     now = Start;
    710     tm = localtime(&now);
    711     now += SECSPERDAY * ((DayNumber - tm->tm_wday + 7) % 7);
    712     now += 7 * SECSPERDAY * (DayOrdinal <= 0 ? DayOrdinal : DayOrdinal - 1);
    713     return DSTcorrect(Start, now);
    714 }
    715 
    716 
    717 static time_t
    718 RelativeMonth(
    719     time_t	Start,
    720     time_t	RelMonth
    721 )
    722 {
    723     struct tm	*tm;
    724     time_t	Month;
    725     time_t	Year;
    726 
    727     if (RelMonth == 0)
    728 	return 0;
    729     tm = localtime(&Start);
    730     if (tm == NULL)
    731 	return -1;
    732     Month = 12 * (tm->tm_year + 1900) + tm->tm_mon + RelMonth;
    733     Year = Month / 12;
    734     Month = Month % 12 + 1;
    735     return DSTcorrect(Start,
    736 	    Convert(Month, (time_t)tm->tm_mday, Year,
    737 		(time_t)tm->tm_hour, (time_t)tm->tm_min, (time_t)tm->tm_sec,
    738 		MER24, DSTmaybe));
    739 }
    740 
    741 
    742 static int
    743 LookupWord(char *buff)
    744 {
    745     register char	*p;
    746     register char	*q;
    747     register const TABLE	*tp;
    748     int			i;
    749     int			abbrev;
    750 
    751     /* Make it lowercase. */
    752     for (p = buff; *p; p++)
    753 	if (isupper((unsigned char)*p))
    754 	    *p = tolower((unsigned char)*p);
    755 
    756     if (strcmp(buff, "am") == 0 || strcmp(buff, "a.m.") == 0) {
    757 	yylval.Meridian = MERam;
    758 	return tMERIDIAN;
    759     }
    760     if (strcmp(buff, "pm") == 0 || strcmp(buff, "p.m.") == 0) {
    761 	yylval.Meridian = MERpm;
    762 	return tMERIDIAN;
    763     }
    764 
    765     /* See if we have an abbreviation for a month. */
    766     if (strlen(buff) == 3)
    767 	abbrev = 1;
    768     else if (strlen(buff) == 4 && buff[3] == '.') {
    769 	abbrev = 1;
    770 	buff[3] = '\0';
    771     }
    772     else
    773 	abbrev = 0;
    774 
    775     for (tp = MonthDayTable; tp->name; tp++) {
    776 	if (abbrev) {
    777 	    if (strncmp(buff, tp->name, 3) == 0) {
    778 		yylval.Number = tp->value;
    779 		return tp->type;
    780 	    }
    781 	}
    782 	else if (strcmp(buff, tp->name) == 0) {
    783 	    yylval.Number = tp->value;
    784 	    return tp->type;
    785 	}
    786     }
    787 
    788     for (tp = TimezoneTable; tp->name; tp++)
    789 	if (strcmp(buff, tp->name) == 0) {
    790 	    yylval.Number = tp->value;
    791 	    return tp->type;
    792 	}
    793 
    794     if (strcmp(buff, "dst") == 0)
    795 	return tDST;
    796 
    797     for (tp = UnitsTable; tp->name; tp++)
    798 	if (strcmp(buff, tp->name) == 0) {
    799 	    yylval.Number = tp->value;
    800 	    return tp->type;
    801 	}
    802 
    803     /* Strip off any plural and try the units table again. */
    804     i = strlen(buff) - 1;
    805     if (buff[i] == 's') {
    806 	buff[i] = '\0';
    807 	for (tp = UnitsTable; tp->name; tp++)
    808 	    if (strcmp(buff, tp->name) == 0) {
    809 		yylval.Number = tp->value;
    810 		return tp->type;
    811 	    }
    812 	buff[i] = 's';		/* Put back for "this" in OtherTable. */
    813     }
    814 
    815     for (tp = OtherTable; tp->name; tp++)
    816 	if (strcmp(buff, tp->name) == 0) {
    817 	    yylval.Number = tp->value;
    818 	    return tp->type;
    819 	}
    820 
    821     /* Military timezones. */
    822     if (buff[1] == '\0' && isalpha((unsigned char)*buff)) {
    823 	for (tp = MilitaryTable; tp->name; tp++)
    824 	    if (strcmp(buff, tp->name) == 0) {
    825 		yylval.Number = tp->value;
    826 		return tp->type;
    827 	    }
    828     }
    829 
    830     /* Drop out any periods and try the timezone table again. */
    831     for (i = 0, p = q = buff; *q; q++)
    832 	if (*q != '.')
    833 	    *p++ = *q;
    834 	else
    835 	    i++;
    836     *p = '\0';
    837     if (i)
    838 	for (tp = TimezoneTable; tp->name; tp++)
    839 	    if (strcmp(buff, tp->name) == 0) {
    840 		yylval.Number = tp->value;
    841 		return tp->type;
    842 	    }
    843 
    844     return tID;
    845 }
    846 
    847 
    848 static int
    849 yylex(void)
    850 {
    851     register char	c;
    852     register char	*p;
    853     char		buff[20];
    854     int			Count;
    855     int			sign;
    856 
    857     for ( ; ; ) {
    858 	while (isspace((unsigned char)*yyInput))
    859 	    yyInput++;
    860 
    861 	if (isdigit((unsigned char)(c = *yyInput)) || c == '-' || c == '+') {
    862 	    if (c == '-' || c == '+') {
    863 		sign = c == '-' ? -1 : 1;
    864 		if (!isdigit((unsigned char)*++yyInput))
    865 		    /* skip the '-' sign */
    866 		    continue;
    867 	    }
    868 	    else
    869 		sign = 0;
    870 	    for (yylval.Number = 0; isdigit((unsigned char)(c = *yyInput++)); )
    871 		yylval.Number = 10 * yylval.Number + c - '0';
    872 	    yyInput--;
    873 	    if (sign < 0)
    874 		yylval.Number = -yylval.Number;
    875 	    return sign ? tSNUMBER : tUNUMBER;
    876 	}
    877 	if (isalpha((unsigned char)c)) {
    878 	    for (p = buff; isalpha((unsigned char)(c = *yyInput++)) || c == '.'; )
    879 		if (p < &buff[sizeof buff - 1])
    880 		    *p++ = c;
    881 	    *p = '\0';
    882 	    yyInput--;
    883 	    return LookupWord(buff);
    884 	}
    885 	if (c == '@') {
    886 	    yyInput++;
    887 	    return AT_SIGN;
    888 	}
    889 	if (c != '(')
    890 	    return *yyInput++;
    891 	Count = 0;
    892 	do {
    893 	    c = *yyInput++;
    894 	    if (c == '\0')
    895 		return c;
    896 	    if (c == '(')
    897 		Count++;
    898 	    else if (c == ')')
    899 		Count--;
    900 	} while (Count > 0);
    901     }
    902 }
    903 
    904 #define TM_YEAR_ORIGIN 1900
    905 
    906 /* Yield A - B, measured in seconds.  */
    907 static time_t
    908 difftm (struct tm *a, struct tm *b)
    909 {
    910   int ay = a->tm_year + (TM_YEAR_ORIGIN - 1);
    911   int by = b->tm_year + (TM_YEAR_ORIGIN - 1);
    912   int days = (
    913 	      /* difference in day of year */
    914 	      a->tm_yday - b->tm_yday
    915 	      /* + intervening leap days */
    916 	      +  ((ay >> 2) - (by >> 2))
    917 	      -  (ay/100 - by/100)
    918 	      +  ((ay/100 >> 2) - (by/100 >> 2))
    919 	      /* + difference in years * 365 */
    920 	      +  (long)(ay-by) * 365
    921 	      );
    922   return ((time_t)60*(60*(24*days + (a->tm_hour - b->tm_hour))
    923 	      + (a->tm_min - b->tm_min))
    924 	  + (a->tm_sec - b->tm_sec));
    925 }
    926 
    927 time_t
    928 parsedate(const char *p, const time_t *now, const int *zone)
    929 {
    930     struct tm gmt, local, *gmt_ptr, *tm;
    931     time_t		nowt;
    932     int			zonet;
    933     time_t		Start;
    934     time_t		tod, rm;
    935 
    936     yyInput = p;
    937     if (now == NULL || zone == NULL) {
    938         now = &nowt;
    939 	zone = &zonet;
    940 	(void)time(&nowt);
    941 
    942 	gmt_ptr = gmtime_r(now, &gmt);
    943 	if ((tm = localtime_r(now, &local)) == NULL)
    944 	    return -1;
    945 
    946 	if (gmt_ptr != NULL)
    947 	    zonet = difftm(&gmt, &local) / 60;
    948 	else
    949 	    /* We are on a system like VMS, where the system clock is
    950 	       in local time and the system has no concept of timezones.
    951 	       Hopefully we can fake this out (for the case in which the
    952 	       user specifies no timezone) by just saying the timezone
    953 	       is zero.  */
    954 	    zonet = 0;
    955 
    956 	if (local.tm_isdst)
    957 	    zonet += 60;
    958     } else {
    959 	if ((tm = localtime_r(now, &local)) == NULL)
    960 	    return -1;
    961     }
    962     yyYear = tm->tm_year + 1900;
    963     yyMonth = tm->tm_mon + 1;
    964     yyDay = tm->tm_mday;
    965     yyTimezone = *zone;
    966     yyDSTmode = DSTmaybe;
    967     yyHour = 0;
    968     yyMinutes = 0;
    969     yySeconds = 0;
    970     yyMeridian = MER24;
    971     yyRelSeconds = 0;
    972     yyRelMonth = 0;
    973     yyHaveDate = 0;
    974     yyHaveDay = 0;
    975     yyHaveRel = 0;
    976     yyHaveTime = 0;
    977     yyHaveZone = 0;
    978 
    979     if (yyparse()
    980      || yyHaveTime > 1 || yyHaveZone > 1 || yyHaveDate > 1 || yyHaveDay > 1)
    981 	return -1;
    982 
    983     if (yyHaveDate || yyHaveTime || yyHaveDay) {
    984 	Start = Convert(yyMonth, yyDay, yyYear, yyHour, yyMinutes, yySeconds,
    985 		    yyMeridian, yyDSTmode);
    986 	if (Start < 0)
    987 	    return -1;
    988     }
    989     else {
    990 	Start = *now;
    991 	if (!yyHaveRel)
    992 	    Start -= ((tm->tm_hour * 60L + tm->tm_min) * 60L) + tm->tm_sec;
    993     }
    994 
    995     Start += yyRelSeconds;
    996     rm = RelativeMonth(Start, yyRelMonth);
    997     if (rm == -1)
    998 	return -1;
    999     Start += rm;
   1000 
   1001     if (yyHaveDay && !yyHaveDate) {
   1002 	tod = RelativeDate(Start, yyDayOrdinal, yyDayNumber);
   1003 	Start += tod;
   1004     }
   1005 
   1006     return Start;
   1007 }
   1008 
   1009 
   1010 #if	defined(TEST)
   1011 
   1012 /* ARGSUSED */
   1013 int
   1014 main(ac, av)
   1015     int		ac;
   1016     char	*av[];
   1017 {
   1018     char	buff[128];
   1019     time_t	d;
   1020 
   1021     (void)printf("Enter date, or blank line to exit.\n\t> ");
   1022     (void)fflush(stdout);
   1023     while (gets(buff) && buff[0]) {
   1024 	d = parsedate(buff, NULL, NULL);
   1025 	if (d == -1)
   1026 	    (void)printf("Bad format - couldn't convert.\n");
   1027 	else
   1028 	    (void)printf("%s", ctime(&d));
   1029 	(void)printf("\t> ");
   1030 	(void)fflush(stdout);
   1031     }
   1032     exit(0);
   1033     /* NOTREACHED */
   1034 }
   1035 #endif	/* defined(TEST) */
   1036