Import upstream version 1.27
[debian/tar] / gnu / parse-datetime.y
index d77955f40d5dec92b7c8e04d829c06529156f6b3..4dce7fa644c6513f8f48619a852776260a0f4b70 100644 (file)
@@ -1,7 +1,7 @@
 %{
 /* Parse a string into an internal time stamp.
 
-   Copyright (C) 1999-2000, 2002-2011 Free Software Foundation, Inc.
+   Copyright (C) 1999-2000, 2002-2013 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
@@ -82,7 +82,7 @@
    - 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.  */
+   of "digit" even when the host does not conform to POSIX.  */
 #define ISDIGIT(c) ((unsigned int) (c) - '0' <= 9)
 
 /* Shift A right by B bits portably, by dividing A by 2**B and
@@ -113,6 +113,11 @@ typedef long int long_time_t;
 typedef time_t long_time_t;
 #endif
 
+/* Convert a possibly-signed character to an unsigned character.  This is
+   a bit safer than casting to unsigned char, since it catches some type
+   errors that the cast doesn't.  */
+static unsigned char to_uchar (char ch) { return ch; }
+
 /* 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)
@@ -204,7 +209,7 @@ typedef struct
   size_t times_seen;
   size_t zones_seen;
 
-  /* Table of local time zone abbrevations, terminated by a null entry.  */
+  /* Table of local time zone abbreviations, terminated by a null entry.  */
   table local_time_zone_table[3];
 } parser_control;
 
@@ -285,8 +290,8 @@ set_hhmmss (parser_control *pc, long int hour, long int minutes,
 %parse-param { parser_control *pc }
 %lex-param { parser_control *pc }
 
-/* This grammar has 20 shift/reduce conflicts. */
-%expect 20
+/* This grammar has 31 shift/reduce conflicts. */
+%expect 31
 
 %union
 {
@@ -296,7 +301,8 @@ set_hhmmss (parser_control *pc, long int hour, long int minutes,
   relative_time rel;
 }
 
-%token tAGO tDST
+%token <intval> tAGO
+%token tDST
 
 %token tYEAR_UNIT tMONTH_UNIT tHOUR_UNIT tMINUTE_UNIT tSEC_UNIT
 %token <intval> tDAY_UNIT tDAY_SHIFT
@@ -307,7 +313,7 @@ set_hhmmss (parser_control *pc, long int hour, long int minutes,
 %token <textintval> tSNUMBER tUNUMBER
 %token <timespec> tSDECIMAL_NUMBER tUDECIMAL_NUMBER
 
-%type <intval> o_colon_minutes o_merid
+%type <intval> o_colon_minutes
 %type <timespec> seconds signed_seconds unsigned_seconds
 
 %type <rel> relunit relunit_snumber dayshift
@@ -333,7 +339,9 @@ items:
   ;
 
 item:
-    time
+    datetime
+      { pc->times_seen++; pc->dates_seen++; }
+  | time
       { pc->times_seen++; }
   | local_zone
       { pc->local_zones_seen++; }
@@ -348,35 +356,61 @@ item:
   | hybrid
   ;
 
+datetime:
+    iso_8601_datetime
+  ;
+
+iso_8601_datetime:
+    iso_8601_date 'T' iso_8601_time
+  ;
+
 time:
     tUNUMBER tMERIDIAN
       {
         set_hhmmss (pc, $1.value, 0, 0, 0);
         pc->meridian = $2;
       }
-  | tUNUMBER ':' tUNUMBER o_merid
+  | tUNUMBER ':' tUNUMBER tMERIDIAN
       {
         set_hhmmss (pc, $1.value, $3.value, 0, 0);
         pc->meridian = $4;
       }
-  | tUNUMBER ':' tUNUMBER tSNUMBER o_colon_minutes
+  | tUNUMBER ':' tUNUMBER ':' unsigned_seconds tMERIDIAN
       {
-        set_hhmmss (pc, $1.value, $3.value, 0, 0);
+        set_hhmmss (pc, $1.value, $3.value, $5.tv_sec, $5.tv_nsec);
+        pc->meridian = $6;
+      }
+  | iso_8601_time
+  ;
+
+iso_8601_time:
+    tUNUMBER zone_offset
+      {
+        set_hhmmss (pc, $1.value, 0, 0, 0);
         pc->meridian = MER24;
-        pc->zones_seen++;
-        pc->time_zone = time_zone_hhmm (pc, $4, $5);
       }
-  | tUNUMBER ':' tUNUMBER ':' unsigned_seconds o_merid
+  | tUNUMBER ':' tUNUMBER o_zone_offset
       {
-        set_hhmmss (pc, $1.value, $3.value, $5.tv_sec, $5.tv_nsec);
-        pc->meridian = $6;
+        set_hhmmss (pc, $1.value, $3.value, 0, 0);
+        pc->meridian = MER24;
       }
-  | tUNUMBER ':' tUNUMBER ':' unsigned_seconds tSNUMBER o_colon_minutes
+  | tUNUMBER ':' tUNUMBER ':' unsigned_seconds o_zone_offset
       {
         set_hhmmss (pc, $1.value, $3.value, $5.tv_sec, $5.tv_nsec);
         pc->meridian = MER24;
+      }
+  ;
+
+o_zone_offset:
+  /* empty */
+  | zone_offset
+  ;
+
+zone_offset:
+    tSNUMBER o_colon_minutes
+      {
         pc->zones_seen++;
-        pc->time_zone = time_zone_hhmm (pc, $6, $7);
+        pc->time_zone = time_zone_hhmm (pc, $1, $2);
       }
   ;
 
@@ -393,12 +427,19 @@ local_zone:
       }
   ;
 
+/* Note 'T' is a special case, as it is used as the separator in ISO
+   8601 date and time of day representation. */
 zone:
     tZONE
       { pc->time_zone = $1; }
+  | 'T'
+      { pc->time_zone = HOUR(7); }
   | tZONE relunit_snumber
       { pc->time_zone = $1;
         apply_relative_time (pc, $2, 1); }
+  | 'T' relunit_snumber
+      { pc->time_zone = HOUR(7);
+        apply_relative_time (pc, $2, 1); }
   | tZONE tSNUMBER o_colon_minutes
       { pc->time_zone = $1 + time_zone_hhmm (pc, $2, $3); }
   | tDAYZONE
@@ -456,13 +497,6 @@ date:
             pc->year = $5;
           }
       }
