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