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