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