merge with 1.8.1h
[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) || (!defined(USG) && defined(HAVE_FTIME))
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         | tMONTH tUNUMBER {
319             yyMonth = $1;
320             yyDay = $2;
321         }
322         | tMONTH tUNUMBER ',' tUNUMBER {
323             yyMonth = $1;
324             yyDay = $2;
325             yyYear = $4;
326         }
327         | tUNUMBER tMONTH {
328             yyMonth = $2;
329             yyDay = $1;
330         }
331         | tUNUMBER tMONTH tUNUMBER {
332             yyMonth = $2;
333             yyDay = $1;
334             yyYear = $3;
335         }
336         ;
337
338 rel     : relunit tAGO {
339             yyRelSeconds = -yyRelSeconds;
340             yyRelMonth = -yyRelMonth;
341         }
342         | relunit
343         ;
344
345 relunit : tUNUMBER tMINUTE_UNIT {
346             yyRelSeconds += $1 * $2 * 60L;
347         }
348         | tSNUMBER tMINUTE_UNIT {
349             yyRelSeconds += $1 * $2 * 60L;
350         }
351         | tMINUTE_UNIT {
352             yyRelSeconds += $1 * 60L;
353         }
354         | tSNUMBER tSEC_UNIT {
355             yyRelSeconds += $1;
356         }
357         | tUNUMBER tSEC_UNIT {
358             yyRelSeconds += $1;
359         }
360         | tSEC_UNIT {
361             yyRelSeconds++;
362         }
363         | tSNUMBER tMONTH_UNIT {
364             yyRelMonth += $1 * $2;
365         }
366         | tUNUMBER tMONTH_UNIT {
367             yyRelMonth += $1 * $2;
368         }
369         | tMONTH_UNIT {
370             yyRelMonth += $1;
371         }
372         ;
373
374 number  : tUNUMBER {
375             if (yyHaveTime && yyHaveDate && !yyHaveRel)
376                 yyYear = $1;
377             else {
378                 if($1>10000) {
379                     time_t date_part;
380
381                     date_part= $1/10000;
382                     yyHaveDate++;
383                     yyDay= (date_part)%100;
384                     yyMonth= (date_part/100)%100;
385                     yyYear = date_part/10000;
386                 } 
387                 yyHaveTime++;
388                 if ($1 < 100) {
389                     yyHour = $1;
390                     yyMinutes = 0;
391                 }
392                 else {
393                     yyHour = $1 / 100;
394                     yyMinutes = $1 % 100;
395                 }
396                 yySeconds = 0;
397                 yyMeridian = MER24;
398             }
399         }
400         ;
401
402 o_merid : /* NULL */ {
403             $$ = MER24;
404         }
405         | tMERIDIAN {
406             $$ = $1;
407         }
408         ;
409
410 %%
411
412 /* Month and day table. */
413 static TABLE const MonthDayTable[] = {
414     { "january",        tMONTH,  1 },
415     { "february",       tMONTH,  2 },
416     { "march",          tMONTH,  3 },
417     { "april",          tMONTH,  4 },
418     { "may",            tMONTH,  5 },
419     { "june",           tMONTH,  6 },
420     { "july",           tMONTH,  7 },
421     { "august",         tMONTH,  8 },
422     { "september",      tMONTH,  9 },
423     { "sept",           tMONTH,  9 },
424     { "october",        tMONTH, 10 },
425     { "november",       tMONTH, 11 },
426     { "december",       tMONTH, 12 },
427     { "sunday",         tDAY, 0 },
428     { "monday",         tDAY, 1 },
429     { "tuesday",        tDAY, 2 },
430     { "tues",           tDAY, 2 },
431     { "wednesday",      tDAY, 3 },
432     { "wednes",         tDAY, 3 },
433     { "thursday",       tDAY, 4 },
434     { "thur",           tDAY, 4 },
435     { "thurs",          tDAY, 4 },
436     { "friday",         tDAY, 5 },
437     { "saturday",       tDAY, 6 },
438     { NULL }
439 };
440
441 /* Time units table. */
442 static TABLE const UnitsTable[] = {
443     { "year",           tMONTH_UNIT,    12 },
444     { "month",          tMONTH_UNIT,    1 },
445     { "fortnight",      tMINUTE_UNIT,   14 * 24 * 60 },
446     { "week",           tMINUTE_UNIT,   7 * 24 * 60 },
447     { "day",            tMINUTE_UNIT,   1 * 24 * 60 },
448     { "hour",           tMINUTE_UNIT,   60 },
449     { "minute",         tMINUTE_UNIT,   1 },
450     { "min",            tMINUTE_UNIT,   1 },
451     { "second",         tSEC_UNIT,      1 },
452     { "sec",            tSEC_UNIT,      1 },
453     { NULL }
454 };
455
456 /* Assorted relative-time words. */
457 static TABLE const OtherTable[] = {
458     { "tomorrow",       tMINUTE_UNIT,   1 * 24 * 60 },
459     { "yesterday",      tMINUTE_UNIT,   -1 * 24 * 60 },
460     { "today",          tMINUTE_UNIT,   0 },
461     { "now",            tMINUTE_UNIT,   0 },
462     { "last",           tUNUMBER,       -1 },
463     { "this",           tMINUTE_UNIT,   0 },
464     { "next",           tUNUMBER,       2 },
465     { "first",          tUNUMBER,       1 },
466 /*  { "second",         tUNUMBER,       2 }, */
467     { "third",          tUNUMBER,       3 },
468     { "fourth",         tUNUMBER,       4 },
469     { "fifth",          tUNUMBER,       5 },
470     { "sixth",          tUNUMBER,       6 },
471     { "seventh",        tUNUMBER,       7 },
472     { "eighth",         tUNUMBER,       8 },
473     { "ninth",          tUNUMBER,       9 },
474     { "tenth",          tUNUMBER,       10 },
475     { "eleventh",       tUNUMBER,       11 },
476     { "twelfth",        tUNUMBER,       12 },
477     { "ago",            tAGO,   1 },
478     { NULL }
479 };
480
481 /* The timezone table. */
482 /* Some of these are commented out because a time_t can't store a float. */
483 static TABLE const TimezoneTable[] = {
484     { "gmt",    tZONE,     HOUR( 0) },  /* Greenwich Mean */
485     { "ut",     tZONE,     HOUR( 0) },  /* Universal (Coordinated) */
486     { "utc",    tZONE,     HOUR( 0) },
487     { "wet",    tZONE,     HOUR( 0) },  /* Western European */
488     { "bst",    tDAYZONE,  HOUR( 0) },  /* British Summer */
489     { "wat",    tZONE,     HOUR( 1) },  /* West Africa */
490     { "at",     tZONE,     HOUR( 2) },  /* Azores */
491 #if     0
492     /* For completeness.  BST is also British Summer, and GST is
493      * also Guam Standard. */
494     { "bst",    tZONE,     HOUR( 3) },  /* Brazil Standard */
495     { "gst",    tZONE,     HOUR( 3) },  /* Greenland Standard */
496 #endif
497 #if 0
498     { "nft",    tZONE,     HOUR(3.5) }, /* Newfoundland */
499     { "nst",    tZONE,     HOUR(3.5) }, /* Newfoundland Standard */
500     { "ndt",    tDAYZONE,  HOUR(3.5) }, /* Newfoundland Daylight */
501 #endif
502     { "ast",    tZONE,     HOUR( 4) },  /* Atlantic Standard */
503     { "adt",    tDAYZONE,  HOUR( 4) },  /* Atlantic Daylight */
504     { "est",    tZONE,     HOUR( 5) },  /* Eastern Standard */
505     { "edt",    tDAYZONE,  HOUR( 5) },  /* Eastern Daylight */
506     { "cst",    tZONE,     HOUR( 6) },  /* Central Standard */
507     { "cdt",    tDAYZONE,  HOUR( 6) },  /* Central Daylight */
508     { "mst",    tZONE,     HOUR( 7) },  /* Mountain Standard */
509     { "mdt",    tDAYZONE,  HOUR( 7) },  /* Mountain Daylight */
510     { "pst",    tZONE,     HOUR( 8) },  /* Pacific Standard */
511     { "pdt",    tDAYZONE,  HOUR( 8) },  /* Pacific Daylight */
512     { "yst",    tZONE,     HOUR( 9) },  /* Yukon Standard */
513     { "ydt",    tDAYZONE,  HOUR( 9) },  /* Yukon Daylight */
514     { "hst",    tZONE,     HOUR(10) },  /* Hawaii Standard */
515     { "hdt",    tDAYZONE,  HOUR(10) },  /* Hawaii Daylight */
516     { "cat",    tZONE,     HOUR(10) },  /* Central Alaska */
517     { "ahst",   tZONE,     HOUR(10) },  /* Alaska-Hawaii Standard */
518     { "nt",     tZONE,     HOUR(11) },  /* Nome */
519     { "idlw",   tZONE,     HOUR(12) },  /* International Date Line West */
520     { "cet",    tZONE,     -HOUR(1) },  /* Central European */
521     { "met",    tZONE,     -HOUR(1) },  /* Middle European */
522     { "mewt",   tZONE,     -HOUR(1) },  /* Middle European Winter */
523     { "mest",   tDAYZONE,  -HOUR(1) },  /* Middle European Summer */
524     { "swt",    tZONE,     -HOUR(1) },  /* Swedish Winter */
525     { "sst",    tDAYZONE,  -HOUR(1) },  /* Swedish Summer */
526     { "fwt",    tZONE,     -HOUR(1) },  /* French Winter */
527     { "fst",    tDAYZONE,  -HOUR(1) },  /* French Summer */
528     { "eet",    tZONE,     -HOUR(2) },  /* Eastern Europe, USSR Zone 1 */
529     { "bt",     tZONE,     -HOUR(3) },  /* Baghdad, USSR Zone 2 */
530 #if 0
531     { "it",     tZONE,     -HOUR(3.5) },/* Iran */
532 #endif
533     { "zp4",    tZONE,     -HOUR(4) },  /* USSR Zone 3 */
534     { "zp5",    tZONE,     -HOUR(5) },  /* USSR Zone 4 */
535 #if 0
536     { "ist",    tZONE,     -HOUR(5.5) },/* Indian Standard */
537 #endif
538     { "zp6",    tZONE,     -HOUR(6) },  /* USSR Zone 5 */
539 #if     0
540     /* For completeness.  NST is also Newfoundland Stanard, and SST is
541      * also Swedish Summer. */
542     { "nst",    tZONE,     -HOUR(6.5) },/* North Sumatra */
543     { "sst",    tZONE,     -HOUR(7) },  /* South Sumatra, USSR Zone 6 */
544 #endif  /* 0 */
545     { "wast",   tZONE,     -HOUR(7) },  /* West Australian Standard */
546     { "wadt",   tDAYZONE,  -HOUR(7) },  /* West Australian Daylight */
547 #if 0
548     { "jt",     tZONE,     -HOUR(7.5) },/* Java (3pm in Cronusland!) */
549 #endif
550     { "cct",    tZONE,     -HOUR(8) },  /* China Coast, USSR Zone 7 */
551     { "jst",    tZONE,     -HOUR(9) },  /* Japan Standard, USSR Zone 8 */
552 #if 0
553     { "cast",   tZONE,     -HOUR(9.5) },/* Central Australian Standard */
554     { "cadt",   tDAYZONE,  -HOUR(9.5) },/* Central Australian Daylight */
555 #endif
556     { "east",   tZONE,     -HOUR(10) }, /* Eastern Australian Standard */
557     { "eadt",   tDAYZONE,  -HOUR(10) }, /* Eastern Australian Daylight */
558     { "gst",    tZONE,     -HOUR(10) }, /* Guam Standard, USSR Zone 9 */
559     { "nzt",    tZONE,     -HOUR(12) }, /* New Zealand */
560     { "nzst",   tZONE,     -HOUR(12) }, /* New Zealand Standard */
561     { "nzdt",   tDAYZONE,  -HOUR(12) }, /* New Zealand Daylight */
562     { "idle",   tZONE,     -HOUR(12) }, /* International Date Line East */
563     {  NULL  }
564 };
565
566 /* Military timezone table. */
567 static TABLE const MilitaryTable[] = {
568     { "a",      tZONE,  HOUR(  1) },
569     { "b",      tZONE,  HOUR(  2) },
570     { "c",      tZONE,  HOUR(  3) },
571     { "d",      tZONE,  HOUR(  4) },
572     { "e",      tZONE,  HOUR(  5) },
573     { "f",      tZONE,  HOUR(  6) },
574     { "g",      tZONE,  HOUR(  7) },
575     { "h",      tZONE,  HOUR(  8) },
576     { "i",      tZONE,  HOUR(  9) },
577     { "k",      tZONE,  HOUR( 10) },
578     { "l",      tZONE,  HOUR( 11) },
579     { "m",      tZONE,  HOUR( 12) },
580     { "n",      tZONE,  HOUR(- 1) },
581     { "o",      tZONE,  HOUR(- 2) },
582     { "p",      tZONE,  HOUR(- 3) },
583     { "q",      tZONE,  HOUR(- 4) },
584     { "r",      tZONE,  HOUR(- 5) },
585     { "s",      tZONE,  HOUR(- 6) },
586     { "t",      tZONE,  HOUR(- 7) },
587     { "u",      tZONE,  HOUR(- 8) },
588     { "v",      tZONE,  HOUR(- 9) },
589     { "w",      tZONE,  HOUR(-10) },
590     { "x",      tZONE,  HOUR(-11) },
591     { "y",      tZONE,  HOUR(-12) },
592     { "z",      tZONE,  HOUR(  0) },
593     { NULL }
594 };
595
596 \f
597
598
599 /* ARGSUSED */
600 static int
601 yyerror(s)
602     char        *s;
603 {
604   return 0;
605 }
606
607
608 static time_t
609 ToSeconds(Hours, Minutes, Seconds, Meridian)
610     time_t      Hours;
611     time_t      Minutes;
612     time_t      Seconds;
613     MERIDIAN    Meridian;
614 {
615     if (Minutes < 0 || Minutes > 59 || Seconds < 0 || Seconds > 59)
616         return -1;
617     switch (Meridian) {
618     case MER24:
619         if (Hours < 0 || Hours > 23)
620             return -1;
621         return (Hours * 60L + Minutes) * 60L + Seconds;
622     case MERam:
623         if (Hours < 1 || Hours > 12)
624             return -1;
625         return (Hours * 60L + Minutes) * 60L + Seconds;
626     case MERpm:
627         if (Hours < 1 || Hours > 12)
628             return -1;
629         return ((Hours + 12) * 60L + Minutes) * 60L + Seconds;
630     }
631     /* NOTREACHED */
632 }
633
634
635 static time_t
636 Convert(Month, Day, Year, Hours, Minutes, Seconds, Meridian, DSTmode)
637     time_t      Month;
638     time_t      Day;
639     time_t      Year;
640     time_t      Hours;
641     time_t      Minutes;
642     time_t      Seconds;
643     MERIDIAN    Meridian;
644     DSTMODE     DSTmode;
645 {
646     static int DaysInMonth[12] = {
647         31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
648     };
649     time_t      tod;
650     time_t      Julian;
651     int         i;
652
653     if (Year < 0)
654         Year = -Year;
655     if (Year < 100)
656         Year += 1900;
657     DaysInMonth[1] = Year % 4 == 0 && (Year % 100 != 0 || Year % 400 == 0)
658                     ? 29 : 28;
659     if (Year < EPOCH || Year > 1999
660      || Month < 1 || Month > 12
661      /* Lint fluff:  "conversion from long may lose accuracy" */
662      || Day < 1 || Day > DaysInMonth[(int)--Month])
663         return -1;
664
665     for (Julian = Day - 1, i = 0; i < Month; i++)
666         Julian += DaysInMonth[i];
667     for (i = EPOCH; i < Year; i++)
668         Julian += 365 + (i % 4 == 0);
669     Julian *= SECSPERDAY;
670     Julian += yyTimezone * 60L;
671     if ((tod = ToSeconds(Hours, Minutes, Seconds, Meridian)) < 0)
672         return -1;
673     Julian += tod;
674     if (DSTmode == DSTon
675      || (DSTmode == DSTmaybe && localtime(&Julian)->tm_isdst))
676         Julian -= 60 * 60;
677     return Julian;
678 }
679
680
681 static time_t
682 DSTcorrect(Start, Future)
683     time_t      Start;
684     time_t      Future;
685 {
686     time_t      StartDay;
687     time_t      FutureDay;
688
689     StartDay = (localtime(&Start)->tm_hour + 1) % 24;
690     FutureDay = (localtime(&Future)->tm_hour + 1) % 24;
691     return (Future - Start) + (StartDay - FutureDay) * 60L * 60L;
692 }
693
694
695 static time_t
696 RelativeDate(Start, DayOrdinal, DayNumber)
697     time_t      Start;
698     time_t      DayOrdinal;
699     time_t      DayNumber;
700 {
701     struct tm   *tm;
702     time_t      now;
703
704     now = Start;
705     tm = localtime(&now);
706     now += SECSPERDAY * ((DayNumber - tm->tm_wday + 7) % 7);
707     now += 7 * SECSPERDAY * (DayOrdinal <= 0 ? DayOrdinal : DayOrdinal - 1);
708     return DSTcorrect(Start, now);
709 }
710
711
712 static time_t
713 RelativeMonth(Start, RelMonth)
714     time_t      Start;
715     time_t      RelMonth;
716 {
717     struct tm   *tm;
718     time_t      Month;
719     time_t      Year;
720
721     if (RelMonth == 0)
722         return 0;
723     tm = localtime(&Start);
724     Month = 12 * tm->tm_year + tm->tm_mon + RelMonth;
725     Year = Month / 12;
726     Month = Month % 12 + 1;
727     return DSTcorrect(Start,
728             Convert(Month, (time_t)tm->tm_mday, Year,
729                 (time_t)tm->tm_hour, (time_t)tm->tm_min, (time_t)tm->tm_sec,
730                 MER24, DSTmaybe));
731 }
732
733
734 static int
735 LookupWord(buff)
736     char                *buff;
737 {
738     register char       *p;
739     register char       *q;
740     register const TABLE        *tp;
741     int                 i;
742     int                 abbrev;
743
744     /* Make it lowercase. */
745     for (p = buff; *p; p++)
746         if (isupper(*p))
747             *p = tolower(*p);
748
749     if (strcmp(buff, "am") == 0 || strcmp(buff, "a.m.") == 0) {
750         yylval.Meridian = MERam;
751         return tMERIDIAN;
752     }
753     if (strcmp(buff, "pm") == 0 || strcmp(buff, "p.m.") == 0) {
754         yylval.Meridian = MERpm;
755         return tMERIDIAN;
756     }
757
758     /* See if we have an abbreviation for a month. */
759     if (strlen(buff) == 3)
760         abbrev = 1;
761     else if (strlen(buff) == 4 && buff[3] == '.') {
762         abbrev = 1;
763         buff[3] = '\0';
764     }
765     else
766         abbrev = 0;
767
768     for (tp = MonthDayTable; tp->name; tp++) {
769         if (abbrev) {
770             if (strncmp(buff, tp->name, 3) == 0) {
771                 yylval.Number = tp->value;
772                 return tp->type;
773             }
774         }
775         else if (strcmp(buff, tp->name) == 0) {
776             yylval.Number = tp->value;
777             return tp->type;
778         }
779     }
780
781     for (tp = TimezoneTable; tp->name; tp++)
782         if (strcmp(buff, tp->name) == 0) {
783             yylval.Number = tp->value;
784             return tp->type;
785         }
786
787     if (strcmp(buff, "dst") == 0) 
788         return tDST;
789
790     for (tp = UnitsTable; tp->name; tp++)
791         if (strcmp(buff, tp->name) == 0) {
792             yylval.Number = tp->value;
793             return tp->type;
794         }
795
796     /* Strip off any plural and try the units table again. */
797     i = strlen(buff) - 1;
798     if (buff[i] == 's') {
799         buff[i] = '\0';
800         for (tp = UnitsTable; tp->name; tp++)
801             if (strcmp(buff, tp->name) == 0) {
802                 yylval.Number = tp->value;
803                 return tp->type;
804             }
805         buff[i] = 's';          /* Put back for "this" in OtherTable. */
806     }
807
808     for (tp = OtherTable; tp->name; tp++)
809         if (strcmp(buff, tp->name) == 0) {
810             yylval.Number = tp->value;
811             return tp->type;
812         }
813
814     /* Military timezones. */
815     if (buff[1] == '\0' && isalpha(*buff)) {
816         for (tp = MilitaryTable; tp->name; tp++)
817             if (strcmp(buff, tp->name) == 0) {
818                 yylval.Number = tp->value;
819                 return tp->type;
820             }
821     }
822
823     /* Drop out any periods and try the timezone table again. */
824     for (i = 0, p = q = buff; *q; q++)
825         if (*q != '.')
826             *p++ = *q;
827         else
828             i++;
829     *p = '\0';
830     if (i)
831         for (tp = TimezoneTable; tp->name; tp++)
832             if (strcmp(buff, tp->name) == 0) {
833                 yylval.Number = tp->value;
834                 return tp->type;
835             }
836
837     return tID;
838 }
839
840
841 static int
842 yylex()
843 {
844     register char       c;
845     register char       *p;
846     char                buff[20];
847     int                 Count;
848     int                 sign;
849
850     for ( ; ; ) {
851         while (isspace(*yyInput))
852             yyInput++;
853
854         if (isdigit(c = *yyInput) || c == '-' || c == '+') {
855             if (c == '-' || c == '+') {
856                 sign = c == '-' ? -1 : 1;
857                 if (!isdigit(*++yyInput))
858                     /* skip the '-' sign */
859                     continue;
860             }
861             else
862                 sign = 0;
863             for (yylval.Number = 0; isdigit(c = *yyInput++); )
864                 yylval.Number = 10 * yylval.Number + c - '0';
865             yyInput--;
866             if (sign < 0)
867                 yylval.Number = -yylval.Number;
868             return sign ? tSNUMBER : tUNUMBER;
869         }
870         if (isalpha(c)) {
871             for (p = buff; isalpha(c = *yyInput++) || c == '.'; )
872                 if (p < &buff[sizeof buff - 1])
873                     *p++ = c;
874             *p = '\0';
875             yyInput--;
876             return LookupWord(buff);
877         }
878         if (c != '(')
879             return *yyInput++;
880         Count = 0;
881         do {
882             c = *yyInput++;
883             if (c == '\0')
884                 return c;
885             if (c == '(')
886                 Count++;
887             else if (c == ')')
888                 Count--;
889         } while (Count > 0);
890     }
891 }
892
893
894 #define TM_YEAR_ORIGIN 1900
895
896 /* Yield A - B, measured in seconds.  */
897 static time_t
898 difftm(a, b)
899      struct tm *a, *b;
900 {
901   int ay = a->tm_year + (TM_YEAR_ORIGIN - 1);
902   int by = b->tm_year + (TM_YEAR_ORIGIN - 1);
903   return
904     (
905      (
906       (
907        /* difference in day of year */
908        a->tm_yday - b->tm_yday
909        /* + intervening leap days */
910        +  ((ay >> 2) - (by >> 2))
911        -  (ay/100 - by/100)
912        +  ((ay/100 >> 2) - (by/100 >> 2))
913        /* + difference in years * 365 */
914        +  (time_t)(ay-by) * 365
915        )*24 + (a->tm_hour - b->tm_hour)
916       )*60 + (a->tm_min - b->tm_min)
917      )*60 + (a->tm_sec - b->tm_sec);
918 }
919
920 time_t
921 get_date(p, now)
922     char                *p;
923     struct timeb        *now;
924 {
925     struct tm           *tm, gmt;
926     struct timeb        ftz;
927     time_t              Start;
928     time_t              tod;
929
930     yyInput = p;
931     if (now == NULL) {
932         now = &ftz;
933         (void)time(&ftz.time);
934
935         if (! (tm = gmtime (&ftz.time)))
936             return -1;
937         gmt = *tm;      /* Make a copy, in case localtime modifies *tm.  */
938         ftz.timezone = difftm (&gmt, localtime (&ftz.time)) / 60;
939     }
940
941     tm = localtime(&now->time);
942     yyYear = tm->tm_year;
943     yyMonth = tm->tm_mon + 1;
944     yyDay = tm->tm_mday;
945     yyTimezone = now->timezone;
946     yyDSTmode = DSTmaybe;
947     yyHour = 0;
948     yyMinutes = 0;
949     yySeconds = 0;
950     yyMeridian = MER24;
951     yyRelSeconds = 0;
952     yyRelMonth = 0;
953     yyHaveDate = 0;
954     yyHaveDay = 0;
955     yyHaveRel = 0;
956     yyHaveTime = 0;
957     yyHaveZone = 0;
958
959     if (yyparse()
960      || yyHaveTime > 1 || yyHaveZone > 1 || yyHaveDate > 1 || yyHaveDay > 1)
961         return -1;
962
963     if (yyHaveDate || yyHaveTime || yyHaveDay) {
964         Start = Convert(yyMonth, yyDay, yyYear, yyHour, yyMinutes, yySeconds,
965                     yyMeridian, yyDSTmode);
966         if (Start < 0)
967             return -1;
968     }
969     else {
970         Start = now->time;
971         if (!yyHaveRel)
972             Start -= ((tm->tm_hour * 60L + tm->tm_min) * 60L) + tm->tm_sec;
973     }
974
975     Start += yyRelSeconds;
976     Start += RelativeMonth(Start, yyRelMonth);
977
978     if (yyHaveDay && !yyHaveDate) {
979         tod = RelativeDate(Start, yyDayOrdinal, yyDayNumber);
980         Start += tod;
981     }
982
983     /* Have to do *something* with a legitimate -1 so it's distinguishable
984      * from the error return value.  (Alternately could set errno on error.) */
985     return Start == -1 ? 0 : Start;
986 }
987
988
989 #if     defined(TEST)
990
991 /* ARGSUSED */
992 main(ac, av)
993     int         ac;
994     char        *av[];
995 {
996     char        buff[128];
997     time_t      d;
998
999     (void)printf("Enter date, or blank line to exit.\n\t> ");
1000     (void)fflush(stdout);
1001     while (gets(buff) && buff[0]) {
1002         d = get_date(buff, (struct timeb *)NULL);
1003         if (d == -1)
1004             (void)printf("Bad format - couldn't convert.\n");
1005         else
1006             (void)printf("%s", ctime(&d));
1007         (void)printf("\t> ");
1008         (void)fflush(stdout);
1009     }
1010     exit(0);
1011     /* NOTREACHED */
1012 }
1013 #endif  /* defined(TEST) */