use separate directives for time formatting and scanning
authorPhilipp Gesang <philipp.gesang@intra2net.com>
Wed, 31 Jan 2018 13:32:59 +0000 (14:32 +0100)
committerThomas Jarosch <thomas.jarosch@intra2net.com>
Wed, 27 Mar 2019 09:31:38 +0000 (10:31 +0100)
Glibc 2.17 has trouble with length modifiers in format directives
which prevents us from using the same strings both ways. This is
fixed in later versions (by 2.25 definitely, maybe earlier) but
for the time being we have to use different format strings
depending on the action.

src/timefunc.cpp

index 8fe5000..e360bb4 100644 (file)
@@ -781,45 +781,79 @@ bool realtime_clock_gettime(long int& seconds, long int& nano_seconds)
  *      - strptime(3) will not parse the leading dash with that format
  *        but apart from that it works well.
  */
-static const char *const iso8601_fmt_d    = "%4Y-%m-%d";
-static const char *const iso8601_fmt_t    = "%T";
-static const char *const iso8601_fmt_tz   = "%TZ%z";
-static const char *const iso8601_fmt_dt   = "%4Y-%m-%dT%T";
-static const char *const iso8601_fmt_dtz  = "%4Y-%m-%dT%TZ%z";
-
-static inline const char *
-pick_iso8601_fmt (const bool date, const bool time, const bool tz)
-{
-    const char *format = NULL;
 
-    if (date) {
-        if (time) {
-            if (tz) {
-                format = iso8601_fmt_dtz;
+namespace iso8601 {
+
+    /*
+     * max: -YYYYYYYYYY-MM-DDThh:mm:ssZ+zzzz ⇒ 32
+     * That is assuming the year in broken down time is an int we
+     * need to reserve ten decimal places.
+     */
+//  static_assert (sizeof (((struct tm *)NULL)->tm_year) == 4);
+    static const size_t bufsize = 33;
+
+    enum kind {
+        d    = 0,
+        t    = 1,
+        tz   = 2,
+        dt   = 3,
+        dtz  = 4,
+        ISO8601_SIZE = 5,
+    };
+
+    /*
+     * Unfortunately the glibc strptime(3) on the Intranator trips over
+     * the length specifier in field descriptors so we can’t reuse the
+     * formatters here. This problem is fixed in newer glibc. For the time
+     * being we keep two tables of formatters and choose the appropriate
+     * at runtime.
+     */
+
+    static const char *const formatter [ISO8601_SIZE] =
+    { /* [iso8601::d  ] = */ "%4Y-%m-%d",
+      /* [iso8601::t  ] = */ "%T",
+      /* [iso8601::tz ] = */ "%TZ%z",
+      /* [iso8601::dt ] = */ "%4Y-%m-%dT%T",
+      /* [iso8601::dtz] = */ "%4Y-%m-%dT%TZ%z",
+    };
+
+    static const char *const scanner [ISO8601_SIZE] =
+    { /* [iso8601::d  ] = */ "%Y-%m-%d",
+      /* [iso8601::t  ] = */ "%T",
+      /* [iso8601::tz ] = */ "%TZ%z",
+      /* [iso8601::dt ] = */ "%Y-%m-%dT%T",
+      /* [iso8601::dtz] = */ "%Y-%m-%dT%TZ%z",
+    };
+
+    static inline const char *
+    pick_fmt (const bool date, const bool time, const bool tz, const bool scan=false)
+    {
+        const char *const  *table  = scan ? iso8601::scanner : iso8601::formatter;
+        enum iso8601::kind  format = iso8601::dtz;
+
+        if (date) {
+            if (time) {
+                if (tz) {
+                    format = iso8601::dtz;
+                } else {
+                    format = iso8601::dt;
+                }
             } else {
-                format = iso8601_fmt_dt;
+                format = iso8601::d;
             }
+        } else if (time && tz) {
+            format = iso8601::tz;
         } else {
-            format = iso8601_fmt_d;
+            format = iso8601::t; /* default to %T */
         }
-    } else if (time && tz) {
-        format = iso8601_fmt_tz;
-    } else {
-        format = iso8601_fmt_t; /* default to %T */
+
+        return table [format];
     }
 
-    return format;
-}
+} /* [iso8601] */
 
-namespace {
 
-    /*
-     * max: -YYYYYYYYYY-MM-DDThh:mm:ssZ+zzzz ⇒ 32
-     * That is assuming the year in broken down time is an int we
-     * need to reserve ten decimal places.
-     */
-//  static_assert (sizeof (((struct tm *)NULL)->tm_year) == 4);
-    static const size_t iso8601_bufsize = 33;
+namespace {
 
     static inline int flip_tm_year (const int y)
     { return (y + 1900) * -1 - 1900; }
@@ -842,9 +876,9 @@ std::string format_iso8601 (const struct tm &tm, const bool date,
                             const bool time, const bool tz)
 {
     struct tm tmp;
-    char buf [iso8601_bufsize] = { 0 };
+    char buf [iso8601::bufsize] = { 0 };
     char *start = &buf [0];
-    const char *format = pick_iso8601_fmt (date, time, tz);
+    const char *format = iso8601::pick_fmt (date, time, tz);
 
     memcpy (&tmp, &tm, sizeof (tmp));
 
@@ -858,7 +892,7 @@ std::string format_iso8601 (const struct tm &tm, const bool date,
      * The sign is *always* handled above so the formatted string her
      * is always one character shorter.
      * */
-    const size_t n = strftime (start, iso8601_bufsize-1, format, &tmp);
+    const size_t n = strftime (start, iso8601::bufsize-1, format, &tmp);
 
     buf [n+1] = '\0';
 
@@ -906,7 +940,7 @@ scan_iso8601 (const char *s,
               const bool date, const bool time, const bool tz) NOEXCEPT
 {
     struct tm tm;
-    const char *format = pick_iso8601_fmt (date, time, tz);
+    const char *format = iso8601::pick_fmt (date, time, tz, true);
     const char *start = s;
     bool negyear = false;