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