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