openat: detect Solaris fchownat bug
[gnulib.git] / lib / getdate.y
index b57e6a5..94160b2 100644 (file)
@@ -1,11 +1,13 @@
 %{
 /* Parse a string into an internal time stamp.
 %{
 /* Parse a string into an internal time stamp.
-   Copyright (C) 1999, 2000, 2002, 2003, 2004 Free Software Foundation, Inc.
 
 
-   This program is free software; you can redistribute it and/or modify
+   Copyright (C) 1999, 2000, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009
+   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
    it under the terms of the GNU General Public License as published by
-   the Free Software Foundation; either version 2, or (at your option)
-   any later version.
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
 
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
 
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
@@ -13,8 +15,7 @@
    GNU General Public License for more details.
 
    You should have received a copy of the GNU General Public License
    GNU General Public License for more details.
 
    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.  */
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
 
 /* Originally written by Steven M. Bellovin <smb@research.att.com> while
    at the University of North Carolina at Chapel Hill.  Later tweaked by
 
 /* Originally written by Steven M. Bellovin <smb@research.att.com> while
    at the University of North Carolina at Chapel Hill.  Later tweaked by
 /* FIXME: Check for arithmetic overflow in all cases, not just
    some of them.  */
 
 /* FIXME: Check for arithmetic overflow in all cases, not just
    some of them.  */
 
-#ifdef HAVE_CONFIG_H
-# include <config.h>
-#endif
+#include <config.h>
 
 #include "getdate.h"
 
 
 #include "getdate.h"
 
-/* Use alloca only if it is known to be builtin.  */
-#if HAVE_ALLOCA
-# include <alloca.h>
-#else
-# define YYSTACK_USE_ALLOCA 0
-#endif
+#include "intprops.h"
+#include "timespec.h"
+#include "verify.h"
 
 
-/* Tell Bison ow much stack space is needed.  20 should be plenty for
+/* 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
    this grammar, which is not right recursive.  Beware setting it too
-   high, since that might cause problems on machines whose alloca
+   high, since that might cause problems on machines whose
    implementations have lame stack-overflow checking.  */
 #define YYMAXDEPTH 20
    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
 
 /* 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
 # undef static
 #endif
 
 # undef static
 #endif
 
-#include <ctype.h>
+#include <c-ctype.h>
 #include <limits.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 
 #include <limits.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 
-#include "setenv.h"
 #include "xalloc.h"
 
 #include "xalloc.h"
 
-#if STDC_HEADERS || (! defined isascii && ! HAVE_ISASCII)
-# define IN_CTYPE_DOMAIN(c) 1
-#else
-# define IN_CTYPE_DOMAIN(c) isascii (c)
-#endif
-
-#define ISSPACE(c) (IN_CTYPE_DOMAIN (c) && isspace (c))
-#define ISALPHA(c) (IN_CTYPE_DOMAIN (c) && isalpha (c))
-#define ISLOWER(c) (IN_CTYPE_DOMAIN (c) && islower (c))
 
 /* ISDIGIT differs from isdigit, as follows:
 
 /* ISDIGIT differs from isdigit, as follows:
-   - Its arg may be any int or unsigned int; it need not be an unsigned char.
-   - It's guaranteed to evaluate its argument exactly once.
+   - Its arg may be any int or unsigned int; it need not be an unsigned char
+     or EOF.
    - It's typically faster.
    POSIX says that only '0' through '9' are digits.  Prefer ISDIGIT to
    isdigit unless it's important to use the locale's definition
    of `digit' even when the host does not conform to POSIX.  */
 #define ISDIGIT(c) ((unsigned int) (c) - '0' <= 9)
 
    - It's typically faster.
    POSIX says that only '0' through '9' are digits.  Prefer ISDIGIT to
    isdigit unless it's important to use the locale's definition
    of `digit' even when the host does not conform to POSIX.  */
 #define ISDIGIT(c) ((unsigned int) (c) - '0' <= 9)
 
-#if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 8) || __STRICT_ANSI__
-# define __attribute__(x)
-#endif
-
-#ifndef ATTRIBUTE_UNUSED
-# 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
 /* 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
 
 #define HOUR(x) ((x) * 60)
 
 
 #define HOUR(x) ((x) * 60)
 
+/* long_time_t is a signed integer type that contains all time_t values.  */
+verify (TYPE_IS_INTEGER (time_t));
+#if TIME_T_FITS_IN_LONG_INT
+typedef long int long_time_t;
+#else
+typedef time_t long_time_t;
+#endif
+
+/* Lots of this code assumes time_t and time_t-like values fit into
+   long_time_t.  */
+verify (TYPE_MINIMUM (long_time_t) <= TYPE_MINIMUM (time_t)
+        && TYPE_MAXIMUM (time_t) <= TYPE_MAXIMUM (long_time_t));
+
+/* FIXME: It also assumes that signed integer overflow silently wraps around,
+   but this is not true any more with recent versions of GCC 4.  */
+
 /* An integer value, and the number of digits in its textual
    representation.  */
 typedef struct
 /* An integer value, and the number of digits in its textual
    representation.  */
 typedef struct
@@ -137,6 +136,25 @@ enum { MERam, MERpm, MER24 };
 
 enum { BILLION = 1000000000, LOG10_BILLION = 9 };
 
 
 enum { BILLION = 1000000000, LOG10_BILLION = 9 };
 
+/* Relative times.  */
+typedef struct
+{
+  /* Relative year, month, day, hour, minutes, seconds, and nanoseconds.  */
+  long int year;
+  long int month;
+  long int day;
+  long int hour;
+  long int minutes;
+  long_time_t seconds;
+  long int ns;
+} relative_time;
+
+#if HAVE_COMPOUND_LITERALS
+# define RELATIVE_TIME_0 ((relative_time) { 0, 0, 0, 0, 0, 0, 0 })
+#else
+static relative_time const RELATIVE_TIME_0;
+#endif
+
 /* Information passed to and from the parser.  */
 typedef struct
 {
 /* Information passed to and from the parser.  */
 typedef struct
 {
@@ -167,20 +185,15 @@ typedef struct
   struct timespec seconds; /* includes nanoseconds */
 
   /* Relative year, month, day, hour, minutes, seconds, and nanoseconds.  */
   struct timespec seconds; /* includes nanoseconds */
 
   /* Relative year, month, day, hour, minutes, seconds, and nanoseconds.  */
-  long int rel_year;
-  long int rel_month;
-  long int rel_day;
-  long int rel_hour;
-  long int rel_minutes;
-  long int rel_seconds;
-  long int rel_ns;
-
-  /* Counts of nonterminals of various flavors parsed so far.  */
+  relative_time rel;
+
+  /* Presence or counts of nonterminals of various flavors parsed so far.  */
   bool timespec_seen;
   bool timespec_seen;
+  bool rels_seen;
   size_t dates_seen;
   size_t days_seen;
   size_t local_zones_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;
 
   size_t times_seen;
   size_t zones_seen;
 
@@ -190,8 +203,72 @@ typedef struct
 
 union YYSTYPE;
 static int yylex (union YYSTYPE *, parser_control *);
 
 union YYSTYPE;
 static int yylex (union YYSTYPE *, parser_control *);
-static int yyerror (parser_control *, char *);
-static long int time_zone_hhmm (textint, long int);
+static int yyerror (parser_control const *, char const *);
+static long int time_zone_hhmm (parser_control *, textint, long int);
+
+/* Extract into *PC any date and time info from a string of digits
+   of the form e.g., YYYYMMDD, YYMMDD, HHMM, HH (and sometimes YYY,
+   YYYY, ...).  */
+static void
+digits_to_date_time (parser_control *pc, textint text_int)
+{
+  if (pc->dates_seen && ! pc->year.digits
+      && ! pc->rels_seen && (pc->times_seen || 2 < text_int.digits))
+    pc->year = text_int;
+  else
+    {
+      if (4 < text_int.digits)
+       {
+         pc->dates_seen++;
+         pc->day = text_int.value % 100;
+         pc->month = (text_int.value / 100) % 100;
+         pc->year.value = text_int.value / 10000;
+         pc->year.digits = text_int.digits - 4;
+       }
+      else
+       {
+         pc->times_seen++;
+         if (text_int.digits <= 2)
+           {
+             pc->hour = text_int.value;
+             pc->minutes = 0;
+           }
+         else
+           {
+             pc->hour = text_int.value / 100;
+             pc->minutes = text_int.value % 100;
+           }
+         pc->seconds.tv_sec = 0;
+         pc->seconds.tv_nsec = 0;
+         pc->meridian = MER24;
+       }
+    }
+}
+
+/* Increment PC->rel by FACTOR * REL (FACTOR is 1 or -1).  */
+static void
+apply_relative_time (parser_control *pc, relative_time rel, int factor)
+{
+  pc->rel.ns += factor * rel.ns;
+  pc->rel.seconds += factor * rel.seconds;
+  pc->rel.minutes += factor * rel.minutes;
+  pc->rel.hour += factor * rel.hour;
+  pc->rel.day += factor * rel.day;
+  pc->rel.month += factor * rel.month;
+  pc->rel.year += factor * rel.year;
+  pc->rels_seen = true;
+}
+
+/* Set PC-> hour, minutes, seconds and nanoseconds members from arguments.  */
+static void
+set_hhmmss (parser_control *pc, long int hour, long int minutes,
+           time_t sec, long int nsec)
+{
+  pc->hour = hour;
+  pc->minutes = minutes;
+  pc->seconds.tv_sec = sec;
+  pc->seconds.tv_nsec = nsec;
+}
 
 %}
 
 
 %}
 
@@ -201,21 +278,24 @@ static long int time_zone_hhmm (textint, long int);
 %parse-param { parser_control *pc }
 %lex-param { parser_control *pc }
 
 %parse-param { parser_control *pc }
 %lex-param { parser_control *pc }
 
-/* This grammar has 14 shift/reduce conflicts. */
-%expect 14
+/* This grammar has 20 shift/reduce conflicts. */
+%expect 20
 
 %union
 {
   long int intval;
   textint textintval;
   struct timespec timespec;
 
 %union
 {
   long int intval;
   textint textintval;
   struct timespec timespec;
+  relative_time rel;
 }
 
 %token tAGO tDST
 
 }
 
 %token tAGO tDST
 
-%token <intval> tDAY tDAY_UNIT tDAYZONE tHOUR_UNIT tLOCAL_ZONE tMERIDIAN
-%token <intval> tMINUTE_UNIT tMONTH tMONTH_UNIT tORDINAL
-%token <intval> tSEC_UNIT tYEAR_UNIT tZONE
+%token tYEAR_UNIT tMONTH_UNIT tHOUR_UNIT tMINUTE_UNIT tSEC_UNIT
+%token <intval> tDAY_UNIT tDAY_SHIFT
+
+%token <intval> tDAY tDAYZONE tLOCAL_ZONE tMERIDIAN
+%token <intval> tMONTH tORDINAL tZONE
 
 %token <textintval> tSNUMBER tUNUMBER
 %token <timespec> tSDECIMAL_NUMBER tUDECIMAL_NUMBER
 
 %token <textintval> tSNUMBER tUNUMBER
 %token <timespec> tSDECIMAL_NUMBER tUDECIMAL_NUMBER
@@ -223,6 +303,8 @@ static long int time_zone_hhmm (textint, long int);
 %type <intval> o_colon_minutes o_merid
 %type <timespec> seconds signed_seconds unsigned_seconds
 
 %type <intval> o_colon_minutes o_merid
 %type <timespec> seconds signed_seconds unsigned_seconds
 
+%type <rel> relunit relunit_snumber dayshift
+
 %%
 
 spec:
 %%
 
 spec:
@@ -255,67 +337,63 @@ item:
   | day
       { pc->days_seen++; }
   | rel
   | day
       { pc->days_seen++; }
   | rel
-      { pc->rels_seen++; }
   | number
   | number
+  | hybrid
   ;
 
 time:
     tUNUMBER tMERIDIAN
       {
   ;
 
 time:
     tUNUMBER tMERIDIAN
       {
-       pc->hour = $1.value;
-       pc->minutes = 0;
-       pc->seconds.tv_sec = 0;
-       pc->seconds.tv_nsec = 0;
+       set_hhmmss (pc, $1.value, 0, 0, 0);
        pc->meridian = $2;
       }
   | tUNUMBER ':' tUNUMBER o_merid
       {
        pc->meridian = $2;
       }
   | tUNUMBER ':' tUNUMBER o_merid
       {
-       pc->hour = $1.value;
-       pc->minutes = $3.value;
-       pc->seconds.tv_sec = 0;
-       pc->seconds.tv_nsec = 0;
+       set_hhmmss (pc, $1.value, $3.value, 0, 0);
        pc->meridian = $4;
       }
   | tUNUMBER ':' tUNUMBER tSNUMBER o_colon_minutes
       {
        pc->meridian = $4;
       }
   | tUNUMBER ':' tUNUMBER tSNUMBER o_colon_minutes
       {
-       pc->hour = $1.value;
-       pc->minutes = $3.value;
-       pc->seconds.tv_sec = 0;
-       pc->seconds.tv_nsec = 0;
+       set_hhmmss (pc, $1.value, $3.value, 0, 0);
        pc->meridian = MER24;
        pc->zones_seen++;
        pc->meridian = MER24;
        pc->zones_seen++;
-       pc->time_zone = time_zone_hhmm ($4, $5);
+       pc->time_zone = time_zone_hhmm (pc, $4, $5);
       }
   | tUNUMBER ':' tUNUMBER ':' unsigned_seconds o_merid
       {
       }
   | tUNUMBER ':' tUNUMBER ':' unsigned_seconds o_merid
       {
-       pc->hour = $1.value;
-       pc->minutes = $3.value;
-       pc->seconds = $5;
+       set_hhmmss (pc, $1.value, $3.value, $5.tv_sec, $5.tv_nsec);
        pc->meridian = $6;
       }
   | tUNUMBER ':' tUNUMBER ':' unsigned_seconds tSNUMBER o_colon_minutes
       {
        pc->meridian = $6;
       }
   | tUNUMBER ':' tUNUMBER ':' unsigned_seconds tSNUMBER o_colon_minutes
       {
-       pc->hour = $1.value;
-       pc->minutes = $3.value;
-       pc->seconds = $5;
+       set_hhmmss (pc, $1.value, $3.value, $5.tv_sec, $5.tv_nsec);
        pc->meridian = MER24;
        pc->zones_seen++;
        pc->meridian = MER24;
        pc->zones_seen++;
-       pc->time_zone = time_zone_hhmm ($6, $7);
+       pc->time_zone = time_zone_hhmm (pc, $6, $7);
       }
   ;
 
 local_zone:
     tLOCAL_ZONE
       }
   ;
 
 local_zone:
     tLOCAL_ZONE
-      { pc->local_isdst = $1; }
+      {
+       pc->local_isdst = $1;
+       pc->dsts_seen += (0 < $1);
+      }
   | tLOCAL_ZONE tDST
   | 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; }
   ;
 
 zone:
     tZONE
       { pc->time_zone = $1; }
+  | tZONE relunit_snumber
+      { pc->time_zone = $1;
+       apply_relative_time (pc, $2, 1); }
   | tZONE tSNUMBER o_colon_minutes
   | tZONE tSNUMBER o_colon_minutes
-      { pc->time_zone = $1 + time_zone_hhmm ($2, $3); }
+      { pc->time_zone = $1 + time_zone_hhmm (pc, $2, $3); }
   | tDAYZONE
       { pc->time_zone = $1 + 60; }
   | tZONE tDST
   | tDAYZONE
       { pc->time_zone = $1 + 60; }
   | tZONE tDST
@@ -325,12 +403,12 @@ zone:
 day:
     tDAY
       {
 day:
     tDAY
       {
-       pc->day_ordinal = 1;
+       pc->day_ordinal = 0;
        pc->day_number = $1;
       }
   | tDAY ','
       {
        pc->day_number = $1;
       }
   | tDAY ','
       {
-       pc->day_ordinal = 1;
+       pc->day_ordinal = 0;
        pc->day_number = $1;
       }
   | tORDINAL tDAY
        pc->day_number = $1;
       }
   | tORDINAL tDAY
@@ -420,71 +498,75 @@ date:
 
 rel:
     relunit tAGO
 
 rel:
     relunit tAGO
-      {
-       pc->rel_ns = -pc->rel_ns;
-       pc->rel_seconds = -pc->rel_seconds;
-       pc->rel_minutes = -pc->rel_minutes;
-       pc->rel_hour = -pc->rel_hour;
-       pc->rel_day = -pc->rel_day;
-       pc->rel_month = -pc->rel_month;
-       pc->rel_year = -pc->rel_year;
-      }
+      { apply_relative_time (pc, $1, -1); }
   | relunit
   | relunit
+      { apply_relative_time (pc, $1, 1); }
+  | dayshift
+      { apply_relative_time (pc, $1, 1); }
   ;
 
 relunit:
     tORDINAL tYEAR_UNIT
   ;
 
 relunit:
     tORDINAL tYEAR_UNIT
-      { pc->rel_year += $1 * $2; }
+      { $$ = RELATIVE_TIME_0; $$.year = $1; }
   | tUNUMBER tYEAR_UNIT
   | tUNUMBER tYEAR_UNIT
-      { pc->rel_year += $1.value * $2; }
-  | tSNUMBER tYEAR_UNIT
-      { pc->rel_year += $1.value * $2; }
+      { $$ = RELATIVE_TIME_0; $$.year = $1.value; }
   | tYEAR_UNIT
   | tYEAR_UNIT
-      { pc->rel_year += $1; }
+      { $$ = RELATIVE_TIME_0; $$.year = 1; }
   | tORDINAL tMONTH_UNIT
   | tORDINAL tMONTH_UNIT
-      { pc->rel_month += $1 * $2; }
+      { $$ = RELATIVE_TIME_0; $$.month = $1; }
   | tUNUMBER tMONTH_UNIT
   | tUNUMBER tMONTH_UNIT
-      { pc->rel_month += $1.value * $2; }
-  | tSNUMBER tMONTH_UNIT
-      { pc->rel_month += $1.value * $2; }
+      { $$ = RELATIVE_TIME_0; $$.month = $1.value; }
   | tMONTH_UNIT
   | tMONTH_UNIT
-      { pc->rel_month += $1; }
+      { $$ = RELATIVE_TIME_0; $$.month = 1; }
   | tORDINAL tDAY_UNIT
   | tORDINAL tDAY_UNIT
-      { pc->rel_day += $1 * $2; }
+      { $$ = RELATIVE_TIME_0; $$.day = $1 * $2; }
   | tUNUMBER tDAY_UNIT
   | tUNUMBER tDAY_UNIT
-      { pc->rel_day += $1.value * $2; }
-  | tSNUMBER tDAY_UNIT
-      { pc->rel_day += $1.value * $2; }
+      { $$ = RELATIVE_TIME_0; $$.day = $1.value * $2; }
   | tDAY_UNIT
   | tDAY_UNIT
-      { pc->rel_day += $1; }
+      { $$ = RELATIVE_TIME_0; $$.day = $1; }
   | tORDINAL tHOUR_UNIT
   | tORDINAL tHOUR_UNIT
-      { pc->rel_hour += $1 * $2; }
+      { $$ = RELATIVE_TIME_0; $$.hour = $1; }
   | tUNUMBER tHOUR_UNIT
   | tUNUMBER tHOUR_UNIT
-      { pc->rel_hour += $1.value * $2; }
-  | tSNUMBER tHOUR_UNIT
-      { pc->rel_hour += $1.value * $2; }
+      { $$ = RELATIVE_TIME_0; $$.hour = $1.value; }
   | tHOUR_UNIT
   | tHOUR_UNIT
-      { pc->rel_hour += $1; }
+      { $$ = RELATIVE_TIME_0; $$.hour = 1; }
   | tORDINAL tMINUTE_UNIT
   | tORDINAL tMINUTE_UNIT
-      { pc->rel_minutes += $1 * $2; }
+      { $$ = RELATIVE_TIME_0; $$.minutes = $1; }
   | tUNUMBER tMINUTE_UNIT
   | tUNUMBER tMINUTE_UNIT
-      { pc->rel_minutes += $1.value * $2; }
-  | tSNUMBER tMINUTE_UNIT
-      { pc->rel_minutes += $1.value * $2; }
+      { $$ = RELATIVE_TIME_0; $$.minutes = $1.value; }
   | tMINUTE_UNIT
   | tMINUTE_UNIT
-      { pc->rel_minutes += $1; }
+      { $$ = RELATIVE_TIME_0; $$.minutes = 1; }
   | tORDINAL tSEC_UNIT
   | tORDINAL tSEC_UNIT
-      { pc->rel_seconds += $1 * $2; }
+      { $$ = RELATIVE_TIME_0; $$.seconds = $1; }
   | tUNUMBER tSEC_UNIT
   | tUNUMBER tSEC_UNIT
-      { pc->rel_seconds += $1.value * $2; }
-  | tSNUMBER tSEC_UNIT
-      { pc->rel_seconds += $1.value * $2; }
+      { $$ = RELATIVE_TIME_0; $$.seconds = $1.value; }
   | tSDECIMAL_NUMBER tSEC_UNIT
   | tSDECIMAL_NUMBER tSEC_UNIT
-      { pc->rel_seconds += $1.tv_sec * $2; pc->rel_ns += $1.tv_nsec * $2; }
+      { $$ = RELATIVE_TIME_0; $$.seconds = $1.tv_sec; $$.ns = $1.tv_nsec; }
   | tUDECIMAL_NUMBER tSEC_UNIT
   | tUDECIMAL_NUMBER tSEC_UNIT
-      { pc->rel_seconds += $1.tv_sec * $2; pc->rel_ns += $1.tv_nsec * $2; }
+      { $$ = RELATIVE_TIME_0; $$.seconds = $1.tv_sec; $$.ns = $1.tv_nsec; }
   | tSEC_UNIT
   | tSEC_UNIT
-      { pc->rel_seconds += $1; }
+      { $$ = RELATIVE_TIME_0; $$.seconds = 1; }
+  | relunit_snumber
+  ;
+
+relunit_snumber:
+    tSNUMBER tYEAR_UNIT
+      { $$ = RELATIVE_TIME_0; $$.year = $1.value; }
+  | tSNUMBER tMONTH_UNIT
+      { $$ = RELATIVE_TIME_0; $$.month = $1.value; }
+  | tSNUMBER tDAY_UNIT
+      { $$ = RELATIVE_TIME_0; $$.day = $1.value * $2; }
+  | tSNUMBER tHOUR_UNIT
+      { $$ = RELATIVE_TIME_0; $$.hour = $1.value; }
+  | tSNUMBER tMINUTE_UNIT
+      { $$ = RELATIVE_TIME_0; $$.minutes = $1.value; }
+  | tSNUMBER tSEC_UNIT
+      { $$ = RELATIVE_TIME_0; $$.seconds = $1.value; }
+  ;
+
+dayshift:
+    tDAY_SHIFT
+      { $$ = RELATIVE_TIME_0; $$.day = $1; }
   ;
 
 seconds: signed_seconds | unsigned_seconds;
   ;
 
 seconds: signed_seconds | unsigned_seconds;
@@ -503,38 +585,16 @@ unsigned_seconds:
 
 number:
     tUNUMBER
 
 number:
     tUNUMBER
+      { digits_to_date_time (pc, $1); }
+  ;
+
+hybrid:
+    tUNUMBER relunit_snumber
       {
       {
-       if (pc->dates_seen
-           && ! pc->rels_seen && (pc->times_seen || 2 < $1.digits))
-         pc->year = $1;
-       else
-         {
-           if (4 < $1.digits)
-             {
-               pc->dates_seen++;
-               pc->day = $1.value % 100;
-               pc->month = ($1.value / 100) % 100;
-               pc->year.value = $1.value / 10000;
-               pc->year.digits = $1.digits - 4;
-             }
-           else
-             {
-               pc->times_seen++;
-               if ($1.digits <= 2)
-                 {
-                   pc->hour = $1.value;
-                   pc->minutes = 0;
-                 }
-               else
-                 {
-                   pc->hour = $1.value / 100;
-                   pc->minutes = $1.value % 100;
-                 }
-               pc->seconds.tv_sec = 0;
-               pc->seconds.tv_nsec = 0;
-               pc->meridian = MER24;
-             }
-         }
+       /* Hybrid all-digit and relative offset, so that we accept e.g.,
+          "YYYYMMDD +N days" as well as "YYYYMMDD N days".  */
+       digits_to_date_time (pc, $1);
+       apply_relative_time (pc, $2, 1);
       }
   ;
 
       }
   ;
 
@@ -615,10 +675,10 @@ static table const time_units_table[] =
 /* Assorted relative-time words. */
 static table const relative_time_table[] =
 {
 /* Assorted relative-time words. */
 static table const relative_time_table[] =
 {
-  { "TOMORROW",        tDAY_UNIT,       1 },
-  { "YESTERDAY",tDAY_UNIT,     -1 },
-  { "TODAY",   tDAY_UNIT,       0 },
-  { "NOW",     tDAY_UNIT,       0 },
+  { "TOMORROW",        tDAY_SHIFT,      1 },
+  { "YESTERDAY",tDAY_SHIFT,    -1 },
+  { "TODAY",   tDAY_SHIFT,      0 },
+  { "NOW",     tDAY_SHIFT,      0 },
   { "LAST",    tORDINAL,       -1 },
   { "THIS",    tORDINAL,        0 },
   { "NEXT",    tORDINAL,        1 },
   { "LAST",    tORDINAL,       -1 },
   { "THIS",    tORDINAL,        0 },
   { "NEXT",    tORDINAL,        1 },
@@ -638,6 +698,17 @@ static table const relative_time_table[] =
   { NULL, 0, 0 }
 };
 
   { 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.
 /* 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.
@@ -645,9 +716,6 @@ static table const relative_time_table[] =
    abbreviations; use numeric abbreviations like `-0500' instead.  */
 static table const time_zone_table[] =
 {
    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 */
   { "WET",     tZONE,     HOUR ( 0) }, /* Western European */
   { "WEST",    tDAYZONE,  HOUR ( 0) }, /* Western European Summer */
   { "BST",     tDAYZONE,  HOUR ( 0) }, /* British Summer */
@@ -695,7 +763,7 @@ static table const time_zone_table[] =
   { "GST",     tZONE,     HOUR (10) }, /* Guam Standard */
   { "NZST",    tZONE,     HOUR (12) }, /* New Zealand Standard */
   { "NZDT",    tDAYZONE,  HOUR (12) }, /* New Zealand Daylight */
   { "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. */
 };
 
 /* Military time zone table. */
@@ -733,15 +801,33 @@ static table const military_table[] =
 
 /* 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
 
 /* 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.  */
+   to be picked apart; otherwise, S is of the form HH.  As specified in
+   http://www.opengroup.org/susv3xbd/xbd_chap08.html#tag_08_03, allow
+   only valid TZ range, and consider first two digits as hours, if no
+   minutes specified.  */
 
 static long int
 
 static long int
-time_zone_hhmm (textint s, long int mm)
+time_zone_hhmm (parser_control *pc, textint s, long int mm)
 {
 {
+  long int n_minutes;
+
+  /* If the length of S is 1 or 2 and no minutes are specified,
+     interpret it as a number of hours.  */
+  if (s.digits <= 2 && mm < 0)
+    s.value *= 100;
+
   if (mm < 0)
   if (mm < 0)
-    return (s.value / 100) * 60 + s.value % 100;
+    n_minutes = (s.value / 100) * 60 + s.value % 100;
   else
   else
-    return s.value * 60 + (s.negative ? -mm : mm);
+    n_minutes = s.value * 60 + (s.negative ? -mm : mm);
+
+  /* If the absolute number of minutes is larger than 24 hours,
+     arrange to reject it by incrementing pc->zones_seen.  Thus,
+     we allow only values in the range UTC-24:00 to UTC+24:00.  */
+  if (24 * 60 < abs (n_minutes))
+    pc->zones_seen++;
+
+  return n_minutes;
 }
 
 static int
 }
 
 static int
@@ -780,7 +866,12 @@ lookup_zone (parser_control const *pc, char const *name)
 {
   table const *tp;
 
 {
   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;
   for (tp = pc->local_time_zone_table; tp->name; tp++)
     if (strcmp (name, tp->name) == 0)
       return tp;
@@ -833,8 +924,7 @@ lookup_word (parser_control const *pc, char *word)
   for (p = word; *p; p++)
     {
       unsigned char ch = *p;
   for (p = word; *p; p++)
     {
       unsigned char ch = *p;
-      if (ISLOWER (ch))
-       *p = toupper (ch);
+      *p = c_toupper (ch);
     }
 
   for (tp = meridian_table; tp->name; tp++)
     }
 
   for (tp = meridian_table; tp->name; tp++)
@@ -899,7 +989,7 @@ yylex (YYSTYPE *lvalp, parser_control *pc)
 
   for (;;)
     {
 
   for (;;)
     {
-      while (c = *pc->input, ISSPACE (c))
+      while (c = *pc->input, c_isspace (c))
        pc->input++;
 
       if (ISDIGIT (c) || c == '-' || c == '+')
        pc->input++;
 
       if (ISDIGIT (c) || c == '-' || c == '+')
@@ -910,7 +1000,7 @@ yylex (YYSTYPE *lvalp, parser_control *pc)
          if (c == '-' || c == '+')
            {
              sign = c == '-' ? -1 : 1;
          if (c == '-' || c == '+')
            {
              sign = c == '-' ? -1 : 1;
-             while (c = *++pc->input, ISSPACE (c))
+             while (c = *++pc->input, c_isspace (c))
                continue;
              if (! ISDIGIT (c))
                /* skip the '-' sign */
                continue;
              if (! ISDIGIT (c))
                /* skip the '-' sign */
@@ -1014,7 +1104,7 @@ yylex (YYSTYPE *lvalp, parser_control *pc)
            }
        }
 
            }
        }
 
-      if (ISALPHA (c))
+      if (c_isalpha (c))
        {
          char buff[20];
          char *p = buff;
        {
          char buff[20];
          char *p = buff;
@@ -1026,7 +1116,7 @@ yylex (YYSTYPE *lvalp, parser_control *pc)
                *p++ = c;
              c = *++pc->input;
            }
                *p++ = c;
              c = *++pc->input;
            }
-         while (ISALPHA (c) || c == '.');
+         while (c_isalpha (c) || c == '.');
 
          *p = '\0';
          tp = lookup_word (pc, buff);
 
          *p = '\0';
          tp = lookup_word (pc, buff);
@@ -1055,7 +1145,8 @@ yylex (YYSTYPE *lvalp, parser_control *pc)
 
 /* Do nothing if the parser reports an error.  */
 static int
 
 /* Do nothing if the parser reports an error.  */
 static int
-yyerror (parser_control *pc ATTRIBUTE_UNUSED, char *s ATTRIBUTE_UNUSED)
+yyerror (parser_control const *pc _UNUSED_PARAMETER_,
+        char const *s _UNUSED_PARAMETER_)
 {
   return 0;
 }
 {
   return 0;
 }
@@ -1127,8 +1218,7 @@ get_date (struct timespec *result, char const *p, struct timespec const *now)
 
   if (! now)
     {
 
   if (! now)
     {
-      if (gettime (&gettime_buffer) != 0)
-       return false;
+      gettime (&gettime_buffer);
       now = &gettime_buffer;
     }
 
       now = &gettime_buffer;
     }
 
@@ -1139,7 +1229,7 @@ get_date (struct timespec *result, char const *p, struct timespec const *now)
   if (! tmp)
     return false;
 
   if (! tmp)
     return false;
 
-  while (c = *p, ISSPACE (c))
+  while (c = *p, c_isspace (c))
     p++;
 
   if (strncmp (p, "TZ=\"", 4) == 0)
     p++;
 
   if (strncmp (p, "TZ=\"", 4) == 0)
@@ -1177,10 +1267,16 @@ get_date (struct timespec *result, char const *p, struct timespec const *now)
          }
     }
 
          }
     }
 
