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