use separate directives for time formatting and scanning
[libi2ncommon] / 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;