+  /* As documented, be careful to treat the empty string just like
+     a date string of "0".  Without this, an empty string would be
+     declared invalid when parsed during a DST transition.  */
+  if (*p == '\0')
+    p = "0";
+
   pc.input = p;
   pc.year.value = tmp->tm_year;
   pc.year.value += TM_YEAR_BASE;
   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.month = tmp->tm_mon + 1;
   pc.day = tmp->tm_mday;
   pc.hour = tmp->tm_hour;
@@ -1190,19 +1286,14 @@ get_date (struct timespec *result, char const *p, struct timespec const *now)
   tm.tm_isdst = tmp->tm_isdst;
 
   pc.meridian = MER24;
   tm.tm_isdst = tmp->tm_isdst;
 
   pc.meridian = MER24;
-  pc.rel_ns = 0;
-  pc.rel_seconds = 0;
-  pc.rel_minutes = 0;
-  pc.rel_hour = 0;
-  pc.rel_day = 0;
-  pc.rel_month = 0;
-  pc.rel_year = 0;
+  pc.rel = RELATIVE_TIME_0;
   pc.timespec_seen = false;
   pc.timespec_seen = false;
+  pc.rels_seen = false;
   pc.dates_seen = 0;
   pc.days_seen = 0;
   pc.dates_seen = 0;
   pc.days_seen = 0;
