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