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