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