parsedate.y revision 1.25 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.25 2015/12/31 09:12:57 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 ((tm = localtime(&Start)) == NULL)
693 return -1;
694 StartDay = (tm->tm_hour + 1) % 24;
695
696 if ((tm = localtime(&Future)) == 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 tm = localtime(&now);
716 if (tm == NULL)
717 return -1;
718 now += SECSPERDAY * ((DayNumber - tm->tm_wday + 7) % 7);
719 now += 7 * SECSPERDAY * (DayOrdinal <= 0 ? DayOrdinal : DayOrdinal - 1);
720 return DSTcorrect(Start, now);
721 }
722
723
724 static time_t
725 RelativeMonth(
726 time_t Start,
727 time_t RelMonth,
728 time_t Timezone
729 )
730 {
731 struct tm *tm;
732 time_t Month;
733 time_t Year;
734
735 if (RelMonth == 0)
736 return 0;
737 tm = localtime(&Start);
738 if (tm == NULL)
739 return -1;
740 Month = 12 * (tm->tm_year + 1900) + tm->tm_mon + RelMonth;
741 Year = Month / 12;
742 Month = Month % 12 + 1;
743 return DSTcorrect(Start,
744 Convert(Month, (time_t)tm->tm_mday, Year,
745 (time_t)tm->tm_hour, (time_t)tm->tm_min, (time_t)tm->tm_sec,
746 Timezone, MER24, DSTmaybe));
747 }
748
749
750 static int
751 LookupWord(YYSTYPE *yylval, char *buff)
752 {
753 register char *p;
754 register char *q;
755 register const TABLE *tp;
756 int i;
757 int abbrev;
758
759 /* Make it lowercase. */
760 for (p = buff; *p; p++)
761 if (isupper((unsigned char)*p))
762 *p = tolower((unsigned char)*p);
763
764 if (strcmp(buff, "am") == 0 || strcmp(buff, "a.m.") == 0) {
765 yylval->Meridian = MERam;
766 return tMERIDIAN;
767 }
768 if (strcmp(buff, "pm") == 0 || strcmp(buff, "p.m.") == 0) {
769 yylval->Meridian = MERpm;
770 return tMERIDIAN;
771 }
772
773 /* See if we have an abbreviation for a month. */
774 if (strlen(buff) == 3)
775 abbrev = 1;
776 else if (strlen(buff) == 4 && buff[3] == '.') {
777 abbrev = 1;
778 buff[3] = '\0';
779 }
780 else
781 abbrev = 0;
782
783 for (tp = MonthDayTable; tp->name; tp++) {
784 if (abbrev) {
785 if (strncmp(buff, tp->name, 3) == 0) {
786 yylval->Number = tp->value;
787 return tp->type;
788 }
789 }
790 else if (strcmp(buff, tp->name) == 0) {
791 yylval->Number = tp->value;
792 return tp->type;
793 }
794 }
795
796 for (tp = TimezoneTable; tp->name; tp++)
797 if (strcmp(buff, tp->name) == 0) {
798 yylval->Number = tp->value;
799 return tp->type;
800 }
801
802 if (strcmp(buff, "dst") == 0)
803 return tDST;
804
805 for (tp = TimeNames; tp->name; tp++)
806 if (strcmp(buff, tp->name) == 0) {
807 yylval->Number = tp->value;
808 return tp->type;
809 }
810
811 for (tp = UnitsTable; tp->name; tp++)
812 if (strcmp(buff, tp->name) == 0) {
813 yylval->Number = tp->value;
814 return tp->type;
815 }
816
817 /* Strip off any plural and try the units table again. */
818 i = strlen(buff) - 1;
819 if (buff[i] == 's') {
820 buff[i] = '\0';
821 for (tp = UnitsTable; tp->name; tp++)
822 if (strcmp(buff, tp->name) == 0) {
823 yylval->Number = tp->value;
824 return tp->type;
825 }
826 buff[i] = 's'; /* Put back for "this" in OtherTable. */
827 }
828
829 for (tp = OtherTable; tp->name; tp++)
830 if (strcmp(buff, tp->name) == 0) {
831 yylval->Number = tp->value;
832 return tp->type;
833 }
834
835 /* Military timezones. */
836 if (buff[1] == '\0' && isalpha((unsigned char)*buff)) {
837 for (tp = MilitaryTable; tp->name; tp++)
838 if (strcmp(buff, tp->name) == 0) {
839 yylval->Number = tp->value;
840 return tp->type;
841 }
842 }
843
844 /* Drop out any periods and try the timezone table again. */
845 for (i = 0, p = q = buff; *q; q++)
846 if (*q != '.')
847 *p++ = *q;
848 else
849 i++;
850 *p = '\0';
851 if (i)
852 for (tp = TimezoneTable; tp->name; tp++)
853 if (strcmp(buff, tp->name) == 0) {
854 yylval->Number = tp->value;
855 return tp->type;
856 }
857
858 return tID;
859 }
860
861
862 static int
863 yylex(YYSTYPE *yylval, const char **yyInput)
864 {
865 register char c;
866 register char *p;
867 char buff[20];
868 int Count;
869 int sign;
870 const char *inp = *yyInput;
871
872 for ( ; ; ) {
873 while (isspace((unsigned char)*inp))
874 inp++;
875
876 if (isdigit((unsigned char)(c = *inp)) || c == '-' || c == '+') {
877 if (c == '-' || c == '+') {
878 sign = c == '-' ? -1 : 1;
879 if (!isdigit((unsigned char)*++inp))
880 /* skip the '-' sign */
881 continue;
882 }
883 else
884 sign = 0;
885 for (yylval->Number = 0; isdigit((unsigned char)(c = *inp++)); )
886 yylval->Number = 10 * yylval->Number + c - '0';
887 if (sign < 0)
888 yylval->Number = -yylval->Number;
889 *yyInput = --inp;
890 return sign ? tSNUMBER : tUNUMBER;
891 }
892 if (isalpha((unsigned char)c)) {
893 for (p = buff; isalpha((unsigned char)(c = *inp++)) || c == '.'; )
894 if (p < &buff[sizeof buff - 1])
895 *p++ = c;
896 *p = '\0';
897 *yyInput = --inp;
898 return LookupWord(yylval, buff);
899 }
900 if (c == '@') {
901 *yyInput = ++inp;
902 return AT_SIGN;
903 }
904 if (c != '(') {
905 *yyInput = ++inp;
906 return c;
907 }
908 Count = 0;
909 do {
910 c = *inp++;
911 if (c == '\0')
912 return c;
913 if (c == '(')
914 Count++;
915 else if (c == ')')
916 Count--;
917 } while (Count > 0);
918 }
919 }
920
921 #define TM_YEAR_ORIGIN 1900
922
923 time_t
924 parsedate(const char *p, const time_t *now, const int *zone)
925 {
926 struct tm local, *tm;
927 time_t nowt;
928 int zonet;
929 time_t Start;
930 time_t tod, rm;
931 struct dateinfo param;
932 int saved_errno;
933
934 saved_errno = errno;
935 errno = 0;
936
937 if (now == NULL) {
938 now = &nowt;
939 (void)time(&nowt);
940 }
941 if (zone == NULL) {
942 zone = &zonet;
943 zonet = USE_LOCAL_TIME;
944 if ((tm = localtime_r(now, &local)) == NULL)
945 return -1;
946 } else {
947 /*
948 * Should use the specified zone, not localtime.
949 * Fake it using gmtime and arithmetic.
950 * This is good enough because we use only the year/month/day,
951 * not other fields of struct tm.
952 */
953 time_t fake = *now + (*zone * 60);
954 if ((tm = gmtime_r(&fake, &local)) == NULL)
955 return -1;
956 }
957 param.yyYear = tm->tm_year + 1900;
958 param.yyMonth = tm->tm_mon + 1;
959 param.yyDay = tm->tm_mday;
960 param.yyTimezone = *zone;
961 param.yyDSTmode = DSTmaybe;
962 param.yyHour = 0;
963 param.yyMinutes = 0;
964 param.yySeconds = 0;
965 param.yyMeridian = MER24;
966 param.yyRelSeconds = 0;
967 param.yyRelMonth = 0;
968 param.yyHaveDate = 0;
969 param.yyHaveFullYear = 0;
970 param.yyHaveDay = 0;
971 param.yyHaveRel = 0;
972 param.yyHaveTime = 0;
973 param.yyHaveZone = 0;
974
975 if (yyparse(¶m, &p) || param.yyHaveTime > 1 || param.yyHaveZone > 1 ||
976 param.yyHaveDate > 1 || param.yyHaveDay > 1) {
977 errno = EINVAL;
978 return -1;
979 }
980
981 if (param.yyHaveDate || param.yyHaveTime || param.yyHaveDay) {
982 if (! param.yyHaveFullYear) {
983 param.yyYear = AdjustYear(param.yyYear);
984 param.yyHaveFullYear = 1;
985 }
986 errno = 0;
987 Start = Convert(param.yyMonth, param.yyDay, param.yyYear, param.yyHour,
988 param.yyMinutes, param.yySeconds, param.yyTimezone,
989 param.yyMeridian, param.yyDSTmode);
990 if (Start == -1 && errno != 0)
991 return -1;
992 }
993 else {
994 Start = *now;
995 if (!param.yyHaveRel)
996 Start -= ((tm->tm_hour * 60L + tm->tm_min) * 60L) + tm->tm_sec;
997 }
998
999 Start += param.yyRelSeconds;
1000 errno = 0;
1001 rm = RelativeMonth(Start, param.yyRelMonth, param.yyTimezone);
1002 if (rm == -1 && errno != 0)
1003 return -1;
1004 Start += rm;
1005
1006 if (param.yyHaveDay && !param.yyHaveDate) {
1007 errno = 0;
1008 tod = RelativeDate(Start, param.yyDayOrdinal, param.yyDayNumber);
1009 if (tod == -1 && errno != 0)
1010 return -1;
1011 Start += tod;
1012 }
1013
1014 errno = saved_errno;
1015 return Start;
1016 }
1017
1018
1019 #if defined(TEST)
1020
1021 /* ARGSUSED */
1022 int
1023 main(int ac, char *av[])
1024 {
1025 char buff[128];
1026 time_t d;
1027
1028 (void)printf("Enter date, or blank line to exit.\n\t> ");
1029 (void)fflush(stdout);
1030 while (fgets(buff, sizeof(buff), stdin) && buff[0] != '\n') {
1031 errno = 0;
1032 d = parsedate(buff, NULL, NULL);
1033 if (d == -1 && errno != 0)
1034 (void)printf("Bad format - couldn't convert: %s\n",
1035 strerror(errno));
1036 else
1037 (void)printf("%jd\t%s", (intmax_t)d, ctime(&d));
1038 (void)printf("\t> ");
1039 (void)fflush(stdout);
1040 }
1041 exit(0);
1042 /* NOTREACHED */
1043 }
1044 #endif /* defined(TEST) */
1045