Initial revision
[gnulib.git] / lib / getdate.y
1 %{
2 /* $Revision: 2.1 $
3 **
4 **  Originally written by Steven M. Bellovin <smb@research.att.com> while
5 **  at the University of North Carolina at Chapel Hill.  Later tweaked by
6 **  a couple of people on Usenet.  Completely overhauled by Rich $alz
7 **  <rsalz@bbn.com> and Jim Berets <jberets@bbn.com> in August, 1990;
8 **  send any email to Rich.
9 **
10 **  This grammar has eight shift/reduce conflicts.
11 **
12 **  This code is in the public domain and has no copyright.
13 */
14 /* SUPPRESS 287 on yaccpar_sccsid *//* Unusd static variable */
15 /* SUPPRESS 288 on yyerrlab *//* Label unused */
16
17 #ifdef __GNUC__
18 #define alloca __builtin_alloca
19 #else
20 #ifdef sparc
21 #include <alloca.h>
22 #else
23 #ifdef _AIX /* for Bison */
24  #pragma alloca
25 #else
26 char *alloca ();
27 #endif
28 #endif
29 #endif
30
31 #include <stdio.h>
32 #include <ctype.h>
33
34 /* The code at the top of get_date which figures out the offset of the
35    current time zone checks various CPP symbols to see if special
36    tricks are need, but defaults to using the gettimeofday system call.
37    Include <sys/time.h> if that will be used.  */
38
39 #if !defined (USG) && !defined (sgi) && !defined (__386BSD__)
40 #include <sys/time.h>
41 #endif
42
43 #if     defined(vms)
44
45 #include <types.h>
46 #include <time.h>
47
48 #else
49
50 #include <sys/types.h>
51
52 #if     defined(USG) || !defined(HAVE_FTIME)
53 /*
54 **  If you need to do a tzset() call to set the
55 **  timezone, and don't have ftime().
56 */
57 struct timeb {
58     time_t              time;           /* Seconds since the epoch      */
59     unsigned short      millitm;        /* Field not used               */
60     short               timezone;
61     short               dstflag;        /* Field not used               */
62 };
63
64 #else
65
66 #include <sys/timeb.h>
67
68 #endif  /* defined(USG) && !defined(HAVE_FTIME) */
69
70 #if     defined(BSD4_2) || defined(BSD4_1C) || (defined (hp9000) && !defined (hpux))
71 #include <sys/time.h>
72 #else
73 #if defined(_AIX)
74 #include <sys/time.h>
75 #endif
76 #include <time.h>
77 #endif  /* defined(BSD4_2) */
78
79 #endif  /* defined(vms) */
80
81 #if defined (STDC_HEADERS) || defined (USG)
82 #include <string.h>
83 #endif
84
85 #if sgi
86 #undef timezone
87 #endif
88
89 extern struct tm        *localtime();
90
91 #define yyparse getdate_yyparse
92 #define yylex getdate_yylex
93 #define yyerror getdate_yyerror
94
95 #if     !defined(lint) && !defined(SABER)
96 static char RCS[] =
97         "$Header: str2date.y,v 2.1 90/09/06 08:15:06 cronan Exp $";
98 #endif  /* !defined(lint) && !defined(SABER) */
99
100
101 #define EPOCH           1970
102 #define HOUR(x)         ((time_t)(x) * 60)
103 #define SECSPERDAY      (24L * 60L * 60L)
104
105
106 /*
107 **  An entry in the lexical lookup table.
108 */
109 typedef struct _TABLE {
110     char        *name;
111     int         type;
112     time_t      value;
113 } TABLE;
114
115
116 /*
117 **  Daylight-savings mode:  on, off, or not yet known.
118 */
119 typedef enum _DSTMODE {
120     DSTon, DSToff, DSTmaybe
121 } DSTMODE;
122
123 /*
124 **  Meridian:  am, pm, or 24-hour style.
125 */
126 typedef enum _MERIDIAN {
127     MERam, MERpm, MER24
128 } MERIDIAN;
129
130
131 /*
132 **  Global variables.  We could get rid of most of these by using a good
133 **  union as the yacc stack.  (This routine was originally written before
134 **  yacc had the %union construct.)  Maybe someday; right now we only use
135 **  the %union very rarely.
136 */
137 static char     *yyInput;
138 static DSTMODE  yyDSTmode;
139 static time_t   yyDayOrdinal;
140 static time_t   yyDayNumber;
141 static int      yyHaveDate;
142 static int      yyHaveDay;
143 static int      yyHaveRel;
144 static int      yyHaveTime;
145 static int      yyHaveZone;
146 static time_t   yyTimezone;
147 static time_t   yyDay;
148 static time_t   yyHour;
149 static time_t   yyMinutes;
150 static time_t   yyMonth;
151 static time_t   yySeconds;
152 static time_t   yyYear;
153 static MERIDIAN yyMeridian;
154 static time_t   yyRelMonth;
155 static time_t   yyRelSeconds;
156
157 %}
158
159 %union {
160     time_t              Number;
161     enum _MERIDIAN      Meridian;
162 }
163
164 %token  tAGO tDAY tDAYZONE tID tMERIDIAN tMINUTE_UNIT tMONTH tMONTH_UNIT
165 %token  tSEC_UNIT tSNUMBER tUNUMBER tZONE tDST
166
167 %type   <Number>        tDAY tDAYZONE tMINUTE_UNIT tMONTH tMONTH_UNIT
168 %type   <Number>        tSEC_UNIT tSNUMBER tUNUMBER tZONE
169 %type   <Meridian>      tMERIDIAN o_merid
170
171 %%
172
173 spec    : /* NULL */
174         | spec item
175         ;
176
177 item    : time {
178             yyHaveTime++;
179         }
180         | zone {
181             yyHaveZone++;
182         }
183         | date {
184             yyHaveDate++;
185         }
186         | day {
187             yyHaveDay++;
188         }
189         | rel {
190             yyHaveRel++;
191         }
192         | number
193         ;
194
195 time    : tUNUMBER tMERIDIAN {
196             yyHour = $1;
197             yyMinutes = 0;
198             yySeconds = 0;
199             yyMeridian = $2;
200         }
201         | tUNUMBER ':' tUNUMBER o_merid {
202             yyHour = $1;
203             yyMinutes = $3;
204             yySeconds = 0;
205             yyMeridian = $4;
206         }
207         | tUNUMBER ':' tUNUMBER tSNUMBER {
208             yyHour = $1;
209             yyMinutes = $3;
210             yyMeridian = MER24;
211             yyDSTmode = DSToff;
212             yyTimezone = - ($4 % 100 + ($4 / 100) * 60);
213         }
214         | tUNUMBER ':' tUNUMBER ':' tUNUMBER o_merid {
215             yyHour = $1;
216             yyMinutes = $3;
217             yySeconds = $5;
218             yyMeridian = $6;
219         }
220         | tUNUMBER ':' tUNUMBER ':' tUNUMBER tSNUMBER {
221             yyHour = $1;
222             yyMinutes = $3;
223             yySeconds = $5;
224             yyMeridian = MER24;
225             yyDSTmode = DSToff;
226             yyTimezone = - ($6 % 100 + ($6 / 100) * 60);
227         }
228         ;
229
230 zone    : tZONE {
231             yyTimezone = $1;
232             yyDSTmode = DSToff;
233         }
234         | tDAYZONE {
235             yyTimezone = $1;
236             yyDSTmode = DSTon;
237         }
238         |
239           tZONE tDST {
240             yyTimezone = $1;
241             yyDSTmode = DSTon;
242         }
243         ;
244
245 day     : tDAY {
246             yyDayOrdinal = 1;
247             yyDayNumber = $1;
248         }
249         | tDAY ',' {
250             yyDayOrdinal = 1;
251             yyDayNumber = $1;
252         }
253         | tUNUMBER tDAY {
254             yyDayOrdinal = $1;
255             yyDayNumber = $2;
256         }
257         ;
258
259 date    : tUNUMBER '/' tUNUMBER {
260             yyMonth = $1;
261             yyDay = $3;
262         }
263         | tUNUMBER '/' tUNUMBER '/' tUNUMBER {
264             yyMonth = $1;
265             yyDay = $3;
266             yyYear = $5;
267         }
268         | tUNUMBER tSNUMBER tSNUMBER {
269             /* ISO 8601 format.  yyyy-mm-dd.  */
270             yyYear = $1;
271             yyMonth = -$2;
272             yyDay = -$3;
273         }
274         | tMONTH tUNUMBER {
275             yyMonth = $1;
276             yyDay = $2;
277         }
278         | tMONTH tUNUMBER ',' tUNUMBER {
279             yyMonth = $1;
280             yyDay = $2;
281             yyYear = $4;
282         }
283         | tUNUMBER tMONTH {
284             yyMonth = $2;
285             yyDay = $1;
286         }
287         | tUNUMBER tMONTH tUNUMBER {
288             yyMonth = $2;
289             yyDay = $1;
290             yyYear = $3;
291         }
292         ;
293
294 rel     : relunit tAGO {
295             yyRelSeconds = -yyRelSeconds;
296             yyRelMonth = -yyRelMonth;
297         }
298         | relunit
299         ;
300
301 relunit : tUNUMBER tMINUTE_UNIT {
302             yyRelSeconds += $1 * $2 * 60L;
303         }
304         | tSNUMBER tMINUTE_UNIT {
305             yyRelSeconds += $1 * $2 * 60L;
306         }
307         | tMINUTE_UNIT {
308             yyRelSeconds += $1 * 60L;
309         }
310         | tSNUMBER tSEC_UNIT {
311             yyRelSeconds += $1;
312         }
313         | tUNUMBER tSEC_UNIT {
314             yyRelSeconds += $1;
315         }
316         | tSEC_UNIT {
317             yyRelSeconds++;
318         }
319         | tSNUMBER tMONTH_UNIT {
320             yyRelMonth += $1 * $2;
321         }
322         | tUNUMBER tMONTH_UNIT {
323             yyRelMonth += $1 * $2;
324         }
325         | tMONTH_UNIT {
326             yyRelMonth += $1;
327         }
328         ;
329
330 number  : tUNUMBER {
331             if (yyHaveTime && yyHaveDate && !yyHaveRel)
332                 yyYear = $1;
333             else {
334                 if($1>10000) {
335                     time_t date_part;
336
337                     date_part= $1/10000;
338                     yyHaveDate++;
339                     yyDay= (date_part)%100;
340                     yyMonth= (date_part/100)%100;
341                     yyYear = date_part/10000;
342                 } 
343                 yyHaveTime++;
344                 if ($1 < 100) {
345                     yyHour = $1;
346                     yyMinutes = 0;
347                 }
348                 else {
349                     yyHour = $1 / 100;
350                     yyMinutes = $1 % 100;
351                 }
352                 yySeconds = 0;
353                 yyMeridian = MER24;
354             }
355         }
356         ;
357
358 o_merid : /* NULL */ {
359             $$ = MER24;
360         }
361         | tMERIDIAN {
362             $$ = $1;
363         }
364         ;
365
366 %%
367
368 /* Month and day table. */
369 static TABLE    MonthDayTable[] = {
370     { "january",        tMONTH,  1 },
371     { "february",       tMONTH,  2 },
372     { "march",          tMONTH,  3 },
373     { "april",          tMONTH,  4 },
374     { "may",            tMONTH,  5 },
375     { "june",           tMONTH,  6 },
376     { "july",           tMONTH,  7 },
377     { "august",         tMONTH,  8 },
378     { "september",      tMONTH,  9 },
379     { "sept",           tMONTH,  9 },
380     { "october",        tMONTH, 10 },
381     { "november",       tMONTH, 11 },
382     { "december",       tMONTH, 12 },
383     { "sunday",         tDAY, 0 },
384     { "monday",         tDAY, 1 },
385     { "tuesday",        tDAY, 2 },
386     { "tues",           tDAY, 2 },
387     { "wednesday",      tDAY, 3 },
388     { "wednes",         tDAY, 3 },
389     { "thursday",       tDAY, 4 },
390     { "thur",           tDAY, 4 },
391     { "thurs",          tDAY, 4 },
392     { "friday",         tDAY, 5 },
393     { "saturday",       tDAY, 6 },
394     { NULL }
395 };
396
397 /* Time units table. */
398 static TABLE    UnitsTable[] = {
399     { "year",           tMONTH_UNIT,    12 },
400     { "month",          tMONTH_UNIT,    1 },
401     { "fortnight",      tMINUTE_UNIT,   14 * 24 * 60 },
402     { "week",           tMINUTE_UNIT,   7 * 24 * 60 },
403     { "day",            tMINUTE_UNIT,   1 * 24 * 60 },
404     { "hour",           tMINUTE_UNIT,   60 },
405     { "minute",         tMINUTE_UNIT,   1 },
406     { "min",            tMINUTE_UNIT,   1 },
407     { "second",         tSEC_UNIT,      1 },
408     { "sec",            tSEC_UNIT,      1 },
409     { NULL }
410 };
411
412 /* Assorted relative-time words. */
413 static TABLE    OtherTable[] = {
414     { "tomorrow",       tMINUTE_UNIT,   1 * 24 * 60 },
415     { "yesterday",      tMINUTE_UNIT,   -1 * 24 * 60 },
416     { "today",          tMINUTE_UNIT,   0 },
417     { "now",            tMINUTE_UNIT,   0 },
418     { "last",           tUNUMBER,       -1 },
419     { "this",           tMINUTE_UNIT,   0 },
420     { "next",           tUNUMBER,       2 },
421     { "first",          tUNUMBER,       1 },
422 /*  { "second",         tUNUMBER,       2 }, */
423     { "third",          tUNUMBER,       3 },
424     { "fourth",         tUNUMBER,       4 },
425     { "fifth",          tUNUMBER,       5 },
426     { "sixth",          tUNUMBER,       6 },
427     { "seventh",        tUNUMBER,       7 },
428     { "eighth",         tUNUMBER,       8 },
429     { "ninth",          tUNUMBER,       9 },
430     { "tenth",          tUNUMBER,       10 },
431     { "eleventh",       tUNUMBER,       11 },
432     { "twelfth",        tUNUMBER,       12 },
433     { "ago",            tAGO,   1 },
434     { NULL }
435 };
436
437 /* The timezone table. */
438 /* Some of these are commented out because a time_t can't store a float. */
439 static TABLE    TimezoneTable[] = {
440     { "gmt",    tZONE,     HOUR( 0) },  /* Greenwich Mean */
441     { "ut",     tZONE,     HOUR( 0) },  /* Universal (Coordinated) */
442     { "utc",    tZONE,     HOUR( 0) },
443     { "wet",    tZONE,     HOUR( 0) },  /* Western European */
444     { "bst",    tDAYZONE,  HOUR( 0) },  /* British Summer */
445     { "wat",    tZONE,     HOUR( 1) },  /* West Africa */
446     { "at",     tZONE,     HOUR( 2) },  /* Azores */
447 #if     0
448     /* For completeness.  BST is also British Summer, and GST is
449      * also Guam Standard. */
450     { "bst",    tZONE,     HOUR( 3) },  /* Brazil Standard */
451     { "gst",    tZONE,     HOUR( 3) },  /* Greenland Standard */
452 #endif
453 #if 0
454     { "nft",    tZONE,     HOUR(3.5) }, /* Newfoundland */
455     { "nst",    tZONE,     HOUR(3.5) }, /* Newfoundland Standard */
456     { "ndt",    tDAYZONE,  HOUR(3.5) }, /* Newfoundland Daylight */
457 #endif
458     { "ast",    tZONE,     HOUR( 4) },  /* Atlantic Standard */
459     { "adt",    tDAYZONE,  HOUR( 4) },  /* Atlantic Daylight */
460     { "est",    tZONE,     HOUR( 5) },  /* Eastern Standard */
461     { "edt",    tDAYZONE,  HOUR( 5) },  /* Eastern Daylight */
462     { "cst",    tZONE,     HOUR( 6) },  /* Central Standard */
463     { "cdt",    tDAYZONE,  HOUR( 6) },  /* Central Daylight */
464     { "mst",    tZONE,     HOUR( 7) },  /* Mountain Standard */
465     { "mdt",    tDAYZONE,  HOUR( 7) },  /* Mountain Daylight */
466     { "pst",    tZONE,     HOUR( 8) },  /* Pacific Standard */
467     { "pdt",    tDAYZONE,  HOUR( 8) },  /* Pacific Daylight */
468     { "yst",    tZONE,     HOUR( 9) },  /* Yukon Standard */
469     { "ydt",    tDAYZONE,  HOUR( 9) },  /* Yukon Daylight */
470     { "hst",    tZONE,     HOUR(10) },  /* Hawaii Standard */
471     { "hdt",    tDAYZONE,  HOUR(10) },  /* Hawaii Daylight */
472     { "cat",    tZONE,     HOUR(10) },  /* Central Alaska */
473     { "ahst",   tZONE,     HOUR(10) },  /* Alaska-Hawaii Standard */
474     { "nt",     tZONE,     HOUR(11) },  /* Nome */
475     { "idlw",   tZONE,     HOUR(12) },  /* International Date Line West */
476     { "cet",    tZONE,     -HOUR(1) },  /* Central European */
477     { "met",    tZONE,     -HOUR(1) },  /* Middle European */
478     { "mewt",   tZONE,     -HOUR(1) },  /* Middle European Winter */
479     { "mest",   tDAYZONE,  -HOUR(1) },  /* Middle European Summer */
480     { "swt",    tZONE,     -HOUR(1) },  /* Swedish Winter */
481     { "sst",    tDAYZONE,  -HOUR(1) },  /* Swedish Summer */
482     { "fwt",    tZONE,     -HOUR(1) },  /* French Winter */
483     { "fst",    tDAYZONE,  -HOUR(1) },  /* French Summer */
484     { "eet",    tZONE,     -HOUR(2) },  /* Eastern Europe, USSR Zone 1 */
485     { "bt",     tZONE,     -HOUR(3) },  /* Baghdad, USSR Zone 2 */
486 #if 0
487     { "it",     tZONE,     -HOUR(3.5) },/* Iran */
488 #endif
489     { "zp4",    tZONE,     -HOUR(4) },  /* USSR Zone 3 */
490     { "zp5",    tZONE,     -HOUR(5) },  /* USSR Zone 4 */
491 #if 0
492     { "ist",    tZONE,     -HOUR(5.5) },/* Indian Standard */
493 #endif
494     { "zp6",    tZONE,     -HOUR(6) },  /* USSR Zone 5 */
495 #if     0
496     /* For completeness.  NST is also Newfoundland Stanard, and SST is
497      * also Swedish Summer. */
498     { "nst",    tZONE,     -HOUR(6.5) },/* North Sumatra */
499     { "sst",    tZONE,     -HOUR(7) },  /* South Sumatra, USSR Zone 6 */
500 #endif  /* 0 */
501     { "wast",   tZONE,     -HOUR(7) },  /* West Australian Standard */
502     { "wadt",   tDAYZONE,  -HOUR(7) },  /* West Australian Daylight */
503 #if 0
504     { "jt",     tZONE,     -HOUR(7.5) },/* Java (3pm in Cronusland!) */
505 #endif
506     { "cct",    tZONE,     -HOUR(8) },  /* China Coast, USSR Zone 7 */
507     { "jst",    tZONE,     -HOUR(9) },  /* Japan Standard, USSR Zone 8 */
508 #if 0
509     { "cast",   tZONE,     -HOUR(9.5) },/* Central Australian Standard */
510     { "cadt",   tDAYZONE,  -HOUR(9.5) },/* Central Australian Daylight */
511 #endif
512     { "east",   tZONE,     -HOUR(10) }, /* Eastern Australian Standard */
513     { "eadt",   tDAYZONE,  -HOUR(10) }, /* Eastern Australian Daylight */
514     { "gst",    tZONE,     -HOUR(10) }, /* Guam Standard, USSR Zone 9 */
515     { "nzt",    tZONE,     -HOUR(12) }, /* New Zealand */
516     { "nzst",   tZONE,     -HOUR(12) }, /* New Zealand Standard */
517     { "nzdt",   tDAYZONE,  -HOUR(12) }, /* New Zealand Daylight */
518     { "idle",   tZONE,     -HOUR(12) }, /* International Date Line East */
519     {  NULL  }
520 };
521
522 /* Military timezone table. */
523 static TABLE    MilitaryTable[] = {
524     { "a",      tZONE,  HOUR(  1) },
525     { "b",      tZONE,  HOUR(  2) },
526     { "c",      tZONE,  HOUR(  3) },
527     { "d",      tZONE,  HOUR(  4) },
528     { "e",      tZONE,  HOUR(  5) },
529     { "f",      tZONE,  HOUR(  6) },
530     { "g",      tZONE,  HOUR(  7) },
531     { "h",      tZONE,  HOUR(  8) },
532     { "i",      tZONE,  HOUR(  9) },
533     { "k",      tZONE,  HOUR( 10) },
534     { "l",      tZONE,  HOUR( 11) },
535     { "m",      tZONE,  HOUR( 12) },
536     { "n",      tZONE,  HOUR(- 1) },
537     { "o",      tZONE,  HOUR(- 2) },
538     { "p",      tZONE,  HOUR(- 3) },
539     { "q",      tZONE,  HOUR(- 4) },
540     { "r",      tZONE,  HOUR(- 5) },
541     { "s",      tZONE,  HOUR(- 6) },
542     { "t",      tZONE,  HOUR(- 7) },
543     { "u",      tZONE,  HOUR(- 8) },
544     { "v",      tZONE,  HOUR(- 9) },
545     { "w",      tZONE,  HOUR(-10) },
546     { "x",      tZONE,  HOUR(-11) },
547     { "y",      tZONE,  HOUR(-12) },
548     { "z",      tZONE,  HOUR(  0) },
549     { NULL }
550 };
551
552 \f
553
554
555 /* ARGSUSED */
556 int
557 yyerror(s)
558     char        *s;
559 {
560   return 0;
561 }
562
563
564 static time_t
565 ToSeconds(Hours, Minutes, Seconds, Meridian)
566     time_t      Hours;
567     time_t      Minutes;
568     time_t      Seconds;
569     MERIDIAN    Meridian;
570 {
571     if (Minutes < 0 || Minutes > 59 || Seconds < 0 || Seconds > 59)
572         return -1;
573     switch (Meridian) {
574     case MER24:
575         if (Hours < 0 || Hours > 23)
576             return -1;
577         return (Hours * 60L + Minutes) * 60L + Seconds;
578     case MERam:
579         if (Hours < 1 || Hours > 12)
580             return -1;
581         return (Hours * 60L + Minutes) * 60L + Seconds;
582     case MERpm:
583         if (Hours < 1 || Hours > 12)
584             return -1;
585         return ((Hours + 12) * 60L + Minutes) * 60L + Seconds;
586     }
587     /* NOTREACHED */
588 }
589
590
591 static time_t
592 Convert(Month, Day, Year, Hours, Minutes, Seconds, Meridian, DSTmode)
593     time_t      Month;
594     time_t      Day;
595     time_t      Year;
596     time_t      Hours;
597     time_t      Minutes;
598     time_t      Seconds;
599     MERIDIAN    Meridian;
600     DSTMODE     DSTmode;
601 {
602     static int  DaysInMonth[12] = {
603         31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
604     };
605     time_t      tod;
606     time_t      Julian;
607     int         i;
608
609     if (Year < 0)
610         Year = -Year;
611     if (Year < 100)
612         Year += 1900;
613     DaysInMonth[1] = Year % 4 == 0 && (Year % 100 != 0 || Year % 400 == 0)
614                     ? 29 : 28;
615     if (Year < EPOCH || Year > 1999
616      || Month < 1 || Month > 12
617      /* Lint fluff:  "conversion from long may lose accuracy" */
618      || Day < 1 || Day > DaysInMonth[(int)--Month])
619         return -1;
620
621     for (Julian = Day - 1, i = 0; i < Month; i++)
622         Julian += DaysInMonth[i];
623     for (i = EPOCH; i < Year; i++)
624         Julian += 365 + (i % 4 == 0);
625     Julian *= SECSPERDAY;
626     Julian += yyTimezone * 60L;
627     if ((tod = ToSeconds(Hours, Minutes, Seconds, Meridian)) < 0)
628         return -1;
629     Julian += tod;
630     if (DSTmode == DSTon
631      || (DSTmode == DSTmaybe && localtime(&Julian)->tm_isdst))
632         Julian -= 60 * 60;
633     return Julian;
634 }
635
636
637 static time_t
638 DSTcorrect(Start, Future)
639     time_t      Start;
640     time_t      Future;
641 {
642     time_t      StartDay;
643     time_t      FutureDay;
644
645     StartDay = (localtime(&Start)->tm_hour + 1) % 24;
646     FutureDay = (localtime(&Future)->tm_hour + 1) % 24;
647     return (Future - Start) + (StartDay - FutureDay) * 60L * 60L;
648 }
649
650
651 static time_t
652 RelativeDate(Start, DayOrdinal, DayNumber)
653     time_t      Start;
654     time_t      DayOrdinal;
655     time_t      DayNumber;
656 {
657     struct tm   *tm;
658     time_t      now;
659
660     now = Start;
661     tm = localtime(&now);
662     now += SECSPERDAY * ((DayNumber - tm->tm_wday + 7) % 7);
663     now += 7 * SECSPERDAY * (DayOrdinal <= 0 ? DayOrdinal : DayOrdinal - 1);
664     return DSTcorrect(Start, now);
665 }
666
667
668 static time_t
669 RelativeMonth(Start, RelMonth)
670     time_t      Start;
671     time_t      RelMonth;
672 {
673     struct tm   *tm;
674     time_t      Month;
675     time_t      Year;
676
677     if (RelMonth == 0)
678         return 0;
679     tm = localtime(&Start);
680     Month = 12 * tm->tm_year + tm->tm_mon + RelMonth;
681     Year = Month / 12;
682     Month = Month % 12 + 1;
683     return DSTcorrect(Start,
684             Convert(Month, (time_t)tm->tm_mday, Year,
685                 (time_t)tm->tm_hour, (time_t)tm->tm_min, (time_t)tm->tm_sec,
686                 MER24, DSTmaybe));
687 }
688
689
690 static int
691 LookupWord(buff)
692     char                *buff;
693 {
694     register char       *p;
695     register char       *q;
696     register TABLE      *tp;
697     int                 i;
698     int                 abbrev;
699
700     /* Make it lowercase. */
701     for (p = buff; *p; p++)
702         if (isupper(*p))
703             *p = tolower(*p);
704
705     if (strcmp(buff, "am") == 0 || strcmp(buff, "a.m.") == 0) {
706         yylval.Meridian = MERam;
707         return tMERIDIAN;
708     }
709     if (strcmp(buff, "pm") == 0 || strcmp(buff, "p.m.") == 0) {
710         yylval.Meridian = MERpm;
711         return tMERIDIAN;
712     }
713
714     /* See if we have an abbreviation for a month. */
715     if (strlen(buff) == 3)
716         abbrev = 1;
717     else if (strlen(buff) == 4 && buff[3] == '.') {
718         abbrev = 1;
719         buff[3] = '\0';
720     }
721     else
722         abbrev = 0;
723
724     for (tp = MonthDayTable; tp->name; tp++) {
725         if (abbrev) {
726             if (strncmp(buff, tp->name, 3) == 0) {
727                 yylval.Number = tp->value;
728                 return tp->type;
729             }
730         }
731         else if (strcmp(buff, tp->name) == 0) {
732             yylval.Number = tp->value;
733             return tp->type;
734         }
735     }
736
737     for (tp = TimezoneTable; tp->name; tp++)
738         if (strcmp(buff, tp->name) == 0) {
739             yylval.Number = tp->value;
740             return tp->type;
741         }
742
743     if (strcmp(buff, "dst") == 0) 
744         return tDST;
745
746     for (tp = UnitsTable; tp->name; tp++)
747         if (strcmp(buff, tp->name) == 0) {
748             yylval.Number = tp->value;
749             return tp->type;
750         }
751
752     /* Strip off any plural and try the units table again. */
753     i = strlen(buff) - 1;
754     if (buff[i] == 's') {
755         buff[i] = '\0';
756         for (tp = UnitsTable; tp->name; tp++)
757             if (strcmp(buff, tp->name) == 0) {
758                 yylval.Number = tp->value;
759                 return tp->type;
760             }
761         buff[i] = 's';          /* Put back for "this" in OtherTable. */
762     }
763
764     for (tp = OtherTable; tp->name; tp++)
765         if (strcmp(buff, tp->name) == 0) {
766             yylval.Number = tp->value;
767             return tp->type;
768         }
769
770     /* Military timezones. */
771     if (buff[1] == '\0' && isalpha(*buff)) {
772         for (tp = MilitaryTable; tp->name; tp++)
773             if (strcmp(buff, tp->name) == 0) {
774                 yylval.Number = tp->value;
775                 return tp->type;
776             }
777     }
778
779     /* Drop out any periods and try the timezone table again. */
780     for (i = 0, p = q = buff; *q; q++)
781         if (*q != '.')
782             *p++ = *q;
783         else
784             i++;
785     *p = '\0';
786     if (i)
787         for (tp = TimezoneTable; tp->name; tp++)
788             if (strcmp(buff, tp->name) == 0) {
789                 yylval.Number = tp->value;
790                 return tp->type;
791             }
792
793     return tID;
794 }
795
796
797 int
798 yylex()
799 {
800     register char       c;
801     register char       *p;
802     char                buff[20];
803     int                 Count;
804     int                 sign;
805
806     for ( ; ; ) {
807         while (isspace(*yyInput))
808             yyInput++;
809
810         if (isdigit(c = *yyInput) || c == '-' || c == '+') {
811             if (c == '-' || c == '+') {
812                 sign = c == '-' ? -1 : 1;
813                 if (!isdigit(*++yyInput))
814                     /* skip the '-' sign */
815                     continue;
816             }
817             else
818                 sign = 0;
819             for (yylval.Number = 0; isdigit(c = *yyInput++); )
820                 yylval.Number = 10 * yylval.Number + c - '0';
821             yyInput--;
822             if (sign < 0)
823                 yylval.Number = -yylval.Number;
824             return sign ? tSNUMBER : tUNUMBER;
825         }
826         if (isalpha(c)) {
827             for (p = buff; isalpha(c = *yyInput++) || c == '.'; )
828                 if (p < &buff[sizeof buff - 1])
829                     *p++ = c;
830             *p = '\0';
831             yyInput--;
832             return LookupWord(buff);
833         }
834         if (c != '(')
835             return *yyInput++;
836         Count = 0;
837         do {
838             c = *yyInput++;
839             if (c == '\0')
840                 return c;
841             if (c == '(')
842                 Count++;
843             else if (c == ')')
844                 Count--;
845         } while (Count > 0);
846     }
847 }
848
849
850 time_t
851 get_date(p, now)
852     char                *p;
853     struct timeb        *now;
854 {
855     struct tm           *tm;
856     struct timeb        ftz;
857     time_t              Start;
858     time_t              tod;
859
860     yyInput = p;
861     if (now == NULL) {
862         now = &ftz;
863 #if     !defined(HAVE_FTIME)
864         (void)time(&ftz.time);
865         /* Set the timezone global. */
866         tzset();
867         {
868 #if sgi
869             ftz.timezone = (int) _timezone / 60;
870 #else /* not sgi */
871 #ifdef __386BSD__
872             ftz.timezone = 0;
873 #else /* neither sgi nor 386BSD */
874 #if defined (USG)
875             extern time_t timezone;
876
877             ftz.timezone = (int) timezone / 60;
878 #else /* neither sgi nor 386BSD nor USG */
879             struct timeval tv;
880             struct timezone tz;
881
882             gettimeofday (&tv, &tz);
883             ftz.timezone = (int) tz.tz_minuteswest;
884 #endif /* neither sgi nor 386BSD nor USG */
885 #endif /* neither sgi nor 386BSD */
886 #endif /* not sgi */
887         }
888 #else /* HAVE_FTIME */
889         (void)ftime(&ftz);
890 #endif /* HAVE_FTIME */
891     }
892
893     tm = localtime(&now->time);
894     yyYear = tm->tm_year;
895     yyMonth = tm->tm_mon + 1;
896     yyDay = tm->tm_mday;
897     yyTimezone = now->timezone;
898     yyDSTmode = DSTmaybe;
899     yyHour = 0;
900     yyMinutes = 0;
901     yySeconds = 0;
902     yyMeridian = MER24;
903     yyRelSeconds = 0;
904     yyRelMonth = 0;
905     yyHaveDate = 0;
906     yyHaveDay = 0;
907     yyHaveRel = 0;
908     yyHaveTime = 0;
909     yyHaveZone = 0;
910
911     if (yyparse()
912      || yyHaveTime > 1 || yyHaveZone > 1 || yyHaveDate > 1 || yyHaveDay > 1)
913         return -1;
914
915     if (yyHaveDate || yyHaveTime || yyHaveDay) {
916         Start = Convert(yyMonth, yyDay, yyYear, yyHour, yyMinutes, yySeconds,
917                     yyMeridian, yyDSTmode);
918         if (Start < 0)
919             return -1;
920     }
921     else {
922         Start = now->time;
923         if (!yyHaveRel)
924             Start -= ((tm->tm_hour * 60L + tm->tm_min) * 60L) + tm->tm_sec;
925     }
926
927     Start += yyRelSeconds;
928     Start += RelativeMonth(Start, yyRelMonth);
929
930     if (yyHaveDay && !yyHaveDate) {
931         tod = RelativeDate(Start, yyDayOrdinal, yyDayNumber);
932         Start += tod;
933     }
934
935     /* Have to do *something* with a legitimate -1 so it's distinguishable
936      * from the error return value.  (Alternately could set errno on error.) */
937     return Start == -1 ? 0 : Start;
938 }
939
940
941 #if     defined(TEST)
942
943 /* ARGSUSED */
944 main(ac, av)
945     int         ac;
946     char        *av[];
947 {
948     char        buff[128];
949     time_t      d;
950
951     (void)printf("Enter date, or blank line to exit.\n\t> ");
952     (void)fflush(stdout);
953     while (gets(buff) && buff[0]) {
954         d = get_date(buff, (struct timeb *)NULL);
955         if (d == -1)
956             (void)printf("Bad format - couldn't convert.\n");
957         else
958             (void)printf("%s", ctime(&d));
959         (void)printf("\t> ");
960         (void)fflush(stdout);
961     }
962     exit(0);
963     /* NOTREACHED */
964 }
965 #endif  /* defined(TEST) */