Home | History | Annotate | Line # | Download | only in libutil
parsedate.y revision 1.26
      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.26 2015/12/31 10:31:07 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	Year;
    733 
    734     if (RelMonth == 0)
    735 	return 0;
    736     if (localtime_r(&Start, &tm) == NULL)
    737 	return -1;
    738     Month = 12 * (tm.tm_year + 1900) + tm.tm_mon + RelMonth;
    739     Year = Month / 12;
    740     Month = Month % 12 + 1;
    741     return DSTcorrect(Start,
    742 	    Convert(Month, (time_t)tm.tm_mday, Year,
    743 		(time_t)tm.tm_hour, (time_t)tm.tm_min, (time_t)tm.tm_sec,
    744 		Timezone, MER24, DSTmaybe));
    745 }
    746 
    747 
    748 static int
    749 LookupWord(YYSTYPE *yylval, char *buff)
    750 {
    751     register char	*p;
    752     register char	*q;
    753     register const TABLE	*tp;
    754     int			i;
    755     int			abbrev;
    756 
    757     /* Make it lowercase. */
    758     for (p = buff; *p; p++)
    759 	if (isupper((unsigned char)*p))
    760 	    *p = tolower((unsigned char)*p);
    761 
    762     if (strcmp(buff, "am") == 0 || strcmp(buff, "a.m.") == 0) {
    763 	yylval->Meridian = MERam;
    764 	return tMERIDIAN;
    765     }
    766     if (strcmp(buff, "pm") == 0 || strcmp(buff, "p.m.") == 0) {
    767 	yylval->Meridian = MERpm;
    768 	return tMERIDIAN;
    769     }
    770 
    771     /* See if we have an abbreviation for a month. */
    772     if (strlen(buff) == 3)
    773 	abbrev = 1;
    774     else if (strlen(buff) == 4 && buff[3] == '.') {
    775 	abbrev = 1;
    776 	buff[3] = '\0';
    777     }
    778     else
    779 	abbrev = 0;
    780 
    781     for (tp = MonthDayTable; tp->name; tp++) {
    782 	if (abbrev) {
    783 	    if (strncmp(buff, tp->name, 3) == 0) {
    784 		yylval->Number = tp->value;
    785 		return tp->type;
    786 	    }
    787 	}
    788 	else if (strcmp(buff, tp->name) == 0) {
    789 	    yylval->Number = tp->value;
    790 	    return tp->type;
    791 	}
    792     }
    793 
    794     for (tp = TimezoneTable; tp->name; tp++)
    795 	if (strcmp(buff, tp->name) == 0) {
    796 	    yylval->Number = tp->value;
    797 	    return tp->type;
    798 	}
    799 
    800     if (strcmp(buff, "dst") == 0)
    801 	return tDST;
    802 
    803     for (tp = TimeNames; tp->name; tp++)
    804 	if (strcmp(buff, tp->name) == 0) {
    805 	    yylval->Number = tp->value;
    806 	    return tp->type;
    807 	}
    808 
    809     for (tp = UnitsTable; tp->name; tp++)
    810 	if (strcmp(buff, tp->name) == 0) {
    811 	    yylval->Number = tp->value;
    812 	    return tp->type;
    813 	}
    814 
    815     /* Strip off any plural and try the units table again. */
    816     i = strlen(buff) - 1;
    817     if (buff[i] == 's') {
    818 	buff[i] = '\0';
    819 	for (tp = UnitsTable; tp->name; tp++)
    820 	    if (strcmp(buff, tp->name) == 0) {
    821 		yylval->Number = tp->value;
    822 		return tp->type;
    823 	    }
    824 	buff[i] = 's';		/* Put back for "this" in OtherTable. */
    825     }
    826 
    827     for (tp = OtherTable; tp->name; tp++)
    828 	if (strcmp(buff, tp->name) == 0) {
    829 	    yylval->Number = tp->value;
    830 	    return tp->type;
    831 	}
    832 
    833     /* Military timezones. */
    834     if (buff[1] == '\0' && isalpha((unsigned char)*buff)) {
    835 	for (tp = MilitaryTable; tp->name; tp++)
    836 	    if (strcmp(buff, tp->name) == 0) {
    837 		yylval->Number = tp->value;
    838 		return tp->type;
    839 	    }
    840     }
    841 
    842     /* Drop out any periods and try the timezone table again. */
    843     for (i = 0, p = q = buff; *q; q++)
    844 	if (*q != '.')
    845 	    *p++ = *q;
    846 	else
    847 	    i++;
    848     *p = '\0';
    849     if (i)
    850 	for (tp = TimezoneTable; tp->name; tp++)
    851 	    if (strcmp(buff, tp->name) == 0) {
    852 		yylval->Number = tp->value;
    853 		return tp->type;
    854 	    }
    855 
    856     return tID;
    857 }
    858 
    859 
    860 static int
    861 yylex(YYSTYPE *yylval, const char **yyInput)
    862 {
    863     register char	c;
    864     register char	*p;
    865     char		buff[20];
    866     int			Count;
    867     int			sign;
    868     const char		*inp = *yyInput;
    869 
    870     for ( ; ; ) {
    871 	while (isspace((unsigned char)*inp))
    872 	    inp++;
    873 
    874 	if (isdigit((unsigned char)(c = *inp)) || c == '-' || c == '+') {
    875 	    if (c == '-' || c == '+') {
    876 		sign = c == '-' ? -1 : 1;
    877 		if (!isdigit((unsigned char)*++inp))
    878 		    /* skip the '-' sign */
    879 		    continue;
    880 	    }
    881 	    else
    882 		sign = 0;
    883 	    for (yylval->Number = 0; isdigit((unsigned char)(c = *inp++)); )
    884 		yylval->Number = 10 * yylval->Number + c - '0';
    885 	    if (sign < 0)
    886 		yylval->Number = -yylval->Number;
    887 	    *yyInput = --inp;
    888 	    return sign ? tSNUMBER : tUNUMBER;
    889 	}
    890 	if (isalpha((unsigned char)c)) {
    891 	    for (p = buff; isalpha((unsigned char)(c = *inp++)) || c == '.'; )
    892 		if (p < &buff[sizeof buff - 1])
    893 		    *p++ = c;
    894 	    *p = '\0';
    895 	    *yyInput = --inp;
    896 	    return LookupWord(yylval, buff);
    897 	}
    898 	if (c == '@') {
    899 	    *yyInput = ++inp;
    900 	    return AT_SIGN;
    901 	}
    902 	if (c != '(') {
    903 	    *yyInput = ++inp;
    904 	    return c;
    905 	}
    906 	Count = 0;
    907 	do {
    908 	    c = *inp++;
    909 	    if (c == '\0')
    910 		return c;
    911 	    if (c == '(')
    912 		Count++;
    913 	    else if (c == ')')
    914 		Count--;
    915 	} while (Count > 0);
    916     }
    917 }
    918 
    919 #define TM_YEAR_ORIGIN 1900
    920 
    921 time_t
    922 parsedate(const char *p, const time_t *now, const int *zone)
    923 {
    924     struct tm		local, *tm;
    925     time_t		nowt;
    926     int			zonet;
    927     time_t		Start;
    928     time_t		tod, rm;
    929     struct dateinfo	param;
    930     int			saved_errno;
    931 
    932     saved_errno = errno;
    933     errno = 0;
    934 
    935     if (now == NULL) {
    936         now = &nowt;
    937 	(void)time(&nowt);
    938     }
    939     if (zone == NULL) {
    940 	zone = &zonet;
    941 	zonet = USE_LOCAL_TIME;
    942 	if ((tm = localtime_r(now, &local)) == NULL)
    943 	    return -1;
    944     } else {
    945 	/*
    946 	 * Should use the specified zone, not localtime.
    947 	 * Fake it using gmtime and arithmetic.
    948 	 * This is good enough because we use only the year/month/day,
    949 	 * not other fields of struct tm.
    950 	 */
    951 	time_t fake = *now + (*zone * 60);
    952 	if ((tm = gmtime_r(&fake, &local)) == NULL)
    953 	    return -1;
    954     }
    955     param.yyYear = tm->tm_year + 1900;
    956     param.yyMonth = tm->tm_mon + 1;
    957     param.yyDay = tm->tm_mday;
    958     param.yyTimezone = *zone;
    959     param.yyDSTmode = DSTmaybe;
    960     param.yyHour = 0;
    961     param.yyMinutes = 0;
    962     param.yySeconds = 0;
    963     param.yyMeridian = MER24;
    964     param.yyRelSeconds = 0;
    965     param.yyRelMonth = 0;
    966     param.yyHaveDate = 0;
    967     param.yyHaveFullYear = 0;
    968     param.yyHaveDay = 0;
    969     param.yyHaveRel = 0;
    970     param.yyHaveTime = 0;
    971     param.yyHaveZone = 0;
    972 
    973     if (yyparse(&param, &p) || param.yyHaveTime > 1 || param.yyHaveZone > 1 ||
    974 	param.yyHaveDate > 1 || param.yyHaveDay > 1) {
    975 	errno = EINVAL;
    976 	return -1;
    977     }
    978 
    979     if (param.yyHaveDate || param.yyHaveTime || param.yyHaveDay) {
    980 	if (! param.yyHaveFullYear) {
    981 		param.yyYear = AdjustYear(param.yyYear);
    982 		param.yyHaveFullYear = 1;
    983 	}
    984 	errno = 0;
    985 	Start = Convert(param.yyMonth, param.yyDay, param.yyYear, param.yyHour,
    986 	    param.yyMinutes, param.yySeconds, param.yyTimezone,
    987 	    param.yyMeridian, param.yyDSTmode);
    988 	if (Start == -1 && errno != 0)
    989 	    return -1;
    990     }
    991     else {
    992 	Start = *now;
    993 	if (!param.yyHaveRel)
    994 	    Start -= ((tm->tm_hour * 60L + tm->tm_min) * 60L) + tm->tm_sec;
    995     }
    996 
    997     Start += param.yyRelSeconds;
    998     errno = 0;
    999     rm = RelativeMonth(Start, param.yyRelMonth, param.yyTimezone);
   1000     if (rm == -1 && errno != 0)
   1001 	return -1;
   1002     Start += rm;
   1003 
   1004     if (param.yyHaveDay && !param.yyHaveDate) {
   1005 	errno = 0;
   1006 	tod = RelativeDate(Start, param.yyDayOrdinal, param.yyDayNumber);
   1007 	if (tod == -1 && errno != 0)
   1008 	    return -1;
   1009 	Start += tod;
   1010     }
   1011 
   1012     errno = saved_errno;
   1013     return Start;
   1014 }
   1015 
   1016 
   1017 #if	defined(TEST)
   1018 
   1019 /* ARGSUSED */
   1020 int
   1021 main(int ac, char *av[])
   1022 {
   1023     char	buff[128];
   1024     time_t	d;
   1025 
   1026     (void)printf("Enter date, or blank line to exit.\n\t> ");
   1027     (void)fflush(stdout);
   1028     while (fgets(buff, sizeof(buff), stdin) && buff[0] != '\n') {
   1029 	errno = 0;
   1030 	d = parsedate(buff, NULL, NULL);
   1031 	if (d == -1 && errno != 0)
   1032 	    (void)printf("Bad format - couldn't convert: %s\n",
   1033 	        strerror(errno));
   1034 	else
   1035 	    (void)printf("%jd\t%s", (intmax_t)d, ctime(&d));
   1036 	(void)printf("\t> ");
   1037 	(void)fflush(stdout);
   1038     }
   1039     exit(0);
   1040     /* NOTREACHED */
   1041 }
   1042 #endif	/* defined(TEST) */
   1043