%{
/* Parse a string into an internal time stamp.
- Copyright (C) 1999, 2000, 2002, 2003, 2004 Free Software Foundation, Inc.
+
+ Copyright (C) 1999, 2000, 2002, 2003, 2004, 2005 Free Software
+ Foundation, Inc.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software Foundation,
- Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
+ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
/* Originally written by Steven M. Bellovin <smb@research.att.com> while
at the University of North Carolina at Chapel Hill. Later tweaked by
#include "getdate.h"
-#include <alloca.h>
+/* There's no need to extend the stack, so there's no need to involve
+ alloca. */
+#define YYSTACK_USE_ALLOCA 0
+
+/* Tell Bison how much stack space is needed. 20 should be plenty for
+ this grammar, which is not right recursive. Beware setting it too
+ high, since that might cause problems on machines whose
+ implementations have lame stack-overflow checking. */
+#define YYMAXDEPTH 20
+#define YYINITDEPTH YYMAXDEPTH
/* Since the code of getdate.y is not included in the Emacs executable
itself, there is no need to #define static in this file. Even if
#include <ctype.h>
#include <limits.h>
+#include <stdio.h>
#include <stdlib.h>
#include <string.h>
# define ATTRIBUTE_UNUSED __attribute__ ((__unused__))
#endif
+/* Shift A right by B bits portably, by dividing A by 2**B and
+ truncating towards minus infinity. A and B should be free of side
+ effects, and B should be in the range 0 <= B <= INT_BITS - 2, where
+ INT_BITS is the number of useful bits in an int. GNU code can
+ assume that INT_BITS is at least 32.
+
+ ISO C99 says that A >> B is implementation-defined if A < 0. Some
+ implementations (e.g., UNICOS 9.0 on a Cray Y-MP EL) don't shift
+ right in the usual way when A < 0, so SHR falls back on division if
+ ordinary A >> B doesn't seem to be the usual signed shift. */
+#define SHR(a, b) \
+ (-1 >> 1 == -1 \
+ ? (a) >> (b) \
+ : (a) / (1 << (b)) - ((a) % (1 << (b)) < 0))
+
#define EPOCH_YEAR 1970
#define TM_YEAR_BASE 1900
representation. */
typedef struct
{
+ bool negative;
long int value;
size_t digits;
} textint;
long int rel_seconds;
long int rel_ns;
- /* Counts of nonterminals of various flavors parsed so far. */
+ /* Presence or counts of nonterminals of various flavors parsed so far. */
bool timespec_seen;
+ bool rels_seen;
size_t dates_seen;
size_t days_seen;
size_t local_zones_seen;
- size_t rels_seen;
+ size_t dsts_seen;
size_t times_seen;
size_t zones_seen;
union YYSTYPE;
static int yylex (union YYSTYPE *, parser_control *);
static int yyerror (parser_control *, char *);
+static long int time_zone_hhmm (textint, long int);
%}
%parse-param { parser_control *pc }
%lex-param { parser_control *pc }
-/* This grammar has 13 shift/reduce conflicts. */
-%expect 13
+/* This grammar has 20 shift/reduce conflicts. */
+%expect 20
%union
{
%token tAGO tDST
%token <intval> tDAY tDAY_UNIT tDAYZONE tHOUR_UNIT tLOCAL_ZONE tMERIDIAN
-%token <intval> tMINUTE_UNIT tMONTH tMONTH_UNIT tSEC_UNIT tYEAR_UNIT tZONE
+%token <intval> tMINUTE_UNIT tMONTH tMONTH_UNIT tORDINAL
+%token <intval> tSEC_UNIT tYEAR_UNIT tZONE
%token <textintval> tSNUMBER tUNUMBER
%token <timespec> tSDECIMAL_NUMBER tUDECIMAL_NUMBER
-%type <intval> o_merid
+%type <intval> o_colon_minutes o_merid
%type <timespec> seconds signed_seconds unsigned_seconds
%%
| day
{ pc->days_seen++; }
| rel
- { pc->rels_seen++; }
+ { pc->rels_seen = true; }
| number
;
pc->seconds.tv_nsec = 0;
pc->meridian = $4;
}
- | tUNUMBER ':' tUNUMBER tSNUMBER
+ | tUNUMBER ':' tUNUMBER tSNUMBER o_colon_minutes
{
pc->hour = $1.value;
pc->minutes = $3.value;
pc->seconds.tv_nsec = 0;
pc->meridian = MER24;
pc->zones_seen++;
- pc->time_zone = $4.value % 100 + ($4.value / 100) * 60;
+ pc->time_zone = time_zone_hhmm ($4, $5);
}
| tUNUMBER ':' tUNUMBER ':' unsigned_seconds o_merid
{
pc->seconds = $5;
pc->meridian = $6;
}
- | tUNUMBER ':' tUNUMBER ':' unsigned_seconds tSNUMBER
+ | tUNUMBER ':' tUNUMBER ':' unsigned_seconds tSNUMBER o_colon_minutes
{
pc->hour = $1.value;
pc->minutes = $3.value;
pc->seconds = $5;
pc->meridian = MER24;
pc->zones_seen++;
- pc->time_zone = $6.value % 100 + ($6.value / 100) * 60;
+ pc->time_zone = time_zone_hhmm ($6, $7);
}
;
local_zone:
tLOCAL_ZONE
- { pc->local_isdst = $1; }
+ {
+ pc->local_isdst = $1;
+ pc->dsts_seen += (0 < $1);
+ }
| tLOCAL_ZONE tDST
- { pc->local_isdst = $1 < 0 ? 1 : $1 + 1; }
+ {
+ pc->local_isdst = 1;
+ pc->dsts_seen += (0 < $1) + 1;
+ }
;
zone:
tZONE
{ pc->time_zone = $1; }
+ | tZONE relunit_snumber
+ { pc->time_zone = $1; pc->rels_seen = true; }
+ | tZONE tSNUMBER o_colon_minutes
+ { pc->time_zone = $1 + time_zone_hhmm ($2, $3); }
| tDAYZONE
{ pc->time_zone = $1 + 60; }
| tZONE tDST
pc->day_ordinal = 1;
pc->day_number = $1;
}
+ | tORDINAL tDAY
+ {
+ pc->day_ordinal = $1;
+ pc->day_number = $2;
+ }
| tUNUMBER tDAY
{
pc->day_ordinal = $1.value;
;
relunit:
- tUNUMBER tYEAR_UNIT
- { pc->rel_year += $1.value * $2; }
- | tSNUMBER tYEAR_UNIT
+ tORDINAL tYEAR_UNIT
+ { pc->rel_year += $1 * $2; }
+ | tUNUMBER tYEAR_UNIT
{ pc->rel_year += $1.value * $2; }
| tYEAR_UNIT
{ pc->rel_year += $1; }
+ | tORDINAL tMONTH_UNIT
+ { pc->rel_month += $1 * $2; }
| tUNUMBER tMONTH_UNIT
{ pc->rel_month += $1.value * $2; }
- | tSNUMBER tMONTH_UNIT
- { pc->rel_month += $1.value * $2; }
| tMONTH_UNIT
{ pc->rel_month += $1; }
+ | tORDINAL tDAY_UNIT
+ { pc->rel_day += $1 * $2; }
| tUNUMBER tDAY_UNIT
{ pc->rel_day += $1.value * $2; }
- | tSNUMBER tDAY_UNIT
- { pc->rel_day += $1.value * $2; }
| tDAY_UNIT
{ pc->rel_day += $1; }
+ | tORDINAL tHOUR_UNIT
+ { pc->rel_hour += $1 * $2; }
| tUNUMBER tHOUR_UNIT
{ pc->rel_hour += $1.value * $2; }
- | tSNUMBER tHOUR_UNIT
- { pc->rel_hour += $1.value * $2; }
| tHOUR_UNIT
{ pc->rel_hour += $1; }
+ | tORDINAL tMINUTE_UNIT
+ { pc->rel_minutes += $1 * $2; }
| tUNUMBER tMINUTE_UNIT
{ pc->rel_minutes += $1.value * $2; }
- | tSNUMBER tMINUTE_UNIT
- { pc->rel_minutes += $1.value * $2; }
| tMINUTE_UNIT
{ pc->rel_minutes += $1; }
+ | tORDINAL tSEC_UNIT
+ { pc->rel_seconds += $1 * $2; }
| tUNUMBER tSEC_UNIT
{ pc->rel_seconds += $1.value * $2; }
- | tSNUMBER tSEC_UNIT
- { pc->rel_seconds += $1.value * $2; }
| tSDECIMAL_NUMBER tSEC_UNIT
{ pc->rel_seconds += $1.tv_sec * $2; pc->rel_ns += $1.tv_nsec * $2; }
| tUDECIMAL_NUMBER tSEC_UNIT
{ pc->rel_seconds += $1.tv_sec * $2; pc->rel_ns += $1.tv_nsec * $2; }
| tSEC_UNIT
{ pc->rel_seconds += $1; }
+ | relunit_snumber
+ ;
+
+relunit_snumber:
+ tSNUMBER tYEAR_UNIT
+ { pc->rel_year += $1.value * $2; }
+ | tSNUMBER tMONTH_UNIT
+ { pc->rel_month += $1.value * $2; }
+ | tSNUMBER tDAY_UNIT
+ { pc->rel_day += $1.value * $2; }
+ | tSNUMBER tHOUR_UNIT
+ { pc->rel_hour += $1.value * $2; }
+ | tSNUMBER tMINUTE_UNIT
+ { pc->rel_minutes += $1.value * $2; }
+ | tSNUMBER tSEC_UNIT
+ { pc->rel_seconds += $1.value * $2; }
;
seconds: signed_seconds | unsigned_seconds;
number:
tUNUMBER
{
- if (pc->dates_seen
+ if (pc->dates_seen && ! pc->year.digits
&& ! pc->rels_seen && (pc->times_seen || 2 < $1.digits))
pc->year = $1;
else
}
;
+o_colon_minutes:
+ /* empty */
+ { $$ = -1; }
+ | ':' tUNUMBER
+ { $$ = $2.value; }
+ ;
+
o_merid:
/* empty */
{ $$ = MER24; }
{ "YESTERDAY",tDAY_UNIT, -1 },
{ "TODAY", tDAY_UNIT, 0 },
{ "NOW", tDAY_UNIT, 0 },
- { "LAST", tUNUMBER, -1 },
- { "THIS", tUNUMBER, 0 },
- { "NEXT", tUNUMBER, 1 },
- { "FIRST", tUNUMBER, 1 },
-/*{ "SECOND", tUNUMBER, 2 }, */
- { "THIRD", tUNUMBER, 3 },
- { "FOURTH", tUNUMBER, 4 },
- { "FIFTH", tUNUMBER, 5 },
- { "SIXTH", tUNUMBER, 6 },
- { "SEVENTH", tUNUMBER, 7 },
- { "EIGHTH", tUNUMBER, 8 },
- { "NINTH", tUNUMBER, 9 },
- { "TENTH", tUNUMBER, 10 },
- { "ELEVENTH", tUNUMBER, 11 },
- { "TWELFTH", tUNUMBER, 12 },
+ { "LAST", tORDINAL, -1 },
+ { "THIS", tORDINAL, 0 },
+ { "NEXT", tORDINAL, 1 },
+ { "FIRST", tORDINAL, 1 },
+/*{ "SECOND", tORDINAL, 2 }, */
+ { "THIRD", tORDINAL, 3 },
+ { "FOURTH", tORDINAL, 4 },
+ { "FIFTH", tORDINAL, 5 },
+ { "SIXTH", tORDINAL, 6 },
+ { "SEVENTH", tORDINAL, 7 },
+ { "EIGHTH", tORDINAL, 8 },
+ { "NINTH", tORDINAL, 9 },
+ { "TENTH", tORDINAL, 10 },
+ { "ELEVENTH", tORDINAL, 11 },
+ { "TWELFTH", tORDINAL, 12 },
{ "AGO", tAGO, 1 },
{ NULL, 0, 0 }
};
+/* The universal time zone table. These labels can be used even for
+ time stamps that would not otherwise be valid, e.g., GMT time
+ stamps in London during summer. */
+static table const universal_time_zone_table[] =
+{
+ { "GMT", tZONE, HOUR ( 0) }, /* Greenwich Mean */
+ { "UT", tZONE, HOUR ( 0) }, /* Universal (Coordinated) */
+ { "UTC", tZONE, HOUR ( 0) },
+ { NULL, 0, 0 }
+};
+
/* The time zone table. This table is necessarily incomplete, as time
zone abbreviations are ambiguous; e.g. Australians interpret "EST"
as Eastern time in Australia, not as US Eastern Standard Time.
abbreviations; use numeric abbreviations like `-0500' instead. */
static table const time_zone_table[] =
{
- { "GMT", tZONE, HOUR ( 0) }, /* Greenwich Mean */
- { "UT", tZONE, HOUR ( 0) }, /* Universal (Coordinated) */
- { "UTC", tZONE, HOUR ( 0) },
{ "WET", tZONE, HOUR ( 0) }, /* Western European */
{ "WEST", tDAYZONE, HOUR ( 0) }, /* Western European Summer */
{ "BST", tDAYZONE, HOUR ( 0) }, /* British Summer */
{ "GST", tZONE, HOUR (10) }, /* Guam Standard */
{ "NZST", tZONE, HOUR (12) }, /* New Zealand Standard */
{ "NZDT", tDAYZONE, HOUR (12) }, /* New Zealand Daylight */
- { NULL, 0, 0 }
+ { NULL, 0, 0 }
};
/* Military time zone table. */
\f
+/* Convert a time zone expressed as HH:MM into an integer count of
+ minutes. If MM is negative, then S is of the form HHMM and needs
+ to be picked apart; otherwise, S is of the form HH. */
+
+static long int
+time_zone_hhmm (textint s, long int mm)
+{
+ if (mm < 0)
+ return (s.value / 100) * 60 + s.value % 100;
+ else
+ return s.value * 60 + (s.negative ? -mm : mm);
+}
+
static int
to_hour (long int hours, int meridian)
{
{
table const *tp;
- /* Try local zone abbreviations first; they're more likely to be right. */
+ for (tp = universal_time_zone_table; tp->name; tp++)
+ if (strcmp (name, tp->name) == 0)
+ return tp;
+
+ /* Try local zone abbreviations before those in time_zone_table, as
+ the local ones are more likely to be right. */
for (tp = pc->local_time_zone_table; tp->name; tp++)
if (strcmp (name, tp->name) == 0)
return tp;
{
/* Compute intervening leap days correctly even if year is negative.
Take care to avoid int overflow in leap day calculations. */
- int a4 = (a->tm_year >> 2) + (TM_YEAR_BASE >> 2) - ! (a->tm_year & 3);
- int b4 = (b->tm_year >> 2) + (TM_YEAR_BASE >> 2) - ! (b->tm_year & 3);
+ int a4 = SHR (a->tm_year, 2) + SHR (TM_YEAR_BASE, 2) - ! (a->tm_year & 3);
+ int b4 = SHR (b->tm_year, 2) + SHR (TM_YEAR_BASE, 2) - ! (b->tm_year & 3);
int a100 = a4 / 25 - (a4 % 25 < 0);
int b100 = b4 / 25 - (b4 % 25 < 0);
- int a400 = a100 >> 2;
- int b400 = b100 >> 2;
+ int a400 = SHR (a100, 2);
+ int b400 = SHR (b100, 2);
int intervening_leap_days = (a4 - b4) - (a100 - b100) + (a400 - b400);
long int ayear = a->tm_year;
long int years = ayear - b->tm_year;
}
else
{
+ lvalp->textintval.negative = sign < 0;
if (sign < 0)
{
lvalp->textintval.value = - value;
if (! now)
{
- if (gettime (&gettime_buffer) != 0)
- return false;
+ gettime (&gettime_buffer);
now = &gettime_buffer;
}
char const *tzbase = p + 4;
size_t tzsize = 1;
char const *s;
-
+
for (s = tzbase; *s; s++, tzsize++)
if (*s == '\\')
{
pc.input = p;
pc.year.value = tmp->tm_year;
pc.year.value += TM_YEAR_BASE;
- pc.year.digits = 4;
+ pc.year.digits = 0;
pc.month = tmp->tm_mon + 1;
pc.day = tmp->tm_mday;
pc.hour = tmp->tm_hour;
pc.rel_month = 0;
pc.rel_year = 0;
pc.timespec_seen = false;
+ pc.rels_seen = false;
pc.dates_seen = 0;
pc.days_seen = 0;
- pc.rels_seen = 0;
pc.times_seen = 0;
pc.local_zones_seen = 0;
+ pc.dsts_seen = 0;
pc.zones_seen = 0;
#if HAVE_STRUCT_TM_TM_ZONE
*result = pc.seconds;
else
{
- if (1 < pc.times_seen || 1 < pc.dates_seen || 1 < pc.days_seen
- || 1 < (pc.local_zones_seen + pc.zones_seen)
- || (pc.local_zones_seen && 1 < pc.local_isdst))
+ if (1 < (pc.times_seen | pc.dates_seen | pc.days_seen | pc.dsts_seen
+ | (pc.local_zones_seen + pc.zones_seen)))
goto fail;
tm.tm_year = to_year (pc.year) - TM_YEAR_BASE;
}
/* Let mktime deduce tm_isdst if we have an absolute time stamp. */
- if (pc.dates_seen | pc.days_seen | pc.times_seen)
+ if (!pc.rels_seen)
tm.tm_isdst = -1;
/* But if the input explicitly specifies local time with or without
int month = tm.tm_mon + pc.rel_month;
int day = tm.tm_mday + pc.rel_day;
if (((year < tm.tm_year) ^ (pc.rel_year < 0))
- | (month < tm.tm_mon) ^ (pc.rel_month < 0)
- | (day < tm.tm_mday) ^ (pc.rel_day < 0))
+ | ((month < tm.tm_mon) ^ (pc.rel_month < 0))
+ | ((day < tm.tm_mday) ^ (pc.rel_day < 0)))
goto fail;
tm.tm_year = year;
tm.tm_mon = month;
#if TEST
-#include <stdio.h>
-
int
main (int ac, char **av)
{