document timefunc api
[libi2ncommon] / src / timefunc.cpp
index b84acb8..0883055 100644 (file)
@@ -24,6 +24,7 @@ on this file might be covered by the GNU General Public License.
  *
  */
 
+#include <cstdio>
 #include <errno.h>
 #include <string>
 #include <sstream>
@@ -781,45 +782,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; }
@@ -833,7 +868,7 @@ namespace {
  * @param tm      Time to format as broken-down \c struct tm.
  * @param date    Include the day part ([-]YYYY-MM-DD).
  * @param time    Include the time part (hh:mm:ss).
- * @param tz      Include the timezone ([±]ZZZZ); only heeded if
+ * @param tz      Include the timezone ([±]ZZZZ); only needed if
  *                \c time is requested as well.
  *
  * @return        The formatted timestamp.
@@ -842,9 +877,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));
 
@@ -855,12 +890,15 @@ std::string format_iso8601 (const struct tm &tm, const bool date,
     }
 
     /*
-     * The sign is *always* handled above so the formatted string her
+     * The sign is *always* handled above so the formatted string here
      * is always one character shorter.
-     * */
-    const size_t n = strftime (start, iso8601_bufsize-1, format, &tmp);
+     */
+    if (strftime (start, iso8601::bufsize-1, format, &tmp) == 0)
+    {
+        return std::string ();
+    }
 
-    buf [n+1] = '\0';
+    buf [iso8601::bufsize-1] = '\0'; /* Just in case. */
 
     return std::string (buf);
 }
@@ -906,7 +944,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;
 
@@ -948,6 +986,32 @@ scan_iso8601 (const char *s,
     return tm;
 }
 
+/**
+ * @brief         Format a \c struct timespec in the schema established by
+ *                time(1): “3m14.159s”.
+ *
+ * @param ts      The time spec to format.
+ *
+ * @return        \c boost:none in case of error during formatting, an optional
+ *                \c std::string otherwise.
+ */
+boost::optional<std::string>
+format_min_sec_msec (const struct timespec &ts)
+{
+    char ms [4] = { '\0', '\0', '\0', '\0' };
+
+    if (snprintf (ms, 4, "%.3ld", ts.tv_nsec / 1000000) < 0) {
+        return boost::none;
+    }
+
+    const time_t min = ts.tv_sec / 60;
+    const time_t sec = ts.tv_sec - min * 60;
+
+    return I2n::to_string (min) + "m"
+         + I2n::to_string (sec) + "."
+         + ms + "s"
+         ;
+}
 
 namespace I2n {
 
@@ -955,6 +1019,18 @@ namespace clock {
 
     namespace {
 
+        /**
+         * @brief         <b>For internal use only</b>. Translates clock
+         *                specification flags to kernel clock types.
+         *
+         * @param id      Master clock id: \c mono, \c real, \c boot, or \c
+         *                cpu.
+         * @param var     Variant of clock if appropriate: \c raw, \c exact, \c
+         *                process, or \c thread. Use \c dflt for the base
+         *                variant.
+         *
+         * @return        The clock id for using with kernel APIs.
+         */
         static inline clockid_t
         clockid_of_flags (const enum type::id      id,
                           const enum type::variant var) NOEXCEPT
@@ -991,9 +1067,7 @@ namespace clock {
                 }
 
                 case type::boot: {
-                    if (var & type::exact) {
-                        cid = CLOCK_BOOTTIME;
-                    }
+                    cid = CLOCK_BOOTTIME;
                     break;
                 }
 
@@ -1022,15 +1096,30 @@ namespace clock {
         , err     (0)
     { }
 
+    /*
+     * Ctor from *struct tm*. On 32 bit systems the conversion to *time_t* will
+     * fail with years outside the range from epoch to 2038.
+     */
     Time::Time (const struct tm          &tm,
                 const enum type::id       id,
-                const enum type::variant  var) NOEXCEPT
+                const enum type::variant  var)
     {
         struct tm tmp_tm; /* dummy for mktime(3) */
         Time   tmp_time;
 
         memcpy (&tmp_tm, &tm, sizeof (tmp_tm));
-        tmp_time = Time (mktime (&tmp_tm), 0l, id, var);
+
+        errno = 0;
+        const time_t t = mktime (&tmp_tm);
+        if (t == - 1) { /* Glibc does not set errno on out-of-range here! */
+            const char *datestr = asctime (&tm);
+            throw conversion_error (errno,
+                                    std::string ("mktime: from struct tm {")
+                                    + std::string (datestr, 0, strlen(datestr)-1)
+                                    + "}");
+        }
+
+        tmp_time = Time (t, 0l, id, var);
 
         this->swap (tmp_time);
     }
@@ -1054,6 +1143,14 @@ namespace clock {
         return *this;
     }
 
+    /*
+     * @note    This operator is an up-assignment from a type containing less
+     *          information than the structure assigned from. Since the
+     *          operator can only be two valued we must normalize the remaining
+     *          fields to the default clock. When assigning from non-default
+     *          clocks, use the appropriate constructor and pass it the desired
+     *          id and variant so as to assign the result.
+     */
     Time &
     Time::operator= (struct timespec ts) NOEXCEPT
     {
@@ -1112,7 +1209,7 @@ namespace clock {
     }
 
     Time &
-    Time::scale (const time_t factor) NOEXCEPT
+    Time::scale (const int64_t factor) NOEXCEPT
     {
         this->value.tv_sec  *= factor;
         this->value.tv_nsec *= factor;
@@ -1122,6 +1219,32 @@ namespace clock {
         return *this;
     }
 
+    /*
+     * Below division code purposely does not attempt to handle divide-
+     * by-zero just as any other C++ division function does. It is up to
+     * the caller to ensure that the divisor is not zero.
+     */
+    Time &
+    Time::divide (const int64_t divisor) NOEXCEPT
+    {
+        const long   sec  = static_cast<long>    (this->value.tv_sec );
+        int64_t      nsec = static_cast<int64_t> (this->value.tv_nsec);
+        const ldiv_t div  = ldiv (sec, divisor);
+
+        if (div.rem != 0) {
+            nsec += div.rem * TIME_CONST_FACTOR_NANO;
+        }
+
+        nsec /= divisor;
+
+        this->value.tv_sec  = static_cast<time_t> (div.quot);
+        this->value.tv_nsec = static_cast<long>   (nsec);
+
+        this->carry_nsec ();
+
+        return *this;
+    }
+
     boost::optional<std::string>
     Time::format_iso8601 (const bool utc,
                           const bool date,
@@ -1138,18 +1261,18 @@ namespace clock {
         return ::format_iso8601 (tm, date, time, tz);
     }
 
-    boost::optional<std::string>
+    std::string
     Time::make_nice_time (void) const
     {
         /* XXX the cast below results in loss of precision with 64 bit time_t! */
         return ::make_nice_time (static_cast<int> (this->value.tv_sec));
     }
 
-    boost::optional<std::string>
+    std::string
     Time::format_full_time (void) const
     { return ::format_full_time (this->value.tv_sec); }
 
-    boost::optional<std::string>
+    std::string
     Time::format_date (void) const
     { return ::format_date (this->value.tv_sec); }
 
@@ -1205,7 +1328,12 @@ namespace clock {
             return boost::none;
         }
 
-        return Time (*tm, id, var);
+        try {
+            return Time (*tm, id, var);
+        }
+        catch (conversion_error &_unused) { }
+
+        return boost::none;
     }
 
 } /* [namespace clock] */