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