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