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