-  | tUNUMBER tSNUMBER tSNUMBER
-      {
-        /* ISO 8601 format.  YYYY-MM-DD.  */
-        pc->year = $1;
-        pc->month = -$2.value;
-        pc->day = -$3.value;
-      }
   | tUNUMBER tMONTH tSNUMBER
       {
         /* e.g. 17-JUN-1992.  */
@@ -501,11 +535,22 @@ date:
         pc->month = $2;
         pc->year = $3;
       }
+  | iso_8601_date
+  ;
+
+iso_8601_date:
+    tUNUMBER tSNUMBER tSNUMBER
+      {
+        /* ISO 8601 format.  YYYY-MM-DD.  */
+        pc->year = $1;
+        pc->month = -$2.value;
+        pc->day = -$3.value;
+      }
   ;
 
 rel:
     relunit tAGO
-      { apply_relative_time (pc, $1, -1); }
+      { apply_relative_time (pc, $1, $2); }
   | relunit
       { apply_relative_time (pc, $1, 1); }
   | dayshift
@@ -612,13 +657,6 @@ o_colon_minutes:
       { $$ = $2.value; }
   ;
 
-o_merid:
-    /* empty */
-      { $$ = MER24; }
-  | tMERIDIAN
-      { $$ = $1; }
-  ;
-
 %%
 
 static table const meridian_table[] =
@@ -701,7 +739,8 @@ static table const relative_time_table[] =
   { "TENTH",    tORDINAL,       10 },
   { "ELEVENTH", tORDINAL,       11 },
   { "TWELFTH",  tORDINAL,       12 },
-  { "AGO",      tAGO,            1 },
+  { "AGO",      tAGO,           -1 },
+  { "HENCE",    tAGO,            1 },
   { NULL, 0, 0 }
 };
 
@@ -720,7 +759,7 @@ static table const universal_time_zone_table[] =
    zone abbreviations are ambiguous; e.g. Australians interpret "EST"
    as Eastern time in Australia, not as US Eastern Standard Time.
    You cannot rely on parse_datetime to handle arbitrary time zone
-   abbreviations; use numeric abbreviations like `-0500' instead.  */
+   abbreviations; use numeric abbreviations like "-0500" instead.  */
 static table const time_zone_table[] =
 {
   { "WET",      tZONE,     HOUR ( 0) }, /* Western European */
@@ -773,7 +812,10 @@ static table const time_zone_table[] =
   { NULL, 0, 0 }
 };
 
-/* Military time zone table. */
+/* Military time zone table.
+
+   Note 'T' is a special case, as it is used as the separator in ISO
+   8601 date and time of day representation. */
 static table const military_table[] =
 {
   { "A", tZONE, -HOUR ( 1) },
@@ -794,7 +836,7 @@ static table const military_table[] =
   { "Q", tZONE,  HOUR ( 4) },
   { "R", tZONE,  HOUR ( 5) },
   { "S", tZONE,  HOUR ( 6) },
-  { "T", tZONE,  HOUR ( 7) },
+  { "T", 'T',    0 },
   { "U", tZONE,  HOUR ( 8) },
   { "V", tZONE,  HOUR ( 9) },
   { "W", tZONE,  HOUR (10) },
@@ -868,7 +910,7 @@ to_year (textint textyear)
   return year;
 }
 
-static table const *
+static table const * _GL_ATTRIBUTE_PURE
 lookup_zone (parser_control const *pc, char const *name)
 {
   table const *tp;
@@ -1119,7 +1161,7 @@ yylex (YYSTYPE *lvalp, parser_control *pc)
 
           do
             {
-              if (p < buff + sizeof buff - 1)
+              if (p - buff < sizeof buff - 1)
                 *p++ = c;
               c = *++pc->input;
             }
@@ -1134,7 +1176,8 @@ yylex (YYSTYPE *lvalp, parser_control *pc)
         }
 
       if (c != '(')
-        return *pc->input++;
+        return to_uchar (*pc->input++);
+
       count = 0;
       do
         {
@@ -1357,7 +1400,7 @@ parse_datetime (struct timespec *result, char const *p,
       && ! strcmp (pc.local_time_zone_table[0].name,
                    pc.local_time_zone_table[1].name))
     {
-      /* This locale uses the same abbrevation for standard and
+      /* This locale uses the same abbreviation for standard and
          daylight times.  So if we see that abbreviation, we don't
          know whether it's daylight time.  */
       pc.local_time_zone_table[0].value = -1;
@@ -1429,7 +1472,7 @@ parse_datetime (struct timespec *result, char const *p,
                           + sizeof pc.time_zone * CHAR_BIT / 3];
               if (!tz_was_altered)
                 tz0 = get_tz (tz0buf);
-              sprintf (tz1buf, "XXX%s%ld:%02d", "-" + (time_zone < 0),
+              sprintf (tz1buf, "XXX%s%ld:%02d", &"-"[time_zone < 0],
                        abs_time_zone_hour, abs_time_zone_min);
               if (setenv ("TZ", tz1buf, 1) != 0)
                 goto fail;