Home | History | Annotate | Line # | Download | only in libutil
parsedate.y revision 1.37
      1   1.1  christos %{
      2   1.1  christos /*
      3   1.1  christos **  Originally written by Steven M. Bellovin <smb (at) research.att.com> while
      4   1.1  christos **  at the University of North Carolina at Chapel Hill.  Later tweaked by
      5   1.1  christos **  a couple of people on Usenet.  Completely overhauled by Rich $alz
      6   1.1  christos **  <rsalz (at) bbn.com> and Jim Berets <jberets (at) bbn.com> in August, 1990;
      7   1.1  christos **
      8   1.1  christos **  This grammar has 10 shift/reduce conflicts.
      9   1.1  christos **
     10   1.1  christos **  This code is in the public domain and has no copyright.
     11   1.1  christos */
     12   1.1  christos /* SUPPRESS 287 on yaccpar_sccsid *//* Unused static variable */
     13   1.1  christos /* SUPPRESS 288 on yyerrlab *//* Label unused */
     14   1.1  christos 
     15  1.15      yamt #include <sys/cdefs.h>
     16  1.15      yamt #ifdef __RCSID
     17  1.37  christos __RCSID("$NetBSD: parsedate.y,v 1.37 2022/04/23 13:02:04 christos Exp $");
     18  1.15      yamt #endif
     19  1.15      yamt 
     20   1.1  christos #include <stdio.h>
     21   1.1  christos #include <ctype.h>
     22  1.14       apb #include <errno.h>
     23  1.36       kre #include <limits.h>
     24   1.1  christos #include <string.h>
     25   1.1  christos #include <time.h>
     26   1.1  christos #include <util.h>
     27   1.3  drochner #include <stdlib.h>
     28   1.1  christos 
     29   1.1  christos /* NOTES on rebuilding parsedate.c (particularly for inclusion in CVS
     30   1.1  christos    releases):
     31   1.1  christos 
     32   1.1  christos    We don't want to mess with all the portability hassles of alloca.
     33   1.1  christos    In particular, most (all?) versions of bison will use alloca in
     34   1.1  christos    their parser.  If bison works on your system (e.g. it should work
     35   1.1  christos    with gcc), then go ahead and use it, but the more general solution
     36   1.1  christos    is to use byacc instead of bison, which should generate a portable
     37   1.1  christos    parser.  I played with adding "#define alloca dont_use_alloca", to
     38   1.1  christos    give an error if the parser generator uses alloca (and thus detect
     39   1.1  christos    unportable parsedate.c's), but that seems to cause as many problems
     40   1.1  christos    as it solves.  */
     41   1.1  christos 
     42   1.1  christos #define EPOCH		1970
     43  1.21  christos #define HOUR(x)		((time_t)((x) * 60))
     44   1.1  christos #define SECSPERDAY	(24L * 60L * 60L)
     45   1.1  christos 
     46  1.28       kre #define	MAXREL	16	/* hours mins secs days weeks months years - maybe twice each ...*/
     47  1.28       kre 
     48  1.20       apb #define USE_LOCAL_TIME	99999 /* special case for Convert() and yyTimezone */
     49   1.1  christos 
     50   1.1  christos /*
     51   1.1  christos **  An entry in the lexical lookup table.
     52   1.1  christos */
     53   1.1  christos typedef struct _TABLE {
     54   1.1  christos     const char	*name;
     55   1.1  christos     int		type;
     56   1.1  christos     time_t	value;
     57   1.1  christos } TABLE;
     58   1.1  christos 
     59   1.1  christos 
     60   1.1  christos /*
     61   1.1  christos **  Daylight-savings mode:  on, off, or not yet known.
     62   1.1  christos */
     63   1.1  christos typedef enum _DSTMODE {
     64   1.1  christos     DSTon, DSToff, DSTmaybe
     65   1.1  christos } DSTMODE;
     66   1.1  christos 
     67   1.1  christos /*
     68  1.31       kre **  Meridian:  am, pm, or 24-hour style (plus "noon" and "midnight").
     69   1.1  christos */
     70   1.1  christos typedef enum _MERIDIAN {
     71  1.31       kre     MERam, MERpm, MER24, MER_NOON, MER_MN
     72   1.1  christos } MERIDIAN;
     73   1.1  christos 
     74   1.1  christos 
     75   1.9  christos struct dateinfo {
     76  1.17       apb 	DSTMODE	yyDSTmode;	/* DST on/off/maybe */
     77   1.9  christos 	time_t	yyDayOrdinal;
     78   1.9  christos 	time_t	yyDayNumber;
     79   1.9  christos 	int	yyHaveDate;
     80  1.17       apb 	int	yyHaveFullYear;	/* if true, year is not abbreviated. */
     81  1.17       apb 				/* if false, need to call AdjustYear(). */
     82   1.9  christos 	int	yyHaveDay;
     83   1.9  christos 	int	yyHaveRel;
     84   1.9  christos 	int	yyHaveTime;
     85   1.9  christos 	int	yyHaveZone;
     86  1.17       apb 	time_t	yyTimezone;	/* Timezone as minutes ahead/east of UTC */
     87  1.17       apb 	time_t	yyDay;		/* Day of month [1-31] */
     88  1.17       apb 	time_t	yyHour;		/* Hour of day [0-24] or [1-12] */
     89  1.17       apb 	time_t	yyMinutes;	/* Minute of hour [0-59] */
     90  1.17       apb 	time_t	yyMonth;	/* Month of year [1-12] */
     91  1.17       apb 	time_t	yySeconds;	/* Second of minute [0-60] */
     92  1.17       apb 	time_t	yyYear;		/* Year, see also yyHaveFullYear */
     93  1.17       apb 	MERIDIAN yyMeridian;	/* Interpret yyHour as AM/PM/24 hour clock */
     94  1.28       kre 	struct {
     95  1.28       kre 		time_t	yyRelVal;
     96  1.28       kre 		int	yyRelMonth;
     97  1.28       kre 	} yyRel[MAXREL];
     98   1.9  christos };
     99  1.36       kre 
    100  1.36       kre static int RelVal(struct dateinfo *, time_t, time_t, int, int);
    101  1.36       kre 
    102  1.36       kre #define CheckRelVal(a, b, c, d, e) do {				\
    103  1.36       kre 		if (!RelVal((a), (b), (c), (d), (e))) {		\
    104  1.36       kre 			YYREJECT;				\
    105  1.36       kre 		}						\
    106  1.36       kre 	} while (0)
    107  1.36       kre 
    108   1.1  christos %}
    109   1.1  christos 
    110   1.1  christos %union {
    111   1.1  christos     time_t		Number;
    112   1.1  christos     enum _MERIDIAN	Meridian;
    113   1.1  christos }
    114   1.1  christos 
    115   1.1  christos %token	tAGO tDAY tDAYZONE tID tMERIDIAN tMINUTE_UNIT tMONTH tMONTH_UNIT
    116  1.23  christos %token	tSEC_UNIT tSNUMBER tUNUMBER tZONE tDST AT_SIGN tTIME
    117   1.1  christos 
    118   1.1  christos %type	<Number>	tDAY tDAYZONE tMINUTE_UNIT tMONTH tMONTH_UNIT
    119  1.23  christos %type	<Number>	tSEC_UNIT tSNUMBER tUNUMBER tZONE tTIME
    120  1.25  dholland %type	<Meridian>	tMERIDIAN
    121  1.25  dholland 
    122  1.25  dholland %type	<Number>	at_number
    123  1.25  dholland %type	<Meridian>	o_merid
    124   1.1  christos 
    125   1.9  christos %parse-param	{ struct dateinfo *param }
    126   1.9  christos %parse-param 	{ const char **yyInput }
    127   1.9  christos %lex-param	{ const char **yyInput }
    128   1.9  christos %pure-parser
    129   1.9  christos 
    130   1.1  christos %%
    131   1.1  christos 
    132  1.25  dholland spec:
    133  1.25  dholland 	  /* empty */
    134   1.1  christos 	| spec item
    135  1.25  dholland ;
    136   1.1  christos 
    137  1.25  dholland item:
    138  1.25  dholland 	  time			{ param->yyHaveTime++; }
    139  1.25  dholland 	| time_numericzone	{ param->yyHaveTime++; param->yyHaveZone++; }
    140  1.25  dholland 	| zone			{ param->yyHaveZone++; }
    141  1.25  dholland 	| date			{ param->yyHaveDate++; }
    142  1.25  dholland 	| day			{ param->yyHaveDay++; }
    143  1.25  dholland 	| rel			{ param->yyHaveRel++; }
    144  1.25  dholland 	| cvsstamp		{ param->yyHaveTime++; param->yyHaveDate++;
    145  1.25  dholland 				  param->yyHaveZone++; }
    146  1.25  dholland 	| epochdate		{ param->yyHaveTime++; param->yyHaveDate++;
    147  1.25  dholland 				  param->yyHaveZone++; }
    148   1.1  christos 	| number
    149  1.25  dholland ;
    150   1.1  christos 
    151  1.25  dholland cvsstamp:
    152  1.25  dholland 	tUNUMBER '.' tUNUMBER '.' tUNUMBER '.'
    153  1.25  dholland 				tUNUMBER '.' tUNUMBER '.' tUNUMBER {
    154  1.25  dholland 		param->yyYear = $1;
    155  1.25  dholland 		if (param->yyYear < 100) {
    156  1.25  dholland 			param->yyYear += 1900;
    157  1.25  dholland 		}
    158  1.25  dholland 		param->yyHaveFullYear = 1;
    159  1.25  dholland 		param->yyMonth = $3;
    160  1.25  dholland 		param->yyDay = $5;
    161  1.25  dholland 		param->yyHour = $7;
    162  1.25  dholland 		param->yyMinutes = $9;
    163  1.25  dholland 		param->yySeconds = $11;
    164  1.25  dholland 		param->yyDSTmode = DSToff;
    165  1.25  dholland 		param->yyTimezone = 0;
    166  1.25  dholland 	}
    167  1.25  dholland ;
    168  1.25  dholland 
    169  1.25  dholland epochdate:
    170  1.25  dholland 	AT_SIGN at_number {
    171  1.25  dholland 		time_t	when = $2;
    172  1.25  dholland 		struct tm tmbuf;
    173  1.25  dholland 
    174  1.25  dholland 		if (gmtime_r(&when, &tmbuf) != NULL) {
    175  1.25  dholland 			param->yyYear = tmbuf.tm_year + 1900;
    176  1.25  dholland 			param->yyMonth = tmbuf.tm_mon + 1;
    177  1.25  dholland 			param->yyDay = tmbuf.tm_mday;
    178  1.25  dholland 
    179  1.25  dholland 			param->yyHour = tmbuf.tm_hour;
    180  1.25  dholland 			param->yyMinutes = tmbuf.tm_min;
    181  1.25  dholland 			param->yySeconds = tmbuf.tm_sec;
    182  1.25  dholland 		} else {
    183  1.25  dholland 			param->yyYear = EPOCH;
    184  1.25  dholland 			param->yyMonth = 1;
    185  1.25  dholland 			param->yyDay = 1;
    186   1.1  christos 
    187  1.25  dholland 			param->yyHour = 0;
    188  1.25  dholland 			param->yyMinutes = 0;
    189  1.25  dholland 			param->yySeconds = 0;
    190  1.25  dholland 		}
    191  1.25  dholland 		param->yyHaveFullYear = 1;
    192  1.25  dholland 		param->yyDSTmode = DSToff;
    193  1.25  dholland 		param->yyTimezone = 0;
    194  1.25  dholland 	}
    195  1.25  dholland ;
    196  1.25  dholland 
    197  1.25  dholland at_number:
    198  1.25  dholland 	  tUNUMBER
    199  1.25  dholland 	| tSNUMBER
    200  1.25  dholland ;
    201  1.25  dholland 
    202  1.25  dholland time:
    203  1.25  dholland 	  tUNUMBER tMERIDIAN {
    204  1.36       kre 		if ($1 > 24)
    205  1.36       kre 			YYREJECT;
    206   1.9  christos 		param->yyMinutes = 0;
    207   1.9  christos 		param->yySeconds = 0;
    208  1.31       kre 		if ($2 == MER_NOON || $2 == MER_MN) {
    209  1.31       kre 			if ($1 == 12) {
    210  1.31       kre 				switch ($2) {
    211  1.31       kre 				case MER_NOON: param->yyHour = 12; break;
    212  1.31       kre 				case MER_MN  : param->yyHour = 0;  break;
    213  1.31       kre 				default:	/* impossible */;  break;
    214  1.31       kre 				}
    215  1.31       kre 				param->yyMeridian = MER24;
    216  1.31       kre 			} else
    217  1.31       kre 				YYREJECT;
    218  1.31       kre 		} else {
    219  1.31       kre 			param->yyHour = $1;
    220  1.31       kre 			param->yyMeridian = $2;
    221  1.31       kre 		}
    222  1.25  dholland 	  }
    223   1.1  christos 	| tUNUMBER ':' tUNUMBER o_merid {
    224  1.36       kre 		if ($1 > 24 || $3 >= 60)
    225  1.36       kre 			YYREJECT;
    226  1.25  dholland 		param->yyMinutes = $3;
    227  1.25  dholland 		param->yySeconds = 0;
    228  1.31       kre 		if ($4 == MER_NOON || $4 == MER_MN) {
    229  1.31       kre 			if ($1 == 12 && $3 == 0) {
    230  1.31       kre 				switch ($4) {
    231  1.31       kre 				case MER_NOON: param->yyHour = 12; break;
    232  1.31       kre 				case MER_MN  : param->yyHour = 0;  break;
    233  1.31       kre 				default:	/* impossible */;  break;
    234  1.31       kre 				}
    235  1.31       kre 				param->yyMeridian = MER24;
    236  1.31       kre 			} else
    237  1.31       kre 				YYREJECT;
    238  1.31       kre 		} else {
    239  1.31       kre 			param->yyHour = $1;
    240  1.31       kre 			param->yyMeridian = $4;
    241  1.31       kre 		}
    242  1.25  dholland 	  }
    243  1.19       apb 	| tUNUMBER ':' tUNUMBER ':' tUNUMBER o_merid {
    244  1.36       kre 		if ($1 > 24 || $3 >= 60 || $5 > 60)
    245  1.36       kre 			YYREJECT;
    246  1.25  dholland 		param->yyMinutes = $3;
    247  1.25  dholland 		param->yySeconds = $5;
    248  1.31       kre 		if ($6 == MER_NOON || $6 == MER_MN) {
    249  1.31       kre 			if ($1 == 12 && $3 == 0 && $5 == 0) {
    250  1.31       kre 				switch ($6) {
    251  1.31       kre 				case MER_NOON: param->yyHour = 12; break;
    252  1.31       kre 				case MER_MN  : param->yyHour = 0;  break;
    253  1.31       kre 				default:	/* impossible */;  break;
    254  1.31       kre 				}
    255  1.31       kre 				param->yyMeridian = MER24;
    256  1.31       kre 			} else
    257  1.31       kre 				YYREJECT;
    258  1.31       kre 		} else {
    259  1.31       kre 			param->yyHour = $1;
    260  1.31       kre 			param->yyMeridian = $6;
    261  1.31       kre 		}
    262  1.25  dholland 	  }
    263  1.19       apb 	| tUNUMBER ':' tUNUMBER ':' tUNUMBER '.' tUNUMBER {
    264  1.36       kre 		if ($1 > 24 || $3 >= 60 || $5 > 60)
    265  1.36       kre 			YYREJECT;
    266  1.25  dholland 		param->yyHour = $1;
    267  1.25  dholland 		param->yyMinutes = $3;
    268  1.25  dholland 		param->yySeconds = $5;
    269  1.25  dholland 		param->yyMeridian = MER24;
    270  1.33       kre 		/* XXX: Do nothing with fractional secs ($7) */
    271  1.33       kre 	  }
    272  1.33       kre 	| tUNUMBER ':' tUNUMBER ':' tUNUMBER ',' tUNUMBER {
    273  1.36       kre 		if ($1 > 24 || $3 >= 60 || $5 > 60)
    274  1.36       kre 			YYREJECT;
    275  1.33       kre 		param->yyHour = $1;
    276  1.33       kre 		param->yyMinutes = $3;
    277  1.33       kre 		param->yySeconds = $5;
    278  1.33       kre 		param->yyMeridian = MER24;
    279  1.33       kre 		/* XXX: Do nothing with fractional seconds ($7) */
    280  1.25  dholland 	  }
    281  1.23  christos 	| tTIME {
    282  1.25  dholland 		param->yyHour = $1;
    283  1.25  dholland 		param->yyMinutes = 0;
    284  1.25  dholland 		param->yySeconds = 0;
    285  1.25  dholland 		param->yyMeridian = MER24;
    286  1.25  dholland 		/* Tues midnight --> Weds 00:00, midnight Tues -> Tues 00:00 */
    287  1.25  dholland 		if ($1 == 0 && param->yyHaveDay)
    288  1.25  dholland 			param->yyDayNumber++;
    289  1.31       kre 	  }
    290  1.31       kre 	| tUNUMBER tTIME {
    291  1.31       kre 		if ($1 == 12 && ($2 == 0 || $2 == 12)) {
    292  1.31       kre 			param->yyHour = $2;
    293  1.31       kre 			param->yyMinutes = 0;
    294  1.31       kre 			param->yySeconds = 0;
    295  1.31       kre 			param->yyMeridian = MER24;
    296  1.31       kre 		} else
    297  1.31       kre 			YYREJECT;
    298  1.31       kre 	  }
    299  1.25  dholland ;
    300  1.25  dholland 
    301  1.25  dholland time_numericzone:
    302  1.25  dholland 	  tUNUMBER ':' tUNUMBER tSNUMBER {
    303  1.36       kre 		if ($4 < -(47 * 100 + 59) || $4 > (47 * 100 + 59))
    304  1.36       kre 			YYREJECT;
    305  1.36       kre 		if ($1 > 24 || $3 > 59)
    306  1.36       kre 			YYREJECT;
    307  1.25  dholland 		param->yyHour = $1;
    308  1.25  dholland 		param->yyMinutes = $3;
    309  1.25  dholland 		param->yyMeridian = MER24;
    310  1.25  dholland 		param->yyDSTmode = DSToff;
    311  1.25  dholland 		param->yyTimezone = - ($4 % 100 + ($4 / 100) * 60);
    312  1.25  dholland 	  }
    313  1.19       apb 	| tUNUMBER ':' tUNUMBER ':' tUNUMBER tSNUMBER {
    314  1.36       kre 		if ($6 < -(47 * 100 + 59) || $6 > (47 * 100 + 59))
    315  1.36       kre 			YYREJECT;
    316  1.36       kre 		if ($1 > 24 || $3 > 59 || $5 > 60)
    317  1.36       kre 			YYREJECT;
    318  1.25  dholland 		param->yyHour = $1;
    319  1.25  dholland 		param->yyMinutes = $3;
    320  1.25  dholland 		param->yySeconds = $5;
    321  1.25  dholland 		param->yyMeridian = MER24;
    322  1.25  dholland 		param->yyDSTmode = DSToff;
    323  1.25  dholland 		param->yyTimezone = - ($6 % 100 + ($6 / 100) * 60);
    324  1.25  dholland 	}
    325  1.25  dholland ;
    326  1.25  dholland 
    327  1.25  dholland zone:
    328  1.25  dholland 	  tZONE		{ param->yyTimezone = $1; param->yyDSTmode = DSToff; }
    329  1.25  dholland 	| tDAYZONE	{ param->yyTimezone = $1; param->yyDSTmode = DSTon; }
    330  1.25  dholland 	| tZONE tDST	{ param->yyTimezone = $1; param->yyDSTmode = DSTon; }
    331  1.34       kre 	| tSNUMBER	{
    332  1.34       kre 			  if (param->yyHaveDate == 0 && param->yyHaveTime == 0)
    333  1.34       kre 				YYREJECT;
    334  1.36       kre 			  if ($1 < -(47 * 100 + 59) || $1 > (47 * 100 + 59))
    335  1.36       kre 				YYREJECT;
    336  1.34       kre 			  param->yyTimezone = - ($1 % 100 + ($1 / 100) * 60);
    337  1.34       kre 			  param->yyDSTmode = DSTmaybe;
    338  1.34       kre 			}
    339  1.25  dholland ;
    340  1.25  dholland 
    341  1.25  dholland day:
    342  1.25  dholland 	  tDAY		{ param->yyDayOrdinal = 1; param->yyDayNumber = $1; }
    343  1.25  dholland 	| tDAY ','	{ param->yyDayOrdinal = 1; param->yyDayNumber = $1; }
    344  1.25  dholland 	| tUNUMBER tDAY	{ param->yyDayOrdinal = $1; param->yyDayNumber = $2; }
    345  1.25  dholland ;
    346   1.1  christos 
    347  1.25  dholland date:
    348  1.25  dholland 	  tUNUMBER '/' tUNUMBER {
    349  1.36       kre 		if ($1 > 12 || $3 > 31 || $1 == 0 || $3 == 0)
    350  1.36       kre 			YYREJECT;
    351   1.9  christos 		param->yyMonth = $1;
    352   1.9  christos 		param->yyDay = $3;
    353  1.25  dholland 	  }
    354  1.25  dholland 	| tUNUMBER '/' tUNUMBER '/' tUNUMBER {
    355  1.25  dholland 		if ($1 >= 100) {
    356  1.36       kre 			if ($3 > 12 || $5 > 31 || $3 == 0 || $5 == 0)
    357  1.36       kre 				YYREJECT;
    358  1.25  dholland 			param->yyYear = $1;
    359  1.25  dholland 			param->yyMonth = $3;
    360  1.25  dholland 			param->yyDay = $5;
    361  1.25  dholland 		} else {
    362  1.37  christos 			if ($1 > 12 || $3 > 31 || $1 == 0 || $3 == 0)
    363  1.36       kre 				YYREJECT;
    364  1.25  dholland 			param->yyMonth = $1;
    365  1.25  dholland 			param->yyDay = $3;
    366  1.25  dholland 			param->yyYear = $5;
    367  1.25  dholland 		}
    368  1.25  dholland 	  }
    369   1.1  christos 	| tUNUMBER tSNUMBER tSNUMBER {
    370  1.25  dholland 		/* ISO 8601 format.  yyyy-mm-dd.  */
    371  1.36       kre 		if ($2 >= 0 || $2 < -12 || $3 >= 0 || $3 < -31)
    372  1.36       kre 			YYREJECT;
    373  1.25  dholland 		param->yyYear = $1;
    374  1.25  dholland 		param->yyHaveFullYear = 1;
    375  1.25  dholland 		param->yyMonth = -$2;
    376  1.25  dholland 		param->yyDay = -$3;
    377  1.25  dholland 	  }
    378   1.1  christos 	| tUNUMBER tMONTH tSNUMBER {
    379  1.36       kre 		if ($3 > 0 || $1 == 0 || $1 > 31)
    380  1.36       kre 			YYREJECT;
    381  1.25  dholland 		/* e.g. 17-JUN-1992.  */
    382  1.25  dholland 		param->yyDay = $1;
    383  1.25  dholland 		param->yyMonth = $2;
    384  1.25  dholland 		param->yyYear = -$3;
    385  1.25  dholland 	  }
    386   1.1  christos 	| tMONTH tUNUMBER {
    387  1.36       kre 		if ($2 == 0 || $2 > 31)
    388  1.36       kre 			YYREJECT;
    389  1.25  dholland 		param->yyMonth = $1;
    390  1.25  dholland 		param->yyDay = $2;
    391  1.25  dholland 	  }
    392   1.1  christos 	| tMONTH tUNUMBER ',' tUNUMBER {
    393  1.36       kre 		if ($2 == 0 || $2 > 31)
    394  1.36       kre 			YYREJECT;
    395  1.25  dholland 		param->yyMonth = $1;
    396  1.25  dholland 		param->yyDay = $2;
    397  1.25  dholland 		param->yyYear = $4;
    398  1.25  dholland 	  }
    399   1.1  christos 	| tUNUMBER tMONTH {
    400  1.36       kre 		if ($1 == 0 || $1 > 31)
    401  1.36       kre 			YYREJECT;
    402  1.25  dholland 		param->yyMonth = $2;
    403  1.25  dholland 		param->yyDay = $1;
    404  1.25  dholland 	  }
    405   1.1  christos 	| tUNUMBER tMONTH tUNUMBER {
    406  1.36       kre 		if ($1 > 31 && $3 > 31)
    407  1.36       kre 			YYREJECT;
    408  1.25  dholland 		if ($1 < 35) {
    409  1.36       kre 			if ($1 == 0)
    410  1.36       kre 				YYREJECT;
    411  1.25  dholland 			param->yyDay = $1;
    412  1.25  dholland 			param->yyYear = $3;
    413  1.25  dholland 		} else {
    414  1.36       kre 			if ($3 == 0)
    415  1.36       kre 				YYREJECT;
    416  1.25  dholland 			param->yyDay = $3;
    417  1.25  dholland 			param->yyYear = $1;
    418  1.25  dholland 		}
    419  1.36       kre 		param->yyMonth = $2;
    420  1.25  dholland 	  }
    421  1.25  dholland ;
    422   1.1  christos 
    423  1.25  dholland rel:
    424  1.25  dholland 	  relunit
    425  1.25  dholland 	| relunit tAGO {
    426  1.28       kre 		param->yyRel[param->yyHaveRel].yyRelVal =
    427  1.28       kre 		    -param->yyRel[param->yyHaveRel].yyRelVal;
    428  1.25  dholland 	  }
    429  1.25  dholland ;
    430  1.25  dholland 
    431  1.25  dholland relunit:
    432  1.36       kre 	  tUNUMBER tMINUTE_UNIT	{ CheckRelVal(param, $1, $2, 60, 0); }
    433  1.36       kre 	| tSNUMBER tMINUTE_UNIT	{ CheckRelVal(param, $1, $2, 60, 0); }
    434  1.36       kre 	| tMINUTE_UNIT		{ CheckRelVal(param,  1, $1, 60, 0); }
    435  1.36       kre 	| tSNUMBER tSEC_UNIT	{ CheckRelVal(param, $1, 1,  1,  0); }
    436  1.36       kre 	| tUNUMBER tSEC_UNIT	{ CheckRelVal(param, $1, 1,  1,  0); }
    437  1.36       kre 	| tSEC_UNIT		{ CheckRelVal(param,  1, 1,  1,  0); }
    438  1.36       kre 	| tSNUMBER tMONTH_UNIT	{ CheckRelVal(param, $1, $2, 1,  1); }
    439  1.36       kre 	| tUNUMBER tMONTH_UNIT	{ CheckRelVal(param, $1, $2, 1,  1); }
    440  1.36       kre 	| tMONTH_UNIT		{ CheckRelVal(param,  1, $1, 1,  1); }
    441  1.25  dholland ;
    442  1.25  dholland 
    443  1.25  dholland number:
    444  1.25  dholland 	tUNUMBER {
    445  1.25  dholland 		if (param->yyHaveTime && param->yyHaveDate &&
    446  1.25  dholland 		    !param->yyHaveRel) {
    447  1.25  dholland 			param->yyYear = $1;
    448  1.25  dholland 		} else {
    449  1.25  dholland 			if ($1 > 10000) {
    450  1.25  dholland 				param->yyHaveDate++;
    451  1.25  dholland 				param->yyDay = ($1)%100;
    452  1.25  dholland 				param->yyMonth = ($1/100)%100;
    453  1.25  dholland 				param->yyYear = $1/10000;
    454  1.25  dholland 			}
    455  1.25  dholland 			else {
    456  1.25  dholland 				param->yyHaveTime++;
    457  1.25  dholland 				if ($1 < 100) {
    458  1.25  dholland 					param->yyHour = $1;
    459  1.25  dholland 					param->yyMinutes = 0;
    460  1.25  dholland 				}
    461  1.25  dholland 				else {
    462  1.25  dholland 					param->yyHour = $1 / 100;
    463  1.25  dholland 					param->yyMinutes = $1 % 100;
    464  1.25  dholland 				}
    465  1.25  dholland 				param->yySeconds = 0;
    466  1.25  dholland 				param->yyMeridian = MER24;
    467  1.25  dholland 			}
    468   1.1  christos 		}
    469   1.1  christos 	}
    470  1.25  dholland ;
    471   1.1  christos 
    472  1.25  dholland o_merid:
    473  1.25  dholland 	  /* empty */		{ $$ = MER24; }
    474  1.25  dholland 	| tMERIDIAN		{ $$ = $1; }
    475  1.31       kre 	| tTIME			{ $$ = $1 == 0 ? MER_MN : MER_NOON; }
    476  1.25  dholland ;
    477   1.1  christos 
    478   1.1  christos %%
    479   1.1  christos 
    480  1.28       kre static short DaysInMonth[12] = {
    481  1.28       kre     31, 28, 31, 30, 31, 30,
    482  1.28       kre     31, 31, 30, 31, 30, 31
    483  1.28       kre };
    484  1.28       kre 
    485  1.28       kre /*
    486  1.28       kre  * works with tm.tm_year (ie: rel to 1900)
    487  1.28       kre  */
    488  1.28       kre #define	isleap(yr)  (((yr) & 3) == 0 && (((yr) % 100) != 0 || \
    489  1.28       kre 			((1900+(yr)) % 400) == 0))
    490  1.28       kre 
    491   1.1  christos /* Month and day table. */
    492  1.12     joerg static const TABLE MonthDayTable[] = {
    493   1.1  christos     { "january",	tMONTH,  1 },
    494   1.1  christos     { "february",	tMONTH,  2 },
    495   1.1  christos     { "march",		tMONTH,  3 },
    496   1.1  christos     { "april",		tMONTH,  4 },
    497   1.1  christos     { "may",		tMONTH,  5 },
    498   1.1  christos     { "june",		tMONTH,  6 },
    499   1.1  christos     { "july",		tMONTH,  7 },
    500   1.1  christos     { "august",		tMONTH,  8 },
    501   1.1  christos     { "september",	tMONTH,  9 },
    502   1.1  christos     { "sept",		tMONTH,  9 },
    503   1.1  christos     { "october",	tMONTH, 10 },
    504   1.1  christos     { "november",	tMONTH, 11 },
    505   1.1  christos     { "december",	tMONTH, 12 },
    506   1.1  christos     { "sunday",		tDAY, 0 },
    507  1.21  christos     { "su",		tDAY, 0 },
    508   1.1  christos     { "monday",		tDAY, 1 },
    509  1.21  christos     { "mo",		tDAY, 1 },
    510   1.1  christos     { "tuesday",	tDAY, 2 },
    511   1.1  christos     { "tues",		tDAY, 2 },
    512  1.21  christos     { "tu",		tDAY, 2 },
    513   1.1  christos     { "wednesday",	tDAY, 3 },
    514   1.1  christos     { "wednes",		tDAY, 3 },
    515  1.21  christos     { "weds",		tDAY, 3 },
    516  1.21  christos     { "we",		tDAY, 3 },
    517   1.1  christos     { "thursday",	tDAY, 4 },
    518  1.21  christos     { "thurs",		tDAY, 4 },
    519   1.1  christos     { "thur",		tDAY, 4 },
    520  1.21  christos     { "th",		tDAY, 4 },
    521   1.1  christos     { "friday",		tDAY, 5 },
    522  1.21  christos     { "fr",		tDAY, 5 },
    523   1.1  christos     { "saturday",	tDAY, 6 },
    524  1.21  christos     { "sa",		tDAY, 6 },
    525   1.1  christos     { NULL,		0,    0 }
    526   1.1  christos };
    527   1.1  christos 
    528   1.1  christos /* Time units table. */
    529  1.12     joerg static const TABLE UnitsTable[] = {
    530   1.1  christos     { "year",		tMONTH_UNIT,	12 },
    531   1.1  christos     { "month",		tMONTH_UNIT,	1 },
    532   1.1  christos     { "fortnight",	tMINUTE_UNIT,	14 * 24 * 60 },
    533   1.1  christos     { "week",		tMINUTE_UNIT,	7 * 24 * 60 },
    534   1.1  christos     { "day",		tMINUTE_UNIT,	1 * 24 * 60 },
    535   1.1  christos     { "hour",		tMINUTE_UNIT,	60 },
    536   1.1  christos     { "minute",		tMINUTE_UNIT,	1 },
    537   1.1  christos     { "min",		tMINUTE_UNIT,	1 },
    538   1.1  christos     { "second",		tSEC_UNIT,	1 },
    539   1.1  christos     { "sec",		tSEC_UNIT,	1 },
    540   1.1  christos     { NULL,		0,		0 }
    541   1.1  christos };
    542   1.1  christos 
    543   1.1  christos /* Assorted relative-time words. */
    544  1.12     joerg static const TABLE OtherTable[] = {
    545   1.1  christos     { "tomorrow",	tMINUTE_UNIT,	1 * 24 * 60 },
    546   1.1  christos     { "yesterday",	tMINUTE_UNIT,	-1 * 24 * 60 },
    547   1.1  christos     { "today",		tMINUTE_UNIT,	0 },
    548   1.1  christos     { "now",		tMINUTE_UNIT,	0 },
    549   1.1  christos     { "last",		tUNUMBER,	-1 },
    550   1.1  christos     { "this",		tMINUTE_UNIT,	0 },
    551   1.1  christos     { "next",		tUNUMBER,	2 },
    552   1.1  christos     { "first",		tUNUMBER,	1 },
    553   1.7  christos     { "one",		tUNUMBER,	1 },
    554   1.1  christos /*  { "second",		tUNUMBER,	2 }, */
    555   1.7  christos     { "two",		tUNUMBER,	2 },
    556   1.1  christos     { "third",		tUNUMBER,	3 },
    557   1.7  christos     { "three",		tUNUMBER,	3 },
    558   1.1  christos     { "fourth",		tUNUMBER,	4 },
    559   1.7  christos     { "four",		tUNUMBER,	4 },
    560   1.1  christos     { "fifth",		tUNUMBER,	5 },
    561   1.7  christos     { "five",		tUNUMBER,	5 },
    562   1.1  christos     { "sixth",		tUNUMBER,	6 },
    563   1.7  christos     { "six",		tUNUMBER,	6 },
    564   1.1  christos     { "seventh",	tUNUMBER,	7 },
    565   1.7  christos     { "seven",		tUNUMBER,	7 },
    566   1.1  christos     { "eighth",		tUNUMBER,	8 },
    567   1.7  christos     { "eight",		tUNUMBER,	8 },
    568   1.1  christos     { "ninth",		tUNUMBER,	9 },
    569   1.7  christos     { "nine",		tUNUMBER,	9 },
    570   1.1  christos     { "tenth",		tUNUMBER,	10 },
    571   1.7  christos     { "ten",		tUNUMBER,	10 },
    572   1.1  christos     { "eleventh",	tUNUMBER,	11 },
    573   1.7  christos     { "eleven",		tUNUMBER,	11 },
    574   1.1  christos     { "twelfth",	tUNUMBER,	12 },
    575   1.7  christos     { "twelve",		tUNUMBER,	12 },
    576   1.1  christos     { "ago",		tAGO,	1 },
    577   1.1  christos     { NULL,		0,	0 }
    578   1.1  christos };
    579   1.1  christos 
    580   1.1  christos /* The timezone table. */
    581   1.1  christos /* Some of these are commented out because a time_t can't store a float. */
    582  1.12     joerg static const TABLE TimezoneTable[] = {
    583   1.1  christos     { "gmt",	tZONE,     HOUR( 0) },	/* Greenwich Mean */
    584   1.1  christos     { "ut",	tZONE,     HOUR( 0) },	/* Universal (Coordinated) */
    585   1.1  christos     { "utc",	tZONE,     HOUR( 0) },
    586   1.1  christos     { "wet",	tZONE,     HOUR( 0) },	/* Western European */
    587   1.1  christos     { "bst",	tDAYZONE,  HOUR( 0) },	/* British Summer */
    588   1.1  christos     { "wat",	tZONE,     HOUR( 1) },	/* West Africa */
    589   1.1  christos     { "at",	tZONE,     HOUR( 2) },	/* Azores */
    590   1.1  christos #if	0
    591   1.1  christos     /* For completeness.  BST is also British Summer, and GST is
    592   1.1  christos      * also Guam Standard. */
    593   1.1  christos     { "bst",	tZONE,     HOUR( 3) },	/* Brazil Standard */
    594   1.1  christos     { "gst",	tZONE,     HOUR( 3) },	/* Greenland Standard */
    595   1.1  christos #endif
    596   1.1  christos     { "nft",	tZONE,     HOUR(3.5) },	/* Newfoundland */
    597   1.1  christos     { "nst",	tZONE,     HOUR(3.5) },	/* Newfoundland Standard */
    598   1.1  christos     { "ndt",	tDAYZONE,  HOUR(3.5) },	/* Newfoundland Daylight */
    599   1.1  christos     { "ast",	tZONE,     HOUR( 4) },	/* Atlantic Standard */
    600   1.1  christos     { "adt",	tDAYZONE,  HOUR( 4) },	/* Atlantic Daylight */
    601   1.1  christos     { "est",	tZONE,     HOUR( 5) },	/* Eastern Standard */
    602   1.1  christos     { "edt",	tDAYZONE,  HOUR( 5) },	/* Eastern Daylight */
    603   1.1  christos     { "cst",	tZONE,     HOUR( 6) },	/* Central Standard */
    604   1.1  christos     { "cdt",	tDAYZONE,  HOUR( 6) },	/* Central Daylight */
    605   1.1  christos     { "mst",	tZONE,     HOUR( 7) },	/* Mountain Standard */
    606   1.1  christos     { "mdt",	tDAYZONE,  HOUR( 7) },	/* Mountain Daylight */
    607   1.1  christos     { "pst",	tZONE,     HOUR( 8) },	/* Pacific Standard */
    608   1.1  christos     { "pdt",	tDAYZONE,  HOUR( 8) },	/* Pacific Daylight */
    609   1.1  christos     { "yst",	tZONE,     HOUR( 9) },	/* Yukon Standard */
    610   1.1  christos     { "ydt",	tDAYZONE,  HOUR( 9) },	/* Yukon Daylight */
    611   1.1  christos     { "hst",	tZONE,     HOUR(10) },	/* Hawaii Standard */
    612   1.1  christos     { "hdt",	tDAYZONE,  HOUR(10) },	/* Hawaii Daylight */
    613   1.1  christos     { "cat",	tZONE,     HOUR(10) },	/* Central Alaska */
    614   1.1  christos     { "ahst",	tZONE,     HOUR(10) },	/* Alaska-Hawaii Standard */
    615   1.1  christos     { "nt",	tZONE,     HOUR(11) },	/* Nome */
    616   1.1  christos     { "idlw",	tZONE,     HOUR(12) },	/* International Date Line West */
    617   1.1  christos     { "cet",	tZONE,     -HOUR(1) },	/* Central European */
    618   1.1  christos     { "met",	tZONE,     -HOUR(1) },	/* Middle European */
    619   1.1  christos     { "mewt",	tZONE,     -HOUR(1) },	/* Middle European Winter */
    620   1.1  christos     { "mest",	tDAYZONE,  -HOUR(1) },	/* Middle European Summer */
    621   1.1  christos     { "swt",	tZONE,     -HOUR(1) },	/* Swedish Winter */
    622   1.1  christos     { "sst",	tDAYZONE,  -HOUR(1) },	/* Swedish Summer */
    623   1.1  christos     { "fwt",	tZONE,     -HOUR(1) },	/* French Winter */
    624   1.1  christos     { "fst",	tDAYZONE,  -HOUR(1) },	/* French Summer */
    625   1.1  christos     { "eet",	tZONE,     -HOUR(2) },	/* Eastern Europe, USSR Zone 1 */
    626   1.1  christos     { "bt",	tZONE,     -HOUR(3) },	/* Baghdad, USSR Zone 2 */
    627   1.1  christos     { "it",	tZONE,     -HOUR(3.5) },/* Iran */
    628   1.1  christos     { "zp4",	tZONE,     -HOUR(4) },	/* USSR Zone 3 */
    629   1.1  christos     { "zp5",	tZONE,     -HOUR(5) },	/* USSR Zone 4 */
    630   1.1  christos     { "ist",	tZONE,     -HOUR(5.5) },/* Indian Standard */
    631   1.1  christos     { "zp6",	tZONE,     -HOUR(6) },	/* USSR Zone 5 */
    632   1.1  christos #if	0
    633   1.1  christos     /* For completeness.  NST is also Newfoundland Stanard, and SST is
    634   1.1  christos      * also Swedish Summer. */
    635   1.1  christos     { "nst",	tZONE,     -HOUR(6.5) },/* North Sumatra */
    636   1.1  christos     { "sst",	tZONE,     -HOUR(7) },	/* South Sumatra, USSR Zone 6 */
    637   1.1  christos #endif	/* 0 */
    638  1.21  christos     { "ict",	tZONE,     -HOUR(7) },	/* Indo China Time (Thai) */
    639  1.21  christos #if 0	/* this one looks to be bogus */
    640   1.1  christos     { "jt",	tZONE,     -HOUR(7.5) },/* Java (3pm in Cronusland!) */
    641   1.1  christos #endif
    642  1.22  christos     { "wast",	tZONE,     -HOUR(8) },	/* West Australian Standard */
    643  1.22  christos     { "awst",	tZONE,     -HOUR(8) },	/* West Australian Standard */
    644  1.22  christos     { "wadt",	tDAYZONE,  -HOUR(8) },	/* West Australian Daylight */
    645  1.22  christos     { "awdt",	tDAYZONE,  -HOUR(8) },	/* West Australian Daylight */
    646   1.1  christos     { "cct",	tZONE,     -HOUR(8) },	/* China Coast, USSR Zone 7 */
    647  1.21  christos     { "sgt",	tZONE,     -HOUR(8) },	/* Singapore */
    648  1.21  christos     { "hkt",	tZONE,     -HOUR(8) },	/* Hong Kong */
    649   1.1  christos     { "jst",	tZONE,     -HOUR(9) },	/* Japan Standard, USSR Zone 8 */
    650   1.1  christos     { "cast",	tZONE,     -HOUR(9.5) },/* Central Australian Standard */
    651  1.21  christos     { "acst",	tZONE,     -HOUR(9.5) },/* Central Australian Standard */
    652   1.1  christos     { "cadt",	tDAYZONE,  -HOUR(9.5) },/* Central Australian Daylight */
    653  1.21  christos     { "acdt",	tDAYZONE,  -HOUR(9.5) },/* Central Australian Daylight */
    654   1.1  christos     { "east",	tZONE,     -HOUR(10) },	/* Eastern Australian Standard */
    655  1.21  christos     { "aest",	tZONE,     -HOUR(10) },	/* Eastern Australian Standard */
    656   1.1  christos     { "eadt",	tDAYZONE,  -HOUR(10) },	/* Eastern Australian Daylight */
    657  1.21  christos     { "aedt",	tDAYZONE,  -HOUR(10) },	/* Eastern Australian Daylight */
    658   1.1  christos     { "gst",	tZONE,     -HOUR(10) },	/* Guam Standard, USSR Zone 9 */
    659   1.1  christos     { "nzt",	tZONE,     -HOUR(12) },	/* New Zealand */
    660   1.1  christos     { "nzst",	tZONE,     -HOUR(12) },	/* New Zealand Standard */
    661   1.1  christos     { "nzdt",	tDAYZONE,  -HOUR(12) },	/* New Zealand Daylight */
    662   1.1  christos     { "idle",	tZONE,     -HOUR(12) },	/* International Date Line East */
    663   1.1  christos     {  NULL,	0,	    0 }
    664   1.1  christos };
    665   1.1  christos 
    666   1.1  christos /* Military timezone table. */
    667  1.12     joerg static const TABLE MilitaryTable[] = {
    668   1.1  christos     { "a",	tZONE,	HOUR(  1) },
    669   1.1  christos     { "b",	tZONE,	HOUR(  2) },
    670   1.1  christos     { "c",	tZONE,	HOUR(  3) },
    671   1.1  christos     { "d",	tZONE,	HOUR(  4) },
    672   1.1  christos     { "e",	tZONE,	HOUR(  5) },
    673   1.1  christos     { "f",	tZONE,	HOUR(  6) },
    674   1.1  christos     { "g",	tZONE,	HOUR(  7) },
    675   1.1  christos     { "h",	tZONE,	HOUR(  8) },
    676   1.1  christos     { "i",	tZONE,	HOUR(  9) },
    677   1.1  christos     { "k",	tZONE,	HOUR( 10) },
    678   1.1  christos     { "l",	tZONE,	HOUR( 11) },
    679   1.1  christos     { "m",	tZONE,	HOUR( 12) },
    680   1.1  christos     { "n",	tZONE,	HOUR(- 1) },
    681   1.1  christos     { "o",	tZONE,	HOUR(- 2) },
    682   1.1  christos     { "p",	tZONE,	HOUR(- 3) },
    683   1.1  christos     { "q",	tZONE,	HOUR(- 4) },
    684   1.1  christos     { "r",	tZONE,	HOUR(- 5) },
    685   1.1  christos     { "s",	tZONE,	HOUR(- 6) },
    686   1.1  christos     { "t",	tZONE,	HOUR(- 7) },
    687   1.1  christos     { "u",	tZONE,	HOUR(- 8) },
    688   1.1  christos     { "v",	tZONE,	HOUR(- 9) },
    689   1.1  christos     { "w",	tZONE,	HOUR(-10) },
    690   1.1  christos     { "x",	tZONE,	HOUR(-11) },
    691   1.1  christos     { "y",	tZONE,	HOUR(-12) },
    692   1.1  christos     { "z",	tZONE,	HOUR(  0) },
    693   1.1  christos     { NULL,	0,	0 }
    694   1.1  christos };
    695   1.1  christos 
    696  1.23  christos static const TABLE TimeNames[] = {
    697  1.23  christos     { "midnight",	tTIME,		 0 },
    698  1.23  christos     { "mn",		tTIME,		 0 },
    699  1.23  christos     { "noon",		tTIME,		12 },
    700  1.28       kre     { "midday",		tTIME,		12 },
    701  1.23  christos     { NULL,		0,		 0 }
    702  1.23  christos };
    703  1.23  christos 
    704   1.1  christos 
    705   1.1  christos 
    707   1.1  christos /* ARGSUSED */
    708   1.9  christos static int
    709   1.1  christos yyerror(struct dateinfo *param, const char **inp, const char *s __unused)
    710   1.1  christos {
    711   1.1  christos   return 0;
    712   1.1  christos }
    713  1.28       kre 
    714  1.28       kre /*
    715  1.28       kre  * Save a relative value, if it fits
    716  1.36       kre  */
    717  1.36       kre static int
    718  1.28       kre RelVal(struct dateinfo *param, time_t num, time_t unit, int scale, int type)
    719  1.28       kre {
    720  1.36       kre 	int i;
    721  1.36       kre 	time_t v;
    722  1.36       kre 	uintmax_t m;
    723  1.28       kre 	int sign = 1;
    724  1.28       kre 
    725  1.36       kre 	if ((i = param->yyHaveRel) >= MAXREL)
    726  1.36       kre 		return 0;
    727  1.36       kre 
    728  1.36       kre 	if (num < 0) {
    729  1.36       kre 		sign = -sign;
    730  1.36       kre 		num = -num;
    731  1.36       kre 	}
    732  1.36       kre 	if (unit < 0) {
    733  1.36       kre 		sign = -sign;
    734  1.36       kre 		unit = -unit;
    735  1.36       kre 	}
    736  1.36       kre 	/* scale is always positive */
    737  1.36       kre 
    738  1.36       kre 	m = LLONG_MAX;		/* TIME_T_MAX */
    739  1.36       kre 	if (scale > 1)
    740  1.36       kre 		m /= scale;
    741  1.36       kre 	if (unit > 1)
    742  1.36       kre 		m /= unit;
    743  1.36       kre 	if ((uintmax_t)num > m)
    744  1.36       kre 		return 0;
    745  1.36       kre 
    746  1.36       kre 	m = num * unit * scale;
    747  1.36       kre 	v = (time_t) m;
    748  1.36       kre 	if (v < 0 || (uintmax_t)v != m)
    749  1.36       kre 		return 0;
    750  1.36       kre 	if (sign < 0)
    751  1.36       kre 		v = -v;
    752  1.28       kre 
    753  1.28       kre 	param->yyRel[i].yyRelMonth = type;
    754  1.36       kre 	param->yyRel[i].yyRelVal = v;
    755  1.36       kre 
    756  1.28       kre 	return 1;
    757  1.28       kre }
    758  1.32       kre 
    759  1.32       kre /*
    760  1.17       apb  * Adjust year from a value that might be abbreviated, to a full value.
    761  1.17       apb  * e.g. convert 70 to 1970.
    762  1.17       apb  * Input Year is either:
    763  1.33       kre  *  - A negative number, which means to use its absolute value (why?)
    764  1.33       kre  *  - A number from 0 to 68, which means a year from 2000 to 2068,
    765  1.17       apb  *  - A number from 69 to 99, which means a year from 1969 to 1999, or
    766  1.32       kre  *  - The actual year (>=100).
    767  1.32       kre  * Returns the full year.
    768  1.17       apb  */
    769  1.17       apb static time_t
    770  1.17       apb AdjustYear(time_t Year)
    771  1.17       apb {
    772  1.17       apb     /* XXX Y2K */
    773  1.17       apb     if (Year < 0)
    774  1.33       kre 	Year = -Year;
    775  1.17       apb     if (Year < 69)	/* POSIX compliant, 0..68 is 2000's, 69-99 1900's */
    776  1.17       apb 	Year += 2000;
    777  1.17       apb     else if (Year < 100)
    778  1.17       apb 	Year += 1900;
    779  1.17       apb     return Year;
    780  1.17       apb }
    781   1.1  christos 
    782   1.1  christos static time_t
    783  1.11       apb Convert(
    784  1.11       apb     time_t	Month,		/* month of year [1-12] */
    785  1.17       apb     time_t	Day,		/* day of month [1-31] */
    786  1.11       apb     time_t	Year,		/* year, not abbreviated in any way */
    787  1.11       apb     time_t	Hours,		/* Hour of day [0-24] */
    788  1.11       apb     time_t	Minutes,	/* Minute of hour [0-59] */
    789  1.20       apb     time_t	Seconds,	/* Second of minute [0-60] */
    790  1.20       apb     time_t	Timezone,	/* Timezone as minutes east of UTC,
    791  1.11       apb 				 * or USE_LOCAL_TIME special case */
    792  1.11       apb     MERIDIAN	Meridian,	/* Hours are am/pm/24 hour clock */
    793   1.1  christos     DSTMODE	DSTmode		/* DST on/off/maybe */
    794   1.1  christos )
    795  1.13       apb {
    796  1.23  christos     struct tm tm = {.tm_sec = 0};
    797  1.13       apb     struct tm otm;
    798   1.1  christos     time_t result;
    799  1.11       apb 
    800  1.11       apb     tm.tm_sec = Seconds;
    801  1.30       kre     tm.tm_min = Minutes;
    802  1.30       kre     tm.tm_hour = ((Hours == 12 && Meridian != MER24) ? 0 : Hours) +
    803  1.30       kre 	(Meridian == MERpm ? 12 : 0);
    804  1.11       apb 
    805  1.11       apb     tm.tm_mday = Day;
    806  1.11       apb     tm.tm_mon = Month - 1;
    807  1.36       kre     tm.tm_year = Year - 1900;
    808  1.36       kre     if ((time_t)tm.tm_year + 1900 != Year) {
    809  1.36       kre 	errno = EOVERFLOW;
    810  1.36       kre 	return -1;
    811  1.20       apb     }
    812  1.21  christos     if (Timezone == USE_LOCAL_TIME) {
    813  1.21  christos 	    switch (DSTmode) {
    814  1.21  christos 	    case DSTon:  tm.tm_isdst = 1; break;
    815  1.21  christos 	    case DSToff: tm.tm_isdst = 0; break;
    816  1.21  christos 	    default:     tm.tm_isdst = -1; break;
    817  1.24  christos 	    }
    818  1.20       apb 	    otm = tm;
    819  1.20       apb 	    result = mktime(&tm);
    820  1.20       apb     } else {
    821  1.21  christos 	    /* We rely on mktime_z(NULL, ...) working in UTC */
    822  1.24  christos 	    tm.tm_isdst = 0;	/* hence cannot be summer time */
    823  1.21  christos 	    otm = tm;
    824  1.20       apb 	    errno = 0;
    825  1.21  christos 	    result = mktime_z(NULL, &tm);
    826  1.21  christos 	    if (result != -1 || errno == 0) {
    827  1.21  christos 		    result += Timezone * 60;
    828  1.21  christos 		    if (DSTmode == DSTon)	/* if specified sumer time */
    829  1.21  christos 			result -= 3600;		/* UTC is 1 hour earlier XXX */
    830  1.20       apb 	    }
    831  1.20       apb     }
    832  1.20       apb 
    833  1.20       apb #if PARSEDATE_DEBUG
    834  1.20       apb     fprintf(stderr, "%s(M=%jd D=%jd Y=%jd H=%jd M=%jd S=%jd Z=%jd"
    835  1.20       apb 		    " mer=%d DST=%d)",
    836  1.20       apb 	__func__,
    837  1.20       apb 	(intmax_t)Month, (intmax_t)Day, (intmax_t)Year,
    838  1.20       apb 	(intmax_t)Hours, (intmax_t)Minutes, (intmax_t)Seconds,
    839  1.20       apb 	(intmax_t)Timezone, (int)Meridian, (int)DSTmode);
    840  1.20       apb     fprintf(stderr, " -> %jd", (intmax_t)result);
    841  1.20       apb     fprintf(stderr, " %s", ctime(&result));
    842  1.20       apb #endif
    843  1.23  christos 
    844  1.23  christos #define	TM_NE(fld) (otm.tm_ ## fld != tm.tm_ ## fld)
    845  1.23  christos     if (TM_NE(year) || TM_NE(mon) || TM_NE(mday) ||
    846  1.23  christos 	TM_NE(hour) || TM_NE(min) || TM_NE(sec)) {
    847  1.23  christos 	    /* mktime() "corrected" our tm, so it must have been invalid */
    848  1.23  christos 	    result = -1;
    849  1.23  christos 	    errno = EAGAIN;
    850  1.23  christos     }
    851  1.23  christos #undef	TM_NE
    852  1.13       apb 
    853   1.1  christos     return result;
    854   1.1  christos }
    855   1.1  christos 
    856   1.1  christos 
    857   1.1  christos static time_t
    858   1.1  christos DSTcorrect(
    859   1.1  christos     time_t	Start,
    860   1.1  christos     time_t	Future
    861   1.1  christos )
    862   1.1  christos {
    863   1.1  christos     time_t	StartDay;
    864  1.26  dholland     time_t	FutureDay;
    865   1.6  christos     struct tm	tm;
    866  1.26  dholland 
    867   1.6  christos     if (localtime_r(&Start, &tm) == NULL)
    868  1.26  dholland 	return -1;
    869   1.6  christos     StartDay = (tm.tm_hour + 1) % 24;
    870  1.26  dholland 
    871   1.6  christos     if (localtime_r(&Future, &tm) == NULL)
    872  1.26  dholland 	return -1;
    873   1.1  christos     FutureDay = (tm.tm_hour + 1) % 24;
    874   1.1  christos 
    875   1.1  christos     return (Future - Start) + (StartDay - FutureDay) * 60L * 60L;
    876   1.1  christos }
    877   1.1  christos 
    878   1.1  christos 
    879   1.1  christos static time_t
    880   1.1  christos RelativeDate(
    881   1.1  christos     time_t	Start,
    882   1.1  christos     time_t	DayOrdinal,
    883   1.1  christos     time_t	DayNumber
    884   1.1  christos )
    885  1.26  dholland {
    886   1.1  christos     struct tm	tm;
    887  1.36       kre     time_t	now;
    888   1.1  christos     time_t	change;
    889   1.1  christos 
    890  1.26  dholland     now = Start;
    891  1.21  christos     if (localtime_r(&now, &tm) == NULL)
    892  1.36       kre 	return -1;
    893  1.36       kre 
    894  1.36       kre     /* should be using TIME_T_MAX but there is no such thing, so just "know" */
    895  1.36       kre     if (llabs(DayOrdinal) >= LLONG_MAX / (7 * SECSPERDAY)) {
    896  1.36       kre 	errno = EOVERFLOW;
    897  1.36       kre 	return -1;
    898  1.36       kre     }
    899  1.36       kre 
    900  1.36       kre     change = SECSPERDAY * ((DayNumber - tm.tm_wday + 7) % 7);
    901  1.36       kre     change += 7 * SECSPERDAY * (DayOrdinal <= 0 ? DayOrdinal : DayOrdinal - 1);
    902  1.36       kre 
    903  1.36       kre     /* same here for _MAX and _MIN */
    904  1.36       kre     if ((change > 0 && LLONG_MAX - change < now) ||
    905  1.36       kre 	(change < 0 && LLONG_MIN - change > now)) {
    906  1.36       kre 	    errno = EOVERFLOW;
    907  1.36       kre 	    return -1;
    908  1.36       kre     }
    909  1.36       kre 
    910   1.1  christos     now += change;
    911   1.1  christos     return DSTcorrect(Start, now);
    912   1.1  christos }
    913   1.1  christos 
    914   1.1  christos 
    915   1.1  christos static time_t
    916   1.1  christos RelativeMonth(
    917   1.9  christos     time_t	Start,
    918   1.9  christos     time_t	RelMonth,
    919   1.1  christos     time_t	Timezone
    920   1.1  christos )
    921  1.26  dholland {
    922   1.1  christos     struct tm	tm;
    923  1.27  dholland     time_t	Month;
    924  1.28       kre     time_t	Then;
    925   1.1  christos     int		Day;
    926   1.1  christos 
    927   1.1  christos     if (RelMonth == 0)
    928  1.27  dholland 	return 0;
    929  1.27  dholland     /*
    930  1.27  dholland      * It doesn't matter what timezone we use to do this computation,
    931  1.27  dholland      * as long as we use the same one to reassemble the time that we
    932  1.27  dholland      * used to disassemble it. So always use localtime and mktime. In
    933  1.27  dholland      * particular, don't use Convert() to reassemble, because it will
    934  1.27  dholland      * not only reassemble with the wrong timezone but it will also
    935  1.27  dholland      * fail if we do e.g. three months from March 31 yielding July 1.
    936  1.27  dholland      */
    937  1.27  dholland     (void)Timezone;
    938  1.26  dholland 
    939   1.6  christos     if (localtime_r(&Start, &tm) == NULL)
    940  1.27  dholland 	return -1;
    941  1.36       kre 
    942  1.36       kre     if (RelMonth >= LLONG_MAX - 12*((time_t)tm.tm_year + 1900) - tm.tm_mon) {
    943  1.36       kre 	errno = EOVERFLOW;
    944  1.36       kre 	return -1;
    945  1.26  dholland     }
    946  1.27  dholland     Month = 12 * (tm.tm_year + 1900) + tm.tm_mon + RelMonth;
    947  1.36       kre     tm.tm_year = (Month / 12) - 1900;
    948  1.36       kre     /* check for tm_year (an int) overflow */
    949  1.36       kre     if (((time_t)tm.tm_year + 1900) != Month/12) {
    950  1.36       kre 	errno = EOVERFLOW;
    951  1.36       kre 	return -1;
    952  1.27  dholland     }
    953  1.28       kre     tm.tm_mon = Month % 12;
    954  1.28       kre     if (tm.tm_mday > (Day = DaysInMonth[tm.tm_mon] +
    955  1.28       kre 	((tm.tm_mon==1) ? isleap(tm.tm_year) : 0)))
    956  1.27  dholland 	    tm.tm_mday = Day;
    957  1.27  dholland     errno = 0;
    958  1.27  dholland     Then = mktime(&tm);
    959  1.27  dholland     if (Then == -1 && errno != 0)
    960  1.27  dholland 	return -1;
    961   1.1  christos     return DSTcorrect(Start, Then);
    962   1.1  christos }
    963   1.1  christos 
    964   1.1  christos 
    965   1.9  christos static int
    966   1.1  christos LookupWord(YYSTYPE *yylval, char *buff)
    967   1.1  christos {
    968   1.1  christos     register char	*p;
    969   1.1  christos     register char	*q;
    970   1.1  christos     register const TABLE	*tp;
    971   1.1  christos     int			i;
    972   1.1  christos     int			abbrev;
    973   1.1  christos 
    974   1.1  christos     /* Make it lowercase. */
    975   1.1  christos     for (p = buff; *p; p++)
    976   1.1  christos 	if (isupper((unsigned char)*p))
    977   1.1  christos 	    *p = tolower((unsigned char)*p);
    978   1.1  christos 
    979   1.9  christos     if (strcmp(buff, "am") == 0 || strcmp(buff, "a.m.") == 0) {
    980   1.1  christos 	yylval->Meridian = MERam;
    981   1.1  christos 	return tMERIDIAN;
    982   1.1  christos     }
    983   1.9  christos     if (strcmp(buff, "pm") == 0 || strcmp(buff, "p.m.") == 0) {
    984   1.1  christos 	yylval->Meridian = MERpm;
    985   1.1  christos 	return tMERIDIAN;
    986   1.1  christos     }
    987   1.1  christos 
    988   1.1  christos     /* See if we have an abbreviation for a month. */
    989   1.1  christos     if (strlen(buff) == 3)
    990   1.1  christos 	abbrev = 1;
    991   1.1  christos     else if (strlen(buff) == 4 && buff[3] == '.') {
    992   1.1  christos 	abbrev = 1;
    993   1.1  christos 	buff[3] = '\0';
    994   1.1  christos     }
    995   1.1  christos     else
    996   1.1  christos 	abbrev = 0;
    997   1.1  christos 
    998   1.1  christos     for (tp = MonthDayTable; tp->name; tp++) {
    999   1.1  christos 	if (abbrev) {
   1000   1.9  christos 	    if (strncmp(buff, tp->name, 3) == 0) {
   1001   1.1  christos 		yylval->Number = tp->value;
   1002   1.1  christos 		return tp->type;
   1003   1.1  christos 	    }
   1004   1.1  christos 	}
   1005   1.9  christos 	else if (strcmp(buff, tp->name) == 0) {
   1006   1.1  christos 	    yylval->Number = tp->value;
   1007   1.1  christos 	    return tp->type;
   1008   1.1  christos 	}
   1009   1.1  christos     }
   1010   1.1  christos 
   1011   1.1  christos     for (tp = TimezoneTable; tp->name; tp++)
   1012   1.9  christos 	if (strcmp(buff, tp->name) == 0) {
   1013   1.1  christos 	    yylval->Number = tp->value;
   1014   1.1  christos 	    return tp->type;
   1015   1.1  christos 	}
   1016   1.1  christos 
   1017   1.1  christos     if (strcmp(buff, "dst") == 0)
   1018   1.1  christos 	return tDST;
   1019  1.23  christos 
   1020  1.23  christos     for (tp = TimeNames; tp->name; tp++)
   1021  1.23  christos 	if (strcmp(buff, tp->name) == 0) {
   1022  1.23  christos 	    yylval->Number = tp->value;
   1023  1.23  christos 	    return tp->type;
   1024  1.23  christos 	}
   1025   1.1  christos 
   1026   1.1  christos     for (tp = UnitsTable; tp->name; tp++)
   1027   1.9  christos 	if (strcmp(buff, tp->name) == 0) {
   1028   1.1  christos 	    yylval->Number = tp->value;
   1029   1.1  christos 	    return tp->type;
   1030   1.1  christos 	}
   1031   1.1  christos 
   1032   1.1  christos     /* Strip off any plural and try the units table again. */
   1033   1.1  christos     i = strlen(buff) - 1;
   1034   1.1  christos     if (buff[i] == 's') {
   1035   1.1  christos 	buff[i] = '\0';
   1036   1.1  christos 	for (tp = UnitsTable; tp->name; tp++)
   1037   1.9  christos 	    if (strcmp(buff, tp->name) == 0) {
   1038   1.1  christos 		yylval->Number = tp->value;
   1039   1.1  christos 		return tp->type;
   1040   1.1  christos 	    }
   1041   1.1  christos 	buff[i] = 's';		/* Put back for "this" in OtherTable. */
   1042   1.1  christos     }
   1043   1.1  christos 
   1044   1.1  christos     for (tp = OtherTable; tp->name; tp++)
   1045   1.9  christos 	if (strcmp(buff, tp->name) == 0) {
   1046   1.1  christos 	    yylval->Number = tp->value;
   1047   1.1  christos 	    return tp->type;
   1048   1.1  christos 	}
   1049   1.1  christos 
   1050   1.1  christos     /* Military timezones. */
   1051   1.1  christos     if (buff[1] == '\0' && isalpha((unsigned char)*buff)) {
   1052   1.1  christos 	for (tp = MilitaryTable; tp->name; tp++)
   1053   1.9  christos 	    if (strcmp(buff, tp->name) == 0) {
   1054   1.1  christos 		yylval->Number = tp->value;
   1055   1.1  christos 		return tp->type;
   1056   1.1  christos 	    }
   1057   1.1  christos     }
   1058   1.1  christos 
   1059   1.1  christos     /* Drop out any periods and try the timezone table again. */
   1060   1.1  christos     for (i = 0, p = q = buff; *q; q++)
   1061   1.1  christos 	if (*q != '.')
   1062   1.1  christos 	    *p++ = *q;
   1063   1.1  christos 	else
   1064   1.1  christos 	    i++;
   1065   1.1  christos     *p = '\0';
   1066   1.1  christos     if (i)
   1067   1.1  christos 	for (tp = TimezoneTable; tp->name; tp++)
   1068   1.9  christos 	    if (strcmp(buff, tp->name) == 0) {
   1069   1.1  christos 		yylval->Number = tp->value;
   1070   1.1  christos 		return tp->type;
   1071   1.1  christos 	    }
   1072   1.1  christos 
   1073   1.1  christos     return tID;
   1074   1.1  christos }
   1075   1.1  christos 
   1076   1.1  christos 
   1077   1.9  christos static int
   1078   1.1  christos yylex(YYSTYPE *yylval, const char **yyInput)
   1079   1.1  christos {
   1080   1.1  christos     register char	c;
   1081   1.1  christos     register char	*p;
   1082   1.1  christos     char		buff[20];
   1083   1.1  christos     int			Count;
   1084   1.9  christos     int			sign;
   1085   1.1  christos     const char		*inp = *yyInput;
   1086   1.1  christos 
   1087   1.9  christos     for ( ; ; ) {
   1088   1.9  christos 	while (isspace((unsigned char)*inp))
   1089   1.1  christos 	    inp++;
   1090   1.9  christos 
   1091   1.1  christos 	if (isdigit((unsigned char)(c = *inp)) || c == '-' || c == '+') {
   1092   1.1  christos 	    if (c == '-' || c == '+') {
   1093   1.9  christos 		sign = c == '-' ? -1 : 1;
   1094   1.1  christos 		if (!isdigit((unsigned char)*++inp))
   1095   1.1  christos 		    /* skip the '-' sign */
   1096   1.1  christos 		    continue;
   1097   1.1  christos 	    }
   1098   1.1  christos 	    else
   1099  1.36       kre 		sign = 0;
   1100  1.36       kre 	    for (yylval->Number = 0; isdigit((unsigned char)(c = *inp++)); ) {
   1101  1.36       kre 	        time_t	v;
   1102  1.36       kre 
   1103  1.36       kre 		v = yylval->Number;
   1104  1.36       kre 		if (v > LLONG_MAX/10 ||
   1105  1.36       kre 		    (v == LLONG_MAX/10 && (v * 10 > LLONG_MAX - (c - '0'))))
   1106  1.36       kre 			yylval->Number = LLONG_MAX;
   1107  1.36       kre 		else
   1108  1.36       kre 			yylval->Number = 10 * yylval->Number + c - '0';
   1109   1.1  christos 	    }
   1110   1.9  christos 	    if (sign < 0)
   1111   1.9  christos 		yylval->Number = -yylval->Number;
   1112   1.1  christos 	    *yyInput = --inp;
   1113   1.1  christos 	    return sign ? tSNUMBER : tUNUMBER;
   1114   1.1  christos 	}
   1115   1.9  christos 	if (isalpha((unsigned char)c)) {
   1116   1.1  christos 	    for (p = buff; isalpha((unsigned char)(c = *inp++)) || c == '.'; )
   1117   1.1  christos 		if (p < &buff[sizeof buff - 1])
   1118   1.1  christos 		    *p++ = c;
   1119   1.9  christos 	    *p = '\0';
   1120   1.9  christos 	    *yyInput = --inp;
   1121   1.1  christos 	    return LookupWord(yylval, buff);
   1122   1.5      tron 	}
   1123   1.9  christos 	if (c == '@') {
   1124   1.5      tron 	    *yyInput = ++inp;
   1125   1.5      tron 	    return AT_SIGN;
   1126   1.9  christos 	}
   1127   1.9  christos 	if (c != '(') {
   1128   1.9  christos 	    *yyInput = ++inp;
   1129   1.9  christos 	    return c;
   1130   1.1  christos 	}
   1131   1.1  christos 	Count = 0;
   1132   1.9  christos 	do {
   1133   1.1  christos 	    c = *inp++;
   1134   1.1  christos 	    if (c == '\0')
   1135   1.1  christos 		return c;
   1136   1.1  christos 	    if (c == '(')
   1137   1.1  christos 		Count++;
   1138   1.1  christos 	    else if (c == ')')
   1139   1.1  christos 		Count--;
   1140   1.1  christos 	} while (Count > 0);
   1141   1.1  christos     }
   1142   1.1  christos }
   1143   1.1  christos 
   1144   1.1  christos #define TM_YEAR_ORIGIN 1900
   1145   1.1  christos 
   1146   1.1  christos time_t
   1147   1.1  christos parsedate(const char *p, const time_t *now, const int *zone)
   1148  1.20       apb {
   1149   1.1  christos     struct tm		local, *tm;
   1150   1.1  christos     time_t		nowt;
   1151   1.1  christos     int			zonet;
   1152   1.6  christos     time_t		Start;
   1153   1.9  christos     time_t		tod, rm;
   1154  1.14       apb     struct dateinfo	param;
   1155  1.28       kre     int			saved_errno;
   1156  1.14       apb     int			i;
   1157  1.14       apb 
   1158  1.14       apb     saved_errno = errno;
   1159   1.1  christos     errno = 0;
   1160  1.20       apb 
   1161   1.1  christos     if (now == NULL) {
   1162  1.20       apb         now = &nowt;
   1163  1.20       apb 	(void)time(&nowt);
   1164  1.20       apb     }
   1165   1.1  christos     if (zone == NULL) {
   1166  1.20       apb 	zone = &zonet;
   1167   1.1  christos 	zonet = USE_LOCAL_TIME;
   1168   1.1  christos 	if ((tm = localtime_r(now, &local)) == NULL)
   1169   1.1  christos 	    return -1;
   1170  1.20       apb     } else {
   1171  1.20       apb 	/*
   1172  1.20       apb 	 * Should use the specified zone, not localtime.
   1173  1.20       apb 	 * Fake it using gmtime and arithmetic.
   1174  1.20       apb 	 * This is good enough because we use only the year/month/day,
   1175  1.20       apb 	 * not other fields of struct tm.
   1176  1.20       apb 	 */
   1177  1.20       apb 	time_t fake = *now + (*zone * 60);
   1178   1.1  christos 	if ((tm = gmtime_r(&fake, &local)) == NULL)
   1179   1.1  christos 	    return -1;
   1180   1.9  christos     }
   1181   1.9  christos     param.yyYear = tm->tm_year + 1900;
   1182   1.9  christos     param.yyMonth = tm->tm_mon + 1;
   1183   1.9  christos     param.yyDay = tm->tm_mday;
   1184   1.9  christos     param.yyTimezone = *zone;
   1185   1.9  christos     param.yyDSTmode = DSTmaybe;
   1186   1.9  christos     param.yyHour = 0;
   1187   1.9  christos     param.yyMinutes = 0;
   1188   1.9  christos     param.yySeconds = 0;
   1189   1.9  christos     param.yyMeridian = MER24;
   1190  1.17       apb     param.yyHaveDate = 0;
   1191   1.9  christos     param.yyHaveFullYear = 0;
   1192   1.9  christos     param.yyHaveDay = 0;
   1193   1.9  christos     param.yyHaveRel = 0;
   1194   1.9  christos     param.yyHaveTime = 0;
   1195   1.9  christos     param.yyHaveZone = 0;
   1196  1.34       kre 
   1197  1.34       kre     /*
   1198  1.34       kre      * This one is too hard to parse using a grammar (the lexer would
   1199  1.34       kre      * confuse the 'T' with the Mil format timezone designator)
   1200  1.34       kre      * so handle it as a special case.
   1201  1.34       kre      */
   1202  1.34       kre     do {
   1203  1.34       kre 	const unsigned char *pp = (const unsigned char *)p;
   1204  1.34       kre 	char *ep;	/* starts as "expected, becomes "end ptr" */
   1205  1.35       kre 	static char format[] = "-dd-ddTdd:dd:dd";
   1206  1.34       kre 	time_t yr;
   1207  1.34       kre 
   1208  1.34       kre 	while (isdigit(*pp))
   1209  1.34       kre 		pp++;
   1210  1.34       kre 
   1211  1.34       kre 	if (pp == (const unsigned char *)p)
   1212  1.34       kre 		break;
   1213  1.34       kre 
   1214  1.34       kre 	for (ep = format; *ep; ep++, pp++) {
   1215  1.34       kre 		switch (*ep) {
   1216  1.34       kre 		case 'd':
   1217  1.34       kre 			if (isdigit(*pp))
   1218  1.34       kre 				continue;
   1219  1.34       kre 			break;
   1220  1.34       kre 		case 'T':
   1221  1.34       kre 			if (*pp == 'T' || *pp == 't' || *pp == ' ')
   1222  1.34       kre 				continue;
   1223  1.34       kre 			break;
   1224  1.34       kre 		default:
   1225  1.34       kre 			if (*pp == *ep)
   1226  1.34       kre 				continue;
   1227  1.34       kre 			break;
   1228  1.34       kre 		}
   1229  1.34       kre 		break;
   1230  1.34       kre 	}
   1231  1.34       kre 	if (*ep != '\0')
   1232  1.34       kre 		break;
   1233  1.34       kre 	if (*pp == '.' || *pp == ',') {
   1234  1.34       kre 		if (!isdigit(pp[1]))
   1235  1.34       kre 			break;
   1236  1.34       kre 		while (isdigit(*++pp))
   1237  1.34       kre 			continue;
   1238  1.34       kre 	}
   1239  1.34       kre 	if (*pp == 'Z' || *pp == 'z')
   1240  1.34       kre 		pp++;
   1241  1.34       kre 	else if (isdigit(*pp))
   1242  1.34       kre 		break;
   1243  1.34       kre 
   1244  1.34       kre 	if (*pp != '\0' && !isspace(*pp))
   1245  1.34       kre 		break;
   1246  1.35       kre 
   1247  1.35       kre 	errno = 0;
   1248  1.35       kre 	yr = (time_t)strtol(p, &ep, 10);
   1249  1.35       kre 	if (errno != 0)			/* out of range (can be big number) */
   1250  1.35       kre 		break;			/* the ones below are all 2 digits */
   1251  1.34       kre 
   1252  1.34       kre 	/*
   1253  1.34       kre 	 * This is good enough to commit to there being an ISO format
   1254  1.34       kre 	 * timestamp leading the input string.   We permit standard
   1255  1.34       kre 	 * parsedate() modifiers to follow but not precede this string.
   1256  1.34       kre 	 */
   1257  1.34       kre 	param.yyHaveTime = 1;
   1258  1.34       kre 	param.yyHaveDate = 1;
   1259  1.34       kre 	param.yyHaveFullYear = 1;
   1260  1.34       kre 
   1261  1.34       kre 	if (pp[-1] == 'Z' || pp[-1] == 'z') {
   1262  1.34       kre 		param.yyTimezone = 0;
   1263  1.34       kre 		param.yyHaveZone = 1;
   1264  1.34       kre 	}
   1265  1.35       kre 
   1266  1.34       kre 	param.yyYear = yr;
   1267  1.34       kre 	param.yyMonth = (time_t)strtol(ep + 1, &ep, 10);
   1268  1.34       kre 	param.yyDay = (time_t)strtol(ep + 1, &ep, 10);
   1269  1.34       kre 	param.yyHour = (time_t)strtol(ep + 1, &ep, 10);
   1270  1.34       kre 	param.yyMinutes = (time_t)strtol(ep + 1, &ep, 10);
   1271  1.34       kre 	param.yySeconds = (time_t)strtol(ep + 1, &ep, 10);
   1272  1.34       kre 	/* ignore any fractional seconds, no way to return them in a time_t */
   1273  1.34       kre 
   1274  1.34       kre 	param.yyMeridian = MER24;
   1275  1.34       kre 
   1276  1.34       kre 	p = (const char *)pp;
   1277  1.34       kre     } while (0);
   1278   1.9  christos 
   1279  1.14       apb     if (yyparse(&param, &p) || param.yyHaveTime > 1 || param.yyHaveZone > 1 ||
   1280  1.14       apb 	param.yyHaveDate > 1 || param.yyHaveDay > 1) {
   1281   1.9  christos 	errno = EINVAL;
   1282  1.14       apb 	return -1;
   1283   1.9  christos     }
   1284   1.9  christos 
   1285  1.17       apb     if (param.yyHaveDate || param.yyHaveTime || param.yyHaveDay) {
   1286  1.17       apb 	if (! param.yyHaveFullYear) {
   1287  1.17       apb 		param.yyYear = AdjustYear(param.yyYear);
   1288  1.17       apb 		param.yyHaveFullYear = 1;
   1289  1.21  christos 	}
   1290   1.9  christos 	errno = 0;
   1291   1.9  christos 	Start = Convert(param.yyMonth, param.yyDay, param.yyYear, param.yyHour,
   1292   1.9  christos 	    param.yyMinutes, param.yySeconds, param.yyTimezone,
   1293  1.14       apb 	    param.yyMeridian, param.yyDSTmode);
   1294   1.1  christos 	if (Start == -1 && errno != 0)
   1295   1.1  christos 	    return -1;
   1296   1.1  christos     }
   1297   1.1  christos     else {
   1298   1.9  christos 	Start = *now;
   1299   1.1  christos 	if (!param.yyHaveRel)
   1300   1.1  christos 	    Start -= ((tm->tm_hour * 60L + tm->tm_min) * 60L) + tm->tm_sec;
   1301   1.1  christos     }
   1302  1.28       kre 
   1303  1.28       kre     if (param.yyHaveRel > MAXREL) {
   1304   1.6  christos 	errno = EINVAL;
   1305  1.28       kre 	return -1;
   1306  1.28       kre     }
   1307  1.28       kre     for (i = 0; i < param.yyHaveRel; i++) {
   1308  1.28       kre 	if (param.yyRel[i].yyRelMonth) {
   1309  1.28       kre 	    errno = 0;
   1310  1.28       kre 	    rm = RelativeMonth(Start, param.yyRel[i].yyRelVal, param.yyTimezone);
   1311  1.28       kre 	    if (rm == -1 && errno != 0)
   1312  1.28       kre 		return -1;
   1313  1.28       kre 	    Start += rm;
   1314  1.28       kre 	} else
   1315  1.28       kre 	    Start += param.yyRel[i].yyRelVal;
   1316   1.1  christos     }
   1317   1.9  christos 
   1318  1.21  christos     if (param.yyHaveDay && !param.yyHaveDate) {
   1319   1.9  christos 	errno = 0;
   1320  1.21  christos 	tod = RelativeDate(Start, param.yyDayOrdinal, param.yyDayNumber);
   1321  1.21  christos 	if (tod == -1 && errno != 0)
   1322   1.1  christos 	    return -1;
   1323   1.1  christos 	Start += tod;
   1324   1.1  christos     }
   1325  1.21  christos 
   1326   1.6  christos     errno = saved_errno;
   1327   1.1  christos     return Start;
   1328   1.1  christos }
   1329   1.1  christos 
   1330   1.1  christos 
   1331   1.1  christos #if	defined(TEST)
   1332   1.1  christos 
   1333   1.1  christos /* ARGSUSED */
   1334  1.14       apb int
   1335   1.1  christos main(int ac, char *av[])
   1336   1.1  christos {
   1337   1.1  christos     char	buff[128];
   1338   1.1  christos     time_t	d;
   1339   1.1  christos 
   1340   1.1  christos     (void)printf("Enter date, or blank line to exit.\n\t> ");
   1341  1.14       apb     (void)fflush(stdout);
   1342  1.14       apb     while (fgets(buff, sizeof(buff), stdin) && buff[0] != '\n') {
   1343   1.1  christos 	errno = 0;
   1344  1.14       apb 	d = parsedate(buff, NULL, NULL);
   1345  1.14       apb 	if (d == -1 && errno != 0)
   1346  1.14       apb 	    (void)printf("Bad format - couldn't convert: %s\n",
   1347   1.1  christos 	        strerror(errno));
   1348  1.14       apb 	else
   1349   1.1  christos 	    (void)printf("%jd\t%s", (intmax_t)d, ctime(&d));
   1350   1.1  christos 	(void)printf("\t> ");
   1351   1.1  christos 	(void)fflush(stdout);
   1352   1.1  christos     }
   1353   1.1  christos     exit(0);
   1354   1.1  christos     /* NOTREACHED */
   1355   1.1  christos }
   1356                 #endif	/* defined(TEST) */
   1357