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