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