parsedate.y revision 1.9 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 const 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 const 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 const 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 const 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 const 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 static time_t
578 ToSeconds(
579 time_t Hours,
580 time_t Minutes,
581 time_t Seconds,
582 MERIDIAN Meridian
583 )
584 {
585 if (Minutes < 0 || Minutes > 59 || Seconds < 0 || Seconds > 59)
586 return -1;
587 switch (Meridian) {
588 case MER24:
589 if (Hours < 0 || Hours > 23)
590 return -1;
591 return (Hours * 60L + Minutes) * 60L + Seconds;
592 case MERam:
593 if (Hours < 1 || Hours > 12)
594 return -1;
595 if (Hours == 12)
596 Hours = 0;
597 return (Hours * 60L + Minutes) * 60L + Seconds;
598 case MERpm:
599 if (Hours < 1 || Hours > 12)
600 return -1;
601 if (Hours == 12)
602 Hours = 0;
603 return ((Hours + 12) * 60L + Minutes) * 60L + Seconds;
604 default:
605 abort ();
606 }
607 /* NOTREACHED */
608 }
609
610 static int
611 isLeap(int year)
612 {
613 return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
614 }
615
616
617 /* Year is either
618 * A negative number, which means to use its absolute value (why?)
619 * A number from 0 to 99, which means a year from 1900 to 1999, or
620 * The actual year (>=100). */
621 static time_t
622 Convert(
623 time_t Month,
624 time_t Day,
625 time_t Year,
626 time_t Hours,
627 time_t Minutes,
628 time_t Seconds,
629 time_t Timezone,
630 MERIDIAN Meridian,
631 DSTMODE DSTmode
632 )
633 {
634 static int DaysInMonth[12] = {
635 31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
636 };
637 time_t tod;
638 time_t Julian, oJulian;
639 int i;
640
641 /* XXX Y2K */
642 if (Year < 0)
643 Year = -Year;
644 if (Year < 70)
645 Year += 2000;
646 else if (Year < 100)
647 Year += 1900;
648 DaysInMonth[1] = isLeap(Year) ? 29 : 28;
649 if (Year < EPOCH || Month < 1 || Month > 12
650 /* Lint fluff: "conversion from long may lose accuracy" */
651 || Day < 1 || Day > DaysInMonth[(int)--Month])
652 /* FIXME:
653 * It would be nice to set a global error string here.
654 * "February 30 is not a valid date" is much more informative than
655 * "Can't parse date/time: 100 months" when the user input was
656 * "100 months" and addition resolved that to February 30, for
657 * example. See rcs2-7 in src/sanity.sh for more. */
658 return -1;
659
660 for (Julian = Day - 1, i = 0; i < Month; i++)
661 Julian += DaysInMonth[i];
662
663 oJulian = Julian;
664 for (i = EPOCH; i < Year; i++) {
665 Julian += 365 + isLeap(i);
666 if (oJulian > Julian)
667 return -1;
668 oJulian = Julian;
669 }
670
671 Julian *= SECSPERDAY;
672 if (oJulian > Julian)
673 return -1;
674 oJulian = Julian;
675 Julian += Timezone * 60L;
676 if (oJulian > Julian)
677 return -1;
678 oJulian = Julian;
679
680 if ((tod = ToSeconds(Hours, Minutes, Seconds, Meridian)) < 0)
681 return -1;
682
683 Julian += tod;
684 if (oJulian > Julian)
685 return -1;
686
687 if (DSTmode == DSTon || (DSTmode == DSTmaybe)) {
688 struct tm *tm;
689 if ((tm = localtime(&Julian)) == NULL)
690 return -1;
691 if (tm->tm_isdst)
692 Julian -= 60 * 60;
693 }
694 return Julian;
695 }
696
697
698 static time_t
699 DSTcorrect(
700 time_t Start,
701 time_t Future
702 )
703 {
704 time_t StartDay;
705 time_t FutureDay;
706 struct tm *tm;
707
708 if ((tm = localtime(&Start)) == NULL)
709 return -1;
710 StartDay = (tm->tm_hour + 1) % 24;
711
712 if ((tm = localtime(&Future)) == NULL)
713 return -1;
714 FutureDay = (tm->tm_hour + 1) % 24;
715
716 return (Future - Start) + (StartDay - FutureDay) * 60L * 60L;
717 }
718
719
720 static time_t
721 RelativeDate(
722 time_t Start,
723 time_t DayOrdinal,
724 time_t DayNumber
725 )
726 {
727 struct tm *tm;
728 time_t now;
729
730 now = Start;
731 tm = localtime(&now);
732 now += SECSPERDAY * ((DayNumber - tm->tm_wday + 7) % 7);
733 now += 7 * SECSPERDAY * (DayOrdinal <= 0 ? DayOrdinal : DayOrdinal - 1);
734 return DSTcorrect(Start, now);
735 }
736
737
738 static time_t
739 RelativeMonth(
740 time_t Start,
741 time_t RelMonth,
742 time_t Timezone
743 )
744 {
745 struct tm *tm;
746 time_t Month;
747 time_t Year;
748
749 if (RelMonth == 0)
750 return 0;
751 tm = localtime(&Start);
752 if (tm == NULL)
753 return -1;
754 Month = 12 * (tm->tm_year + 1900) + tm->tm_mon + RelMonth;
755 Year = Month / 12;
756 Month = Month % 12 + 1;
757 return DSTcorrect(Start,
758 Convert(Month, (time_t)tm->tm_mday, Year,
759 (time_t)tm->tm_hour, (time_t)tm->tm_min, (time_t)tm->tm_sec,
760 Timezone, MER24, DSTmaybe));
761 }
762
763
764 static int
765 LookupWord(YYSTYPE *yylval, char *buff)
766 {
767 register char *p;
768 register char *q;
769 register const TABLE *tp;
770 int i;
771 int abbrev;
772
773 /* Make it lowercase. */
774 for (p = buff; *p; p++)
775 if (isupper((unsigned char)*p))
776 *p = tolower((unsigned char)*p);
777
778 if (strcmp(buff, "am") == 0 || strcmp(buff, "a.m.") == 0) {
779 yylval->Meridian = MERam;
780 return tMERIDIAN;
781 }
782 if (strcmp(buff, "pm") == 0 || strcmp(buff, "p.m.") == 0) {
783 yylval->Meridian = MERpm;
784 return tMERIDIAN;
785 }
786
787 /* See if we have an abbreviation for a month. */
788 if (strlen(buff) == 3)
789 abbrev = 1;
790 else if (strlen(buff) == 4 && buff[3] == '.') {
791 abbrev = 1;
792 buff[3] = '\0';
793 }
794 else
795 abbrev = 0;
796
797 for (tp = MonthDayTable; tp->name; tp++) {
798 if (abbrev) {
799 if (strncmp(buff, tp->name, 3) == 0) {
800 yylval->Number = tp->value;
801 return tp->type;
802 }
803 }
804 else if (strcmp(buff, tp->name) == 0) {
805 yylval->Number = tp->value;
806 return tp->type;
807 }
808 }
809
810 for (tp = TimezoneTable; tp->name; tp++)
811 if (strcmp(buff, tp->name) == 0) {
812 yylval->Number = tp->value;
813 return tp->type;
814 }
815
816 if (strcmp(buff, "dst") == 0)
817 return tDST;
818
819 for (tp = UnitsTable; tp->name; tp++)
820 if (strcmp(buff, tp->name) == 0) {
821 yylval->Number = tp->value;
822 return tp->type;
823 }
824
825 /* Strip off any plural and try the units table again. */
826 i = strlen(buff) - 1;
827 if (buff[i] == 's') {
828 buff[i] = '\0';
829 for (tp = UnitsTable; tp->name; tp++)
830 if (strcmp(buff, tp->name) == 0) {
831 yylval->Number = tp->value;
832 return tp->type;
833 }
834 buff[i] = 's'; /* Put back for "this" in OtherTable. */
835 }
836
837 for (tp = OtherTable; tp->name; tp++)
838 if (strcmp(buff, tp->name) == 0) {
839 yylval->Number = tp->value;
840 return tp->type;
841 }
842
843 /* Military timezones. */
844 if (buff[1] == '\0' && isalpha((unsigned char)*buff)) {
845 for (tp = MilitaryTable; tp->name; tp++)
846 if (strcmp(buff, tp->name) == 0) {
847 yylval->Number = tp->value;
848 return tp->type;
849 }
850 }
851
852 /* Drop out any periods and try the timezone table again. */
853 for (i = 0, p = q = buff; *q; q++)
854 if (*q != '.')
855 *p++ = *q;
856 else
857 i++;
858 *p = '\0';
859 if (i)
860 for (tp = TimezoneTable; tp->name; tp++)
861 if (strcmp(buff, tp->name) == 0) {
862 yylval->Number = tp->value;
863 return tp->type;
864 }
865
866 return tID;
867 }
868
869
870 static int
871 yylex(YYSTYPE *yylval, const char **yyInput)
872 {
873 register char c;
874 register char *p;
875 char buff[20];
876 int Count;
877 int sign;
878 const char *inp = *yyInput;
879
880 for ( ; ; ) {
881 while (isspace((unsigned char)*inp))
882 inp++;
883
884 if (isdigit((unsigned char)(c = *inp)) || c == '-' || c == '+') {
885 if (c == '-' || c == '+') {
886 sign = c == '-' ? -1 : 1;
887 if (!isdigit((unsigned char)*++inp))
888 /* skip the '-' sign */
889 continue;
890 }
891 else
892 sign = 0;
893 for (yylval->Number = 0; isdigit((unsigned char)(c = *inp++)); )
894 yylval->Number = 10 * yylval->Number + c - '0';
895 if (sign < 0)
896 yylval->Number = -yylval->Number;
897 *yyInput = --inp;
898 return sign ? tSNUMBER : tUNUMBER;
899 }
900 if (isalpha((unsigned char)c)) {
901 for (p = buff; isalpha((unsigned char)(c = *inp++)) || c == '.'; )
902 if (p < &buff[sizeof buff - 1])
903 *p++ = c;
904 *p = '\0';
905 *yyInput = --inp;
906 return LookupWord(yylval, buff);
907 }
908 if (c == '@') {
909 *yyInput = ++inp;
910 return AT_SIGN;
911 }
912 if (c != '(') {
913 *yyInput = ++inp;
914 return c;
915 }
916 Count = 0;
917 do {
918 c = *inp++;
919 if (c == '\0')
920 return c;
921 if (c == '(')
922 Count++;
923 else if (c == ')')
924 Count--;
925 } while (Count > 0);
926 }
927 }
928
929 #define TM_YEAR_ORIGIN 1900
930
931 /* Yield A - B, measured in seconds. */
932 static time_t
933 difftm (struct tm *a, struct tm *b)
934 {
935 int ay = a->tm_year + (TM_YEAR_ORIGIN - 1);
936 int by = b->tm_year + (TM_YEAR_ORIGIN - 1);
937 int days = (
938 /* difference in day of year */
939 a->tm_yday - b->tm_yday
940 /* + intervening leap days */
941 + ((ay >> 2) - (by >> 2))
942 - (ay/100 - by/100)
943 + ((ay/100 >> 2) - (by/100 >> 2))
944 /* + difference in years * 365 */
945 + (long)(ay-by) * 365
946 );
947 return ((time_t)60*(60*(24*days + (a->tm_hour - b->tm_hour))
948 + (a->tm_min - b->tm_min))
949 + (a->tm_sec - b->tm_sec));
950 }
951
952 time_t
953 parsedate(const char *p, const time_t *now, const int *zone)
954 {
955 struct tm gmt, local, *gmt_ptr, *tm;
956 time_t nowt;
957 int zonet;
958 time_t Start;
959 time_t tod, rm;
960 struct dateinfo param;
961
962 if (now == NULL || zone == NULL) {
963 now = &nowt;
964 zone = &zonet;
965 (void)time(&nowt);
966
967 gmt_ptr = gmtime_r(now, &gmt);
968 if ((tm = localtime_r(now, &local)) == NULL)
969 return -1;
970
971 if (gmt_ptr != NULL)
972 zonet = difftm(&gmt, &local) / 60;
973 else
974 /* We are on a system like VMS, where the system clock is
975 in local time and the system has no concept of timezones.
976 Hopefully we can fake this out (for the case in which the
977 user specifies no timezone) by just saying the timezone
978 is zero. */
979 zonet = 0;
980
981 if (local.tm_isdst)
982 zonet += 60;
983 } else {
984 if ((tm = localtime_r(now, &local)) == NULL)
985 return -1;
986 }
987 param.yyYear = tm->tm_year + 1900;
988 param.yyMonth = tm->tm_mon + 1;
989 param.yyDay = tm->tm_mday;
990 param.yyTimezone = *zone;
991 param.yyDSTmode = DSTmaybe;
992 param.yyHour = 0;
993 param.yyMinutes = 0;
994 param.yySeconds = 0;
995 param.yyMeridian = MER24;
996 param.yyRelSeconds = 0;
997 param.yyRelMonth = 0;
998 param.yyHaveDate = 0;
999 param.yyHaveDay = 0;
1000 param.yyHaveRel = 0;
1001 param.yyHaveTime = 0;
1002 param.yyHaveZone = 0;
1003
1004 if (yyparse(¶m, &p) || param.yyHaveTime > 1 || param.yyHaveZone > 1 ||
1005 param.yyHaveDate > 1 || param.yyHaveDay > 1)
1006 return -1;
1007
1008 if (param.yyHaveDate || param.yyHaveTime || param.yyHaveDay) {
1009 Start = Convert(param.yyMonth, param.yyDay, param.yyYear, param.yyHour,
1010 param.yyMinutes, param.yySeconds, param.yyTimezone,
1011 param.yyMeridian, param.yyDSTmode);
1012 if (Start < 0)
1013 return -1;
1014 }
1015 else {
1016 Start = *now;
1017 if (!param.yyHaveRel)
1018 Start -= ((tm->tm_hour * 60L + tm->tm_min) * 60L) + tm->tm_sec;
1019 }
1020
1021 Start += param.yyRelSeconds;
1022 rm = RelativeMonth(Start, param.yyRelMonth, param.yyTimezone);
1023 if (rm == -1)
1024 return -1;
1025 Start += rm;
1026
1027 if (param.yyHaveDay && !param.yyHaveDate) {
1028 tod = RelativeDate(Start, param.yyDayOrdinal, param.yyDayNumber);
1029 Start += tod;
1030 }
1031
1032 return Start;
1033 }
1034
1035
1036 #if defined(TEST)
1037
1038 /* ARGSUSED */
1039 int
1040 main(ac, av)
1041 int ac;
1042 char *av[];
1043 {
1044 char buff[128];
1045 time_t d;
1046
1047 (void)printf("Enter date, or blank line to exit.\n\t> ");
1048 (void)fflush(stdout);
1049 while (gets(buff) && buff[0]) {
1050 d = parsedate(buff, NULL, NULL);
1051 if (d == -1)
1052 (void)printf("Bad format - couldn't convert.\n");
1053 else
1054 (void)printf("%s", ctime(&d));
1055 (void)printf("\t> ");
1056 (void)fflush(stdout);
1057 }
1058 exit(0);
1059 /* NOTREACHED */
1060 }
1061 #endif /* defined(TEST) */
1062