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