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