Line data Source code
1 : %{
2 : /* Parse a string into an internal time stamp.
3 : Copyright (C) 1999, 2000, 2002 Free Software Foundation, Inc.
4 :
5 : This program is free software; you can redistribute it and/or modify
6 : it under the terms of the GNU General Public License as published by
7 : the Free Software Foundation; either version 2, or (at your option)
8 : any later version.
9 :
10 : This program is distributed in the hope that it will be useful,
11 : but WITHOUT ANY WARRANTY; without even the implied warranty of
12 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 : GNU General Public License for more details.
14 :
15 : You should have received a copy of the GNU General Public License
16 : along with this program; if not, see <http://www.gnu.org/licenses/>. */
17 :
18 : /* Originally written by Steven M. Bellovin <smb@research.att.com> while
19 : at the University of North Carolina at Chapel Hill. Later tweaked by
20 : a couple of people on Usenet. Completely overhauled by Rich $alz
21 : <rsalz@bbn.com> and Jim Berets <jberets@bbn.com> in August, 1990.
22 :
23 : Modified by Paul Eggert <eggert@twinsun.com> in August 1999 to do
24 : the right thing about local DST. Unlike previous versions, this
25 : version is reentrant. */
26 :
27 : #ifdef HAVE_CONFIG_H
28 : # include <config.h>
29 : # ifdef HAVE_ALLOCA_H
30 : # include <alloca.h>
31 : # endif
32 : #endif
33 :
34 : /* Since the code of getdate.y is not included in the Emacs executable
35 : itself, there is no need to #define static in this file. Even if
36 : the code were included in the Emacs executable, it probably
37 : wouldn't do any harm to #undef it here; this will only cause
38 : problems if we try to write to a static variable, which I don't
39 : think this code needs to do. */
40 : #ifdef emacs
41 : # undef static
42 : #endif
43 :
44 : #include <ctype.h>
45 : #include <string.h>
46 :
47 : #ifdef HAVE_STDLIB_H
48 : # include <stdlib.h> /* for `free'; used by Bison 1.27 */
49 : #endif
50 :
51 : #if STDC_HEADERS || (! defined isascii && ! HAVE_ISASCII)
52 : # define IN_CTYPE_DOMAIN(c) 1
53 : #else
54 : # define IN_CTYPE_DOMAIN(c) isascii (c)
55 : #endif
56 :
57 : #define ISSPACE(c) (IN_CTYPE_DOMAIN (c) && isspace (c))
58 : #define ISALPHA(c) (IN_CTYPE_DOMAIN (c) && isalpha (c))
59 : #define ISLOWER(c) (IN_CTYPE_DOMAIN (c) && islower (c))
60 : #define ISDIGIT_LOCALE(c) (IN_CTYPE_DOMAIN (c) && isdigit (c))
61 :
62 : /* ISDIGIT differs from ISDIGIT_LOCALE, as follows:
63 : - Its arg may be any int or unsigned int; it need not be an unsigned char.
64 : - It's guaranteed to evaluate its argument exactly once.
65 : - It's typically faster.
66 : POSIX says that only '0' through '9' are digits. Prefer ISDIGIT to
67 : ISDIGIT_LOCALE unless it's important to use the locale's definition
68 : of `digit' even when the host does not conform to POSIX. */
69 : #define ISDIGIT(c) ((unsigned) (c) - '0' <= 9)
70 :
71 : #if STDC_HEADERS || HAVE_STRING_H
72 : # include <string.h>
73 : #endif
74 :
75 : #ifndef HAVE___ATTRIBUTE__
76 : # define __attribute__(x)
77 : #endif
78 :
79 : #ifndef ATTRIBUTE_UNUSED
80 : # define ATTRIBUTE_UNUSED __attribute__ ((__unused__))
81 : #endif
82 :
83 : #define EPOCH_YEAR 1970
84 : #define TM_YEAR_BASE 1900
85 :
86 : #define HOUR(x) ((x) * 60)
87 :
88 : /* An integer value, and the number of digits in its textual
89 : representation. */
90 : typedef struct
91 : {
92 : int value;
93 : int digits;
94 : } textint;
95 :
96 : /* An entry in the lexical lookup table. */
97 : typedef struct
98 : {
99 : char const *name;
100 : int type;
101 : int value;
102 : } table;
103 :
104 : /* Meridian: am, pm, or 24-hour style. */
105 : enum { MERam, MERpm, MER24 };
106 :
107 : /* Information passed to and from the parser. */
108 : struct parser_control
109 : {
110 : /* The input string remaining to be parsed. */
111 : const char *input;
112 :
113 : /* N, if this is the Nth Tuesday. */
114 : int day_ordinal;
115 :
116 : /* Day of week; Sunday is 0. */
117 : int day_number;
118 :
119 : /* tm_isdst flag for the local zone. */
120 : int local_isdst;
121 :
122 : /* Time zone, in minutes east of UTC. */
123 : int time_zone;
124 :
125 : /* Style used for time. */
126 : int meridian;
127 :
128 : /* Gregorian year, month, day, hour, minutes, and seconds. */
129 : textint year;
130 : int month;
131 : int day;
132 : int hour;
133 : int minutes;
134 : int seconds;
135 :
136 : /* Relative year, month, day, hour, minutes, and seconds. */
137 : int rel_year;
138 : int rel_month;
139 : int rel_day;
140 : int rel_hour;
141 : int rel_minutes;
142 : int rel_seconds;
143 :
144 : /* Counts of nonterminals of various flavors parsed so far. */
145 : int dates_seen;
146 : int days_seen;
147 : int local_zones_seen;
148 : int rels_seen;
149 : int times_seen;
150 : int zones_seen;
151 :
152 : /* Table of local time zone abbrevations, terminated by a null entry. */
153 : table local_time_zone_table[3];
154 : };
155 :
156 : %}
157 :
158 : %lex-param {struct parser_control *pc}
159 : %parse-param {struct parser_control *pc}
160 :
161 : /* We want a reentrant parser. */
162 : %pure-parser
163 :
164 : /* This grammar has 13 shift/reduce conflicts. */
165 : %expect 13
166 :
167 : %union
168 : {
169 : int intval;
170 : textint textintval;
171 : }
172 :
173 : %{
174 :
175 : static int yyerror(struct parser_control *, const char *);
176 : static int yylex(YYSTYPE *, struct parser_control *);
177 :
178 : %}
179 :
180 : %token tAGO tDST
181 :
182 : %token <intval> tDAY tDAY_UNIT tDAYZONE tHOUR_UNIT tLOCAL_ZONE tMERIDIAN
183 : %token <intval> tMINUTE_UNIT tMONTH tMONTH_UNIT tSEC_UNIT tYEAR_UNIT tZONE
184 :
185 : %token <textintval> tSNUMBER tUNUMBER
186 :
187 : %type <intval> o_merid
188 :
189 : %%
190 :
191 : spec:
192 : /* empty */
193 : | spec item
194 : ;
195 :
196 : item:
197 : time
198 0 : { pc->times_seen++; }
199 : | local_zone
200 0 : { pc->local_zones_seen++; }
201 : | zone
202 0 : { pc->zones_seen++; }
203 : | date
204 0 : { pc->dates_seen++; }
205 : | day
206 0 : { pc->days_seen++; }
207 : | rel
208 0 : { pc->rels_seen++; }
209 : | number
210 : ;
211 :
212 : time:
213 : tUNUMBER tMERIDIAN
214 : {
215 0 : pc->hour = $1.value;
216 0 : pc->minutes = 0;
217 0 : pc->seconds = 0;
218 0 : pc->meridian = $2;
219 : }
220 : | tUNUMBER ':' tUNUMBER o_merid
221 : {
222 0 : pc->hour = $1.value;
223 0 : pc->minutes = $3.value;
224 0 : pc->seconds = 0;
225 0 : pc->meridian = $4;
226 : }
227 : | tUNUMBER ':' tUNUMBER tSNUMBER
228 : {
229 0 : pc->hour = $1.value;
230 0 : pc->minutes = $3.value;
231 0 : pc->meridian = MER24;
232 0 : pc->zones_seen++;
233 0 : pc->time_zone = $4.value % 100 + ($4.value / 100) * 60;
234 : }
235 : | tUNUMBER ':' tUNUMBER ':' tUNUMBER o_merid
236 : {
237 0 : pc->hour = $1.value;
238 0 : pc->minutes = $3.value;
239 0 : pc->seconds = $5.value;
240 0 : pc->meridian = $6;
241 : }
242 : | tUNUMBER ':' tUNUMBER ':' tUNUMBER tSNUMBER
243 : {
244 0 : pc->hour = $1.value;
245 0 : pc->minutes = $3.value;
246 0 : pc->seconds = $5.value;
247 0 : pc->meridian = MER24;
248 0 : pc->zones_seen++;
249 0 : pc->time_zone = $6.value % 100 + ($6.value / 100) * 60;
250 : }
251 : ;
252 :
253 : local_zone:
254 : tLOCAL_ZONE
255 0 : { pc->local_isdst = $1; }
256 : | tLOCAL_ZONE tDST
257 0 : { pc->local_isdst = $1 < 0 ? 1 : $1 + 1; }
258 : ;
259 :
260 : zone:
261 : tZONE
262 0 : { pc->time_zone = $1; }
263 : | tDAYZONE
264 0 : { pc->time_zone = $1 + 60; }
265 : | tZONE tDST
266 0 : { pc->time_zone = $1 + 60; }
267 : ;
268 :
269 : day:
270 : tDAY
271 : {
272 0 : pc->day_ordinal = 1;
273 0 : pc->day_number = $1;
274 : }
275 : | tDAY ','
276 : {
277 0 : pc->day_ordinal = 1;
278 0 : pc->day_number = $1;
279 : }
280 : | tUNUMBER tDAY
281 : {
282 0 : pc->day_ordinal = $1.value;
283 0 : pc->day_number = $2;
284 : }
285 : ;
286 :
287 : date:
288 : tUNUMBER '/' tUNUMBER
289 : {
290 0 : pc->month = $1.value;
291 0 : pc->day = $3.value;
292 : }
293 : | tUNUMBER '/' tUNUMBER '/' tUNUMBER
294 : {
295 : /* Interpret as YYYY/MM/DD if the first value has 4 or more digits,
296 : otherwise as MM/DD/YY.
297 : The goal in recognizing YYYY/MM/DD is solely to support legacy
298 : machine-generated dates like those in an RCS log listing. If
299 : you want portability, use the ISO 8601 format. */
300 0 : if (4 <= $1.digits)
301 : {
302 0 : pc->year = $1;
303 0 : pc->month = $3.value;
304 0 : pc->day = $5.value;
305 : }
306 : else
307 : {
308 0 : pc->month = $1.value;
309 0 : pc->day = $3.value;
310 0 : pc->year = $5;
311 : }
312 : }
313 : | tUNUMBER tSNUMBER tSNUMBER
314 : {
315 : /* ISO 8601 format. YYYY-MM-DD. */
316 0 : pc->year = $1;
317 0 : pc->month = -$2.value;
318 0 : pc->day = -$3.value;
319 : }
320 : | tUNUMBER tMONTH tSNUMBER
321 : {
322 : /* e.g. 17-JUN-1992. */
323 0 : pc->day = $1.value;
324 0 : pc->month = $2;
325 0 : pc->year.value = -$3.value;
326 0 : pc->year.digits = $3.digits;
327 : }
328 : | tMONTH tUNUMBER
329 : {
330 0 : pc->month = $1;
331 0 : pc->day = $2.value;
332 : }
333 : | tMONTH tUNUMBER ',' tUNUMBER
334 : {
335 0 : pc->month = $1;
336 0 : pc->day = $2.value;
337 0 : pc->year = $4;
338 : }
339 : | tUNUMBER tMONTH
340 : {
341 0 : pc->day = $1.value;
342 0 : pc->month = $2;
343 : }
344 : | tUNUMBER tMONTH tUNUMBER
345 : {
346 0 : pc->day = $1.value;
347 0 : pc->month = $2;
348 0 : pc->year = $3;
349 : }
350 : ;
351 :
352 : rel:
353 : relunit tAGO
354 : {
355 0 : pc->rel_seconds = -pc->rel_seconds;
356 0 : pc->rel_minutes = -pc->rel_minutes;
357 0 : pc->rel_hour = -pc->rel_hour;
358 0 : pc->rel_day = -pc->rel_day;
359 0 : pc->rel_month = -pc->rel_month;
360 0 : pc->rel_year = -pc->rel_year;
361 : }
362 : | relunit
363 : ;
364 :
365 : relunit:
366 : tUNUMBER tYEAR_UNIT
367 0 : { pc->rel_year += $1.value * $2; }
368 : | tSNUMBER tYEAR_UNIT
369 0 : { pc->rel_year += $1.value * $2; }
370 : | tYEAR_UNIT
371 0 : { pc->rel_year += $1; }
372 : | tUNUMBER tMONTH_UNIT
373 0 : { pc->rel_month += $1.value * $2; }
374 : | tSNUMBER tMONTH_UNIT
375 0 : { pc->rel_month += $1.value * $2; }
376 : | tMONTH_UNIT
377 0 : { pc->rel_month += $1; }
378 : | tUNUMBER tDAY_UNIT
379 0 : { pc->rel_day += $1.value * $2; }
380 : | tSNUMBER tDAY_UNIT
381 0 : { pc->rel_day += $1.value * $2; }
382 : | tDAY_UNIT
383 0 : { pc->rel_day += $1; }
384 : | tUNUMBER tHOUR_UNIT
385 0 : { pc->rel_hour += $1.value * $2; }
386 : | tSNUMBER tHOUR_UNIT
387 0 : { pc->rel_hour += $1.value * $2; }
388 : | tHOUR_UNIT
389 0 : { pc->rel_hour += $1; }
390 : | tUNUMBER tMINUTE_UNIT
391 0 : { pc->rel_minutes += $1.value * $2; }
392 : | tSNUMBER tMINUTE_UNIT
393 0 : { pc->rel_minutes += $1.value * $2; }
394 : | tMINUTE_UNIT
395 0 : { pc->rel_minutes += $1; }
396 : | tUNUMBER tSEC_UNIT
397 0 : { pc->rel_seconds += $1.value * $2; }
398 : | tSNUMBER tSEC_UNIT
399 0 : { pc->rel_seconds += $1.value * $2; }
400 : | tSEC_UNIT
401 0 : { pc->rel_seconds += $1; }
402 : ;
403 :
404 : number:
405 : tUNUMBER
406 : {
407 0 : if (pc->dates_seen
408 0 : && ! pc->rels_seen && (pc->times_seen || 2 < $1.digits))
409 0 : pc->year = $1;
410 : else
411 : {
412 0 : if (4 < $1.digits)
413 : {
414 0 : pc->dates_seen++;
415 0 : pc->day = $1.value % 100;
416 0 : pc->month = ($1.value / 100) % 100;
417 0 : pc->year.value = $1.value / 10000;
418 0 : pc->year.digits = $1.digits - 4;
419 : }
420 : else
421 : {
422 0 : pc->times_seen++;
423 0 : if ($1.digits <= 2)
424 : {
425 0 : pc->hour = $1.value;
426 0 : pc->minutes = 0;
427 : }
428 : else
429 : {
430 0 : pc->hour = $1.value / 100;
431 0 : pc->minutes = $1.value % 100;
432 : }
433 0 : pc->seconds = 0;
434 0 : pc->meridian = MER24;
435 : }
436 : }
437 : }
438 : ;
439 :
440 : o_merid:
441 : /* empty */
442 0 : { $$ = MER24; }
443 : | tMERIDIAN
444 0 : { $$ = $1; }
445 : ;
446 :
447 : %%
448 :
449 : /* Include this file down here because bison inserts code above which
450 : may define-away `const'. We want the prototype for get_date to have
451 : the same signature as the function definition. */
452 : #include "modules/getdate.h"
453 :
454 : #ifndef gmtime
455 : struct tm *gmtime (const time_t *);
456 : #endif
457 : #ifndef localtime
458 : struct tm *localtime (const time_t *);
459 : #endif
460 : #ifndef mktime
461 : time_t mktime (struct tm *);
462 : #endif
463 :
464 : static table const meridian_table[] =
465 : {
466 : { "AM", tMERIDIAN, MERam },
467 : { "A.M.", tMERIDIAN, MERam },
468 : { "PM", tMERIDIAN, MERpm },
469 : { "P.M.", tMERIDIAN, MERpm },
470 : { 0, 0, 0 }
471 : };
472 :
473 : static table const dst_table[] =
474 : {
475 : { "DST", tDST, 0 }
476 : };
477 :
478 : static table const month_and_day_table[] =
479 : {
480 : { "JANUARY", tMONTH, 1 },
481 : { "FEBRUARY", tMONTH, 2 },
482 : { "MARCH", tMONTH, 3 },
483 : { "APRIL", tMONTH, 4 },
484 : { "MAY", tMONTH, 5 },
485 : { "JUNE", tMONTH, 6 },
486 : { "JULY", tMONTH, 7 },
487 : { "AUGUST", tMONTH, 8 },
488 : { "SEPTEMBER",tMONTH, 9 },
489 : { "SEPT", tMONTH, 9 },
490 : { "OCTOBER", tMONTH, 10 },
491 : { "NOVEMBER", tMONTH, 11 },
492 : { "DECEMBER", tMONTH, 12 },
493 : { "SUNDAY", tDAY, 0 },
494 : { "MONDAY", tDAY, 1 },
495 : { "TUESDAY", tDAY, 2 },
496 : { "TUES", tDAY, 2 },
497 : { "WEDNESDAY",tDAY, 3 },
498 : { "WEDNES", tDAY, 3 },
499 : { "THURSDAY", tDAY, 4 },
500 : { "THUR", tDAY, 4 },
501 : { "THURS", tDAY, 4 },
502 : { "FRIDAY", tDAY, 5 },
503 : { "SATURDAY", tDAY, 6 },
504 : { 0, 0, 0 }
505 : };
506 :
507 : static table const time_units_table[] =
508 : {
509 : { "YEAR", tYEAR_UNIT, 1 },
510 : { "MONTH", tMONTH_UNIT, 1 },
511 : { "FORTNIGHT",tDAY_UNIT, 14 },
512 : { "WEEK", tDAY_UNIT, 7 },
513 : { "DAY", tDAY_UNIT, 1 },
514 : { "HOUR", tHOUR_UNIT, 1 },
515 : { "MINUTE", tMINUTE_UNIT, 1 },
516 : { "MIN", tMINUTE_UNIT, 1 },
517 : { "SECOND", tSEC_UNIT, 1 },
518 : { "SEC", tSEC_UNIT, 1 },
519 : { 0, 0, 0 }
520 : };
521 :
522 : /* Assorted relative-time words. */
523 : static table const relative_time_table[] =
524 : {
525 : { "TOMORROW", tMINUTE_UNIT, 24 * 60 },
526 : { "YESTERDAY",tMINUTE_UNIT, - (24 * 60) },
527 : { "TODAY", tMINUTE_UNIT, 0 },
528 : { "NOW", tMINUTE_UNIT, 0 },
529 : { "LAST", tUNUMBER, -1 },
530 : { "THIS", tUNUMBER, 0 },
531 : { "NEXT", tUNUMBER, 1 },
532 : { "FIRST", tUNUMBER, 1 },
533 : /*{ "SECOND", tUNUMBER, 2 }, */
534 : { "THIRD", tUNUMBER, 3 },
535 : { "FOURTH", tUNUMBER, 4 },
536 : { "FIFTH", tUNUMBER, 5 },
537 : { "SIXTH", tUNUMBER, 6 },
538 : { "SEVENTH", tUNUMBER, 7 },
539 : { "EIGHTH", tUNUMBER, 8 },
540 : { "NINTH", tUNUMBER, 9 },
541 : { "TENTH", tUNUMBER, 10 },
542 : { "ELEVENTH", tUNUMBER, 11 },
543 : { "TWELFTH", tUNUMBER, 12 },
544 : { "AGO", tAGO, 1 },
545 : { 0, 0, 0 }
546 : };
547 :
548 : /* The time zone table. This table is necessarily incomplete, as time
549 : zone abbreviations are ambiguous; e.g. Australians interpret "EST"
550 : as Eastern time in Australia, not as US Eastern Standard Time.
551 : You cannot rely on getdate to handle arbitrary time zone
552 : abbreviations; use numeric abbreviations like `-0500' instead. */
553 : static table const time_zone_table[] =
554 : {
555 : { "GMT", tZONE, HOUR ( 0) }, /* Greenwich Mean */
556 : { "UT", tZONE, HOUR ( 0) }, /* Universal (Coordinated) */
557 : { "UTC", tZONE, HOUR ( 0) },
558 : { "WET", tZONE, HOUR ( 0) }, /* Western European */
559 : { "WEST", tDAYZONE, HOUR ( 0) }, /* Western European Summer */
560 : { "BST", tDAYZONE, HOUR ( 0) }, /* British Summer */
561 : { "ART", tZONE, -HOUR ( 3) }, /* Argentina */
562 : { "BRT", tZONE, -HOUR ( 3) }, /* Brazil */
563 : { "BRST", tDAYZONE, -HOUR ( 3) }, /* Brazil Summer */
564 : { "NST", tZONE, -(HOUR ( 3) + 30) }, /* Newfoundland Standard */
565 : { "NDT", tDAYZONE,-(HOUR ( 3) + 30) }, /* Newfoundland Daylight */
566 : { "AST", tZONE, -HOUR ( 4) }, /* Atlantic Standard */
567 : { "ADT", tDAYZONE, -HOUR ( 4) }, /* Atlantic Daylight */
568 : { "CLT", tZONE, -HOUR ( 4) }, /* Chile */
569 : { "CLST", tDAYZONE, -HOUR ( 4) }, /* Chile Summer */
570 : { "EST", tZONE, -HOUR ( 5) }, /* Eastern Standard */
571 : { "EDT", tDAYZONE, -HOUR ( 5) }, /* Eastern Daylight */
572 : { "CST", tZONE, -HOUR ( 6) }, /* Central Standard */
573 : { "CDT", tDAYZONE, -HOUR ( 6) }, /* Central Daylight */
574 : { "MST", tZONE, -HOUR ( 7) }, /* Mountain Standard */
575 : { "MDT", tDAYZONE, -HOUR ( 7) }, /* Mountain Daylight */
576 : { "PST", tZONE, -HOUR ( 8) }, /* Pacific Standard */
577 : { "PDT", tDAYZONE, -HOUR ( 8) }, /* Pacific Daylight */
578 : { "AKST", tZONE, -HOUR ( 9) }, /* Alaska Standard */
579 : { "AKDT", tDAYZONE, -HOUR ( 9) }, /* Alaska Daylight */
580 : { "HST", tZONE, -HOUR (10) }, /* Hawaii Standard */
581 : { "HAST", tZONE, -HOUR (10) }, /* Hawaii-Aleutian Standard */
582 : { "HADT", tDAYZONE, -HOUR (10) }, /* Hawaii-Aleutian Daylight */
583 : { "SST", tZONE, -HOUR (12) }, /* Samoa Standard */
584 : { "WAT", tZONE, HOUR ( 1) }, /* West Africa */
585 : { "CET", tZONE, HOUR ( 1) }, /* Central European */
586 : { "CEST", tDAYZONE, HOUR ( 1) }, /* Central European Summer */
587 : { "MET", tZONE, HOUR ( 1) }, /* Middle European */
588 : { "MEZ", tZONE, HOUR ( 1) }, /* Middle European */
589 : { "MEST", tDAYZONE, HOUR ( 1) }, /* Middle European Summer */
590 : { "MESZ", tDAYZONE, HOUR ( 1) }, /* Middle European Summer */
591 : { "EET", tZONE, HOUR ( 2) }, /* Eastern European */
592 : { "EEST", tDAYZONE, HOUR ( 2) }, /* Eastern European Summer */
593 : { "CAT", tZONE, HOUR ( 2) }, /* Central Africa */
594 : { "SAST", tZONE, HOUR ( 2) }, /* South Africa Standard */
595 : { "EAT", tZONE, HOUR ( 3) }, /* East Africa */
596 : { "MSK", tZONE, HOUR ( 3) }, /* Moscow */
597 : { "MSD", tDAYZONE, HOUR ( 3) }, /* Moscow Daylight */
598 : { "IST", tZONE, (HOUR ( 5) + 30) }, /* India Standard */
599 : { "SGT", tZONE, HOUR ( 8) }, /* Singapore */
600 : { "KST", tZONE, HOUR ( 9) }, /* Korea Standard */
601 : { "JST", tZONE, HOUR ( 9) }, /* Japan Standard */
602 : { "GST", tZONE, HOUR (10) }, /* Guam Standard */
603 : { "NZST", tZONE, HOUR (12) }, /* New Zealand Standard */
604 : { "NZDT", tDAYZONE, HOUR (12) }, /* New Zealand Daylight */
605 : { 0, 0, 0 }
606 : };
607 :
608 : /* Military time zone table. */
609 : static table const military_table[] =
610 : {
611 : { "A", tZONE, -HOUR ( 1) },
612 : { "B", tZONE, -HOUR ( 2) },
613 : { "C", tZONE, -HOUR ( 3) },
614 : { "D", tZONE, -HOUR ( 4) },
615 : { "E", tZONE, -HOUR ( 5) },
616 : { "F", tZONE, -HOUR ( 6) },
617 : { "G", tZONE, -HOUR ( 7) },
618 : { "H", tZONE, -HOUR ( 8) },
619 : { "I", tZONE, -HOUR ( 9) },
620 : { "K", tZONE, -HOUR (10) },
621 : { "L", tZONE, -HOUR (11) },
622 : { "M", tZONE, -HOUR (12) },
623 : { "N", tZONE, HOUR ( 1) },
624 : { "O", tZONE, HOUR ( 2) },
625 : { "P", tZONE, HOUR ( 3) },
626 : { "Q", tZONE, HOUR ( 4) },
627 : { "R", tZONE, HOUR ( 5) },
628 : { "S", tZONE, HOUR ( 6) },
629 : { "T", tZONE, HOUR ( 7) },
630 : { "U", tZONE, HOUR ( 8) },
631 : { "V", tZONE, HOUR ( 9) },
632 : { "W", tZONE, HOUR (10) },
633 : { "X", tZONE, HOUR (11) },
634 : { "Y", tZONE, HOUR (12) },
635 : { "Z", tZONE, HOUR ( 0) },
636 : { 0, 0, 0 }
637 : };
638 :
639 :
640 :
641 : static int
642 0 : to_hour (int hours, int meridian)
643 : {
644 0 : switch (meridian)
645 : {
646 0 : case MER24:
647 0 : return 0 <= hours && hours < 24 ? hours : -1;
648 0 : case MERam:
649 0 : return 0 < hours && hours < 12 ? hours : hours == 12 ? 0 : -1;
650 0 : case MERpm:
651 0 : return 0 < hours && hours < 12 ? hours + 12 : hours == 12 ? 12 : -1;
652 0 : default:
653 0 : abort ();
654 : }
655 : /* NOTREACHED */
656 : return 0;
657 : }
658 :
659 : static int
660 0 : to_year (textint textyear)
661 : {
662 0 : int year = textyear.value;
663 :
664 0 : if (year < 0)
665 0 : year = -year;
666 :
667 : /* XPG4 suggests that years 00-68 map to 2000-2068, and
668 : years 69-99 map to 1969-1999. */
669 0 : if (textyear.digits == 2)
670 0 : year += year < 69 ? 2000 : 1900;
671 :
672 0 : return year;
673 : }
674 :
675 : static table const *
676 0 : lookup_zone (struct parser_control const *pc, char const *name)
677 : {
678 : table const *tp;
679 :
680 : /* Try local zone abbreviations first; they're more likely to be right. */
681 0 : for (tp = pc->local_time_zone_table; tp->name; tp++)
682 0 : if (strcmp (name, tp->name) == 0)
683 0 : return tp;
684 :
685 0 : for (tp = time_zone_table; tp->name; tp++)
686 0 : if (strcmp (name, tp->name) == 0)
687 0 : return tp;
688 :
689 0 : return 0;
690 : }
691 :
692 : #if ! HAVE_TM_GMTOFF
693 : /* Yield the difference between *A and *B,
694 : measured in seconds, ignoring leap seconds.
695 : The body of this function is taken directly from the GNU C Library;
696 : see src/strftime.c. */
697 : static int
698 0 : tm_diff (struct tm const *a, struct tm const *b)
699 : {
700 : /* Compute intervening leap days correctly even if year is negative.
701 : Take care to avoid int overflow in leap day calculations,
702 : but it's OK to assume that A and B are close to each other. */
703 0 : int a4 = (a->tm_year >> 2) + (TM_YEAR_BASE >> 2) - ! (a->tm_year & 3);
704 0 : int b4 = (b->tm_year >> 2) + (TM_YEAR_BASE >> 2) - ! (b->tm_year & 3);
705 0 : int a100 = a4 / 25 - (a4 % 25 < 0);
706 0 : int b100 = b4 / 25 - (b4 % 25 < 0);
707 0 : int a400 = a100 >> 2;
708 0 : int b400 = b100 >> 2;
709 0 : int intervening_leap_days = (a4 - b4) - (a100 - b100) + (a400 - b400);
710 0 : int years = a->tm_year - b->tm_year;
711 0 : int days = (365 * years + intervening_leap_days
712 0 : + (a->tm_yday - b->tm_yday));
713 0 : return (60 * (60 * (24 * days + (a->tm_hour - b->tm_hour))
714 0 : + (a->tm_min - b->tm_min))
715 0 : + (a->tm_sec - b->tm_sec));
716 : }
717 : #endif /* ! HAVE_TM_GMTOFF */
718 :
719 : static table const *
720 0 : lookup_word (struct parser_control const *pc, char *word)
721 : {
722 : char *p;
723 : char *q;
724 : size_t wordlen;
725 : table const *tp;
726 : int i;
727 : int abbrev;
728 :
729 : /* Make it uppercase. */
730 0 : for (p = word; *p; p++)
731 0 : if (ISLOWER ((unsigned char) *p))
732 0 : *p = toupper ((unsigned char) *p);
733 :
734 0 : for (tp = meridian_table; tp->name; tp++)
735 0 : if (strcmp (word, tp->name) == 0)
736 0 : return tp;
737 :
738 : /* See if we have an abbreviation for a month. */
739 0 : wordlen = strlen (word);
740 0 : abbrev = wordlen == 3 || (wordlen == 4 && word[3] == '.');
741 :
742 0 : for (tp = month_and_day_table; tp->name; tp++)
743 0 : if ((abbrev ? strncmp (word, tp->name, 3) : strcmp (word, tp->name)) == 0)
744 0 : return tp;
745 :
746 0 : if ((tp = lookup_zone (pc, word)))
747 0 : return tp;
748 :
749 0 : if (strcmp (word, dst_table[0].name) == 0)
750 0 : return dst_table;
751 :
752 0 : for (tp = time_units_table; tp->name; tp++)
753 0 : if (strcmp (word, tp->name) == 0)
754 0 : return tp;
755 :
756 : /* Strip off any plural and try the units table again. */
757 0 : if (word[wordlen - 1] == 'S')
758 : {
759 0 : word[wordlen - 1] = '\0';
760 0 : for (tp = time_units_table; tp->name; tp++)
761 0 : if (strcmp (word, tp->name) == 0)
762 0 : return tp;
763 0 : word[wordlen - 1] = 'S'; /* For "this" in relative_time_table. */
764 : }
765 :
766 0 : for (tp = relative_time_table; tp->name; tp++)
767 0 : if (strcmp (word, tp->name) == 0)
768 0 : return tp;
769 :
770 : /* Military time zones. */
771 0 : if (wordlen == 1)
772 0 : for (tp = military_table; tp->name; tp++)
773 0 : if (word[0] == tp->name[0])
774 0 : return tp;
775 :
776 : /* Drop out any periods and try the time zone table again. */
777 0 : for (i = 0, p = q = word; (*p = *q); q++)
778 0 : if (*q == '.')
779 0 : i = 1;
780 : else
781 0 : p++;
782 0 : if (i && (tp = lookup_zone (pc, word)))
783 0 : return tp;
784 :
785 0 : return 0;
786 : }
787 :
788 : static int
789 0 : yylex (YYSTYPE *lvalp, struct parser_control *pc)
790 : {
791 : unsigned char c;
792 : size_t count;
793 :
794 : for (;;)
795 : {
796 0 : while (c = *pc->input, ISSPACE (c))
797 0 : pc->input++;
798 :
799 0 : if (ISDIGIT (c) || c == '-' || c == '+')
800 : {
801 : char const *p;
802 : int sign;
803 : int value;
804 0 : if (c == '-' || c == '+')
805 : {
806 0 : sign = c == '-' ? -1 : 1;
807 0 : c = *++pc->input;
808 0 : if (! ISDIGIT (c))
809 : /* skip the '-' sign */
810 0 : continue;
811 : }
812 : else
813 0 : sign = 0;
814 0 : p = pc->input;
815 0 : value = 0;
816 : do
817 : {
818 0 : value = 10 * value + c - '0';
819 0 : c = *++p;
820 : }
821 0 : while (ISDIGIT (c));
822 0 : lvalp->textintval.value = sign < 0 ? -value : value;
823 0 : lvalp->textintval.digits = p - pc->input;
824 0 : pc->input = p;
825 0 : return sign ? tSNUMBER : tUNUMBER;
826 : }
827 :
828 0 : if (ISALPHA (c))
829 : {
830 : char buff[20];
831 0 : size_t i = 0;
832 : table const *tp;
833 :
834 : do
835 : {
836 0 : if (i < 20)
837 0 : buff[i++] = c;
838 0 : c = *++pc->input;
839 : }
840 0 : while (ISALPHA (c) || c == '.');
841 :
842 0 : buff[i] = '\0';
843 0 : tp = lookup_word (pc, buff);
844 0 : if (! tp)
845 0 : return '?';
846 0 : lvalp->intval = tp->value;
847 0 : return tp->type;
848 : }
849 :
850 0 : if (c != '(')
851 0 : return *pc->input++;
852 0 : count = 0;
853 : do
854 : {
855 0 : c = *pc->input++;
856 0 : if (c == '\0')
857 0 : return c;
858 0 : if (c == '(')
859 0 : count++;
860 0 : else if (c == ')')
861 0 : count--;
862 : }
863 0 : while (count > 0);
864 : }
865 : }
866 :
867 : /* Do nothing if the parser reports an error. */
868 : static int
869 0 : yyerror (struct parser_control *pc ATTRIBUTE_UNUSED, const char *s ATTRIBUTE_UNUSED)
870 : {
871 0 : return 0;
872 : }
873 :
874 : /* Parse a date/time string P. Return the corresponding time_t value,
875 : or (time_t) -1 if there is an error. P can be an incomplete or
876 : relative time specification; if so, use *NOW as the basis for the
877 : returned time. */
878 : time_t
879 0 : get_date (const char *p, const time_t *now)
880 : {
881 0 : time_t Start = now ? *now : time (0);
882 0 : struct tm *tmp = localtime (&Start);
883 : struct tm tm;
884 : struct tm tm0;
885 : struct parser_control pc;
886 :
887 0 : if (! tmp)
888 0 : return -1;
889 :
890 0 : pc.input = p;
891 0 : pc.year.value = tmp->tm_year + TM_YEAR_BASE;
892 0 : pc.year.digits = 4;
893 0 : pc.month = tmp->tm_mon + 1;
894 0 : pc.day = tmp->tm_mday;
895 0 : pc.hour = tmp->tm_hour;
896 0 : pc.minutes = tmp->tm_min;
897 0 : pc.seconds = tmp->tm_sec;
898 0 : tm.tm_isdst = tmp->tm_isdst;
899 :
900 0 : pc.meridian = MER24;
901 0 : pc.rel_seconds = 0;
902 0 : pc.rel_minutes = 0;
903 0 : pc.rel_hour = 0;
904 0 : pc.rel_day = 0;
905 0 : pc.rel_month = 0;
906 0 : pc.rel_year = 0;
907 0 : pc.dates_seen = 0;
908 0 : pc.days_seen = 0;
909 0 : pc.rels_seen = 0;
910 0 : pc.times_seen = 0;
911 0 : pc.local_zones_seen = 0;
912 0 : pc.zones_seen = 0;
913 :
914 : #ifdef HAVE_STRUCT_TM_TM_ZONE
915 : pc.local_time_zone_table[0].name = tmp->tm_zone;
916 : pc.local_time_zone_table[0].type = tLOCAL_ZONE;
917 : pc.local_time_zone_table[0].value = tmp->tm_isdst;
918 : pc.local_time_zone_table[1].name = 0;
919 :
920 : /* Probe the names used in the next three calendar quarters, looking
921 : for a tm_isdst different from the one we already have. */
922 : {
923 : int quarter;
924 : for (quarter = 1; quarter <= 3; quarter++)
925 : {
926 : time_t probe = Start + quarter * (90 * 24 * 60 * 60);
927 : struct tm *probe_tm = localtime (&probe);
928 : if (probe_tm && probe_tm->tm_zone
929 : && probe_tm->tm_isdst != pc.local_time_zone_table[0].value)
930 : {
931 : {
932 : pc.local_time_zone_table[1].name = probe_tm->tm_zone;
933 : pc.local_time_zone_table[1].type = tLOCAL_ZONE;
934 : pc.local_time_zone_table[1].value = probe_tm->tm_isdst;
935 : pc.local_time_zone_table[2].name = 0;
936 : }
937 : break;
938 : }
939 : }
940 : }
941 : #else
942 : #ifdef HAVE_TZNAME
943 : {
944 : # ifndef tzname
945 : extern char *tzname[];
946 : # endif
947 : int i;
948 : for (i = 0; i < 2; i++)
949 : {
950 : pc.local_time_zone_table[i].name = tzname[i];
951 : pc.local_time_zone_table[i].type = tLOCAL_ZONE;
952 : pc.local_time_zone_table[i].value = i;
953 : }
954 : pc.local_time_zone_table[i].name = 0;
955 : }
956 : #else
957 0 : pc.local_time_zone_table[0].name = 0;
958 : #endif
959 : #endif
960 :
961 0 : if (pc.local_time_zone_table[0].name && pc.local_time_zone_table[1].name
962 0 : && ! strcmp (pc.local_time_zone_table[0].name,
963 : pc.local_time_zone_table[1].name))
964 : {
965 : /* This locale uses the same abbrevation for standard and
966 : daylight times. So if we see that abbreviation, we don't
967 : know whether it's daylight time. */
968 0 : pc.local_time_zone_table[0].value = -1;
969 0 : pc.local_time_zone_table[1].name = 0;
970 : }
971 :
972 0 : if (yyparse (&pc) != 0
973 0 : || 1 < pc.times_seen || 1 < pc.dates_seen || 1 < pc.days_seen
974 0 : || 1 < (pc.local_zones_seen + pc.zones_seen)
975 0 : || (pc.local_zones_seen && 1 < pc.local_isdst))
976 0 : return -1;
977 :
978 0 : tm.tm_year = to_year (pc.year) - TM_YEAR_BASE + pc.rel_year;
979 0 : tm.tm_mon = pc.month - 1 + pc.rel_month;
980 0 : tm.tm_mday = pc.day + pc.rel_day;
981 0 : if (pc.times_seen || (pc.rels_seen && ! pc.dates_seen && ! pc.days_seen))
982 : {
983 0 : tm.tm_hour = to_hour (pc.hour, pc.meridian);
984 0 : if (tm.tm_hour < 0)
985 0 : return -1;
986 0 : tm.tm_min = pc.minutes;
987 0 : tm.tm_sec = pc.seconds;
988 : }
989 : else
990 : {
991 0 : tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
992 : }
993 :
994 : /* Let mktime deduce tm_isdst if we have an absolute time stamp,
995 : or if the relative time stamp mentions days, months, or years. */
996 0 : if (pc.dates_seen | pc.days_seen | pc.times_seen | pc.rel_day
997 0 : | pc.rel_month | pc.rel_year)
998 0 : tm.tm_isdst = -1;
999 :
1000 : /* But if the input explicitly specifies local time with or without
1001 : DST, give mktime that information. */
1002 0 : if (pc.local_zones_seen)
1003 0 : tm.tm_isdst = pc.local_isdst;
1004 :
1005 0 : tm0 = tm;
1006 :
1007 0 : Start = mktime (&tm);
1008 :
1009 0 : if (Start == (time_t) -1)
1010 : {
1011 :
1012 : /* Guard against falsely reporting errors near the time_t boundaries
1013 : when parsing times in other time zones. For example, if the min
1014 : time_t value is 1970-01-01 00:00:00 UTC and we are 8 hours ahead
1015 : of UTC, then the min localtime value is 1970-01-01 08:00:00; if
1016 : we apply mktime to 1970-01-01 00:00:00 we will get an error, so
1017 : we apply mktime to 1970-01-02 08:00:00 instead and adjust the time
1018 : zone by 24 hours to compensate. This algorithm assumes that
1019 : there is no DST transition within a day of the time_t boundaries. */
1020 0 : if (pc.zones_seen)
1021 : {
1022 0 : tm = tm0;
1023 0 : if (tm.tm_year <= EPOCH_YEAR - TM_YEAR_BASE)
1024 : {
1025 0 : tm.tm_mday++;
1026 0 : pc.time_zone += 24 * 60;
1027 : }
1028 : else
1029 : {
1030 0 : tm.tm_mday--;
1031 0 : pc.time_zone -= 24 * 60;
1032 : }
1033 0 : Start = mktime (&tm);
1034 : }
1035 :
1036 0 : if (Start == (time_t) -1)
1037 0 : return Start;
1038 : }
1039 :
1040 0 : if (pc.days_seen && ! pc.dates_seen)
1041 : {
1042 0 : tm.tm_mday += ((pc.day_number - tm.tm_wday + 7) % 7
1043 0 : + 7 * (pc.day_ordinal - (0 < pc.day_ordinal)));
1044 0 : tm.tm_isdst = -1;
1045 0 : Start = mktime (&tm);
1046 0 : if (Start == (time_t) -1)
1047 0 : return Start;
1048 : }
1049 :
1050 0 : if (pc.zones_seen)
1051 : {
1052 0 : int delta = pc.time_zone * 60;
1053 : #ifdef HAVE_TM_GMTOFF
1054 : delta -= tm.tm_gmtoff;
1055 : #else
1056 0 : struct tm *gmt = gmtime (&Start);
1057 0 : if (! gmt)
1058 0 : return -1;
1059 0 : delta -= tm_diff (&tm, gmt);
1060 : #endif
1061 0 : if ((Start < Start - delta) != (delta < 0))
1062 0 : return -1; /* time_t overflow */
1063 0 : Start -= delta;
1064 : }
1065 :
1066 : /* Add relative hours, minutes, and seconds. Ignore leap seconds;
1067 : i.e. "+ 10 minutes" means 600 seconds, even if one of them is a
1068 : leap second. Typically this is not what the user wants, but it's
1069 : too hard to do it the other way, because the time zone indicator
1070 : must be applied before relative times, and if mktime is applied
1071 : again the time zone will be lost. */
1072 : {
1073 0 : time_t t0 = Start;
1074 0 : long d1 = 60 * 60 * (long) pc.rel_hour;
1075 0 : time_t t1 = t0 + d1;
1076 0 : long d2 = 60 * (long) pc.rel_minutes;
1077 0 : time_t t2 = t1 + d2;
1078 0 : int d3 = pc.rel_seconds;
1079 0 : time_t t3 = t2 + d3;
1080 0 : if ((d1 / (60 * 60) ^ pc.rel_hour)
1081 0 : | (d2 / 60 ^ pc.rel_minutes)
1082 0 : | ((t0 + d1 < t0) ^ (d1 < 0))
1083 0 : | ((t1 + d2 < t1) ^ (d2 < 0))
1084 0 : | ((t2 + d3 < t2) ^ (d3 < 0)))
1085 0 : return -1;
1086 0 : Start = t3;
1087 : }
1088 :
1089 0 : return Start;
1090 : }
1091 :
1092 : #if TEST
1093 :
1094 : #include <stdio.h>
1095 :
1096 : int
1097 : main (int ac, char **av)
1098 : {
1099 : char buff[BUFSIZ];
1100 : time_t d;
1101 :
1102 : printf ("Enter date, or blank line to exit.\n\t> ");
1103 : fflush (stdout);
1104 :
1105 : buff[BUFSIZ - 1] = 0;
1106 : while (fgets (buff, BUFSIZ - 1, stdin) && buff[0])
1107 : {
1108 : d = get_date (buff, 0);
1109 : if (d == (time_t) -1)
1110 : printf ("Bad format - couldn't convert.\n");
1111 : else
1112 : printf ("%s", ctime (&d));
1113 : printf ("\t> ");
1114 : fflush (stdout);
1115 : }
1116 : return 0;
1117 : }
1118 : #endif /* defined TEST */
|