]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - usr.bin/find/parsedate.y
mdoc(7) police: markup nits.
[FreeBSD/FreeBSD.git] / usr.bin / find / parsedate.y
1 %{
2 /*  $Id: parsedate.y,v 1.9.2.1 2000/12/26 08:39:49 kondou Exp $
3 **  $FreeBSD$
4 **
5 **  Originally written by Steven M. Bellovin <smb@research.att.com> while
6 **  at the University of North Carolina at Chapel Hill.  Later tweaked by
7 **  a couple of people on Usenet.  Completely overhauled by Rich $alz
8 **  <rsalz@osf.org> and Jim Berets <jberets@bbn.com> in August, 1990.
9 **  Further revised (removed obsolete constructs and cleaned up timezone
10 **  names) in August, 1991, by Rich.  Paul Eggert <eggert@twinsun.com>
11 **  helped in September, 1992.
12 **
13 **  This grammar has six shift/reduce conflicts.
14 **
15 **  This code is in the public domain and has no copyright.
16 */
17 /* SUPPRESS 530 *//* Empty body for statement */
18 /* SUPPRESS 593 on yyerrlab *//* Label was not used */
19 /* SUPPRESS 593 on yynewstate *//* Label was not used */
20 /* SUPPRESS 595 on yypvt *//* Automatic variable may be used before set */
21
22 #include <sys/types.h>
23 #include <sys/time.h>
24 #include <sys/timeb.h>
25 #include <ctype.h>
26 #include <fts.h>
27 #include <string.h>
28 #include <time.h>
29
30 #include "find.h"
31
32 #define CTYPE(isXXXXX, c)       (isXXXXX((c)))
33 #define SIZEOF(array)           (sizeof array / sizeof array[0])
34 #define ENDOF(array)            (&array[SIZEOF(array)])
35
36 typedef const char      *STRING;
37 typedef struct timeb    TIMEINFO;
38
39 #define yylhs           date_yylhs
40 #define yylen           date_yylen
41 #define yydefred        date_yydefred
42 #define yydgoto         date_yydgoto
43 #define yysindex        date_yysindex
44 #define yyrindex        date_yyrindex
45 #define yygindex        date_yygindex
46 #define yytable         date_yytable
47 #define yycheck         date_yycheck
48 #define yyparse         date_parse
49 #define yylex           date_lex
50 #define yyerror         date_error
51
52
53 static int date_lex(void);
54 int yyparse(void);
55
56     /* See the LeapYears table in Convert. */
57 #define EPOCH           1970
58 #define END_OF_TIME     2038
59     /* Constants for general time calculations. */
60 #define DST_OFFSET      1
61 #define SECSPERDAY      (24L * 60L * 60L)
62     /* Readability for TABLE stuff. */
63 #define HOUR(x)         (x * 60)
64
65 #define LPAREN          '('
66 #define RPAREN          ')'
67 #define IS7BIT(x)       ((unsigned int)(x) < 0200)
68
69
70 /*
71 **  An entry in the lexical lookup table.
72 */
73 typedef struct _TABLE {
74     STRING      name;
75     int         type;
76     time_t      value;
77 } TABLE;
78
79 /*
80 **  Daylight-savings mode:  on, off, or not yet known.
81 */
82 typedef enum _DSTMODE {
83     DSTon, DSToff, DSTmaybe
84 } DSTMODE;
85
86 /*
87 **  Meridian:  am, pm, or 24-hour style.
88 */
89 typedef enum _MERIDIAN {
90     MERam, MERpm, MER24
91 } MERIDIAN;
92
93
94 /*
95 **  Global variables.  We could get rid of most of them by using a yacc
96 **  union, but this is more efficient.  (This routine predates the
97 **  yacc %union construct.)
98 */
99 static char     *yyInput;
100 static DSTMODE  yyDSTmode;
101 static int      yyHaveDate;
102 static int      yyHaveRel;
103 static int      yyHaveTime;
104 static time_t   yyTimezone;
105 static time_t   yyDay;
106 static time_t   yyHour;
107 static time_t   yyMinutes;
108 static time_t   yyMonth;
109 static time_t   yySeconds;
110 static time_t   yyYear;
111 static MERIDIAN yyMeridian;
112 static time_t   yyRelMonth;
113 static time_t   yyRelSeconds;
114
115 static void             date_error(const char *);
116 %}
117
118 %union {
119     time_t              Number;
120     enum _MERIDIAN      Meridian;
121 }
122
123 %token  tDAY tDAYZONE tMERIDIAN tMONTH tMONTH_UNIT tSEC_UNIT tSNUMBER
124 %token  tUNUMBER tZONE
125
126 %type   <Number>        tDAYZONE tMONTH tMONTH_UNIT tSEC_UNIT
127 %type   <Number>        tSNUMBER tUNUMBER tZONE numzone zone
128 %type   <Meridian>      tMERIDIAN o_merid
129
130 %%
131
132 spec    : /* NULL */
133         | spec item
134         ;
135
136 item    : time {
137             yyHaveTime++;
138 #if     defined(lint)
139             /* I am compulsive about lint natterings... */
140             if (yyHaveTime == -1) {
141                 YYERROR;
142             }
143 #endif  /* defined(lint) */
144         }
145         | time zone {
146             yyHaveTime++;
147             yyTimezone = $2;
148         }
149         | date {
150             yyHaveDate++;
151         }
152         | rel {
153             yyHaveRel = 1;
154         }
155         ;
156
157 time    : tUNUMBER o_merid {
158             if ($1 < 100) {
159                 yyHour = $1;
160                 yyMinutes = 0;
161             }
162             else {
163                 yyHour = $1 / 100;
164                 yyMinutes = $1 % 100;
165             }
166             yySeconds = 0;
167             yyMeridian = $2;
168         }
169         | tUNUMBER ':' tUNUMBER o_merid {
170             yyHour = $1;
171             yyMinutes = $3;
172             yySeconds = 0;
173             yyMeridian = $4;
174         }
175         | tUNUMBER ':' tUNUMBER numzone {
176             yyHour = $1;
177             yyMinutes = $3;
178             yyTimezone = $4;
179             yyMeridian = MER24;
180             yyDSTmode = DSToff;
181         }
182         | tUNUMBER ':' tUNUMBER ':' tUNUMBER o_merid {
183             yyHour = $1;
184             yyMinutes = $3;
185             yySeconds = $5;
186             yyMeridian = $6;
187         }
188         | tUNUMBER ':' tUNUMBER ':' tUNUMBER numzone {
189             yyHour = $1;
190             yyMinutes = $3;
191             yySeconds = $5;
192             yyTimezone = $6;
193             yyMeridian = MER24;
194             yyDSTmode = DSToff;
195         }
196         ;
197
198 zone    : tZONE {
199             $$ = $1;
200             yyDSTmode = DSToff;
201         }
202         | tDAYZONE {
203             $$ = $1;
204             yyDSTmode = DSTon;
205         }
206         | tZONE numzone {
207             /* Only allow "GMT+300" and "GMT-0800" */
208             if ($1 != 0) {
209                 YYABORT;
210             }
211             $$ = $2;
212             yyDSTmode = DSToff;
213         }
214         | numzone {
215             $$ = $1;
216             yyDSTmode = DSToff;
217         }
218         ;
219
220 numzone : tSNUMBER {
221             int         i;
222
223             /* Unix and GMT and numeric timezones -- a little confusing. */
224             if ($1 < 0) {
225                 /* Don't work with negative modulus. */
226                 $1 = -$1;
227                 if ($1 > 9999 || (i = $1 % 100) >= 60) {
228                     YYABORT;
229                 }
230                 $$ = ($1 / 100) * 60 + i;
231             }
232             else {
233                 if ($1 > 9999 || (i = $1 % 100) >= 60) {
234                     YYABORT;
235                 }
236                 $$ = -(($1 / 100) * 60 + i);
237             }
238         }
239         ;
240
241 date    : tUNUMBER '/' tUNUMBER {
242             yyMonth = $1;
243             yyDay = $3;
244         }
245         | tUNUMBER '/' tUNUMBER '/' tUNUMBER {
246             if ($1 > 100) {
247                 /* assume YYYY/MM/DD format, so need not to add 1900 */
248                 if ($1 > 999) {
249                     yyYear = $1;
250                 } else {
251                     yyYear = 1900 + $1;
252                 }
253                 yyMonth = $3;
254                 yyDay = $5;
255             }
256             else {
257                 /* assume MM/DD/YY* format */
258                 yyMonth = $1;
259                 yyDay = $3;
260                 if ($5 > 999) {
261                     /* assume year is YYYY format, so need not to add 1900 */
262                     yyYear = $5;
263                 } else if ($5 < 100) {
264                     /* assume year is YY format, so need to add 1900 */
265                     yyYear = $5 + (yyYear / 100 + (yyYear % 100 - $5) / 50) * 100;
266                 } else {
267                     yyYear = 1900 + $5;
268                 }
269             }
270         }
271         | tMONTH tUNUMBER {
272             yyMonth = $1;
273             yyDay = $2;
274         }
275         | tMONTH tUNUMBER ',' tUNUMBER {
276             yyMonth = $1;
277             yyDay = $2;
278             if ($4 > 999) {
279                 /* assume year is YYYY format, so need not to add 1900 */
280                 yyYear = $4;
281             } else if ($4 < 100) {
282                 /* assume year is YY format, so need to add 1900 */
283                 yyYear = $4 + (yyYear / 100 + (yyYear % 100 - $4) / 50) * 100;
284             } else {
285                 yyYear = 1900 + $4;
286             }
287         }
288         | tUNUMBER tMONTH {
289             yyDay = $1;
290             yyMonth = $2;
291         }
292         | tUNUMBER tMONTH tUNUMBER {
293             yyDay = $1;
294             yyMonth = $2;
295             if ($3 > 999) {
296                 /* assume year is YYYY format, so need not to add 1900 */
297                 yyYear = $3;
298             } else if ($3 < 100) {
299                 /* assume year is YY format, so need to add 1900 */
300                 yyYear = $3 + (yyYear / 100 + (yyYear % 100 - $3) / 50) * 100;
301             } else {
302                 yyYear = 1900 + $3;
303             }
304         }
305         | tDAY ',' tUNUMBER tMONTH tUNUMBER {
306             yyDay = $3;
307             yyMonth = $4;
308             if ($5 > 999) {
309                 /* assume year is YYYY format, so need not to add 1900 */
310                 yyYear = $5;
311             } else if ($5 < 100) {
312                 /* assume year is YY format, so need to add 1900 */
313                 yyYear = $5 + (yyYear / 100 + (yyYear % 100 - $5) / 50) * 100;
314             } else {
315                 yyYear = 1900 + $5;
316             }
317         }
318         ;
319
320 rel     : tSNUMBER tSEC_UNIT {
321             yyRelSeconds += $1 * $2;
322         }
323         | tUNUMBER tSEC_UNIT {
324             yyRelSeconds += $1 * $2;
325         }
326         | tSNUMBER tMONTH_UNIT {
327             yyRelMonth += $1 * $2;
328         }
329         | tUNUMBER tMONTH_UNIT {
330             yyRelMonth += $1 * $2;
331         }
332         ;
333
334 o_merid : /* NULL */ {
335             $$ = MER24;
336         }
337         | tMERIDIAN {
338             $$ = $1;
339         }
340         ;
341
342 %%
343
344 /* Month and day table. */
345 static TABLE    MonthDayTable[] = {
346     { "january",        tMONTH,  1 },
347     { "february",       tMONTH,  2 },
348     { "march",          tMONTH,  3 },
349     { "april",          tMONTH,  4 },
350     { "may",            tMONTH,  5 },
351     { "june",           tMONTH,  6 },
352     { "july",           tMONTH,  7 },
353     { "august",         tMONTH,  8 },
354     { "september",      tMONTH,  9 },
355     { "october",        tMONTH, 10 },
356     { "november",       tMONTH, 11 },
357     { "december",       tMONTH, 12 },
358         /* The value of the day isn't used... */
359     { "sunday",         tDAY, 0 },
360     { "monday",         tDAY, 0 },
361     { "tuesday",        tDAY, 0 },
362     { "wednesday",      tDAY, 0 },
363     { "thursday",       tDAY, 0 },
364     { "friday",         tDAY, 0 },
365     { "saturday",       tDAY, 0 },
366 };
367
368 /* Time units table. */
369 static TABLE    UnitsTable[] = {
370     { "year",           tMONTH_UNIT,    12 },
371     { "month",          tMONTH_UNIT,    1 },
372     { "week",           tSEC_UNIT,      7 * 24 * 60 * 60 },
373     { "day",            tSEC_UNIT,      1 * 24 * 60 * 60 },
374     { "hour",           tSEC_UNIT,      60 * 60 },
375     { "minute",         tSEC_UNIT,      60 },
376     { "min",            tSEC_UNIT,      60 },
377     { "second",         tSEC_UNIT,      1 },
378     { "sec",            tSEC_UNIT,      1 },
379 };
380
381 /* Timezone table. */
382 static TABLE    TimezoneTable[] = {
383     { "gmt",    tZONE,     HOUR( 0) },  /* Greenwich Mean */
384     { "ut",     tZONE,     HOUR( 0) },  /* Universal */
385     { "utc",    tZONE,     HOUR( 0) },  /* Universal Coordinated */
386     { "cut",    tZONE,     HOUR( 0) },  /* Coordinated Universal */
387     { "z",      tZONE,     HOUR( 0) },  /* Greenwich Mean */
388     { "wet",    tZONE,     HOUR( 0) },  /* Western European */
389     { "bst",    tDAYZONE,  HOUR( 0) },  /* British Summer */
390     { "nst",    tZONE,     HOUR(3)+30 }, /* Newfoundland Standard */
391     { "ndt",    tDAYZONE,  HOUR(3)+30 }, /* Newfoundland Daylight */
392     { "ast",    tZONE,     HOUR( 4) },  /* Atlantic Standard */
393     { "adt",    tDAYZONE,  HOUR( 4) },  /* Atlantic Daylight */
394     { "est",    tZONE,     HOUR( 5) },  /* Eastern Standard */
395     { "edt",    tDAYZONE,  HOUR( 5) },  /* Eastern Daylight */
396     { "cst",    tZONE,     HOUR( 6) },  /* Central Standard */
397     { "cdt",    tDAYZONE,  HOUR( 6) },  /* Central Daylight */
398     { "mst",    tZONE,     HOUR( 7) },  /* Mountain Standard */
399     { "mdt",    tDAYZONE,  HOUR( 7) },  /* Mountain Daylight */
400     { "pst",    tZONE,     HOUR( 8) },  /* Pacific Standard */
401     { "pdt",    tDAYZONE,  HOUR( 8) },  /* Pacific Daylight */
402     { "yst",    tZONE,     HOUR( 9) },  /* Yukon Standard */
403     { "ydt",    tDAYZONE,  HOUR( 9) },  /* Yukon Daylight */
404     { "akst",   tZONE,     HOUR( 9) },  /* Alaska Standard */
405     { "akdt",   tDAYZONE,  HOUR( 9) },  /* Alaska Daylight */
406     { "hst",    tZONE,     HOUR(10) },  /* Hawaii Standard */
407     { "hast",   tZONE,     HOUR(10) },  /* Hawaii-Aleutian Standard */
408     { "hadt",   tDAYZONE,  HOUR(10) },  /* Hawaii-Aleutian Daylight */
409     { "ces",    tDAYZONE,  -HOUR(1) },  /* Central European Summer */
410     { "cest",   tDAYZONE,  -HOUR(1) },  /* Central European Summer */
411     { "mez",    tZONE,     -HOUR(1) },  /* Middle European */
412     { "mezt",   tDAYZONE,  -HOUR(1) },  /* Middle European Summer */
413     { "cet",    tZONE,     -HOUR(1) },  /* Central European */
414     { "met",    tZONE,     -HOUR(1) },  /* Middle European */
415     { "eet",    tZONE,     -HOUR(2) },  /* Eastern Europe */
416     { "msk",    tZONE,     -HOUR(3) },  /* Moscow Winter */
417     { "msd",    tDAYZONE,  -HOUR(3) },  /* Moscow Summer */
418     { "wast",   tZONE,     -HOUR(8) },  /* West Australian Standard */
419     { "wadt",   tDAYZONE,  -HOUR(8) },  /* West Australian Daylight */
420     { "hkt",    tZONE,     -HOUR(8) },  /* Hong Kong */
421     { "cct",    tZONE,     -HOUR(8) },  /* China Coast */
422     { "jst",    tZONE,     -HOUR(9) },  /* Japan Standard */
423     { "kst",    tZONE,     -HOUR(9) },  /* Korean Standard */
424     { "kdt",    tZONE,     -HOUR(9) },  /* Korean Daylight */
425     { "cast",   tZONE,     -(HOUR(9)+30) }, /* Central Australian Standard */
426     { "cadt",   tDAYZONE,  -(HOUR(9)+30) }, /* Central Australian Daylight */
427     { "east",   tZONE,     -HOUR(10) }, /* Eastern Australian Standard */
428     { "eadt",   tDAYZONE,  -HOUR(10) }, /* Eastern Australian Daylight */
429     { "nzst",   tZONE,     -HOUR(12) }, /* New Zealand Standard */
430     { "nzdt",   tDAYZONE,  -HOUR(12) }, /* New Zealand Daylight */
431
432     /* For completeness we include the following entries. */
433 #if     0
434
435     /* Duplicate names.  Either they conflict with a zone listed above
436      * (which is either more likely to be seen or just been in circulation
437      * longer), or they conflict with another zone in this section and
438      * we could not reasonably choose one over the other. */
439     { "fst",    tZONE,     HOUR( 2) },  /* Fernando De Noronha Standard */
440     { "fdt",    tDAYZONE,  HOUR( 2) },  /* Fernando De Noronha Daylight */
441     { "bst",    tZONE,     HOUR( 3) },  /* Brazil Standard */
442     { "est",    tZONE,     HOUR( 3) },  /* Eastern Standard (Brazil) */
443     { "edt",    tDAYZONE,  HOUR( 3) },  /* Eastern Daylight (Brazil) */
444     { "wst",    tZONE,     HOUR( 4) },  /* Western Standard (Brazil) */
445     { "wdt",    tDAYZONE,  HOUR( 4) },  /* Western Daylight (Brazil) */
446     { "cst",    tZONE,     HOUR( 5) },  /* Chile Standard */
447     { "cdt",    tDAYZONE,  HOUR( 5) },  /* Chile Daylight */
448     { "ast",    tZONE,     HOUR( 5) },  /* Acre Standard */
449     { "adt",    tDAYZONE,  HOUR( 5) },  /* Acre Daylight */
450     { "cst",    tZONE,     HOUR( 5) },  /* Cuba Standard */
451     { "cdt",    tDAYZONE,  HOUR( 5) },  /* Cuba Daylight */
452     { "est",    tZONE,     HOUR( 6) },  /* Easter Island Standard */
453     { "edt",    tDAYZONE,  HOUR( 6) },  /* Easter Island Daylight */
454     { "sst",    tZONE,     HOUR(11) },  /* Samoa Standard */
455     { "ist",    tZONE,     -HOUR(2) },  /* Israel Standard */
456     { "idt",    tDAYZONE,  -HOUR(2) },  /* Israel Daylight */
457     { "idt",    tDAYZONE,  -(HOUR(3)+30) }, /* Iran Daylight */
458     { "ist",    tZONE,     -(HOUR(3)+30) }, /* Iran Standard */
459     { "cst",     tZONE,     -HOUR(8) }, /* China Standard */
460     { "cdt",     tDAYZONE,  -HOUR(8) }, /* China Daylight */
461     { "sst",     tZONE,     -HOUR(8) }, /* Singapore Standard */
462
463     /* Dubious (e.g., not in Olson's TIMEZONE package) or obsolete. */
464     { "gst",    tZONE,     HOUR( 3) },  /* Greenland Standard */
465     { "wat",    tZONE,     -HOUR(1) },  /* West Africa */
466     { "at",     tZONE,     HOUR( 2) },  /* Azores */
467     { "gst",    tZONE,     -HOUR(10) }, /* Guam Standard */
468     { "nft",    tZONE,     HOUR(3)+30 }, /* Newfoundland */
469     { "idlw",   tZONE,     HOUR(12) },  /* International Date Line West */
470     { "mewt",   tZONE,     -HOUR(1) },  /* Middle European Winter */
471     { "mest",   tDAYZONE,  -HOUR(1) },  /* Middle European Summer */
472     { "swt",    tZONE,     -HOUR(1) },  /* Swedish Winter */
473     { "sst",    tDAYZONE,  -HOUR(1) },  /* Swedish Summer */
474     { "fwt",    tZONE,     -HOUR(1) },  /* French Winter */
475     { "fst",    tDAYZONE,  -HOUR(1) },  /* French Summer */
476     { "bt",     tZONE,     -HOUR(3) },  /* Baghdad */
477     { "it",     tZONE,     -(HOUR(3)+30) }, /* Iran */
478     { "zp4",    tZONE,     -HOUR(4) },  /* USSR Zone 3 */
479     { "zp5",    tZONE,     -HOUR(5) },  /* USSR Zone 4 */
480     { "ist",    tZONE,     -(HOUR(5)+30) }, /* Indian Standard */
481     { "zp6",    tZONE,     -HOUR(6) },  /* USSR Zone 5 */
482     { "nst",    tZONE,     -HOUR(7) },  /* North Sumatra */
483     { "sst",    tZONE,     -HOUR(7) },  /* South Sumatra */
484     { "jt",     tZONE,     -(HOUR(7)+30) }, /* Java (3pm in Cronusland!) */
485     { "nzt",    tZONE,     -HOUR(12) }, /* New Zealand */
486     { "idle",   tZONE,     -HOUR(12) }, /* International Date Line East */
487     { "cat",    tZONE,     HOUR(10) },  /* -- expired 1967 */
488     { "nt",     tZONE,     HOUR(11) },  /* -- expired 1967 */
489     { "ahst",   tZONE,     HOUR(10) },  /* -- expired 1983 */
490     { "hdt",    tDAYZONE,  HOUR(10) },  /* -- expired 1986 */
491 #endif  /* 0 */
492 };
493
494 \f
495
496 static int
497 GetTimeInfo(TIMEINFO *Now)
498 {
499     static time_t       NextHour;
500     static long         LastTzone;
501     struct tm           *tm;
502     int                 secondsUntilNextHour;
503     struct timeval      tv;
504
505     /* Get the basic time. */
506     if (gettimeofday(&tv, (struct timezone *) 0) == -1)
507         return -1;
508     Now->time = tv.tv_sec;
509     Now->millitm = tv.tv_usec;
510
511     /* Now get the timezone if the last time < HH:00:00 <= now for some HH.  */
512     if (NextHour <= Now->time) {
513         tm = localtime(&Now->time);
514         if (tm == NULL)
515             return -1;
516         secondsUntilNextHour = 60 * (60 - tm->tm_min) - tm->tm_sec;
517         LastTzone = (0 - tm->tm_gmtoff) / 60;
518         NextHour = Now->time + secondsUntilNextHour;
519     }
520     Now->timezone = LastTzone;
521     return 0;
522 }
523
524
525 /* ARGSUSED */
526 static void
527 date_error(const char *s __unused)
528 {
529     /* NOTREACHED */
530 }
531
532
533 static time_t
534 ToSeconds(time_t Hours, time_t Minutes, time_t Seconds, MERIDIAN Meridian)
535 {
536     if (Minutes < 0 || Minutes > 59 || Seconds < 0 || Seconds > 61)
537         return -1;
538     if (Meridian == MER24) {
539         if (Hours < 0 || Hours > 23)
540             return -1;
541     }
542     else {
543         if (Hours < 1 || Hours > 12)
544             return -1;
545         if (Hours == 12)
546             Hours = 0;
547         if (Meridian == MERpm)
548             Hours += 12;
549     }
550     return (Hours * 60L + Minutes) * 60L + Seconds;
551 }
552
553
554 static time_t
555 Convert(time_t Month, time_t Day, time_t Year, time_t Hours, time_t Minutes, time_t Seconds, MERIDIAN Meridian, DSTMODE dst)
556 {
557     static int  DaysNormal[13] = {
558         0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
559     };
560     static int  DaysLeap[13] = {
561         0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
562     };
563     static int  LeapYears[] = {
564         1972, 1976, 1980, 1984, 1988, 1992, 1996,
565         2000, 2004, 2008, 2012, 2016, 2020, 2024, 2028, 2032, 2036
566     };
567     int                 *yp;
568     int                 *mp;
569     time_t              Julian;
570     int                 i;
571     time_t              tod;
572
573     /* Year should not be passed as a relative value, but absolute one.
574        so this should not happen, but just ensure it */
575     if (Year < 0)
576         Year = -Year;
577     if (Year < 100) {
578         Year += 1900;
579         if (Year < EPOCH)
580             Year += 100;
581     }
582     for (mp = DaysNormal, yp = LeapYears; yp < ENDOF(LeapYears); yp++)
583         if (Year == *yp) {
584             mp = DaysLeap;
585             break;
586         }
587     if (Year < EPOCH || Year > END_OF_TIME
588      || Month < 1 || Month > 12
589      /* NOSTRICT *//* conversion from long may lose accuracy */
590      || Day < 1 || Day > mp[(int)Month])
591         return -1;
592
593     Julian = Day - 1 + (Year - EPOCH) * 365;
594     for (yp = LeapYears; yp < ENDOF(LeapYears); yp++, Julian++)
595         if (Year <= *yp)
596             break;
597     for (i = 1; i < Month; i++)
598         Julian += *++mp;
599     Julian *= SECSPERDAY;
600     Julian += yyTimezone * 60L;
601     if ((tod = ToSeconds(Hours, Minutes, Seconds, Meridian)) < 0)
602         return -1;
603     Julian += tod;
604     tod = Julian;
605     if (dst == DSTon || (dst == DSTmaybe && localtime(&tod)->tm_isdst))
606         Julian -= DST_OFFSET * 60 * 60;
607     return Julian;
608 }
609
610
611 static time_t
612 DSTcorrect(time_t Start, time_t Future)
613 {
614     time_t      StartDay;
615     time_t      FutureDay;
616
617     StartDay = (localtime(&Start)->tm_hour + 1) % 24;
618     FutureDay = (localtime(&Future)->tm_hour + 1) % 24;
619     return (Future - Start) + (StartDay - FutureDay) * DST_OFFSET * 60 * 60;
620 }
621
622
623 static time_t
624 RelativeMonth(time_t Start, time_t RelMonth)
625 {
626     struct tm   *tm;
627     time_t      Month;
628     time_t      Year;
629
630     tm = localtime(&Start);
631     Month = 12 * tm->tm_year + tm->tm_mon + RelMonth;
632     Year = Month / 12;
633     Year += 1900;
634     Month = Month % 12 + 1;
635     return DSTcorrect(Start,
636             Convert(Month, (time_t)tm->tm_mday, Year,
637                 (time_t)tm->tm_hour, (time_t)tm->tm_min, (time_t)tm->tm_sec,
638                 MER24, DSTmaybe));
639 }
640
641
642 static int LookupWord(char *buff, int length)
643 {
644     char                *p;
645     STRING              q;
646     TABLE               *tp;
647     int                 c;
648
649     p = buff;
650     c = p[0];
651
652     /* See if we have an abbreviation for a month. */
653     if (length == 3 || (length == 4 && p[3] == '.'))
654         for (tp = MonthDayTable; tp < ENDOF(MonthDayTable); tp++) {
655             q = tp->name;
656             if (c == q[0] && p[1] == q[1] && p[2] == q[2]) {
657                 yylval.Number = tp->value;
658                 return tp->type;
659             }
660         }
661     else
662         for (tp = MonthDayTable; tp < ENDOF(MonthDayTable); tp++)
663             if (c == tp->name[0] && strcmp(p, tp->name) == 0) {
664                 yylval.Number = tp->value;
665                 return tp->type;
666             }
667
668     /* Try for a timezone. */
669     for (tp = TimezoneTable; tp < ENDOF(TimezoneTable); tp++)
670         if (c == tp->name[0] && p[1] == tp->name[1]
671          && strcmp(p, tp->name) == 0) {
672             yylval.Number = tp->value;
673             return tp->type;
674         }
675
676     /* Try the units table. */
677     for (tp = UnitsTable; tp < ENDOF(UnitsTable); tp++)
678         if (c == tp->name[0] && strcmp(p, tp->name) == 0) {
679             yylval.Number = tp->value;
680             return tp->type;
681         }
682
683     /* Strip off any plural and try the units table again. */
684     if (--length > 0 && p[length] == 's') {
685         p[length] = '\0';
686         for (tp = UnitsTable; tp < ENDOF(UnitsTable); tp++)
687             if (c == tp->name[0] && strcmp(p, tp->name) == 0) {
688                 p[length] = 's';
689                 yylval.Number = tp->value;
690                 return tp->type;
691             }
692         p[length] = 's';
693     }
694     length++;
695
696     /* Drop out any periods. */
697     for (p = buff, q = (STRING)buff; *q; q++)
698         if (*q != '.')
699             *p++ = *q;
700     *p = '\0';
701
702     /* Try the meridians. */
703     if (buff[1] == 'm' && buff[2] == '\0') {
704         if (buff[0] == 'a') {
705             yylval.Meridian = MERam;
706             return tMERIDIAN;
707         }
708         if (buff[0] == 'p') {
709             yylval.Meridian = MERpm;
710             return tMERIDIAN;
711         }
712     }
713
714     /* If we saw any periods, try the timezones again. */
715     if (p - buff != length) {
716         c = buff[0];
717         for (p = buff, tp = TimezoneTable; tp < ENDOF(TimezoneTable); tp++)
718             if (c == tp->name[0] && p[1] == tp->name[1]
719             && strcmp(p, tp->name) == 0) {
720                 yylval.Number = tp->value;
721                 return tp->type;
722             }
723     }
724
725     /* Unknown word -- assume GMT timezone. */
726     yylval.Number = 0;
727     return tZONE;
728 }
729
730
731 static int date_lex(void)
732 {
733     char                c;
734     char                *p;
735     char                buff[20];
736     int                 sign;
737     int                 i;
738     int                 nesting;
739
740     for ( ; ; ) {
741         /* Get first character after the whitespace. */
742         for ( ; ; ) {
743             while (CTYPE(isspace, (int)*yyInput))
744                 yyInput++;
745             c = *yyInput;
746
747             /* Ignore RFC 822 comments, typically time zone names. */
748             if (c != LPAREN)
749                 break;
750             for (nesting = 1; (c = *++yyInput) != RPAREN || --nesting; )
751                 if (c == LPAREN)
752                     nesting++;
753                 else if (!IS7BIT(c) || c == '\0' || c == '\r'
754                      || (c == '\\' && ((c = *++yyInput) == '\0' || !IS7BIT(c))))
755                     /* Lexical error: bad comment. */
756                     return '?';
757             yyInput++;
758         }
759
760         /* A number? */
761         if (CTYPE(isdigit, (int)c) || c == '-' || c == '+') {
762             if (c == '-' || c == '+') {
763                 sign = c == '-' ? -1 : 1;
764                 yyInput++;
765                 if (!CTYPE(isdigit, (int)*yyInput))
766                     /* Skip the plus or minus sign. */
767                     continue;
768             }
769             else
770                 sign = 0;
771             for (i = 0; (c = *yyInput++) != '\0' && CTYPE(isdigit, (int)c); )
772                 i = 10 * i + c - '0';
773             yyInput--;
774             yylval.Number = sign < 0 ? -i : i;
775             return sign ? tSNUMBER : tUNUMBER;
776         }
777
778         /* A word? */
779         if (CTYPE(isalpha, (int)c)) {
780             for (p = buff; (c = *yyInput++) == '.' || CTYPE(isalpha, (int)c); )
781                 if (p < &buff[sizeof buff - 1])
782                     *p++ = CTYPE(isupper, (int)c) ? tolower(c) : c;
783             *p = '\0';
784             yyInput--;
785             return LookupWord(buff, p - buff);
786         }
787
788         return *yyInput++;
789     }
790 }
791
792
793 time_t parsedate(char *p, TIMEINFO *now)
794 {
795     struct tm           *tm;
796     TIMEINFO            ti;
797     time_t              Start;
798
799     yyInput = p;
800     if (now == NULL) {
801         now = &ti;
802         (void)GetTimeInfo(&ti);
803     }
804
805     tm = localtime(&now->time);
806     yyYear = tm->tm_year + 1900;
807     yyMonth = tm->tm_mon + 1;
808     yyDay = tm->tm_mday;
809     yyTimezone = now->timezone;
810     yyDSTmode = DSTmaybe;
811     yyHour = 0;
812     yyMinutes = 0;
813     yySeconds = 0;
814     yyMeridian = MER24;
815     yyRelSeconds = 0;
816     yyRelMonth = 0;
817     yyHaveDate = 0;
818     yyHaveRel = 0;
819     yyHaveTime = 0;
820
821     if (date_parse() || yyHaveTime > 1 || yyHaveDate > 1)
822         return -1;
823
824     if (yyHaveDate || yyHaveTime) {
825         Start = Convert(yyMonth, yyDay, yyYear, yyHour, yyMinutes, yySeconds,
826                     yyMeridian, yyDSTmode);
827         if (Start < 0)
828             return -1;
829     }
830     else {
831         Start = now->time;
832         if (!yyHaveRel)
833             Start -= (tm->tm_hour * 60L + tm->tm_min) * 60L + tm->tm_sec;
834     }
835
836     Start += yyRelSeconds;
837     if (yyRelMonth)
838         Start += RelativeMonth(Start, yyRelMonth);
839
840     /* Have to do *something* with a legitimate -1 so it's distinguishable
841      * from the error return value.  (Alternately could set errno on error.) */
842     return Start == -1 ? 0 : Start;
843 }
844
845
846 #if     defined(TEST)
847
848 #if     YYDEBUG
849 extern int      yydebug;
850 #endif  /* YYDEBUG */
851
852 /* ARGSUSED */
853 int
854 main(ac, av)
855     int         ac;
856     char        *av[];
857 {
858     char        buff[128];
859     time_t      d;
860
861 #if     YYDEBUG
862     yydebug = 1;
863 #endif  /* YYDEBUG */
864
865     (void)printf("Enter date, or blank line to exit.\n\t> ");
866     for ( ; ; ) {
867         (void)printf("\t> ");
868         (void)fflush(stdout);
869         if (gets(buff) == NULL || buff[0] == '\n')
870             break;
871 #if     YYDEBUG
872         if (strcmp(buff, "yydebug") == 0) {
873             yydebug = !yydebug;
874             printf("yydebug = %s\n", yydebug ? "on" : "off");
875             continue;
876         }
877 #endif  /* YYDEBUG */
878         d = parsedate(buff, (TIMEINFO *)NULL);
879         if (d == -1)
880             (void)printf("Bad format - couldn't convert.\n");
881         else
882             (void)printf("%s", ctime(&d));
883     }
884
885     exit(0);
886     /* NOTREACHED */
887 }
888 #endif  /* defined(TEST) */