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