.
[gnulib.git] / 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 nine shift/reduce conflicts.
10 **
11 **  This code is in the public domain and has no copyright.
12 */
13 /* SUPPRESS 287 on yaccpar_sccsid *//* Unusd static variable */
14 /* SUPPRESS 288 on yyerrlab *//* Label unused */
15
16 #ifdef HAVE_CONFIG_H
17 #if defined (emacs) || defined (CONFIG_BROKETS)
18 #include <config.h>
19 #else
20 #include "config.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 /* The following block of alloca-related preprocessor directives is here
35    solely to allow compilation by non GNU-C compilers of the C parser
36    produced from this file by old versions of bison.  Newer versions of
37    bison include a block similar to this one in bison.simple.  */
38    
39 #ifdef __GNUC__
40 #define alloca __builtin_alloca
41 #else
42 #ifdef HAVE_ALLOCA_H
43 #include <alloca.h>
44 #else
45 #ifdef _AIX
46  #pragma alloca
47 #else
48 void *alloca ();
49 #endif
50 #endif
51 #endif
52
53 #ifdef __GNUC__
54 #undef alloca
55 #define alloca __builtin_alloca
56 #else
57 #ifdef HAVE_ALLOCA_H
58 #include <alloca.h>
59 #else
60 #ifdef _AIX /* for Bison */
61  #pragma alloca
62 #else
63 void *alloca ();
64 #endif
65 #endif
66 #endif
67
68 #include <stdio.h>
69 #include <ctype.h>
70
71 /* The code at the top of get_date which figures out the offset of the
72    current time zone checks various CPP symbols to see if special
73    tricks are need, but defaults to using the gettimeofday system call.
74    Include <sys/time.h> if that will be used.  */
75
76 #if     defined(vms)
77
78 #include <types.h>
79 #include <time.h>
80
81 #else
82
83 #include <sys/types.h>
84
85 #ifdef TIME_WITH_SYS_TIME
86 #include <sys/time.h>
87 #include <time.h>
88 #else
89 #ifdef HAVE_SYS_TIME_H
90 #include <sys/time.h>
91 #else
92 #include <time.h>
93 #endif
94 #endif
95
96 #ifdef timezone
97 #undef timezone /* needed for sgi */
98 #endif
99
100 #if defined(HAVE_SYS_TIMEB_H)
101 #include <sys/timeb.h>
102 #else
103 /*
104 ** We use the obsolete `struct timeb' as part of our interface!
105 ** Since the system doesn't have it, we define it here;
106 ** our callers must do likewise.
107 */
108 struct timeb {
109     time_t              time;           /* Seconds since the epoch      */
110     unsigned short      millitm;        /* Field not used               */
111     short               timezone;       /* Minutes west of GMT          */
112     short               dstflag;        /* Field not used               */
113 };
114 #endif /* defined(HAVE_SYS_TIMEB_H) */
115
116 #endif  /* defined(vms) */
117
118 #if defined (STDC_HEADERS) || defined (USG)
119 #include <string.h>
120 #endif
121
122 /* Some old versions of bison generate parsers that use bcopy.
123    That loses on systems that don't provide the function, so we have
124    to redefine it here.  */
125 #if !defined (HAVE_BCOPY) && defined (HAVE_MEMCPY) && !defined (bcopy)
126 #define bcopy(from, to, len) memcpy ((to), (from), (len))
127 #endif
128
129 extern struct tm        *gmtime();
130 extern struct tm        *localtime();
131
132 #define yyparse getdate_yyparse
133 #define yylex getdate_yylex
134 #define yyerror getdate_yyerror
135
136 static int yylex ();
137 static int yyerror ();
138
139 #if     !defined(lint) && !defined(SABER)
140 static char RCS[] =
141         "$Header: str2date.y,v 2.1 90/09/06 08:15:06 cronan Exp $";
142 #endif  /* !defined(lint) && !defined(SABER) */
143
144
145 #define EPOCH           1970
146 #define HOUR(x)         ((time_t)(x) * 60)
147 #define SECSPERDAY      (24L * 60L * 60L)
148
149
150 /*
151 **  An entry in the lexical lookup table.
152 */
153 typedef struct _TABLE {
154     char        *name;
155     int         type;
156     time_t      value;
157 } TABLE;
158
159
160 /*
161 **  Daylight-savings mode:  on, off, or not yet known.
162 */
163 typedef enum _DSTMODE {
164     DSTon, DSToff, DSTmaybe
165 } DSTMODE;
166
167 /*
168 **  Meridian:  am, pm, or 24-hour style.
169 */
170 typedef enum _MERIDIAN {
171     MERam, MERpm, MER24
172 } MERIDIAN;
173
174
175 /*
176 **  Global variables.  We could get rid of most of these by using a good
177 **  union as the yacc stack.  (This routine was originally written before
178 **  yacc had the %union construct.)  Maybe someday; right now we only use
179 **  the %union very rarely.
180 */
181 static char     *yyInput;
182 static DSTMODE  yyDSTmode;
183 static time_t   yyDayOrdinal;
184 static time_t   yyDayNumber;
185 static int      yyHaveDate;
186 static int      yyHaveDay;
187 static int      yyHaveRel;
188 static int      yyHaveTime;
189 static int      yyHaveZone;
190 static time_t   yyTimezone;
191 static time_t   yyDay;
192 static time_t   yyHour;
193 static time_t   yyMinutes;
194 static time_t   yyMonth;
195 static time_t   yySeconds;
196 static time_t   yyYear;
197 static MERIDIAN yyMeridian;
198 static time_t   yyRelMonth;
199 static time_t   yyRelSeconds;
200
201 %}
202
203 %union {
204     time_t              Number;
205     enum _MERIDIAN      Meridian;
206 }
207
208 %token  tAGO tDAY tDAYZONE tID tMERIDIAN tMINUTE_UNIT tMONTH tMONTH_UNIT
209 %token  tSEC_UNIT tSNUMBER tUNUMBER tZONE tDST
210
211 %type   <Number>        tDAY tDAYZONE tMINUTE_UNIT tMONTH tMONTH_UNIT
212 %type   <Number>        tSEC_UNIT tSNUMBER tUNUMBER tZONE
213 %type   <Meridian>      tMERIDIAN o_merid
214
215 %%
216
217 spec    : /* NULL */
218         | spec item
219         ;
220
221 item    : time {
222             yyHaveTime++;
223         }
224         | zone {
225             yyHaveZone++;
226         }
227         | date {
228             yyHaveDate++;
229         }
230         | day {
231             yyHaveDay++;
232         }
233         | rel {
234             yyHaveRel++;
235         }
236         | number
237         ;
238
239 time    : tUNUMBER tMERIDIAN {
240             yyHour = $1;
241             yyMinutes = 0;
242             yySeconds = 0;
243             yyMeridian = $2;
244         }
245         | tUNUMBER ':' tUNUMBER o_merid {
246             yyHour = $1;
247             yyMinutes = $3;
248             yySeconds = 0;
249             yyMeridian = $4;
250         }
251         | tUNUMBER ':' tUNUMBER tSNUMBER {
252             yyHour = $1;
253             yyMinutes = $3;
254             yyMeridian = MER24;
255             yyDSTmode = DSToff;
256             yyTimezone = - ($4 % 100 + ($4 / 100) * 60);
257         }
258         | tUNUMBER ':' tUNUMBER ':' tUNUMBER o_merid {
259             yyHour = $1;
260             yyMinutes = $3;
261             yySeconds = $5;
262             yyMeridian = $6;
263         }
264         | tUNUMBER ':' tUNUMBER ':' tUNUMBER tSNUMBER {
265             yyHour = $1;
266             yyMinutes = $3;
267             yySeconds = $5;
268             yyMeridian = MER24;
269             yyDSTmode = DSToff;
270             yyTimezone = - ($6 % 100 + ($6 / 100) * 60);
271         }
272         ;
273
274 zone    : tZONE {
275             yyTimezone = $1;
276             yyDSTmode = DSToff;
277         }
278         | tDAYZONE {
279             yyTimezone = $1;
280             yyDSTmode = DSTon;
281         }
282         |
283           tZONE tDST {
284             yyTimezone = $1;
285             yyDSTmode = DSTon;
286         }
287         ;
288
289 day     : tDAY {
290             yyDayOrdinal = 1;
291             yyDayNumber = $1;
292         }
293         | tDAY ',' {
294             yyDayOrdinal = 1;
295             yyDayNumber = $1;
296         }
297         | tUNUMBER tDAY {
298             yyDayOrdinal = $1;
299             yyDayNumber = $2;
300         }
301         ;
302
303 date    : tUNUMBER '/' tUNUMBER {
304             yyMonth = $1;
305             yyDay = $3;
306         }
307         | tUNUMBER '/' tUNUMBER '/' tUNUMBER {
308             yyMonth = $1;
309             yyDay = $3;
310             yyYear = $5;
311         }
312         | tUNUMBER tSNUMBER tSNUMBER {
313             /* ISO 8601 format.  yyyy-mm-dd.  */
314             yyYear = $1;
315             yyMonth = -$2;
316             yyDay = -$3;
317         }
318         | tUNUMBER tMONTH tSNUMBER {
319             /* e.g. 17-JUN-1992.  */
320             yyDay = $1;
321             yyMonth = $2;
322             yyYear = -$3;
323         }
324         | tMONTH tUNUMBER {
325             yyMonth = $1;
326             yyDay = $2;
327         }
328         | tMONTH tUNUMBER ',' tUNUMBER {
329             yyMonth = $1;
330             yyDay = $2;
331             yyYear = $4;
332         }
333         | tUNUMBER tMONTH {
334             yyMonth = $2;
335             yyDay = $1;
336         }
337         | tUNUMBER tMONTH tUNUMBER {
338             yyMonth = $2;
339             yyDay = $1;
340             yyYear = $3;
341         }
342         ;
343
344 rel     : relunit tAGO {
345             yyRelSeconds = -yyRelSeconds;
346             yyRelMonth = -yyRelMonth;
347         }
348         | relunit
349         ;
350
351 relunit : tUNUMBER tMINUTE_UNIT {
352             yyRelSeconds += $1 * $2 * 60L;
353         }
354         | tSNUMBER tMINUTE_UNIT {
355             yyRelSeconds += $1 * $2 * 60L;
356         }
357         | tMINUTE_UNIT {
358             yyRelSeconds += $1 * 60L;
359         }
360         | tSNUMBER tSEC_UNIT {
361             yyRelSeconds += $1;
362         }
363         | tUNUMBER tSEC_UNIT {
364             yyRelSeconds += $1;
365         }
366         | tSEC_UNIT {
367             yyRelSeconds++;
368         }
369         | tSNUMBER tMONTH_UNIT {
370             yyRelMonth += $1 * $2;
371         }
372         | tUNUMBER tMONTH_UNIT {
373             yyRelMonth += $1 * $2;
374         }
375         | tMONTH_UNIT {
376             yyRelMonth += $1;
377         }
378         ;
379
380 number  : tUNUMBER {
381             if (yyHaveTime && yyHaveDate && !yyHaveRel)
382                 yyYear = $1;
383             else {
384                 if($1>10000) {
385                     time_t date_part;
386
387                     date_part= $1/10000;
388                     yyHaveDate++;
389                     yyDay= (date_part)%100;
390                     yyMonth= (date_part/100)%100;
391                     yyYear = date_part/10000;
392                 } 
393                 yyHaveTime++;
394                 if ($1 < 100) {
395                     yyHour = $1;
396                     yyMinutes = 0;
397                 }
398                 else {
399                     yyHour = $1 / 100;
400                     yyMinutes = $1 % 100;
401                 }
402                 yySeconds = 0;
403                 yyMeridian = MER24;
404             }
405         }
406         ;
407
408 o_merid : /* NULL */ {
409             $$ = MER24;
410         }
411         | tMERIDIAN {
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",           tMONTH_UNIT,    12 },
450     { "month",          tMONTH_UNIT,    1 },
451     { "fortnight",      tMINUTE_UNIT,   14 * 24 * 60 },
452     { "week",           tMINUTE_UNIT,   7 * 24 * 60 },
453     { "day",            tMINUTE_UNIT,   1 * 24 * 60 },
454     { "hour",           tMINUTE_UNIT,   60 },
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,       2 },
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 /* Some of these are commented out because a time_t can't store a float. */
489 static TABLE const TimezoneTable[] = {
490     { "gmt",    tZONE,     HOUR( 0) },  /* Greenwich Mean */
491     { "ut",     tZONE,     HOUR( 0) },  /* Universal (Coordinated) */
492     { "utc",    tZONE,     HOUR( 0) },
493     { "wet",    tZONE,     HOUR( 0) },  /* Western European */
494     { "bst",    tDAYZONE,  HOUR( 0) },  /* British Summer */
495     { "wat",    tZONE,     HOUR( 1) },  /* West Africa */
496     { "at",     tZONE,     HOUR( 2) },  /* Azores */
497 #if     0
498     /* For completeness.  BST is also British Summer, and GST is
499      * also Guam Standard. */
500     { "bst",    tZONE,     HOUR( 3) },  /* Brazil Standard */
501     { "gst",    tZONE,     HOUR( 3) },  /* Greenland Standard */
502 #endif
503 #if 0
504     { "nft",    tZONE,     HOUR(3.5) }, /* Newfoundland */
505     { "nst",    tZONE,     HOUR(3.5) }, /* Newfoundland Standard */
506     { "ndt",    tDAYZONE,  HOUR(3.5) }, /* Newfoundland Daylight */
507 #endif
508     { "ast",    tZONE,     HOUR( 4) },  /* Atlantic Standard */
509     { "adt",    tDAYZONE,  HOUR( 4) },  /* Atlantic Daylight */
510     { "est",    tZONE,     HOUR( 5) },  /* Eastern Standard */
511     { "edt",    tDAYZONE,  HOUR( 5) },  /* Eastern Daylight */
512     { "cst",    tZONE,     HOUR( 6) },  /* Central Standard */
513     { "cdt",    tDAYZONE,  HOUR( 6) },  /* Central Daylight */
514     { "mst",    tZONE,     HOUR( 7) },  /* Mountain Standard */
515     { "mdt",    tDAYZONE,  HOUR( 7) },  /* Mountain Daylight */
516     { "pst",    tZONE,     HOUR( 8) },  /* Pacific Standard */
517     { "pdt",    tDAYZONE,  HOUR( 8) },  /* Pacific Daylight */
518     { "yst",    tZONE,     HOUR( 9) },  /* Yukon Standard */
519     { "ydt",    tDAYZONE,  HOUR( 9) },  /* Yukon Daylight */
520     { "hst",    tZONE,     HOUR(10) },  /* Hawaii Standard */
521     { "hdt",    tDAYZONE,  HOUR(10) },  /* Hawaii Daylight */
522     { "cat",    tZONE,     HOUR(10) },  /* Central Alaska */
523     { "ahst",   tZONE,     HOUR(10) },  /* Alaska-Hawaii Standard */
524     { "nt",     tZONE,     HOUR(11) },  /* Nome */
525     { "idlw",   tZONE,     HOUR(12) },  /* International Date Line West */
526     { "cet",    tZONE,     -HOUR(1) },  /* Central European */
527     { "met",    tZONE,     -HOUR(1) },  /* Middle European */
528     { "mewt",   tZONE,     -HOUR(1) },  /* Middle European Winter */
529     { "mest",   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 Stanard, 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
614 static time_t
615 ToSeconds(Hours, Minutes, Seconds, Meridian)
616     time_t      Hours;
617     time_t      Minutes;
618     time_t      Seconds;
619     MERIDIAN    Meridian;
620 {
621     if (Minutes < 0 || Minutes > 59 || Seconds < 0 || Seconds > 59)
622         return -1;
623     switch (Meridian) {
624     case MER24:
625         if (Hours < 0 || Hours > 23)
626             return -1;
627         return (Hours * 60L + Minutes) * 60L + Seconds;
628     case MERam:
629         if (Hours < 1 || Hours > 12)
630             return -1;
631         return (Hours * 60L + Minutes) * 60L + Seconds;
632     case MERpm:
633         if (Hours < 1 || Hours > 12)
634             return -1;
635         return ((Hours + 12) * 60L + Minutes) * 60L + Seconds;
636     default:
637         abort ();
638     }
639     /* NOTREACHED */
640 }
641
642
643 static time_t
644 Convert(Month, Day, Year, Hours, Minutes, Seconds, Meridian, DSTmode)
645     time_t      Month;
646     time_t      Day;
647     time_t      Year;
648     time_t      Hours;
649     time_t      Minutes;
650     time_t      Seconds;
651     MERIDIAN    Meridian;
652     DSTMODE     DSTmode;
653 {
654     static int DaysInMonth[12] = {
655         31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
656     };
657     time_t      tod;
658     time_t      Julian;
659     int         i;
660
661     if (Year < 0)
662         Year = -Year;
663     if (Year < 100)
664         Year += 1900;
665     DaysInMonth[1] = Year % 4 == 0 && (Year % 100 != 0 || Year % 400 == 0)
666                     ? 29 : 28;
667     if (Year < EPOCH || Year > 1999
668      || Month < 1 || Month > 12
669      /* Lint fluff:  "conversion from long may lose accuracy" */
670      || Day < 1 || Day > DaysInMonth[(int)--Month])
671         return -1;
672
673     for (Julian = Day - 1, i = 0; i < Month; i++)
674         Julian += DaysInMonth[i];
675     for (i = EPOCH; i < Year; i++)
676         Julian += 365 + (i % 4 == 0);
677     Julian *= SECSPERDAY;
678     Julian += yyTimezone * 60L;
679     if ((tod = ToSeconds(Hours, Minutes, Seconds, Meridian)) < 0)
680         return -1;
681     Julian += tod;
682     if (DSTmode == DSTon
683      || (DSTmode == DSTmaybe && localtime(&Julian)->tm_isdst))
684         Julian -= 60 * 60;
685     return Julian;
686 }
687
688
689 static time_t
690 DSTcorrect(Start, Future)
691     time_t      Start;
692     time_t      Future;
693 {
694     time_t      StartDay;
695     time_t      FutureDay;
696
697     StartDay = (localtime(&Start)->tm_hour + 1) % 24;
698     FutureDay = (localtime(&Future)->tm_hour + 1) % 24;
699     return (Future - Start) + (StartDay - FutureDay) * 60L * 60L;
700 }
701
702
703 static time_t
704 RelativeDate(Start, DayOrdinal, DayNumber)
705     time_t      Start;
706     time_t      DayOrdinal;
707     time_t      DayNumber;
708 {
709     struct tm   *tm;
710     time_t      now;
711
712     now = Start;
713     tm = localtime(&now);
714     now += SECSPERDAY * ((DayNumber - tm->tm_wday + 7) % 7);
715     now += 7 * SECSPERDAY * (DayOrdinal <= 0 ? DayOrdinal : DayOrdinal - 1);
716     return DSTcorrect(Start, now);
717 }
718
719
720 static time_t
721 RelativeMonth(Start, RelMonth)
722     time_t      Start;
723     time_t      RelMonth;
724 {
725     struct tm   *tm;
726     time_t      Month;
727     time_t      Year;
728
729     if (RelMonth == 0)
730         return 0;
731     tm = localtime(&Start);
732     Month = 12 * tm->tm_year + tm->tm_mon + RelMonth;
733     Year = Month / 12;
734     Month = Month % 12 + 1;
735     return DSTcorrect(Start,
736             Convert(Month, (time_t)tm->tm_mday, Year,
737                 (time_t)tm->tm_hour, (time_t)tm->tm_min, (time_t)tm->tm_sec,
738                 MER24, DSTmaybe));
739 }
740
741
742 static int
743 LookupWord(buff)
744     char                *buff;
745 {
746     register char       *p;
747     register char       *q;
748     register const TABLE        *tp;
749     int                 i;
750     int                 abbrev;
751
752     /* Make it lowercase. */
753     for (p = buff; *p; p++)
754         if (isupper(*p))
755             *p = tolower(*p);
756
757     if (strcmp(buff, "am") == 0 || strcmp(buff, "a.m.") == 0) {
758         yylval.Meridian = MERam;
759         return tMERIDIAN;
760     }
761     if (strcmp(buff, "pm") == 0 || strcmp(buff, "p.m.") == 0) {
762         yylval.Meridian = MERpm;
763         return tMERIDIAN;
764     }
765
766     /* See if we have an abbreviation for a month. */
767     if (strlen(buff) == 3)
768         abbrev = 1;
769     else if (strlen(buff) == 4 && buff[3] == '.') {
770         abbrev = 1;
771         buff[3] = '\0';
772     }
773     else
774         abbrev = 0;
775
776     for (tp = MonthDayTable; tp->name; tp++) {
777         if (abbrev) {
778             if (strncmp(buff, tp->name, 3) == 0) {
779                 yylval.Number = tp->value;
780                 return tp->type;
781             }
782         }
783         else if (strcmp(buff, tp->name) == 0) {
784             yylval.Number = tp->value;
785             return tp->type;
786         }
787     }
788
789     for (tp = TimezoneTable; tp->name; tp++)
790         if (strcmp(buff, tp->name) == 0) {
791             yylval.Number = tp->value;
792             return tp->type;
793         }
794
795     if (strcmp(buff, "dst") == 0) 
796         return tDST;
797
798     for (tp = UnitsTable; tp->name; tp++)
799         if (strcmp(buff, tp->name) == 0) {
800             yylval.Number = tp->value;
801             return tp->type;
802         }
803
804     /* Strip off any plural and try the units table again. */
805     i = strlen(buff) - 1;
806     if (buff[i] == 's') {
807         buff[i] = '\0';
808         for (tp = UnitsTable; tp->name; tp++)
809             if (strcmp(buff, tp->name) == 0) {
810                 yylval.Number = tp->value;
811                 return tp->type;
812             }
813         buff[i] = 's';          /* Put back for "this" in OtherTable. */
814     }
815
816     for (tp = OtherTable; tp->name; tp++)
817         if (strcmp(buff, tp->name) == 0) {
818             yylval.Number = tp->value;
819             return tp->type;
820         }
821
822     /* Military timezones. */
823     if (buff[1] == '\0' && isalpha(*buff)) {
824         for (tp = MilitaryTable; tp->name; tp++)
825             if (strcmp(buff, tp->name) == 0) {
826                 yylval.Number = tp->value;
827                 return tp->type;
828             }
829     }
830
831     /* Drop out any periods and try the timezone table again. */
832     for (i = 0, p = q = buff; *q; q++)
833         if (*q != '.')
834             *p++ = *q;
835         else
836             i++;
837     *p = '\0';
838     if (i)
839         for (tp = TimezoneTable; tp->name; tp++)
840             if (strcmp(buff, tp->name) == 0) {
841                 yylval.Number = tp->value;
842                 return tp->type;
843             }
844
845     return tID;
846 }
847
848
849 static int
850 yylex()
851 {
852     register char       c;
853     register char       *p;
854     char                buff[20];
855     int                 Count;
856     int                 sign;
857
858     for ( ; ; ) {
859         while (isspace(*yyInput))
860             yyInput++;
861
862         if (isdigit(c = *yyInput) || c == '-' || c == '+') {
863             if (c == '-' || c == '+') {
864                 sign = c == '-' ? -1 : 1;
865                 if (!isdigit(*++yyInput))
866                     /* skip the '-' sign */
867                     continue;
868             }
869             else
870                 sign = 0;
871             for (yylval.Number = 0; isdigit(c = *yyInput++); )
872                 yylval.Number = 10 * yylval.Number + c - '0';
873             yyInput--;
874             if (sign < 0)
875                 yylval.Number = -yylval.Number;
876             return sign ? tSNUMBER : tUNUMBER;
877         }
878         if (isalpha(c)) {
879             for (p = buff; isalpha(c = *yyInput++) || c == '.'; )
880                 if (p < &buff[sizeof buff - 1])
881                     *p++ = c;
882             *p = '\0';
883             yyInput--;
884             return LookupWord(buff);
885         }
886         if (c != '(')
887             return *yyInput++;
888         Count = 0;
889         do {
890             c = *yyInput++;
891             if (c == '\0')
892                 return c;
893             if (c == '(')
894                 Count++;
895             else if (c == ')')
896                 Count--;
897         } while (Count > 0);
898     }
899 }
900
901
902 #define TM_YEAR_ORIGIN 1900
903
904 /* Yield A - B, measured in seconds.  */
905 static time_t
906 difftm(a, b)
907      struct tm *a, *b;
908 {
909   int ay = a->tm_year + (TM_YEAR_ORIGIN - 1);
910   int by = b->tm_year + (TM_YEAR_ORIGIN - 1);
911   return
912     (
913      (
914       (
915        /* difference in day of year */
916        a->tm_yday - b->tm_yday
917        /* + intervening leap days */
918        +  ((ay >> 2) - (by >> 2))
919        -  (ay/100 - by/100)
920        +  ((ay/100 >> 2) - (by/100 >> 2))
921        /* + difference in years * 365 */
922        +  (time_t)(ay-by) * 365
923        )*24 + (a->tm_hour - b->tm_hour)
924       )*60 + (a->tm_min - b->tm_min)
925      )*60 + (a->tm_sec - b->tm_sec);
926 }
927
928 time_t
929 get_date(p, now)
930     char                *p;
931     struct timeb        *now;
932 {
933     struct tm           *tm, gmt;
934     struct timeb        ftz;
935     time_t              Start;
936     time_t              tod;
937
938     yyInput = p;
939     if (now == NULL) {
940         now = &ftz;
941         (void)time(&ftz.time);
942
943         if (! (tm = gmtime (&ftz.time)))
944             return -1;
945         gmt = *tm;      /* Make a copy, in case localtime modifies *tm.  */
946         ftz.timezone = difftm (&gmt, localtime (&ftz.time)) / 60;
947     }
948
949     tm = localtime(&now->time);
950     yyYear = tm->tm_year;
951     yyMonth = tm->tm_mon + 1;
952     yyDay = tm->tm_mday;
953     yyTimezone = now->timezone;
954     yyDSTmode = DSTmaybe;
955     yyHour = 0;
956     yyMinutes = 0;
957     yySeconds = 0;
958     yyMeridian = MER24;
959     yyRelSeconds = 0;
960     yyRelMonth = 0;
961     yyHaveDate = 0;
962     yyHaveDay = 0;
963     yyHaveRel = 0;
964     yyHaveTime = 0;
965     yyHaveZone = 0;
966
967     if (yyparse()
968      || yyHaveTime > 1 || yyHaveZone > 1 || yyHaveDate > 1 || yyHaveDay > 1)
969         return -1;
970
971     if (yyHaveDate || yyHaveTime || yyHaveDay) {
972         Start = Convert(yyMonth, yyDay, yyYear, yyHour, yyMinutes, yySeconds,
973                     yyMeridian, yyDSTmode);
974         if (Start < 0)
975             return -1;
976     }
977     else {
978         Start = now->time;
979         if (!yyHaveRel)
980             Start -= ((tm->tm_hour * 60L + tm->tm_min) * 60L) + tm->tm_sec;
981     }
982
983     Start += yyRelSeconds;
984     Start += RelativeMonth(Start, yyRelMonth);
985
986     if (yyHaveDay && !yyHaveDate) {
987         tod = RelativeDate(Start, yyDayOrdinal, yyDayNumber);
988         Start += tod;
989     }
990
991     /* Have to do *something* with a legitimate -1 so it's distinguishable
992      * from the error return value.  (Alternately could set errno on error.) */
993     return Start == -1 ? 0 : Start;
994 }
995
996
997 #if     defined(TEST)
998
999 /* ARGSUSED */
1000 main(ac, av)
1001     int         ac;
1002     char        *av[];
1003 {
1004     char        buff[128];
1005     time_t      d;
1006
1007     (void)printf("Enter date, or blank line to exit.\n\t> ");
1008     (void)fflush(stdout);
1009     while (gets(buff) && buff[0]) {
1010         d = get_date(buff, (struct timeb *)NULL);
1011         if (d == -1)
1012             (void)printf("Bad format - couldn't convert.\n");
1013         else
1014             (void)printf("%s", ctime(&d));
1015         (void)printf("\t> ");
1016         (void)fflush(stdout);
1017     }
1018     exit(0);
1019     /* NOTREACHED */
1020 }
1021 #endif  /* defined(TEST) */