-  pc.rels_seen = 0;
   pc.times_seen = 0;
   pc.local_zones_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
   pc.zones_seen = 0;
 
 #if HAVE_STRUCT_TM_TM_ZONE
@@ -1235,7 +1326,7 @@ get_date (struct timespec *result, char const *p, struct timespec const *now)
 #else
 #if HAVE_TZNAME
   {
 #else
 #if HAVE_TZNAME
   {
-# ifndef tzname
+# if !HAVE_DECL_TZNAME
     extern char *tzname[];
 # endif
     int i;
     extern char *tzname[];
 # endif
     int i;
@@ -1270,9 +1361,8 @@ get_date (struct timespec *result, char const *p, struct timespec const *now)
     *result = pc.seconds;
   else
     {
     *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;
        goto fail;
 
       tm.tm_year = to_year (pc.year) - TM_YEAR_BASE;
@@ -1344,13 +1434,39 @@ get_date (struct timespec *result, char const *p, struct timespec const *now)
       if (pc.days_seen && ! pc.dates_seen)
        {
          tm.tm_mday += ((pc.day_number - tm.tm_wday + 7) % 7
       if (pc.days_seen && ! pc.dates_seen)
        {
          tm.tm_mday += ((pc.day_number - tm.tm_wday + 7) % 7
-                        + 7 * (pc.day_ordinal - (0 < pc.day_ordinal)));
+                        + 7 * (pc.day_ordinal
+                               - (0 < pc.day_ordinal
+                                  && tm.tm_wday != pc.day_number)));
          tm.tm_isdst = -1;
          Start = mktime (&tm);
          if (Start == (time_t) -1)
            goto fail;
        }
 
          tm.tm_isdst = -1;
          Start = mktime (&tm);
          if (Start == (time_t) -1)
            goto fail;
        }
 
+      /* Add relative date.  */
+      if (pc.rel.year | pc.rel.month | pc.rel.day)
+       {
+         int year = tm.tm_year + pc.rel.year;
+         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)))
+           goto fail;
+         tm.tm_year = year;
+         tm.tm_mon = month;
+         tm.tm_mday = day;
+         tm.tm_hour = tm0.tm_hour;
+         tm.tm_min = tm0.tm_min;
+         tm.tm_sec = tm0.tm_sec;
+         tm.tm_isdst = tm0.tm_isdst;
+         Start = mktime (&tm);
+         if (Start == (time_t) -1)
+           goto fail;
+       }
+
+      /* The only "output" of this if-block is an updated Start value,
+        so this block must follow others that clobber Start.  */
       if (pc.zones_seen)
        {
          long int delta = pc.time_zone * 60;
       if (pc.zones_seen)
        {
          long int delta = pc.time_zone * 60;
@@ -1370,24 +1486,6 @@ get_date (struct timespec *result, char const *p, struct timespec const *now)
          Start = t1;
        }
 
          Start = t1;
        }
 
