Subject: bug#14097: [PATCH] Add support for ISO 8601 basic format

From: Mihai Capotă <[email-masked]>
Date: Sat, 30 Mar 2013 20:18:13 +0100

The parser now accepts the basic format for combined date and time
representations, which ommits the date and time separators, "-" and ":".

See bug 23767 for GNU coreutils, <https://savannah.gnu.org/bugs/?23767>.

* lib/parse-datetime.y: Parse combined date and time representations in
ISO 8601 basic format.
(set_hhmmss_iso_8601_basic_time) New function.
* tests/test-parse-datetime.c: Add new tests for combined date and time
representations in ISO 8601 basic format.
---
 lib/parse-datetime.y        |   78 +++++++++++++++++++++++++++++++++++++++++--
 tests/test-parse-datetime.c |   61 +++++++++++++++++++++++++++++++++
 2 files changed, 137 insertions(+), 2 deletions(-)

diff --git a/lib/parse-datetime.y b/lib/parse-datetime.y
index 77d95b7..20bf1ac 100644
--- a/lib/parse-datetime.y
+++ b/lib/parse-datetime.y
@@ -282,6 +282,60 @@ set_hhmmss (parser_control *pc, long int hour, long int minutes,
   pc->seconds.tv_nsec = nsec;
 }
 
+/* Set PC-> hour, minutes, seconds and nanoseconds members from ISO 8601 basic
+   time.  */
+static void
+set_hhmmss_iso_8601_basic_time (parser_control *pc, long int integer_part,
+                                long int fractional_part)
+{
+  if (integer_part / 1000000 > 0)
+    {
+      /* not ISO 8601 time, forcing mktime error */
+      pc->hour = 90;
+      pc->minutes = 0;
+      pc->seconds.tv_sec = 0;
+      pc->seconds.tv_nsec = 0;
+    }
+  else
+    {
+      pc->hour = integer_part / 10000;
+      if (pc->hour > 0) /* HHMMSS */
+        {
+          pc->minutes = (integer_part % 10000) / 100;
+          pc->seconds.tv_sec = integer_part % 100;
+          pc->seconds.tv_nsec = fractional_part;
+        }
+      else
+        {
+          if (fractional_part != 0)
+            {
+              /* FIXME support fractional part for minutes and hours */
+              pc->hour = 90;
+              pc->minutes = 0;
+              pc->seconds.tv_sec = 0;
+              pc->seconds.tv_nsec = 0;
+            }
+          else
+            {
+              pc->hour = integer_part / 100;
+              if (pc->hour > 0) /* HHMM */
+                {
+                  pc->minutes = integer_part % 100;
+                  pc->seconds.tv_sec = 0;
+                  pc->seconds.tv_nsec = 0;
+                }
+              else /* HH */
+                {
+                  pc->hour = integer_part;
+                  pc->minutes = 0;
+                  pc->seconds.tv_sec = 0;
+                  pc->seconds.tv_nsec = 0;
+                }
+            }
+        }
+    }
+}
+
 %}
 
 /* We want a reentrant parser, even if the TZ manipulation and the calls to
@@ -290,8 +344,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 31 shift/reduce conflicts. */
-%expect 31
+/* This grammar has 34 shift/reduce conflicts. */
+%expect 34
 
 %union
 {
@@ -358,12 +412,18 @@ item:
 
 datetime:
     iso_8601_datetime
+  | iso_8601_basic_datetime
   ;
 
 iso_8601_datetime:
     iso_8601_date 'T' iso_8601_time
   ;
 
+iso_8601_basic_datetime:
+    number 'T' iso_8601_basic_time
+      { pc->dates_seen--; } /* already incremented in digits_to_date_time */
+  ;
+
 time:
     tUNUMBER tMERIDIAN
       {
@@ -401,6 +461,20 @@ iso_8601_time:
       }
   ;
 
+iso_8601_basic_time:
+    tUNUMBER o_zone_offset
+      {
+        set_hhmmss_iso_8601_basic_time (pc, $1.value, 0);
+        pc->meridian = MER24;
+      }
+  | tUDECIMAL_NUMBER o_zone_offset
+      {
+        /* FIXME avoid time_t to long int cast */
+        set_hhmmss_iso_8601_basic_time (pc, (long int)$1.tv_sec, $1.tv_nsec);
+        pc->meridian = MER24;
+      }
+  ;
+
 o_zone_offset:
   /* empty */
   | zone_offset
diff --git a/tests/test-parse-datetime.c b/tests/test-parse-datetime.c
index 7eba9ad..c620009 100644
--- a/tests/test-parse-datetime.c
+++ b/tests/test-parse-datetime.c
@@ -216,6 +216,67 @@ main (int argc _GL_UNUSED, char **argv)
           && expected.tv_nsec == result.tv_nsec);

+  /* ISO 8601 basic date and time of day representation,
+     'T' separator, local time zone */
+  p = "20110501T115518";
+  expected.tv_sec = ref_time - gmtoff;
+  expected.tv_nsec = 0;
+  ASSERT (parse_datetime (&result, p, 0));
+  LOG (p, expected, result);
+  ASSERT (expected.tv_sec == result.tv_sec
+          && expected.tv_nsec == result.tv_nsec);
+
+
+  /* ISO 8601 basic date and time of day representation,
+     'T' separator, UTC */
+  p = "20110501T115518Z";
+  expected.tv_sec = ref_time;
+  expected.tv_nsec = 0;
+  ASSERT (parse_datetime (&result, p, 0));
+  LOG (p, expected, result);
+  ASSERT (expected.tv_sec == result.tv_sec
+          && expected.tv_nsec == result.tv_nsec);
+
+
+  /* ISO 8601 basic date and time of day representation,
+     'T' separator, w/UTC offset */
+  p = "20110501T115518-0700";
+  expected.tv_sec = 1304276118;
+  expected.tv_nsec = 0;
+  ASSERT (parse_datetime (&result, p, 0));
+  LOG (p, expected, result);
+  ASSERT (expected.tv_sec == result.tv_sec
+          && expected.tv_nsec == result.tv_nsec);
+
+
+  /* ISO 8601 basic date and time of day representation,
+     'T' separator, w/hour only UTC offset */
+  p = "20110501T115518-07";
+  expected.tv_sec = 1304276118;
+  expected.tv_nsec = 0;
+  ASSERT (parse_datetime (&result, p, 0));
+  LOG (p, expected, result);
+  ASSERT (expected.tv_sec == result.tv_sec
+          && expected.tv_nsec == result.tv_nsec);
+
+
+  /* ISO 8601 basic date and time of day representation,
+     'T' separator, w/hour only UTC offset, with ns */
+  p = "20110501T115518,123456789-07";
+  expected.tv_sec = 1304276118;
+  expected.tv_nsec = 123456789;
+  ASSERT (parse_datetime (&result, p, 0));
+  LOG (p, expected, result);
+  ASSERT (expected.tv_sec == result.tv_sec
+          && expected.tv_nsec == result.tv_nsec);
+
+
+  /* Invalid ISO 8601 basic date and time of day representation,
+     too many digits for time */
+  p = "20110501T11551800";
+  ASSERT (!parse_datetime (&result, p, 0));
+
+
   now.tv_sec = 4711;
   now.tv_nsec = 1267;
   p = "now";
-- 
1.7.9.5