source: branches/samba-3.3.x/source/modules/getdate.y@ 222

Last change on this file since 222 was 206, checked in by Herwig Bauernfeind, 16 years ago

Import Samba 3.3 branch at 3.0.0 level (psmedley's port)

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, 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#if 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#if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 8) || __STRICT_ANSI__
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. */
90typedef struct
91{
92 int value;
93 int digits;
94} textint;
95
96/* An entry in the lexical lookup table. */
97typedef struct
98{
99 char const *name;
100 int type;
101 int value;
102} table;
103
104/* Meridian: am, pm, or 24-hour style. */
105enum { MERam, MERpm, MER24 };
106
107/* Information passed to and from the parser. */
108typedef struct
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} parser_control;
155
156#define PC (* (parser_control *) parm)
157#define YYLEX_PARAM parm
158#define YYPARSE_PARAM parm
159
160static int yyerror ();
161static int yylex ();
162
163%}
164
165/* We want a reentrant parser. */
166%pure_parser
167
168/* This grammar has 13 shift/reduce conflicts. */
169%expect 13
170
171%union
172{
173 int intval;
174 textint textintval;
175}
176
177%token tAGO tDST
178
179%token <intval> tDAY tDAY_UNIT tDAYZONE tHOUR_UNIT tLOCAL_ZONE tMERIDIAN
180%token <intval> tMINUTE_UNIT tMONTH tMONTH_UNIT tSEC_UNIT tYEAR_UNIT tZONE
181
182%token <textintval> tSNUMBER tUNUMBER
183
184%type <intval> o_merid
185
186%%
187
188spec:
189 /* empty */
190 | spec item
191 ;
192
193item:
194 time
195 { PC.times_seen++; }
196 | local_zone
197 { PC.local_zones_seen++; }
198 | zone
199 { PC.zones_seen++; }
200 | date
201 { PC.dates_seen++; }
202 | day
203 { PC.days_seen++; }
204 | rel
205 { PC.rels_seen++; }
206 | number
207 ;
208
209time:
210 tUNUMBER tMERIDIAN
211 {
212 PC.hour = $1.value;
213 PC.minutes = 0;
214 PC.seconds = 0;
215 PC.meridian = $2;
216 }
217 | tUNUMBER ':' tUNUMBER o_merid
218 {
219 PC.hour = $1.value;
220 PC.minutes = $3.value;
221 PC.seconds = 0;
222 PC.meridian = $4;
223 }
224 | tUNUMBER ':' tUNUMBER tSNUMBER
225 {
226 PC.hour = $1.value;
227 PC.minutes = $3.value;
228 PC.meridian = MER24;
229 PC.zones_seen++;
230 PC.time_zone = $4.value % 100 + ($4.value / 100) * 60;
231 }
232 | tUNUMBER ':' tUNUMBER ':' tUNUMBER o_merid
233 {
234 PC.hour = $1.value;
235 PC.minutes = $3.value;
236 PC.seconds = $5.value;
237 PC.meridian = $6;
238 }
239 | tUNUMBER ':' tUNUMBER ':' tUNUMBER tSNUMBER
240 {
241 PC.hour = $1.value;
242 PC.minutes = $3.value;
243 PC.seconds = $5.value;
244 PC.meridian = MER24;
245 PC.zones_seen++;
246 PC.time_zone = $6.value % 100 + ($6.value / 100) * 60;
247 }
248 ;
249
250local_zone:
251 tLOCAL_ZONE
252 { PC.local_isdst = $1; }
253 | tLOCAL_ZONE tDST
254 { PC.local_isdst = $1 < 0 ? 1 : $1 + 1; }
255 ;
256
257zone:
258 tZONE
259 { PC.time_zone = $1; }
260 | tDAYZONE
261 { PC.time_zone = $1 + 60; }
262 | tZONE tDST
263 { PC.time_zone = $1 + 60; }
264 ;
265
266day:
267 tDAY
268 {
269 PC.day_ordinal = 1;
270 PC.day_number = $1;
271 }
272 | tDAY ','
273 {
274 PC.day_ordinal = 1;
275 PC.day_number = $1;
276 }
277 | tUNUMBER tDAY
278 {
279 PC.day_ordinal = $1.value;
280 PC.day_number = $2;
281 }
282 ;
283
284date:
285 tUNUMBER '/' tUNUMBER
286 {
287 PC.month = $1.value;
288 PC.day = $3.value;
289 }
290 | tUNUMBER '/' tUNUMBER '/' tUNUMBER
291 {
292 /* Interpret as YYYY/MM/DD if the first value has 4 or more digits,
293 otherwise as MM/DD/YY.
294 The goal in recognizing YYYY/MM/DD is solely to support legacy
295 machine-generated dates like those in an RCS log listing. If
296 you want portability, use the ISO 8601 format. */
297 if (4 <= $1.digits)
298 {
299 PC.year = $1;
300 PC.month = $3.value;
301 PC.day = $5.value;
302 }
303 else
304 {
305 PC.month = $1.value;
306 PC.day = $3.value;
307 PC.year = $5;
308 }
309 }
310 | tUNUMBER tSNUMBER tSNUMBER
311 {
312 /* ISO 8601 format. YYYY-MM-DD. */
313 PC.year = $1;
314 PC.month = -$2.value;
315 PC.day = -$3.value;
316 }
317 | tUNUMBER tMONTH tSNUMBER
318 {
319 /* e.g. 17-JUN-1992. */
320 PC.day = $1.value;
321 PC.month = $2;
322 PC.year.value = -$3.value;
323 PC.year.digits = $3.digits;
324 }
325 | tMONTH tUNUMBER
326 {
327 PC.month = $1;
328 PC.day = $2.value;
329 }
330 | tMONTH tUNUMBER ',' tUNUMBER
331 {
332 PC.month = $1;
333 PC.day = $2.value;
334 PC.year = $4;
335 }
336 | tUNUMBER tMONTH
337 {
338 PC.day = $1.value;
339 PC.month = $2;
340 }
341 | tUNUMBER tMONTH tUNUMBER
342 {
343 PC.day = $1.value;
344 PC.month = $2;
345 PC.year = $3;
346 }
347 ;
348
349rel:
350 relunit tAGO
351 {
352 PC.rel_seconds = -PC.rel_seconds;
353 PC.rel_minutes = -PC.rel_minutes;
354 PC.rel_hour = -PC.rel_hour;
355 PC.rel_day = -PC.rel_day;
356 PC.rel_month = -PC.rel_month;
357 PC.rel_year = -PC.rel_year;
358 }
359 | relunit
360 ;
361
362relunit:
363 tUNUMBER tYEAR_UNIT
364 { PC.rel_year += $1.value * $2; }
365 | tSNUMBER tYEAR_UNIT
366 { PC.rel_year += $1.value * $2; }
367 | tYEAR_UNIT
368 { PC.rel_year += $1; }
369 | tUNUMBER tMONTH_UNIT
370 { PC.rel_month += $1.value * $2; }
371 | tSNUMBER tMONTH_UNIT
372 { PC.rel_month += $1.value * $2; }
373 | tMONTH_UNIT
374 { PC.rel_month += $1; }
375 | tUNUMBER tDAY_UNIT
376 { PC.rel_day += $1.value * $2; }
377 | tSNUMBER tDAY_UNIT
378 { PC.rel_day += $1.value * $2; }
379 | tDAY_UNIT
380 { PC.rel_day += $1; }
381 | tUNUMBER tHOUR_UNIT
382 { PC.rel_hour += $1.value * $2; }
383 | tSNUMBER tHOUR_UNIT
384 { PC.rel_hour += $1.value * $2; }
385 | tHOUR_UNIT
386 { PC.rel_hour += $1; }
387 | tUNUMBER tMINUTE_UNIT
388 { PC.rel_minutes += $1.value * $2; }
389 | tSNUMBER tMINUTE_UNIT
390 { PC.rel_minutes += $1.value * $2; }
391 | tMINUTE_UNIT
392 { PC.rel_minutes += $1; }
393 | tUNUMBER tSEC_UNIT
394 { PC.rel_seconds += $1.value * $2; }
395 | tSNUMBER tSEC_UNIT
396 { PC.rel_seconds += $1.value * $2; }
397 | tSEC_UNIT
398 { PC.rel_seconds += $1; }
399 ;
400
401number:
402 tUNUMBER
403 {
404 if (PC.dates_seen
405 && ! PC.rels_seen && (PC.times_seen || 2 < $1.digits))
406 PC.year = $1;
407 else
408 {
409 if (4 < $1.digits)
410 {
411 PC.dates_seen++;
412 PC.day = $1.value % 100;
413 PC.month = ($1.value / 100) % 100;
414 PC.year.value = $1.value / 10000;
415 PC.year.digits = $1.digits - 4;
416 }
417 else
418 {
419 PC.times_seen++;
420 if ($1.digits <= 2)
421 {
422 PC.hour = $1.value;
423 PC.minutes = 0;
424 }
425 else
426 {
427 PC.hour = $1.value / 100;
428 PC.minutes = $1.value % 100;
429 }
430 PC.seconds = 0;
431 PC.meridian = MER24;
432 }
433 }
434 }
435 ;
436
437o_merid:
438 /* empty */
439 { $$ = MER24; }
440 | tMERIDIAN
441 { $$ = $1; }
442 ;
443
444%%
445
446/* Include this file down here because bison inserts code above which
447 may define-away `const'. We want the prototype for get_date to have
448 the same signature as the function definition. */
449#include "modules/getdate.h"
450
451#ifndef gmtime
452struct tm *gmtime ();
453#endif
454#ifndef localtime
455struct tm *localtime ();
456#endif
457#ifndef mktime
458time_t mktime ();
459#endif
460
461static 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
470static table const dst_table[] =
471{
472 { "DST", tDST, 0 }
473};
474
475static 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
504static 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. */
520static 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. */
550static 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. */
606static 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
637
638
639static int
640to_hour (int hours, int meridian)
641{
642 switch (meridian)
643 {
644 case MER24:
645 return 0 <= hours && hours < 24 ? hours : -1;
646 case MERam:
647 return 0 < hours && hours < 12 ? hours : hours == 12 ? 0 : -1;
648 case MERpm:
649 return 0 < hours && hours < 12 ? hours + 12 : hours == 12 ? 12 : -1;
650 default:
651 abort ();
652 }
653 /* NOTREACHED */
654 return 0;
655}
656
657static int
658to_year (textint textyear)
659{
660 int year = textyear.value;
661
662 if (year < 0)
663 year = -year;
664
665 /* XPG4 suggests that years 00-68 map to 2000-2068, and
666 years 69-99 map to 1969-1999. */
667 if (textyear.digits == 2)
668 year += year < 69 ? 2000 : 1900;
669
670 return year;
671}
672
673static table const *
674lookup_zone (parser_control const *pc, char const *name)
675{
676 table const *tp;
677
678 /* Try local zone abbreviations first; they're more likely to be right. */
679 for (tp = pc->local_time_zone_table; tp->name; tp++)
680 if (strcmp (name, tp->name) == 0)
681 return tp;
682
683 for (tp = time_zone_table; tp->name; tp++)
684 if (strcmp (name, tp->name) == 0)
685 return tp;
686
687 return 0;
688}
689
690#if ! HAVE_TM_GMTOFF
691/* Yield the difference between *A and *B,
692 measured in seconds, ignoring leap seconds.
693 The body of this function is taken directly from the GNU C Library;
694 see src/strftime.c. */
695static int
696tm_diff (struct tm const *a, struct tm const *b)
697{
698 /* Compute intervening leap days correctly even if year is negative.
699 Take care to avoid int overflow in leap day calculations,
700 but it's OK to assume that A and B are close to each other. */
701 int a4 = (a->tm_year >> 2) + (TM_YEAR_BASE >> 2) - ! (a->tm_year & 3);
702 int b4 = (b->tm_year >> 2) + (TM_YEAR_BASE >> 2) - ! (b->tm_year & 3);
703 int a100 = a4 / 25 - (a4 % 25 < 0);
704 int b100 = b4 / 25 - (b4 % 25 < 0);
705 int a400 = a100 >> 2;
706 int b400 = b100 >> 2;
707 int intervening_leap_days = (a4 - b4) - (a100 - b100) + (a400 - b400);
708 int years = a->tm_year - b->tm_year;
709 int days = (365 * years + intervening_leap_days
710 + (a->tm_yday - b->tm_yday));
711 return (60 * (60 * (24 * days + (a->tm_hour - b->tm_hour))
712 + (a->tm_min - b->tm_min))
713 + (a->tm_sec - b->tm_sec));
714}
715#endif /* ! HAVE_TM_GMTOFF */
716
717static table const *
718lookup_word (parser_control const *pc, char *word)
719{
720 char *p;
721 char *q;
722 size_t wordlen;
723 table const *tp;
724 int i;
725 int abbrev;
726
727 /* Make it uppercase. */
728 for (p = word; *p; p++)
729 if (ISLOWER ((unsigned char) *p))
730 *p = toupper ((unsigned char) *p);
731
732 for (tp = meridian_table; tp->name; tp++)
733 if (strcmp (word, tp->name) == 0)
734 return tp;
735
736 /* See if we have an abbreviation for a month. */
737 wordlen = strlen (word);
738 abbrev = wordlen == 3 || (wordlen == 4 && word[3] == '.');
739
740 for (tp = month_and_day_table; tp->name; tp++)
741 if ((abbrev ? strncmp (word, tp->name, 3) : strcmp (word, tp->name)) == 0)
742 return tp;
743
744 if ((tp = lookup_zone (pc, word)))
745 return tp;
746
747 if (strcmp (word, dst_table[0].name) == 0)
748 return dst_table;
749
750 for (tp = time_units_table; tp->name; tp++)
751 if (strcmp (word, tp->name) == 0)
752 return tp;
753
754 /* Strip off any plural and try the units table again. */
755 if (word[wordlen - 1] == 'S')
756 {
757 word[wordlen - 1] = '\0';
758 for (tp = time_units_table; tp->name; tp++)
759 if (strcmp (word, tp->name) == 0)
760 return tp;
761 word[wordlen - 1] = 'S'; /* For "this" in relative_time_table. */
762 }
763
764 for (tp = relative_time_table; tp->name; tp++)
765 if (strcmp (word, tp->name) == 0)
766 return tp;
767
768 /* Military time zones. */
769 if (wordlen == 1)
770 for (tp = military_table; tp->name; tp++)
771 if (word[0] == tp->name[0])
772 return tp;
773
774 /* Drop out any periods and try the time zone table again. */
775 for (i = 0, p = q = word; (*p = *q); q++)
776 if (*q == '.')
777 i = 1;
778 else
779 p++;
780 if (i && (tp = lookup_zone (pc, word)))
781 return tp;
782
783 return 0;
784}
785
786static int
787yylex (YYSTYPE *lvalp, parser_control *pc)
788{
789 unsigned char c;
790 int count;
791
792 for (;;)
793 {
794 while (c = *pc->input, ISSPACE (c))
795 pc->input++;
796
797 if (ISDIGIT (c) || c == '-' || c == '+')
798 {
799 char const *p;
800 int sign;
801 int value;
802 if (c == '-' || c == '+')
803 {
804 sign = c == '-' ? -1 : 1;
805 c = *++pc->input;
806 if (! ISDIGIT (c))
807 /* skip the '-' sign */
808 continue;
809 }
810 else
811 sign = 0;
812 p = pc->input;
813 value = 0;
814 do
815 {
816 value = 10 * value + c - '0';
817 c = *++p;
818 }
819 while (ISDIGIT (c));
820 lvalp->textintval.value = sign < 0 ? -value : value;
821 lvalp->textintval.digits = p - pc->input;
822 pc->input = p;
823 return sign ? tSNUMBER : tUNUMBER;
824 }
825
826 if (ISALPHA (c))
827 {
828 char buff[20];
829 char *p = buff;
830 table const *tp;
831
832 do
833 {
834 if (p < buff + sizeof buff - 1)
835 *p++ = c;
836 c = *++pc->input;
837 }
838 while (ISALPHA (c) || c == '.');
839
840 *p = '\0';
841 tp = lookup_word (pc, buff);
842 if (! tp)
843 return '?';
844 lvalp->intval = tp->value;
845 return tp->type;
846 }
847
848 if (c != '(')
849 return *pc->input++;
850 count = 0;
851 do
852 {
853 c = *pc->input++;
854 if (c == '\0')
855 return c;
856 if (c == '(')
857 count++;
858 else if (c == ')')
859 count--;
860 }
861 while (count > 0);
862 }
863}
864
865/* Do nothing if the parser reports an error. */
866static int
867yyerror (char *s ATTRIBUTE_UNUSED)
868{
869 return 0;
870}
871
872/* Parse a date/time string P. Return the corresponding time_t value,
873 or (time_t) -1 if there is an error. P can be an incomplete or
874 relative time specification; if so, use *NOW as the basis for the
875 returned time. */
876time_t
877get_date (const char *p, const time_t *now)
878{
879 time_t Start = now ? *now : time (0);
880 struct tm *tmp = localtime (&Start);
881 struct tm tm;
882 struct tm tm0;
883 parser_control pc;
884
885 if (! tmp)
886 return -1;
887
888 pc.input = p;
889 pc.year.value = tmp->tm_year + TM_YEAR_BASE;
890 pc.year.digits = 4;
891 pc.month = tmp->tm_mon + 1;
892 pc.day = tmp->tm_mday;
893 pc.hour = tmp->tm_hour;
894 pc.minutes = tmp->tm_min;
895 pc.seconds = tmp->tm_sec;
896 tm.tm_isdst = tmp->tm_isdst;
897
898 pc.meridian = MER24;
899 pc.rel_seconds = 0;
900 pc.rel_minutes = 0;
901 pc.rel_hour = 0;
902 pc.rel_day = 0;
903 pc.rel_month = 0;
904 pc.rel_year = 0;
905 pc.dates_seen = 0;
906 pc.days_seen = 0;
907 pc.rels_seen = 0;
908 pc.times_seen = 0;
909 pc.local_zones_seen = 0;
910 pc.zones_seen = 0;
911
912#if HAVE_STRUCT_TM_TM_ZONE
913 pc.local_time_zone_table[0].name = tmp->tm_zone;
914 pc.local_time_zone_table[0].type = tLOCAL_ZONE;
915 pc.local_time_zone_table[0].value = tmp->tm_isdst;
916 pc.local_time_zone_table[1].name = 0;
917
918 /* Probe the names used in the next three calendar quarters, looking
919 for a tm_isdst different from the one we already have. */
920 {
921 int quarter;
922 for (quarter = 1; quarter <= 3; quarter++)
923 {
924 time_t probe = Start + quarter * (90 * 24 * 60 * 60);
925 struct tm *probe_tm = localtime (&probe);
926 if (probe_tm && probe_tm->tm_zone
927 && probe_tm->tm_isdst != pc.local_time_zone_table[0].value)
928 {
929 {
930 pc.local_time_zone_table[1].name = probe_tm->tm_zone;
931 pc.local_time_zone_table[1].type = tLOCAL_ZONE;
932 pc.local_time_zone_table[1].value = probe_tm->tm_isdst;
933 pc.local_time_zone_table[2].name = 0;
934 }
935 break;
936 }
937 }
938 }
939#else
940#if HAVE_TZNAME
941 {
942# ifndef tzname
943 extern char *tzname[];
944# endif
945 int i;
946 for (i = 0; i < 2; i++)
947 {
948 pc.local_time_zone_table[i].name = tzname[i];
949 pc.local_time_zone_table[i].type = tLOCAL_ZONE;
950 pc.local_time_zone_table[i].value = i;
951 }
952 pc.local_time_zone_table[i].name = 0;
953 }
954#else
955 pc.local_time_zone_table[0].name = 0;
956#endif
957#endif
958
959 if (pc.local_time_zone_table[0].name && pc.local_time_zone_table[1].name
960 && ! strcmp (pc.local_time_zone_table[0].name,
961 pc.local_time_zone_table[1].name))
962 {
963 /* This locale uses the same abbrevation for standard and
964 daylight times. So if we see that abbreviation, we don't
965 know whether it's daylight time. */
966 pc.local_time_zone_table[0].value = -1;
967 pc.local_time_zone_table[1].name = 0;
968 }
969
970 if (yyparse (&pc) != 0
971 || 1 < pc.times_seen || 1 < pc.dates_seen || 1 < pc.days_seen
972 || 1 < (pc.local_zones_seen + pc.zones_seen)
973 || (pc.local_zones_seen && 1 < pc.local_isdst))
974 return -1;
975
976 tm.tm_year = to_year (pc.year) - TM_YEAR_BASE + pc.rel_year;
977 tm.tm_mon = pc.month - 1 + pc.rel_month;
978 tm.tm_mday = pc.day + pc.rel_day;
979 if (pc.times_seen || (pc.rels_seen && ! pc.dates_seen && ! pc.days_seen))
980 {
981 tm.tm_hour = to_hour (pc.hour, pc.meridian);
982 if (tm.tm_hour < 0)
983 return -1;
984 tm.tm_min = pc.minutes;
985 tm.tm_sec = pc.seconds;
986 }
987 else
988 {
989 tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
990 }
991
992 /* Let mktime deduce tm_isdst if we have an absolute time stamp,
993 or if the relative time stamp mentions days, months, or years. */
994 if (pc.dates_seen | pc.days_seen | pc.times_seen | pc.rel_day
995 | pc.rel_month | pc.rel_year)
996 tm.tm_isdst = -1;
997
998 /* But if the input explicitly specifies local time with or without
999 DST, give mktime that information. */
1000 if (pc.local_zones_seen)
1001 tm.tm_isdst = pc.local_isdst;
1002
1003 tm0 = tm;
1004
1005 Start = mktime (&tm);
1006
1007 if (Start == (time_t) -1)
1008 {
1009
1010 /* Guard against falsely reporting errors near the time_t boundaries
1011 when parsing times in other time zones. For example, if the min
1012 time_t value is 1970-01-01 00:00:00 UTC and we are 8 hours ahead
1013 of UTC, then the min localtime value is 1970-01-01 08:00:00; if
1014 we apply mktime to 1970-01-01 00:00:00 we will get an error, so
1015 we apply mktime to 1970-01-02 08:00:00 instead and adjust the time
1016 zone by 24 hours to compensate. This algorithm assumes that
1017 there is no DST transition within a day of the time_t boundaries. */
1018 if (pc.zones_seen)
1019 {
1020 tm = tm0;
1021 if (tm.tm_year <= EPOCH_YEAR - TM_YEAR_BASE)
1022 {
1023 tm.tm_mday++;
1024 pc.time_zone += 24 * 60;
1025 }
1026 else
1027 {
1028 tm.tm_mday--;
1029 pc.time_zone -= 24 * 60;
1030 }
1031 Start = mktime (&tm);
1032 }
1033
1034 if (Start == (time_t) -1)
1035 return Start;
1036 }
1037
1038 if (pc.days_seen && ! pc.dates_seen)
1039 {
1040 tm.tm_mday += ((pc.day_number - tm.tm_wday + 7) % 7
1041 + 7 * (pc.day_ordinal - (0 < pc.day_ordinal)));
1042 tm.tm_isdst = -1;
1043 Start = mktime (&tm);
1044 if (Start == (time_t) -1)
1045 return Start;
1046 }
1047
1048 if (pc.zones_seen)
1049 {
1050 int delta = pc.time_zone * 60;
1051#ifdef HAVE_TM_GMTOFF
1052 delta -= tm.tm_gmtoff;
1053#else
1054 struct tm *gmt = gmtime (&Start);
1055 if (! gmt)
1056 return -1;
1057 delta -= tm_diff (&tm, gmt);
1058#endif
1059 if ((Start < Start - delta) != (delta < 0))
1060 return -1; /* time_t overflow */
1061 Start -= delta;
1062 }
1063
1064 /* Add relative hours, minutes, and seconds. Ignore leap seconds;
1065 i.e. "+ 10 minutes" means 600 seconds, even if one of them is a
1066 leap second. Typically this is not what the user wants, but it's
1067 too hard to do it the other way, because the time zone indicator
1068 must be applied before relative times, and if mktime is applied
1069 again the time zone will be lost. */
1070 {
1071 time_t t0 = Start;
1072 long d1 = 60 * 60 * (long) pc.rel_hour;
1073 time_t t1 = t0 + d1;
1074 long d2 = 60 * (long) pc.rel_minutes;
1075 time_t t2 = t1 + d2;
1076 int d3 = pc.rel_seconds;
1077 time_t t3 = t2 + d3;
1078 if ((d1 / (60 * 60) ^ pc.rel_hour)
1079 | (d2 / 60 ^ pc.rel_minutes)
1080 | ((t0 + d1 < t0) ^ (d1 < 0))
1081 | ((t1 + d2 < t1) ^ (d2 < 0))
1082 | ((t2 + d3 < t2) ^ (d3 < 0)))
1083 return -1;
1084 Start = t3;
1085 }
1086
1087 return Start;
1088}
1089
1090#if TEST
1091
1092#include <stdio.h>
1093
1094int
1095main (int ac, char **av)
1096{
1097 char buff[BUFSIZ];
1098 time_t d;
1099
1100 printf ("Enter date, or blank line to exit.\n\t> ");
1101 fflush (stdout);
1102
1103 buff[BUFSIZ - 1] = 0;
1104 while (fgets (buff, BUFSIZ - 1, stdin) && buff[0])
1105 {
1106 d = get_date (buff, 0);
1107 if (d == (time_t) -1)
1108 printf ("Bad format - couldn't convert.\n");
1109 else
1110 printf ("%s", ctime (&d));
1111 printf ("\t> ");
1112 fflush (stdout);
1113 }
1114 return 0;
1115}
1116#endif /* defined TEST */
Note: See TracBrowser for help on using the repository browser.