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
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
638static int
639to_hour (int hours, int meridian)
640{
641  switch (meridian)
642    {
643    case MER24:
644      return 0 <= hours && hours < 24 ? hours : -1;
645    case MERam:
646      return 0 < hours && hours < 12 ? hours : hours == 12 ? 0 : -1;
647    case MERpm:
648      return 0 < hours && hours < 12 ? hours + 12 : hours == 12 ? 12 : -1;
649    default:
650      abort ();
651    }
652  /* NOTREACHED */
653    return 0;
654}
655
656static int
657to_year (textint textyear)
658{
659  int year = textyear.value;
660
661  if (year < 0)
662    year = -year;
663
664  /* XPG4 suggests that years 00-68 map to 2000-2068, and
665     years 69-99 map to 1969-1999.  */
666  if (textyear.digits == 2)
667    year += year < 69 ? 2000 : 1900;
668
669  return year;
670}
671
672static table const *
673lookup_zone (parser_control const *pc, char const *name)
674{
675  table const *tp;
676
677  /* Try local zone abbreviations first; they're more likely to be right.  */
678  for (tp = pc->local_time_zone_table; tp->name; tp++)
679    if (strcmp (name, tp->name) == 0)
680      return tp;
681
682  for (tp = time_zone_table; tp->name; tp++)
683    if (strcmp (name, tp->name) == 0)
684      return tp;
685
686  return 0;
687}
688
689#if ! HAVE_TM_GMTOFF
690/* Yield the difference between *A and *B,
691   measured in seconds, ignoring leap seconds.
692   The body of this function is taken directly from the GNU C Library;
693   see src/strftime.c.  */
694static int
695tm_diff (struct tm const *a, struct tm const *b)
696{
697  /* Compute intervening leap days correctly even if year is negative.
698     Take care to avoid int overflow in leap day calculations,
699     but it's OK to assume that A and B are close to each other.  */
700  int a4 = (a->tm_year >> 2) + (TM_YEAR_BASE >> 2) - ! (a->tm_year & 3);
701  int b4 = (b->tm_year >> 2) + (TM_YEAR_BASE >> 2) - ! (b->tm_year & 3);
702  int a100 = a4 / 25 - (a4 % 25 < 0);
703  int b100 = b4 / 25 - (b4 % 25 < 0);
704  int a400 = a100 >> 2;
705  int b400 = b100 >> 2;
706  int intervening_leap_days = (a4 - b4) - (a100 - b100) + (a400 - b400);
707  int years = a->tm_year - b->tm_year;
708  int days = (365 * years + intervening_leap_days
709	      + (a->tm_yday - b->tm_yday));
710  return (60 * (60 * (24 * days + (a->tm_hour - b->tm_hour))
711		+ (a->tm_min - b->tm_min))
712	  + (a->tm_sec - b->tm_sec));
713}
714#endif /* ! HAVE_TM_GMTOFF */
715
716static table const *
717lookup_word (parser_control const *pc, char *word)
718{
719  char *p;
720  char *q;
721  size_t wordlen;
722  table const *tp;
723  int i;
724  int abbrev;
725
726  /* Make it uppercase.  */
727  for (p = word; *p; p++)
728    if (ISLOWER ((unsigned char) *p))
729      *p = toupper ((unsigned char) *p);
730
731  for (tp = meridian_table; tp->name; tp++)
732    if (strcmp (word, tp->name) == 0)
733      return tp;
734
735  /* See if we have an abbreviation for a month. */
736  wordlen = strlen (word);
737  abbrev = wordlen == 3 || (wordlen == 4 && word[3] == '.');
738
739  for (tp = month_and_day_table; tp->name; tp++)
740    if ((abbrev ? strncmp (word, tp->name, 3) : strcmp (word, tp->name)) == 0)
741      return tp;
742
743  if ((tp = lookup_zone (pc, word)))
744    return tp;
745
746  if (strcmp (word, dst_table[0].name) == 0)
747    return dst_table;
748
749  for (tp = time_units_table; tp->name; tp++)
750    if (strcmp (word, tp->name) == 0)
751      return tp;
752
753  /* Strip off any plural and try the units table again. */
754  if (word[wordlen - 1] == 'S')
755    {
756      word[wordlen - 1] = '\0';
757      for (tp = time_units_table; tp->name; tp++)
758	if (strcmp (word, tp->name) == 0)
759	  return tp;
760      word[wordlen - 1] = 'S';	/* For "this" in relative_time_table.  */
761    }
762
763  for (tp = relative_time_table; tp->name; tp++)
764    if (strcmp (word, tp->name) == 0)
765      return tp;
766
767  /* Military time zones. */
768  if (wordlen == 1)
769    for (tp = military_table; tp->name; tp++)
770      if (word[0] == tp->name[0])
771	return tp;
772
773  /* Drop out any periods and try the time zone table again. */
774  for (i = 0, p = q = word; (*p = *q); q++)
775    if (*q == '.')
776      i = 1;
777    else
778      p++;
779  if (i && (tp = lookup_zone (pc, word)))
780    return tp;
781
782  return 0;
783}
784
785static int
786yylex (YYSTYPE *lvalp, parser_control *pc)
787{
788  unsigned char c;
789  int count;
790
791  for (;;)
792    {
793      while (c = *pc->input, ISSPACE (c))
794	pc->input++;
795
796      if (ISDIGIT (c) || c == '-' || c == '+')
797	{
798	  char const *p;
799	  int sign;
800	  int value;
801	  if (c == '-' || c == '+')
802	    {
803	      sign = c == '-' ? -1 : 1;
804	      c = *++pc->input;
805	      if (! ISDIGIT (c))
806		/* skip the '-' sign */
807		continue;
808	    }
809	  else
810	    sign = 0;
811	  p = pc->input;
812	  value = 0;
813	  do
814	    {
815	      value = 10 * value + c - '0';
816	      c = *++p;
817	    }
818	  while (ISDIGIT (c));
819	  lvalp->textintval.value = sign < 0 ? -value : value;
820	  lvalp->textintval.digits = p - pc->input;
821	  pc->input = p;
822	  return sign ? tSNUMBER : tUNUMBER;
823	}
824
825      if (ISALPHA (c))
826	{
827	  char buff[20];
828	  char *p = buff;
829	  table const *tp;
830
831	  do
832	    {
833	      if (p < buff + sizeof buff - 1)
834		*p++ = c;
835	      c = *++pc->input;
836	    }
837	  while (ISALPHA (c) || c == '.');
838
839	  *p = '\0';
840	  tp = lookup_word (pc, buff);
841	  if (! tp)
842	    return '?';
843	  lvalp->intval = tp->value;
844	  return tp->type;
845	}
846
847      if (c != '(')
848	return *pc->input++;
849      count = 0;
850      do
851	{
852	  c = *pc->input++;
853	  if (c == '\0')
854	    return c;
855	  if (c == '(')
856	    count++;
857	  else if (c == ')')
858	    count--;
859	}
860      while (count > 0);
861    }
862}
863
864/* Do nothing if the parser reports an error.  */
865static int
866yyerror (char *s ATTRIBUTE_UNUSED)
867{
868  return 0;
869}
870
871/* Parse a date/time string P.  Return the corresponding time_t value,
872   or (time_t) -1 if there is an error.  P can be an incomplete or
873   relative time specification; if so, use *NOW as the basis for the
874   returned time.  */
875time_t
876get_date (const char *p, const time_t *now)
877{
878  time_t Start = now ? *now : time (0);
879  struct tm *tmp = localtime (&Start);
880  struct tm tm;
881  struct tm tm0;
882  parser_control pc;
883
884  if (! tmp)
885    return -1;
886
887  pc.input = p;
888  pc.year.value = tmp->tm_year + TM_YEAR_BASE;
889  pc.year.digits = 4;
890  pc.month = tmp->tm_mon + 1;
891  pc.day = tmp->tm_mday;
892  pc.hour = tmp->tm_hour;
893  pc.minutes = tmp->tm_min;
894  pc.seconds = tmp->tm_sec;
895  tm.tm_isdst = tmp->tm_isdst;
896
897  pc.meridian = MER24;
898  pc.rel_seconds = 0;
899  pc.rel_minutes = 0;
900  pc.rel_hour = 0;
901  pc.rel_day = 0;
902  pc.rel_month = 0;
903  pc.rel_year = 0;
904  pc.dates_seen = 0;
905  pc.days_seen = 0;
906  pc.rels_seen = 0;
907  pc.times_seen = 0;
908  pc.local_zones_seen = 0;
909  pc.zones_seen = 0;
910
911#if HAVE_STRUCT_TM_TM_ZONE
912  pc.local_time_zone_table[0].name = tmp->tm_zone;
913  pc.local_time_zone_table[0].type = tLOCAL_ZONE;
914  pc.local_time_zone_table[0].value = tmp->tm_isdst;
915  pc.local_time_zone_table[1].name = 0;
916
917  /* Probe the names used in the next three calendar quarters, looking
918     for a tm_isdst different from the one we already have.  */
919  {
920    int quarter;
921    for (quarter = 1; quarter <= 3; quarter++)
922      {
923	time_t probe = Start + quarter * (90 * 24 * 60 * 60);
924	struct tm *probe_tm = localtime (&probe);
925	if (probe_tm && probe_tm->tm_zone
926	    && probe_tm->tm_isdst != pc.local_time_zone_table[0].value)
927	  {
928	      {
929		pc.local_time_zone_table[1].name = probe_tm->tm_zone;
930		pc.local_time_zone_table[1].type = tLOCAL_ZONE;
931		pc.local_time_zone_table[1].value = probe_tm->tm_isdst;
932		pc.local_time_zone_table[2].name = 0;
933	      }
934	    break;
935	  }
936      }
937  }
938#else
939#if HAVE_TZNAME
940  {
941# ifndef tzname
942    extern char *tzname[];
943# endif
944    int i;
945    for (i = 0; i < 2; i++)
946      {
947	pc.local_time_zone_table[i].name = tzname[i];
948	pc.local_time_zone_table[i].type = tLOCAL_ZONE;
949	pc.local_time_zone_table[i].value = i;
950      }
951    pc.local_time_zone_table[i].name = 0;
952  }
953#else
954  pc.local_time_zone_table[0].name = 0;
955#endif
956#endif
957
958  if (pc.local_time_zone_table[0].name && pc.local_time_zone_table[1].name
959      && ! strcmp (pc.local_time_zone_table[0].name,
960		   pc.local_time_zone_table[1].name))
961    {
962      /* This locale uses the same abbrevation for standard and
963	 daylight times.  So if we see that abbreviation, we don't
964	 know whether it's daylight time.  */
965      pc.local_time_zone_table[0].value = -1;
966      pc.local_time_zone_table[1].name = 0;
967    }
968
969  if (yyparse (&pc) != 0
970      || 1 < pc.times_seen || 1 < pc.dates_seen || 1 < pc.days_seen
971      || 1 < (pc.local_zones_seen + pc.zones_seen)
972      || (pc.local_zones_seen && 1 < pc.local_isdst))
973    return -1;
974
975  tm.tm_year = to_year (pc.year) - TM_YEAR_BASE + pc.rel_year;
976  tm.tm_mon = pc.month - 1 + pc.rel_month;
977  tm.tm_mday = pc.day + pc.rel_day;
978  if (pc.times_seen || (pc.rels_seen && ! pc.dates_seen && ! pc.days_seen))
979    {
980      tm.tm_hour = to_hour (pc.hour, pc.meridian);
981      if (tm.tm_hour < 0)
982	return -1;
983      tm.tm_min = pc.minutes;
984      tm.tm_sec = pc.seconds;
985    }
986  else
987    {
988      tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
989    }
990
991  /* Let mktime deduce tm_isdst if we have an absolute time stamp,
992     or if the relative time stamp mentions days, months, or years.  */
993  if (pc.dates_seen | pc.days_seen | pc.times_seen | pc.rel_day
994      | pc.rel_month | pc.rel_year)
995    tm.tm_isdst = -1;
996
997  /* But if the input explicitly specifies local time with or without
998     DST, give mktime that information.  */
999  if (pc.local_zones_seen)
1000    tm.tm_isdst = pc.local_isdst;
1001
1002  tm0 = tm;
1003
1004  Start = mktime (&tm);
1005
1006  if (Start == (time_t) -1)
1007    {
1008
1009      /* Guard against falsely reporting errors near the time_t boundaries
1010         when parsing times in other time zones.  For example, if the min
1011         time_t value is 1970-01-01 00:00:00 UTC and we are 8 hours ahead
1012         of UTC, then the min localtime value is 1970-01-01 08:00:00; if
1013         we apply mktime to 1970-01-01 00:00:00 we will get an error, so
1014         we apply mktime to 1970-01-02 08:00:00 instead and adjust the time
1015         zone by 24 hours to compensate.  This algorithm assumes that
1016         there is no DST transition within a day of the time_t boundaries.  */
1017      if (pc.zones_seen)
1018	{
1019	  tm = tm0;
1020	  if (tm.tm_year <= EPOCH_YEAR - TM_YEAR_BASE)
1021	    {
1022	      tm.tm_mday++;
1023	      pc.time_zone += 24 * 60;
1024	    }
1025	  else
1026	    {
1027	      tm.tm_mday--;
1028	      pc.time_zone -= 24 * 60;
1029	    }
1030	  Start = mktime (&tm);
1031	}
1032
1033      if (Start == (time_t) -1)
1034	return Start;
1035    }
1036
1037  if (pc.days_seen && ! pc.dates_seen)
1038    {
1039      tm.tm_mday += ((pc.day_number - tm.tm_wday + 7) % 7
1040		     + 7 * (pc.day_ordinal - (0 < pc.day_ordinal)));
1041      tm.tm_isdst = -1;
1042      Start = mktime (&tm);
1043      if (Start == (time_t) -1)
1044	return Start;
1045    }
1046
1047  if (pc.zones_seen)
1048    {
1049      int delta = pc.time_zone * 60;
1050#ifdef HAVE_TM_GMTOFF
1051      delta -= tm.tm_gmtoff;
1052#else
1053      struct tm *gmt = gmtime (&Start);
1054      if (! gmt)
1055	return -1;
1056      delta -= tm_diff (&tm, gmt);
1057#endif
1058      if ((Start < Start - delta) != (delta < 0))
1059	return -1;	/* time_t overflow */
1060      Start -= delta;
1061    }
1062
1063  /* Add relative hours, minutes, and seconds.  Ignore leap seconds;
1064     i.e. "+ 10 minutes" means 600 seconds, even if one of them is a
1065     leap second.  Typically this is not what the user wants, but it's
1066     too hard to do it the other way, because the time zone indicator
1067     must be applied before relative times, and if mktime is applied
1068     again the time zone will be lost.  */
1069  {
1070    time_t t0 = Start;
1071    long d1 = 60 * 60 * (long) pc.rel_hour;
1072    time_t t1 = t0 + d1;
1073    long d2 = 60 * (long) pc.rel_minutes;
1074    time_t t2 = t1 + d2;
1075    int d3 = pc.rel_seconds;
1076    time_t t3 = t2 + d3;
1077    if ((d1 / (60 * 60) ^ pc.rel_hour)
1078	| (d2 / 60 ^ pc.rel_minutes)
1079	| ((t0 + d1 < t0) ^ (d1 < 0))
1080	| ((t1 + d2 < t1) ^ (d2 < 0))
1081	| ((t2 + d3 < t2) ^ (d3 < 0)))
1082      return -1;
1083    Start = t3;
1084  }
1085
1086  return Start;
1087}
1088
1089#if TEST
1090
1091#include <stdio.h>
1092
1093int
1094main (int ac, char **av)
1095{
1096  char buff[BUFSIZ];
1097  time_t d;
1098
1099  printf ("Enter date, or blank line to exit.\n\t> ");
1100  fflush (stdout);
1101
1102  buff[BUFSIZ - 1] = 0;
1103  while (fgets (buff, BUFSIZ - 1, stdin) && buff[0])
1104    {
1105      d = get_date (buff, 0);
1106      if (d == (time_t) -1)
1107	printf ("Bad format - couldn't convert.\n");
1108      else
1109	printf ("%s", ctime (&d));
1110      printf ("\t> ");
1111      fflush (stdout);
1112    }
1113  return 0;
1114}
1115#endif /* defined TEST */
1116