-      /* Add relative date.  */
-      if (pc.rel_year | pc.rel_month | pc.rel_day)
-       {
-         int year = tm.tm_year + pc.rel_year;
-         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)))
-           goto fail;
-         tm.tm_year = year;
-         tm.tm_mon = month;
-         tm.tm_mday = day;
-         Start = mktime (&tm);
-         if (Start == (time_t) -1)
-           goto fail;
-       }
-
       /* Add relative hours, minutes, and seconds.  On hosts that support
         leap seconds, ignore the possibility of leap seconds; e.g.,
         "+ 10 minutes" adds 600 seconds, even if one of them is a
       /* Add relative hours, minutes, and seconds.  On hosts that support
         leap seconds, ignore the possibility of leap seconds; e.g.,
         "+ 10 minutes" adds 600 seconds, even if one of them is a
@@ -1396,27 +1494,29 @@ get_date (struct timespec *result, char const *p, struct timespec const *now)
         must be applied before relative times, and if mktime is applied
         again the time zone will be lost.  */
       {
         must be applied before relative times, and if mktime is applied
         again the time zone will be lost.  */
       {
-       long int sum_ns = pc.seconds.tv_nsec + pc.rel_ns;
+       long int sum_ns = pc.seconds.tv_nsec + pc.rel.ns;
        long int normalized_ns = (sum_ns % BILLION + BILLION) % BILLION;
        time_t t0 = Start;
        long int normalized_ns = (sum_ns % BILLION + BILLION) % BILLION;
        time_t t0 = Start;
-       long int d1 = 60 * 60 * pc.rel_hour;
+       long int d1 = 60 * 60 * pc.rel.hour;
        time_t t1 = t0 + d1;
        time_t t1 = t0 + d1;
-       long int d2 = 60 * pc.rel_minutes;
+       long int d2 = 60 * pc.rel.minutes;
        time_t t2 = t1 + d2;
        time_t t2 = t1 + d2;
-       long int d3 = pc.rel_seconds;
-       time_t t3 = t2 + d3;
+       long_time_t d3 = pc.rel.seconds;
+       long_time_t t3 = t2 + d3;
        long int d4 = (sum_ns - normalized_ns) / BILLION;
        long int d4 = (sum_ns - normalized_ns) / BILLION;
-       time_t t4 = t3 + d4;
+       long_time_t t4 = t3 + d4;
+       time_t t5 = t4;
 
 
-       if ((d1 / (60 * 60) ^ pc.rel_hour)
-           | (d2 / 60 ^ pc.rel_minutes)
+       if ((d1 / (60 * 60) ^ pc.rel.hour)
+           | (d2 / 60 ^ pc.rel.minutes)
            | ((t1 < t0) ^ (d1 < 0))
            | ((t2 < t1) ^ (d2 < 0))
            | ((t3 < t2) ^ (d3 < 0))
            | ((t1 < t0) ^ (d1 < 0))
            | ((t2 < t1) ^ (d2 < 0))
            | ((t3 < t2) ^ (d3 < 0))
-           | ((t4 < t3) ^ (d4 < 0)))
+           | ((t4 < t3) ^ (d4 < 0))
+           | (t5 != t4))
          goto fail;
 
          goto fail;
 
-       result->tv_sec = t4;
+       result->tv_sec = t5;
        result->tv_nsec = normalized_ns;
       }
     }
        result->tv_nsec = normalized_ns;
       }
     }