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