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