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