parsedate.y revision 1.34 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.34 2020/10/19 15:08:17 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 | tSNUMBER {
304 if (param->yyHaveDate == 0 && param->yyHaveTime == 0)
305 YYREJECT;
306 param->yyTimezone = - ($1 % 100 + ($1 / 100) * 60);
307 param->yyDSTmode = DSTmaybe;
308 }
309 ;
310
311 day:
312 tDAY { param->yyDayOrdinal = 1; param->yyDayNumber = $1; }
313 | tDAY ',' { param->yyDayOrdinal = 1; param->yyDayNumber = $1; }
314 | tUNUMBER tDAY { param->yyDayOrdinal = $1; param->yyDayNumber = $2; }
315 ;
316
317 date:
318 tUNUMBER '/' tUNUMBER {
319 param->yyMonth = $1;
320 param->yyDay = $3;
321 }
322 | tUNUMBER '/' tUNUMBER '/' tUNUMBER {
323 if ($1 >= 100) {
324 param->yyYear = $1;
325 param->yyMonth = $3;
326 param->yyDay = $5;
327 } else {
328 param->yyMonth = $1;
329 param->yyDay = $3;
330 param->yyYear = $5;
331 }
332 }
333 | tUNUMBER tSNUMBER tSNUMBER {
334 /* ISO 8601 format. yyyy-mm-dd. */
335 param->yyYear = $1;
336 param->yyHaveFullYear = 1;
337 param->yyMonth = -$2;
338 param->yyDay = -$3;
339 }
340 | tUNUMBER tMONTH tSNUMBER {
341 /* e.g. 17-JUN-1992. */
342 param->yyDay = $1;
343 param->yyMonth = $2;
344 param->yyYear = -$3;
345 }
346 | tMONTH tUNUMBER {
347 param->yyMonth = $1;
348 param->yyDay = $2;
349 }
350 | tMONTH tUNUMBER ',' tUNUMBER {
351 param->yyMonth = $1;
352 param->yyDay = $2;
353 param->yyYear = $4;
354 }
355 | tUNUMBER tMONTH {
356 param->yyMonth = $2;
357 param->yyDay = $1;
358 }
359 | tUNUMBER tMONTH tUNUMBER {
360 param->yyMonth = $2;
361 if ($1 < 35) {
362 param->yyDay = $1;
363 param->yyYear = $3;
364 } else {
365 param->yyDay = $3;
366 param->yyYear = $1;
367 }
368 }
369 ;
370
371 rel:
372 relunit
373 | relunit tAGO {
374 param->yyRel[param->yyHaveRel].yyRelVal =
375 -param->yyRel[param->yyHaveRel].yyRelVal;
376 }
377 ;
378
379 relunit:
380 tUNUMBER tMINUTE_UNIT { RelVal(param, $1 * $2 * 60L, 0); }
381 | tSNUMBER tMINUTE_UNIT { RelVal(param, $1 * $2 * 60L, 0); }
382 | tMINUTE_UNIT { RelVal(param, $1 * 60L, 0); }
383 | tSNUMBER tSEC_UNIT { RelVal(param, $1, 0); }
384 | tUNUMBER tSEC_UNIT { RelVal(param, $1, 0); }
385 | tSEC_UNIT { RelVal(param, 1L, 0); }
386 | tSNUMBER tMONTH_UNIT { RelVal(param, $1 * $2, 1); }
387 | tUNUMBER tMONTH_UNIT { RelVal(param, $1 * $2, 1); }
388 | tMONTH_UNIT { RelVal(param, $1, 1); }
389 ;
390
391 number:
392 tUNUMBER {
393 if (param->yyHaveTime && param->yyHaveDate &&
394 !param->yyHaveRel) {
395 param->yyYear = $1;
396 } else {
397 if ($1 > 10000) {
398 param->yyHaveDate++;
399 param->yyDay = ($1)%100;
400 param->yyMonth = ($1/100)%100;
401 param->yyYear = $1/10000;
402 }
403 else {
404 param->yyHaveTime++;
405 if ($1 < 100) {
406 param->yyHour = $1;
407 param->yyMinutes = 0;
408 }
409 else {
410 param->yyHour = $1 / 100;
411 param->yyMinutes = $1 % 100;
412 }
413 param->yySeconds = 0;
414 param->yyMeridian = MER24;
415 }
416 }
417 }
418 ;
419
420 o_merid:
421 /* empty */ { $$ = MER24; }
422 | tMERIDIAN { $$ = $1; }
423 | tTIME { $$ = $1 == 0 ? MER_MN : MER_NOON; }
424 ;
425
426 %%
427
428 static short DaysInMonth[12] = {
429 31, 28, 31, 30, 31, 30,
430 31, 31, 30, 31, 30, 31
431 };
432
433 /*
434 * works with tm.tm_year (ie: rel to 1900)
435 */
436 #define isleap(yr) (((yr) & 3) == 0 && (((yr) % 100) != 0 || \
437 ((1900+(yr)) % 400) == 0))
438
439 /* Month and day table. */
440 static const TABLE MonthDayTable[] = {
441 { "january", tMONTH, 1 },
442 { "february", tMONTH, 2 },
443 { "march", tMONTH, 3 },
444 { "april", tMONTH, 4 },
445 { "may", tMONTH, 5 },
446 { "june", tMONTH, 6 },
447 { "july", tMONTH, 7 },
448 { "august", tMONTH, 8 },
449 { "september", tMONTH, 9 },
450 { "sept", tMONTH, 9 },
451 { "october", tMONTH, 10 },
452 { "november", tMONTH, 11 },
453 { "december", tMONTH, 12 },
454 { "sunday", tDAY, 0 },
455 { "su", tDAY, 0 },
456 { "monday", tDAY, 1 },
457 { "mo", tDAY, 1 },
458 { "tuesday", tDAY, 2 },
459 { "tues", tDAY, 2 },
460 { "tu", tDAY, 2 },
461 { "wednesday", tDAY, 3 },
462 { "wednes", tDAY, 3 },
463 { "weds", tDAY, 3 },
464 { "we", tDAY, 3 },
465 { "thursday", tDAY, 4 },
466 { "thurs", tDAY, 4 },
467 { "thur", tDAY, 4 },
468 { "th", tDAY, 4 },
469 { "friday", tDAY, 5 },
470 { "fr", tDAY, 5 },
471 { "saturday", tDAY, 6 },
472 { "sa", tDAY, 6 },
473 { NULL, 0, 0 }
474 };
475
476 /* Time units table. */
477 static const TABLE UnitsTable[] = {
478 { "year", tMONTH_UNIT, 12 },
479 { "month", tMONTH_UNIT, 1 },
480 { "fortnight", tMINUTE_UNIT, 14 * 24 * 60 },
481 { "week", tMINUTE_UNIT, 7 * 24 * 60 },
482 { "day", tMINUTE_UNIT, 1 * 24 * 60 },
483 { "hour", tMINUTE_UNIT, 60 },
484 { "minute", tMINUTE_UNIT, 1 },
485 { "min", tMINUTE_UNIT, 1 },
486 { "second", tSEC_UNIT, 1 },
487 { "sec", tSEC_UNIT, 1 },
488 { NULL, 0, 0 }
489 };
490
491 /* Assorted relative-time words. */
492 static const TABLE OtherTable[] = {
493 { "tomorrow", tMINUTE_UNIT, 1 * 24 * 60 },
494 { "yesterday", tMINUTE_UNIT, -1 * 24 * 60 },
495 { "today", tMINUTE_UNIT, 0 },
496 { "now", tMINUTE_UNIT, 0 },
497 { "last", tUNUMBER, -1 },
498 { "this", tMINUTE_UNIT, 0 },
499 { "next", tUNUMBER, 2 },
500 { "first", tUNUMBER, 1 },
501 { "one", tUNUMBER, 1 },
502 /* { "second", tUNUMBER, 2 }, */
503 { "two", tUNUMBER, 2 },
504 { "third", tUNUMBER, 3 },
505 { "three", tUNUMBER, 3 },
506 { "fourth", tUNUMBER, 4 },
507 { "four", tUNUMBER, 4 },
508 { "fifth", tUNUMBER, 5 },
509 { "five", tUNUMBER, 5 },
510 { "sixth", tUNUMBER, 6 },
511 { "six", tUNUMBER, 6 },
512 { "seventh", tUNUMBER, 7 },
513 { "seven", tUNUMBER, 7 },
514 { "eighth", tUNUMBER, 8 },
515 { "eight", tUNUMBER, 8 },
516 { "ninth", tUNUMBER, 9 },
517 { "nine", tUNUMBER, 9 },
518 { "tenth", tUNUMBER, 10 },
519 { "ten", tUNUMBER, 10 },
520 { "eleventh", tUNUMBER, 11 },
521 { "eleven", tUNUMBER, 11 },
522 { "twelfth", tUNUMBER, 12 },
523 { "twelve", tUNUMBER, 12 },
524 { "ago", tAGO, 1 },
525 { NULL, 0, 0 }
526 };
527
528 /* The timezone table. */
529 /* Some of these are commented out because a time_t can't store a float. */
530 static const TABLE TimezoneTable[] = {
531 { "gmt", tZONE, HOUR( 0) }, /* Greenwich Mean */
532 { "ut", tZONE, HOUR( 0) }, /* Universal (Coordinated) */
533 { "utc", tZONE, HOUR( 0) },
534 { "wet", tZONE, HOUR( 0) }, /* Western European */
535 { "bst", tDAYZONE, HOUR( 0) }, /* British Summer */
536 { "wat", tZONE, HOUR( 1) }, /* West Africa */
537 { "at", tZONE, HOUR( 2) }, /* Azores */
538 #if 0
539 /* For completeness. BST is also British Summer, and GST is
540 * also Guam Standard. */
541 { "bst", tZONE, HOUR( 3) }, /* Brazil Standard */
542 { "gst", tZONE, HOUR( 3) }, /* Greenland Standard */
543 #endif
544 { "nft", tZONE, HOUR(3.5) }, /* Newfoundland */
545 { "nst", tZONE, HOUR(3.5) }, /* Newfoundland Standard */
546 { "ndt", tDAYZONE, HOUR(3.5) }, /* Newfoundland Daylight */
547 { "ast", tZONE, HOUR( 4) }, /* Atlantic Standard */
548 { "adt", tDAYZONE, HOUR( 4) }, /* Atlantic Daylight */
549 { "est", tZONE, HOUR( 5) }, /* Eastern Standard */
550 { "edt", tDAYZONE, HOUR( 5) }, /* Eastern Daylight */
551 { "cst", tZONE, HOUR( 6) }, /* Central Standard */
552 { "cdt", tDAYZONE, HOUR( 6) }, /* Central Daylight */
553 { "mst", tZONE, HOUR( 7) }, /* Mountain Standard */
554 { "mdt", tDAYZONE, HOUR( 7) }, /* Mountain Daylight */
555 { "pst", tZONE, HOUR( 8) }, /* Pacific Standard */
556 { "pdt", tDAYZONE, HOUR( 8) }, /* Pacific Daylight */
557 { "yst", tZONE, HOUR( 9) }, /* Yukon Standard */
558 { "ydt", tDAYZONE, HOUR( 9) }, /* Yukon Daylight */
559 { "hst", tZONE, HOUR(10) }, /* Hawaii Standard */
560 { "hdt", tDAYZONE, HOUR(10) }, /* Hawaii Daylight */
561 { "cat", tZONE, HOUR(10) }, /* Central Alaska */
562 { "ahst", tZONE, HOUR(10) }, /* Alaska-Hawaii Standard */
563 { "nt", tZONE, HOUR(11) }, /* Nome */
564 { "idlw", tZONE, HOUR(12) }, /* International Date Line West */
565 { "cet", tZONE, -HOUR(1) }, /* Central European */
566 { "met", tZONE, -HOUR(1) }, /* Middle European */
567 { "mewt", tZONE, -HOUR(1) }, /* Middle European Winter */
568 { "mest", tDAYZONE, -HOUR(1) }, /* Middle European Summer */
569 { "swt", tZONE, -HOUR(1) }, /* Swedish Winter */
570 { "sst", tDAYZONE, -HOUR(1) }, /* Swedish Summer */
571 { "fwt", tZONE, -HOUR(1) }, /* French Winter */
572 { "fst", tDAYZONE, -HOUR(1) }, /* French Summer */
573 { "eet", tZONE, -HOUR(2) }, /* Eastern Europe, USSR Zone 1 */
574 { "bt", tZONE, -HOUR(3) }, /* Baghdad, USSR Zone 2 */
575 { "it", tZONE, -HOUR(3.5) },/* Iran */
576 { "zp4", tZONE, -HOUR(4) }, /* USSR Zone 3 */
577 { "zp5", tZONE, -HOUR(5) }, /* USSR Zone 4 */
578 { "ist", tZONE, -HOUR(5.5) },/* Indian Standard */
579 { "zp6", tZONE, -HOUR(6) }, /* USSR Zone 5 */
580 #if 0
581 /* For completeness. NST is also Newfoundland Stanard, and SST is
582 * also Swedish Summer. */
583 { "nst", tZONE, -HOUR(6.5) },/* North Sumatra */
584 { "sst", tZONE, -HOUR(7) }, /* South Sumatra, USSR Zone 6 */
585 #endif /* 0 */
586 { "ict", tZONE, -HOUR(7) }, /* Indo China Time (Thai) */
587 #if 0 /* this one looks to be bogus */
588 { "jt", tZONE, -HOUR(7.5) },/* Java (3pm in Cronusland!) */
589 #endif
590 { "wast", tZONE, -HOUR(8) }, /* West Australian Standard */
591 { "awst", tZONE, -HOUR(8) }, /* West Australian Standard */
592 { "wadt", tDAYZONE, -HOUR(8) }, /* West Australian Daylight */
593 { "awdt", tDAYZONE, -HOUR(8) }, /* West Australian Daylight */
594 { "cct", tZONE, -HOUR(8) }, /* China Coast, USSR Zone 7 */
595 { "sgt", tZONE, -HOUR(8) }, /* Singapore */
596 { "hkt", tZONE, -HOUR(8) }, /* Hong Kong */
597 { "jst", tZONE, -HOUR(9) }, /* Japan Standard, USSR Zone 8 */
598 { "cast", tZONE, -HOUR(9.5) },/* Central Australian Standard */
599 { "acst", tZONE, -HOUR(9.5) },/* Central Australian Standard */
600 { "cadt", tDAYZONE, -HOUR(9.5) },/* Central Australian Daylight */
601 { "acdt", tDAYZONE, -HOUR(9.5) },/* Central Australian Daylight */
602 { "east", tZONE, -HOUR(10) }, /* Eastern Australian Standard */
603 { "aest", tZONE, -HOUR(10) }, /* Eastern Australian Standard */
604 { "eadt", tDAYZONE, -HOUR(10) }, /* Eastern Australian Daylight */
605 { "aedt", tDAYZONE, -HOUR(10) }, /* Eastern Australian Daylight */
606 { "gst", tZONE, -HOUR(10) }, /* Guam Standard, USSR Zone 9 */
607 { "nzt", tZONE, -HOUR(12) }, /* New Zealand */
608 { "nzst", tZONE, -HOUR(12) }, /* New Zealand Standard */
609 { "nzdt", tDAYZONE, -HOUR(12) }, /* New Zealand Daylight */
610 { "idle", tZONE, -HOUR(12) }, /* International Date Line East */
611 { NULL, 0, 0 }
612 };
613
614 /* Military timezone table. */
615 static const TABLE MilitaryTable[] = {
616 { "a", tZONE, HOUR( 1) },
617 { "b", tZONE, HOUR( 2) },
618 { "c", tZONE, HOUR( 3) },
619 { "d", tZONE, HOUR( 4) },
620 { "e", tZONE, HOUR( 5) },
621 { "f", tZONE, HOUR( 6) },
622 { "g", tZONE, HOUR( 7) },
623 { "h", tZONE, HOUR( 8) },
624 { "i", tZONE, HOUR( 9) },
625 { "k", tZONE, HOUR( 10) },
626 { "l", tZONE, HOUR( 11) },
627 { "m", tZONE, HOUR( 12) },
628 { "n", tZONE, HOUR(- 1) },
629 { "o", tZONE, HOUR(- 2) },
630 { "p", tZONE, HOUR(- 3) },
631 { "q", tZONE, HOUR(- 4) },
632 { "r", tZONE, HOUR(- 5) },
633 { "s", tZONE, HOUR(- 6) },
634 { "t", tZONE, HOUR(- 7) },
635 { "u", tZONE, HOUR(- 8) },
636 { "v", tZONE, HOUR(- 9) },
637 { "w", tZONE, HOUR(-10) },
638 { "x", tZONE, HOUR(-11) },
639 { "y", tZONE, HOUR(-12) },
640 { "z", tZONE, HOUR( 0) },
641 { NULL, 0, 0 }
642 };
643
644 static const TABLE TimeNames[] = {
645 { "midnight", tTIME, 0 },
646 { "mn", tTIME, 0 },
647 { "noon", tTIME, 12 },
648 { "midday", tTIME, 12 },
649 { NULL, 0, 0 }
650 };
651
652
653
655 /* ARGSUSED */
656 static int
657 yyerror(struct dateinfo *param, const char **inp, const char *s __unused)
658 {
659 return 0;
660 }
661
662 /*
663 * Save a relative value, if it fits
664 */
665 static void
666 RelVal(struct dateinfo *param, time_t v, int type)
667 {
668 int i;
669
670 if ((i = param->yyHaveRel) >= MAXREL)
671 return;
672 param->yyRel[i].yyRelMonth = type;
673 param->yyRel[i].yyRelVal = v;
674 }
675
676 /*
677 * Adjust year from a value that might be abbreviated, to a full value.
678 * e.g. convert 70 to 1970.
679 * Input Year is either:
680 * - A negative number, which means to use its absolute value (why?)
681 * - A number from 0 to 68, which means a year from 2000 to 2068,
682 * - A number from 69 to 99, which means a year from 1969 to 1999, or
683 * - The actual year (>=100).
684 * Returns the full year.
685 */
686 static time_t
687 AdjustYear(time_t Year)
688 {
689 /* XXX Y2K */
690 if (Year < 0)
691 Year = -Year;
692 if (Year < 69) /* POSIX compliant, 0..68 is 2000's, 69-99 1900's */
693 Year += 2000;
694 else if (Year < 100)
695 Year += 1900;
696 return Year;
697 }
698
699 static time_t
700 Convert(
701 time_t Month, /* month of year [1-12] */
702 time_t Day, /* day of month [1-31] */
703 time_t Year, /* year, not abbreviated in any way */
704 time_t Hours, /* Hour of day [0-24] */
705 time_t Minutes, /* Minute of hour [0-59] */
706 time_t Seconds, /* Second of minute [0-60] */
707 time_t Timezone, /* Timezone as minutes east of UTC,
708 * or USE_LOCAL_TIME special case */
709 MERIDIAN Meridian, /* Hours are am/pm/24 hour clock */
710 DSTMODE DSTmode /* DST on/off/maybe */
711 )
712 {
713 struct tm tm = {.tm_sec = 0};
714 struct tm otm;
715 time_t result;
716
717 tm.tm_sec = Seconds;
718 tm.tm_min = Minutes;
719 tm.tm_hour = ((Hours == 12 && Meridian != MER24) ? 0 : Hours) +
720 (Meridian == MERpm ? 12 : 0);
721
722 tm.tm_mday = Day;
723 tm.tm_mon = Month - 1;
724 tm.tm_year = Year - 1900;
725 if (Timezone == USE_LOCAL_TIME) {
726 switch (DSTmode) {
727 case DSTon: tm.tm_isdst = 1; break;
728 case DSToff: tm.tm_isdst = 0; break;
729 default: tm.tm_isdst = -1; break;
730 }
731 otm = tm;
732 result = mktime(&tm);
733 } else {
734 /* We rely on mktime_z(NULL, ...) working in UTC */
735 tm.tm_isdst = 0; /* hence cannot be summer time */
736 otm = tm;
737 errno = 0;
738 result = mktime_z(NULL, &tm);
739 if (result != -1 || errno == 0) {
740 result += Timezone * 60;
741 if (DSTmode == DSTon) /* if specified sumer time */
742 result -= 3600; /* UTC is 1 hour earlier XXX */
743 }
744 }
745
746 #if PARSEDATE_DEBUG
747 fprintf(stderr, "%s(M=%jd D=%jd Y=%jd H=%jd M=%jd S=%jd Z=%jd"
748 " mer=%d DST=%d)",
749 __func__,
750 (intmax_t)Month, (intmax_t)Day, (intmax_t)Year,
751 (intmax_t)Hours, (intmax_t)Minutes, (intmax_t)Seconds,
752 (intmax_t)Timezone, (int)Meridian, (int)DSTmode);
753 fprintf(stderr, " -> %jd", (intmax_t)result);
754 fprintf(stderr, " %s", ctime(&result));
755 #endif
756
757 #define TM_NE(fld) (otm.tm_ ## fld != tm.tm_ ## fld)
758 if (TM_NE(year) || TM_NE(mon) || TM_NE(mday) ||
759 TM_NE(hour) || TM_NE(min) || TM_NE(sec)) {
760 /* mktime() "corrected" our tm, so it must have been invalid */
761 result = -1;
762 errno = EAGAIN;
763 }
764 #undef TM_NE
765
766 return result;
767 }
768
769
770 static time_t
771 DSTcorrect(
772 time_t Start,
773 time_t Future
774 )
775 {
776 time_t StartDay;
777 time_t FutureDay;
778 struct tm tm;
779
780 if (localtime_r(&Start, &tm) == NULL)
781 return -1;
782 StartDay = (tm.tm_hour + 1) % 24;
783
784 if (localtime_r(&Future, &tm) == NULL)
785 return -1;
786 FutureDay = (tm.tm_hour + 1) % 24;
787
788 return (Future - Start) + (StartDay - FutureDay) * 60L * 60L;
789 }
790
791
792 static time_t
793 RelativeDate(
794 time_t Start,
795 time_t DayOrdinal,
796 time_t DayNumber
797 )
798 {
799 struct tm tm;
800 time_t now;
801
802 now = Start;
803 if (localtime_r(&now, &tm) == NULL)
804 return -1;
805 now += SECSPERDAY * ((DayNumber - tm.tm_wday + 7) % 7);
806 now += 7 * SECSPERDAY * (DayOrdinal <= 0 ? DayOrdinal : DayOrdinal - 1);
807 return DSTcorrect(Start, now);
808 }
809
810
811 static time_t
812 RelativeMonth(
813 time_t Start,
814 time_t RelMonth,
815 time_t Timezone
816 )
817 {
818 struct tm tm;
819 time_t Month;
820 time_t Then;
821 int Day;
822
823 if (RelMonth == 0)
824 return 0;
825 /*
826 * It doesn't matter what timezone we use to do this computation,
827 * as long as we use the same one to reassemble the time that we
828 * used to disassemble it. So always use localtime and mktime. In
829 * particular, don't use Convert() to reassemble, because it will
830 * not only reassemble with the wrong timezone but it will also
831 * fail if we do e.g. three months from March 31 yielding July 1.
832 */
833 (void)Timezone;
834
835 if (localtime_r(&Start, &tm) == NULL)
836 return -1;
837
838 Month = 12 * (tm.tm_year + 1900) + tm.tm_mon + RelMonth;
839 tm.tm_year = (Month / 12) - 1900;
840 tm.tm_mon = Month % 12;
841 if (tm.tm_mday > (Day = DaysInMonth[tm.tm_mon] +
842 ((tm.tm_mon==1) ? isleap(tm.tm_year) : 0)))
843 tm.tm_mday = Day;
844 errno = 0;
845 Then = mktime(&tm);
846 if (Then == -1 && errno != 0)
847 return -1;
848 return DSTcorrect(Start, Then);
849 }
850
851
852 static int
853 LookupWord(YYSTYPE *yylval, char *buff)
854 {
855 register char *p;
856 register char *q;
857 register const TABLE *tp;
858 int i;
859 int abbrev;
860
861 /* Make it lowercase. */
862 for (p = buff; *p; p++)
863 if (isupper((unsigned char)*p))
864 *p = tolower((unsigned char)*p);
865
866 if (strcmp(buff, "am") == 0 || strcmp(buff, "a.m.") == 0) {
867 yylval->Meridian = MERam;
868 return tMERIDIAN;
869 }
870 if (strcmp(buff, "pm") == 0 || strcmp(buff, "p.m.") == 0) {
871 yylval->Meridian = MERpm;
872 return tMERIDIAN;
873 }
874
875 /* See if we have an abbreviation for a month. */
876 if (strlen(buff) == 3)
877 abbrev = 1;
878 else if (strlen(buff) == 4 && buff[3] == '.') {
879 abbrev = 1;
880 buff[3] = '\0';
881 }
882 else
883 abbrev = 0;
884
885 for (tp = MonthDayTable; tp->name; tp++) {
886 if (abbrev) {
887 if (strncmp(buff, tp->name, 3) == 0) {
888 yylval->Number = tp->value;
889 return tp->type;
890 }
891 }
892 else if (strcmp(buff, tp->name) == 0) {
893 yylval->Number = tp->value;
894 return tp->type;
895 }
896 }
897
898 for (tp = TimezoneTable; tp->name; tp++)
899 if (strcmp(buff, tp->name) == 0) {
900 yylval->Number = tp->value;
901 return tp->type;
902 }
903
904 if (strcmp(buff, "dst") == 0)
905 return tDST;
906
907 for (tp = TimeNames; tp->name; tp++)
908 if (strcmp(buff, tp->name) == 0) {
909 yylval->Number = tp->value;
910 return tp->type;
911 }
912
913 for (tp = UnitsTable; tp->name; tp++)
914 if (strcmp(buff, tp->name) == 0) {
915 yylval->Number = tp->value;
916 return tp->type;
917 }
918
919 /* Strip off any plural and try the units table again. */
920 i = strlen(buff) - 1;
921 if (buff[i] == 's') {
922 buff[i] = '\0';
923 for (tp = UnitsTable; tp->name; tp++)
924 if (strcmp(buff, tp->name) == 0) {
925 yylval->Number = tp->value;
926 return tp->type;
927 }
928 buff[i] = 's'; /* Put back for "this" in OtherTable. */
929 }
930
931 for (tp = OtherTable; tp->name; tp++)
932 if (strcmp(buff, tp->name) == 0) {
933 yylval->Number = tp->value;
934 return tp->type;
935 }
936
937 /* Military timezones. */
938 if (buff[1] == '\0' && isalpha((unsigned char)*buff)) {
939 for (tp = MilitaryTable; tp->name; tp++)
940 if (strcmp(buff, tp->name) == 0) {
941 yylval->Number = tp->value;
942 return tp->type;
943 }
944 }
945
946 /* Drop out any periods and try the timezone table again. */
947 for (i = 0, p = q = buff; *q; q++)
948 if (*q != '.')
949 *p++ = *q;
950 else
951 i++;
952 *p = '\0';
953 if (i)
954 for (tp = TimezoneTable; tp->name; tp++)
955 if (strcmp(buff, tp->name) == 0) {
956 yylval->Number = tp->value;
957 return tp->type;
958 }
959
960 return tID;
961 }
962
963
964 static int
965 yylex(YYSTYPE *yylval, const char **yyInput)
966 {
967 register char c;
968 register char *p;
969 char buff[20];
970 int Count;
971 int sign;
972 const char *inp = *yyInput;
973
974 for ( ; ; ) {
975 while (isspace((unsigned char)*inp))
976 inp++;
977
978 if (isdigit((unsigned char)(c = *inp)) || c == '-' || c == '+') {
979 if (c == '-' || c == '+') {
980 sign = c == '-' ? -1 : 1;
981 if (!isdigit((unsigned char)*++inp))
982 /* skip the '-' sign */
983 continue;
984 }
985 else
986 sign = 0;
987 for (yylval->Number = 0; isdigit((unsigned char)(c = *inp++)); )
988 yylval->Number = 10 * yylval->Number + c - '0';
989 if (sign < 0)
990 yylval->Number = -yylval->Number;
991 *yyInput = --inp;
992 return sign ? tSNUMBER : tUNUMBER;
993 }
994 if (isalpha((unsigned char)c)) {
995 for (p = buff; isalpha((unsigned char)(c = *inp++)) || c == '.'; )
996 if (p < &buff[sizeof buff - 1])
997 *p++ = c;
998 *p = '\0';
999 *yyInput = --inp;
1000 return LookupWord(yylval, buff);
1001 }
1002 if (c == '@') {
1003 *yyInput = ++inp;
1004 return AT_SIGN;
1005 }
1006 if (c != '(') {
1007 *yyInput = ++inp;
1008 return c;
1009 }
1010 Count = 0;
1011 do {
1012 c = *inp++;
1013 if (c == '\0')
1014 return c;
1015 if (c == '(')
1016 Count++;
1017 else if (c == ')')
1018 Count--;
1019 } while (Count > 0);
1020 }
1021 }
1022
1023 #define TM_YEAR_ORIGIN 1900
1024
1025 time_t
1026 parsedate(const char *p, const time_t *now, const int *zone)
1027 {
1028 struct tm local, *tm;
1029 time_t nowt;
1030 int zonet;
1031 time_t Start;
1032 time_t tod, rm;
1033 struct dateinfo param;
1034 int saved_errno;
1035 int i;
1036
1037 saved_errno = errno;
1038 errno = 0;
1039
1040 if (now == NULL) {
1041 now = &nowt;
1042 (void)time(&nowt);
1043 }
1044 if (zone == NULL) {
1045 zone = &zonet;
1046 zonet = USE_LOCAL_TIME;
1047 if ((tm = localtime_r(now, &local)) == NULL)
1048 return -1;
1049 } else {
1050 /*
1051 * Should use the specified zone, not localtime.
1052 * Fake it using gmtime and arithmetic.
1053 * This is good enough because we use only the year/month/day,
1054 * not other fields of struct tm.
1055 */
1056 time_t fake = *now + (*zone * 60);
1057 if ((tm = gmtime_r(&fake, &local)) == NULL)
1058 return -1;
1059 }
1060 param.yyYear = tm->tm_year + 1900;
1061 param.yyMonth = tm->tm_mon + 1;
1062 param.yyDay = tm->tm_mday;
1063 param.yyTimezone = *zone;
1064 param.yyDSTmode = DSTmaybe;
1065 param.yyHour = 0;
1066 param.yyMinutes = 0;
1067 param.yySeconds = 0;
1068 param.yyMeridian = MER24;
1069 param.yyHaveDate = 0;
1070 param.yyHaveFullYear = 0;
1071 param.yyHaveDay = 0;
1072 param.yyHaveRel = 0;
1073 param.yyHaveTime = 0;
1074 param.yyHaveZone = 0;
1075
1076 /*
1077 * This one is too hard to parse using a grammar (the lexer would
1078 * confuse the 'T' with the Mil format timezone designator)
1079 * so handle it as a special case.
1080 */
1081 do {
1082 const unsigned char *pp = (const unsigned char *)p;
1083 char *ep; /* starts as "expected, becomes "end ptr" */
1084 static char format[] = "-dd-ddTdd:dd:dd";
1085
1086 while (isdigit(*pp))
1087 pp++;
1088
1089 if (pp == (const unsigned char *)p)
1090 break;
1091
1092 for (ep = format; *ep; ep++, pp++) {
1093 switch (*ep) {
1094 case 'd':
1095 if (isdigit(*pp))
1096 continue;
1097 break;
1098 case 'T':
1099 if (*pp == 'T' || *pp == 't' || *pp == ' ')
1100 continue;
1101 break;
1102 default:
1103 if (*pp == *ep)
1104 continue;
1105 break;
1106 }
1107 break;
1108 }
1109 if (*ep != '\0')
1110 break;
1111 if (*pp == '.' || *pp == ',') {
1112 if (!isdigit(pp[1]))
1113 break;
1114 while (isdigit(*++pp))
1115 continue;
1116 }
1117 if (*pp == 'Z' || *pp == 'z')
1118 pp++;
1119 else if (isdigit(*pp))
1120 break;
1121
1122 if (*pp != '\0' && !isspace(*pp))
1123 break;
1124
1125 /*
1126 * This is good enough to commit to there being an ISO format
1127 * timestamp leading the input string. We permit standard
1128 * parsedate() modifiers to follow but not precede this string.
1129 */
1130 param.yyHaveTime = 1;
1131 param.yyHaveDate = 1;
1132 param.yyHaveFullYear = 1;
1133
1134 if (pp[-1] == 'Z' || pp[-1] == 'z') {
1135 param.yyTimezone = 0;
1136 param.yyHaveZone = 1;
1137 }
1138
1139 errno = 0;
1140 param.yyYear = (time_t)strtol(p, &ep, 10);
1141 if (errno != 0) /* out of range (can be big number) */
1142 break; /* the ones below are all 2 digits */
1143 param.yyMonth = (time_t)strtol(ep + 1, &ep, 10);
1144 param.yyDay = (time_t)strtol(ep + 1, &ep, 10);
1145 param.yyHour = (time_t)strtol(ep + 1, &ep, 10);
1146 param.yyMinutes = (time_t)strtol(ep + 1, &ep, 10);
1147 param.yySeconds = (time_t)strtol(ep + 1, &ep, 10);
1148 /* ignore any fractional seconds, no way to return them in a time_t */
1149
1150 param.yyMeridian = MER24;
1151
1152 p = (const char *)pp;
1153 } while (0);
1154
1155 if (yyparse(¶m, &p) || param.yyHaveTime > 1 || param.yyHaveZone > 1 ||
1156 param.yyHaveDate > 1 || param.yyHaveDay > 1) {
1157 errno = EINVAL;
1158 return -1;
1159 }
1160
1161 if (param.yyHaveDate || param.yyHaveTime || param.yyHaveDay) {
1162 if (! param.yyHaveFullYear) {
1163 param.yyYear = AdjustYear(param.yyYear);
1164 param.yyHaveFullYear = 1;
1165 }
1166 errno = 0;
1167 Start = Convert(param.yyMonth, param.yyDay, param.yyYear, param.yyHour,
1168 param.yyMinutes, param.yySeconds, param.yyTimezone,
1169 param.yyMeridian, param.yyDSTmode);
1170 if (Start == -1 && errno != 0)
1171 return -1;
1172 }
1173 else {
1174 Start = *now;
1175 if (!param.yyHaveRel)
1176 Start -= ((tm->tm_hour * 60L + tm->tm_min) * 60L) + tm->tm_sec;
1177 }
1178
1179 if (param.yyHaveRel > MAXREL) {
1180 errno = EINVAL;
1181 return -1;
1182 }
1183 for (i = 0; i < param.yyHaveRel; i++) {
1184 if (param.yyRel[i].yyRelMonth) {
1185 errno = 0;
1186 rm = RelativeMonth(Start, param.yyRel[i].yyRelVal, param.yyTimezone);
1187 if (rm == -1 && errno != 0)
1188 return -1;
1189 Start += rm;
1190 } else
1191 Start += param.yyRel[i].yyRelVal;
1192 }
1193
1194 if (param.yyHaveDay && !param.yyHaveDate) {
1195 errno = 0;
1196 tod = RelativeDate(Start, param.yyDayOrdinal, param.yyDayNumber);
1197 if (tod == -1 && errno != 0)
1198 return -1;
1199 Start += tod;
1200 }
1201
1202 errno = saved_errno;
1203 return Start;
1204 }
1205
1206
1207 #if defined(TEST)
1208
1209 /* ARGSUSED */
1210 int
1211 main(int ac, char *av[])
1212 {
1213 char buff[128];
1214 time_t d;
1215
1216 (void)printf("Enter date, or blank line to exit.\n\t> ");
1217 (void)fflush(stdout);
1218 while (fgets(buff, sizeof(buff), stdin) && buff[0] != '\n') {
1219 errno = 0;
1220 d = parsedate(buff, NULL, NULL);
1221 if (d == -1 && errno != 0)
1222 (void)printf("Bad format - couldn't convert: %s\n",
1223 strerror(errno));
1224 else
1225 (void)printf("%jd\t%s", (intmax_t)d, ctime(&d));
1226 (void)printf("\t> ");
1227 (void)fflush(stdout);
1228 }
1229 exit(0);
1230 /* NOTREACHED */
1231 }
1232 #endif /* defined(TEST) */
1233