source: branches/samba-3.0/source/modules/getdate.y@ 422

Last change on this file since 422 was 1, checked in by Paul Smedley, 18 years ago

Initial code import

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