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