Merge branch 'timefunc-new'
authorThomas Jarosch <thomas.jarosch@intra2net.com>
Fri, 5 Apr 2019 14:23:44 +0000 (16:23 +0200)
committerThomas Jarosch <thomas.jarosch@intra2net.com>
Fri, 5 Apr 2019 14:23:44 +0000 (16:23 +0200)
src/timefunc.cpp
src/timefunc.hxx
test/test_timefunc.cpp

index e8b1917..a4d38b8 100644 (file)
@@ -24,6 +24,8 @@ on this file might be covered by the GNU General Public License.
  *
  */
 
+#include <cstdio>
+#include <errno.h>
 #include <string>
 #include <sstream>
 #include <iostream>
@@ -771,3 +773,626 @@ bool realtime_clock_gettime(long int& seconds, long int& nano_seconds)
 } // eo realtime_clock_gettime(long int&,long int&)
 
 
+/*
+ * There is a discrepancy of one input character
+ * due to the lack of sign handling in strptime(3):
+ *
+ *      - strftime(3) needs the year specified as %5Y to account for the
+ *        leading dash;
+ *      - strptime(3) will not parse the leading dash with that format
+ *        but apart from that it works well.
+ */
+
+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::d;
+            }
+        } else if (time && tz) {
+            format = iso8601::tz;
+        } else {
+            format = iso8601::t; /* default to %T */
+        }
+
+        return table [format];
+    }
+
+} /* [iso8601] */
+
+
+namespace {
+
+    static inline int flip_tm_year (const int y)
+    { return (y + 1900) * -1 - 1900; }
+} /* [namespace] */
+
+/**
+ * @brief         Format a time structure according to ISO-8601, e. g.
+ *                “2018-01-09T10:40:00Z+0100”; see \c strftime(3) for
+ *                the details.
+ *
+ * @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 needed if
+ *                \c time is requested as well.
+ *
+ * @return        The formatted timestamp.
+ */
+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 *start = &buf [0];
+    const char *format = iso8601::pick_fmt (date, time, tz);
+
+    memcpy (&tmp, &tm, sizeof (tmp));
+
+    if (tmp.tm_year < -1900) { /* negative year */
+        *start = '-';
+        start++;
+        tmp.tm_year = flip_tm_year (tmp.tm_year);
+    }
+
+    /*
+     * The sign is *always* handled above so the formatted string here
+     * is always one character shorter.
+     */
+    if (strftime (start, iso8601::bufsize-1, format, &tmp) == 0)
+    {
+        return std::string ();
+    }
+
+    buf [iso8601::bufsize-1] = '\0'; /* Just in case. */
+
+    return std::string (buf);
+}
+
+typedef struct tm * (*time_breakdown_fn) (const time_t *, struct tm *);
+
+/**
+ * @brief         Format a UNIX timestamp according to ISO-8601. Converts
+ *                to broken down time first.
+ *
+ * @param t       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
+ *                \c time is requested as well.
+ *
+ * @return        The formatted timestamp.
+ */
+std::string format_iso8601 (time_t t, const bool utc, const bool date,
+                            const bool time, const bool tz)
+{
+    time_breakdown_fn breakdown = utc ? gmtime_r : localtime_r;
+    struct tm tm;
+
+    errno = 0;
+    if (breakdown (&t, &tm) == NULL) {
+        return std::string ("error analyzing timestamp: ") + strerror (errno);
+    }
+
+    return format_iso8601 (tm, date, time, tz);
+}
+
+/**
+ * @brief         Read a ISO-8601 formatted date stamp into broken down time.
+ *
+ * @param s       String containing the timestamp.
+ *
+ * @return        \c boost::none if the input string was \c NULL or malformed,
+ *                an optional \c struct tm with the extracted values otherwise.
+ */
+boost::optional<struct tm>
+scan_iso8601 (const char *s,
+              const bool date, const bool time, const bool tz) NOEXCEPT
+{
+    struct tm tm;
+    const char *format = iso8601::pick_fmt (date, time, tz, true);
+    const char *start = s;
+    bool negyear = false;
+
+    if (s == NULL) {
+        return boost::none;
+    }
+
+    switch (s [0]) {
+        case '\0': {
+            return boost::none;
+            break;
+        }
+        /*
+         * Contrary to what the man page indicates, strptime(3) is *not*
+         * the inverse operation of strftime(3)! The later correctly formats
+         * negative year numbers with the %F modifier wheres the former trips
+         * over the sign character.
+         */
+        case '-': {
+            negyear = true;
+            start++;
+            break;
+        }
+        default: {
+            break;
+        }
+    }
+
+    memset (&tm, 0, sizeof (tm));
+
+    if (strptime (start, format, &tm) == NULL) {
+        return boost::none;
+    }
+
+    if (negyear) {
+        tm.tm_year = flip_tm_year (tm.tm_year);
+    }
+
+    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 {
+
+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
+        {
+            clockid_t cid = CLOCK_MONOTONIC_COARSE;
+
+            switch (id) {
+
+                default:
+                case type::mono: {
+                    switch (var) {
+                        default: {
+                            break;
+                        }
+                        case type::raw: {
+                            cid = CLOCK_MONOTONIC_RAW;
+                            break;
+                        }
+                        case type::exact: {
+                            cid = CLOCK_MONOTONIC;
+                            break;
+                        }
+                    }
+                    break;
+                }
+
+                case type::real: {
+                    if (var == type::exact) {
+                        cid = CLOCK_REALTIME;
+                    } else {
+                        cid = CLOCK_REALTIME_COARSE;
+                    }
+                    break;
+                }
+
+                case type::boot: {
+                    cid = CLOCK_BOOTTIME;
+                    break;
+                }
+
+                case type::cpu: {
+                    if (var == type::thread) {
+                        cid = CLOCK_THREAD_CPUTIME_ID;
+                    } else {
+                        cid = CLOCK_PROCESS_CPUTIME_ID;
+                    }
+                    break;
+                }
+            } /* [switch id] */
+
+            return cid;
+        }
+
+        static const struct timespec zero_time = { 0, 0 };
+
+    } /* [namespace] */
+
+    Time::Time (const enum type::id id,
+                const enum type::variant var) NOEXCEPT
+        : value   (zero_time)
+        , id      (id)
+        , variant (var)
+        , 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)
+    {
+        struct tm tmp_tm; /* dummy for mktime(3) */
+        Time   tmp_time;
+
+        memcpy (&tmp_tm, &tm, sizeof (tmp_tm));
+
+        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);
+    }
+
+    int64_t
+    Time::as_nanosec (void) const NOEXCEPT
+    {
+        return int64_t (this->value.tv_sec) * TIME_CONST_FACTOR_NANO
+             + this->value.tv_nsec;
+    }
+
+    long
+    Time::as_nanosec_L (void) const NOEXCEPT /* likely to overflow */
+    { return static_cast<long>(this->as_nanosec ()); }
+
+    Time &
+    Time::operator= (Time t2) NOEXCEPT
+    {
+        this->swap (t2);
+
+        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
+    {
+        std::swap (this->value, ts);
+        this->id      = clock::type::mono;
+        this->variant = clock::type::dflt;
+        this->err     = 0;
+
+        return *this;
+    }
+
+    void
+    Time::unset (void) NOEXCEPT
+    { this->value = zero_time; }
+
+    bool
+    Time::set (void) NOEXCEPT
+    {
+        struct timespec now;
+
+        errno = 0;
+        if (clock_gettime (clockid_of_flags (this->id, this->variant), &now)
+            == -1)
+        {
+            this->err = errno;
+            this->unset ();
+
+            return false;
+        }
+        this->err   = 0;
+        this->value = now;
+
+        return true;
+    }
+
+    Time &
+    Time::add (const time_t sec, const long nsec) NOEXCEPT
+    {
+        this->value.tv_sec  += sec;
+        this->value.tv_nsec += nsec;
+
+        this->carry_nsec ();
+
+        return *this;
+    }
+
+    Time &
+    Time::subtract (const time_t sec, const long nsec) NOEXCEPT
+    {
+        this->value.tv_sec  -= sec;
+        this->value.tv_nsec -= nsec;
+
+        this->carry_nsec ();
+
+        return *this;
+    }
+
+    Time &
+    Time::scale (const int64_t factor) NOEXCEPT
+    {
+        this->value.tv_sec  *= factor;
+        this->value.tv_nsec *= factor;
+
+        this->carry_nsec ();
+
+        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;
+    }
+
+    /**
+     * @brief         Format timestamp according to the ISO standard rules.
+     *
+     * @param utc     Whether to normalize the timestamp to UTC or local time.
+     * @param date    Whether to include the date (%F).
+     * @param time    Whether to include the time (%T).
+     * @param tz      Whether to include the UTC offset (%z).
+     *
+     * @return        \c none if the formatting operation failed, the
+     *                formatted timestamp otherwise.
+     *
+     * @note          The standard allows for extending the format using
+     *                a fractional component. However, this is subject to
+     *                local conventions so we don’t support it. For more
+     *                than seconds granularity use a better suited format
+     *                like LDAP Generalized time instead.
+     */
+    boost::optional<std::string>
+    Time::format_iso8601 (const bool utc,
+                          const bool date,
+                          const bool time,
+                          const bool tz) const
+    {
+        time_breakdown_fn breakdown = utc ? gmtime_r : localtime_r;
+        struct tm tm;
+
+        if (breakdown (&this->value.tv_sec, &tm) == NULL) {
+            return boost::none;
+        }
+
+        return ::format_iso8601 (tm, date, time, tz);
+    }
+
+    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));
+    }
+
+    std::string
+    Time::format_full_time (void) const
+    { return ::format_full_time (this->value.tv_sec); }
+
+    std::string
+    Time::format_date (void) const
+    { return ::format_date (this->value.tv_sec); }
+
+    /**
+     * @brief         Obtain the current time wrt. the given
+     *                clock variants.
+     *
+     * @param id      Clock id.
+     * @param var     Clock variant.
+     *
+     * @return        \c none if the underlying \c clock_gettime() operation
+     *                failed, a fully initialized \c struct Time otherwise.
+     */
+    boost::optional<Time>
+    now (const enum type::id id, const enum type::variant var) NOEXCEPT
+    {
+        Time ret (id, var);
+
+        if (!ret.set ()) {
+            return boost::none;
+        }
+
+        return ret;
+    }
+
+    Time
+    zero (const enum type::id id, const enum type::variant var) NOEXCEPT
+    { return Time (id, var); }
+
+    /**
+     * @brief         Standard three-way comparison for \c struct Time
+     *                relying on strict total ordering.
+     *
+     * @param t1      Comparand.
+     * @param t2      Comparand.
+     *
+     * @return        -1, 0, 1 depending on whether t1 is less-than, equal,
+     *                or greater than t2.
+     *
+     * @note          This should be used to implement the spaceship operator
+     *                (P0515R0) when we get a new compiler.
+     */
+    int
+    compare (const Time &t1, const Time &t2) NOEXCEPT
+    {
+        if (t1.value.tv_sec < t2.value.tv_sec) {
+            return -1;
+        }
+
+        if (t1.value.tv_sec > t2.value.tv_sec) {
+            return 1;
+        }
+
+        if (t1.value.tv_nsec < t2.value.tv_nsec) {
+            return -1;
+        }
+
+        if (t1.value.tv_nsec > t2.value.tv_nsec) {
+            return 1;
+        }
+
+        return 0;
+    }
+
+    /**
+     * @brief         Interpret string as timestamp according to the ISO
+     *                standard rules.
+     *
+     *                This is the inverse operation of \c format_iso8601().
+     *
+     * @param s       Input string to read. The entire string is interpreted
+     *                and it must not contain any trailing data.
+     * @param date    Whether to parse the date (%F).
+     * @param time    Whether to parse the time (%T).
+     * @param tz      Whether to parse the UTC offset (%z).
+     * @param id      Clock id to assign the result.
+     * @param var     Clock variant to assign the result.
+     *
+     * @return        \c none if the input could not be parsed according to
+     *                ISO rules, a \c struct Time otherwise.
+     */
+    boost::optional<Time>
+    time_of_iso8601 (const std::string        &s,
+                     const bool                date,
+                     const bool                time,
+                     const bool                tz,
+                     const enum type::id       id,
+                     const enum type::variant  var) NOEXCEPT
+    {
+        boost::optional<struct tm> tm = scan_iso8601 (s, date, time, tz);
+
+        if (!tm) {
+            return boost::none;
+        }
+
+        try {
+            return Time (*tm, id, var);
+        }
+        catch (conversion_error &_unused) { }
+
+        return boost::none;
+    }
+
+} /* [namespace clock] */
+
+} /* [namespace I2n] */
index 18b0314..3cb34ad 100644 (file)
@@ -20,17 +20,34 @@ on this file might be covered by the GNU General Public License.
 /** @file
  * @brief time related functions.
  *
- * @copyright Copyright &copy; 2001-2008 by Intra2net AG
+ * @copyright Copyright &copy; 2001-2018 by Intra2net AG
  */
 
 #ifndef __TIMEFUNC_HXX
 #define __TIMEFUNC_HXX
 
-#include <string>
+#include <climits>
+#include <errno.h>
 #include <list>
+#include <string>
+#include <cstdlib>
+
+#include <boost/optional.hpp>
 
 #include <week.hpp>
 
+#include "stringfunc.hxx"
+
+#if __cplusplus >= 201103
+# define CONSTEXPR constexpr
+# define NOEXCEPT  noexcept
+#else
+# define CONSTEXPR
+# define NOEXCEPT
+#endif
+
+#define TIME_CONST_FACTOR_NANO (1000L * 1000 * 1000)
+
 double prec_time(void);
 
 time_t date_to_seconds(const std::string &date);
@@ -38,6 +55,36 @@ time_t date_to_seconds(const std::string &date);
 std::string make_nice_time(int seconds);
 std::string format_full_time(time_t seconds);
 std::string format_date(time_t seconds);
+std::string format_iso8601(const struct tm &tm, const bool date=true,
+                           const bool time=true, const bool tz=true);
+std::string format_iso8601(time_t t, const bool utc=true,
+                           const bool date=true, const bool time=true,
+                           const bool tz=true);
+
+inline std::string format_iso8601(const struct timespec &ts, const bool utc=true,
+                                  const bool date=true, const bool time=true,
+                                  const bool tz=true)
+{ return format_iso8601 (ts.tv_sec, utc, date, time, tz); }
+
+inline std::string format_sec_msec (const struct timespec &ts)
+{
+    return I2n::to_string (ts.tv_sec) + "s "
+         + I2n::to_string (ts.tv_nsec / 1000000) + "ms"
+         ;
+}
+
+boost::optional<std::string> format_min_sec_msec (const struct timespec &ts);
+
+boost::optional<struct tm> scan_iso8601 (const char *s,
+                                         const bool date=true, const bool time=true,
+                                         const bool tz=true) NOEXCEPT;
+inline boost::optional<struct tm> scan_iso8601 (const std::string &s,
+                                                const bool date=true,
+                                                const bool time=true,
+                                                const bool tz=true) NOEXCEPT
+{ return scan_iso8601 (s.c_str (), date, time, tz); }
+
+
 void seconds_to_hour_minute(int seconds, int *hour, int *minute);
 void split_daysec(int daysec, int *outhours=NULL, int *outminutes=NULL, int *outseconds=NULL);
 std::string output_hour_minute(int hour, int minute, bool h_for_00=true, int seconds=0);
@@ -213,6 +260,435 @@ long long monotonic_clock_gettime_nano();
 
 bool realtime_clock_gettime(long int& seconds, long int& nano_seconds);
 
+namespace I2n {
+
+namespace clock {
+
+    class conversion_error : public std::exception
+    {
+        public:
+            const int         err;
+            const std::string context;
+
+            conversion_error (const int err, const std::string &context)
+                : err     (err)
+                , context (context)
+            { }
+
+            virtual ~conversion_error (void) throw() { };
+
+            operator std::string (void)
+            {
+                return std::string ("errno=") + I2n::to_string (this->err)
+                     + " [" + this->context + "]";
+            }
+    };
+
+    namespace {
+
+        /* helper for ctor initializer list; we still lack aggregate initializers */
+        struct timespec
+        timespec_of_parts (const time_t sec, const long nsec)
+        {
+            struct timespec ret = { sec, nsec};
+
+            return ret;
+        }
+
+    } /* [namespace] */
+
+    namespace type {
+        /*
+         * represent the clock id options from clock_gettime(2) as
+         * a pair of enums. by default, CLOCK_MONOTONIC_COARSE is used
+         * everywhere since that’s what we want in most cases.
+         */
+        enum id {
+            mono,      /* CLOCK_MONOTONIC_COARSE */
+            real,      /* CLOCK_REALIME_COARSE */
+            boot,      /* CLOCK_BOOTTIME */
+            cpu,       /* CLOCK_CPUTIME_* */
+        };
+
+        /*
+         * for clocks that support it: non-coarse or raw variants; if a variant
+         * does not apply to a given clock, it is ignored
+         */
+        enum variant {
+            dflt,       /* all */
+            exact,      /* mono, real: not (*_COARSE) */
+            raw,        /* mono: _RAW */
+            process,    /* cpu: *_PROCESS_* */
+            thread,     /* cpu: *_THREAD_* */
+        };
+
+    } /* [namespace type] */
+
+
+    class Time {
+
+        private:
+            struct timespec     value;
+
+        public:
+            enum type::id       id;
+            enum type::variant  variant;
+            int                 err;
+
+        private:
+            /*
+             * Handle decimal part (nanosecond) overflow; this is performed
+             * after arithmetic operations and whenever we there is the
+             * possibility of unsanitized input for example in constructors
+             * from other types and suchlike.
+             *
+             * POSIX defines the ns part as *long*. Technically, that means
+             * that on machines where *sizeof long* equals *sizeof int*, it can
+             * represent only up to around 2.1 seconds. In this range, the loop
+             * version is most likely faster than division. However, since in
+             * practice *long* is 8 bytes just about anywhere, we have to
+             * handle greater dividends first.
+             */
+            inline void
+            carry_nsec (void)
+            {
+#           if LONG_BIT > 32
+                if (   this->value.tv_nsec < -3L * TIME_CONST_FACTOR_NANO
+                    || this->value.tv_nsec >  3L * TIME_CONST_FACTOR_NANO)
+                {
+                    const long sec = this->value.tv_nsec / TIME_CONST_FACTOR_NANO;
+                    this->value.tv_nsec -= sec * TIME_CONST_FACTOR_NANO;
+                    this->value.tv_sec  += sec;
+                }
+#           endif /* [LONG_BIT > 32] */
+                while (this->value.tv_nsec >= TIME_CONST_FACTOR_NANO) {
+                    this->value.tv_sec  += 1;
+                    this->value.tv_nsec -= TIME_CONST_FACTOR_NANO;
+                }
+
+                while (this->value.tv_nsec < 0) {
+                    this->value.tv_sec  -= 1;
+                    this->value.tv_nsec += TIME_CONST_FACTOR_NANO;
+                }
+            }
+
+        /* ctors *************************************************************/
+        public:
+
+            Time (const enum type::id      id  = type::mono,
+                  const enum type::variant var = type::dflt) NOEXCEPT;
+
+            inline Time (const Time &t) NOEXCEPT
+                : value   (t.value)
+                , id      (t.id)
+                , variant (t.variant)
+                , err     (t.err)
+            { }
+
+            inline
+            Time (const time_t sec,
+                  const long               nsec = 0,
+                  const enum type::id      id   = type::mono,
+                  const enum type::variant var  = type::dflt,
+                  const int                err  = 0) NOEXCEPT
+                : value   (timespec_of_parts (sec, nsec))
+                , id      (id)
+                , variant (var)
+                , err     (err)
+            { this->carry_nsec (); }
+
+            explicit
+            Time (const struct tm          &tm,
+                  const enum type::id       id  = type::mono,
+                  const enum type::variant  var = type::dflt);
+
+        /* value read access *************************************************/
+        public:
+
+            inline CONSTEXPR const struct timespec &get_time (void) const NOEXCEPT
+            { return this->value; }
+
+            inline CONSTEXPR const time_t &get_sec (void) const NOEXCEPT
+            { return this->value.tv_sec; }
+
+            inline CONSTEXPR const long &get_nsec (void) const NOEXCEPT
+            { return this->value.tv_nsec; }
+
+            inline CONSTEXPR const long get_msec (void) const NOEXCEPT
+            { return this->get_nsec () / 1000000; }
+
+            int64_t as_nanosec (void) const NOEXCEPT;
+
+            long as_nanosec_L (void) const NOEXCEPT;
+
+        /* value write access ************************************************/
+        public:
+
+            inline void
+            swap (Time &t) NOEXCEPT
+            {
+                std::swap (this->value  , t.value  );
+                std::swap (this->id     , t.id     );
+                std::swap (this->variant, t.variant);
+                std::swap (this->err    , t.err    );
+            }
+
+            Time &operator= (Time t) NOEXCEPT;
+
+            Time &operator= (struct timespec ts) NOEXCEPT;
+
+            inline void
+            set (const struct timespec &ts) NOEXCEPT
+            {
+                this->value = ts;
+                this->carry_nsec ();
+            }
+
+            inline void
+            set (const time_t sec,
+                 const long   nsec,
+                 const enum type::id      id  = type::mono,
+                 const enum type::variant var = type::dflt) NOEXCEPT
+            {
+                this->value.tv_sec  = sec;
+                this->value.tv_nsec = nsec;
+                this->id      = id;
+                this->variant = var;
+
+                this->carry_nsec ();
+            }
+
+            bool set (void) NOEXCEPT;
+
+            void unset (void) NOEXCEPT;
+
+        /* arithmetic ********************************************************/
+        public:
+
+            Time &add (const time_t sec, const long nsec) NOEXCEPT;
+
+            Time &subtract (const time_t sec, const long nsec) NOEXCEPT;
+
+            inline Time &add (const Time &t2) NOEXCEPT
+            { return this->add (t2.value.tv_sec, t2.value.tv_nsec); }
+
+            inline Time &add (const time_t t2) NOEXCEPT
+            { return this->add (t2, 0L); };
+
+            inline Time &subtract (const Time &t2) NOEXCEPT
+            { return this->subtract (t2.value.tv_sec, t2.value.tv_nsec); }
+
+            inline Time &subtract (const time_t t2) NOEXCEPT
+            { return this->subtract (t2, 0L); };
+
+            Time &scale (const int64_t factor) NOEXCEPT;
+
+            Time &divide (const int64_t divisor) NOEXCEPT;
+
+            friend int compare (const Time &t1, const Time &t2) NOEXCEPT;
+
+            inline Time
+            difference (const Time &t) NOEXCEPT
+            { return (*this < t) ? t - *this : *this - t; }
+
+        /* overloads *********************************************************/
+        public:
+
+            inline Time
+            operator+ (const Time &t2) const NOEXCEPT
+            { return Time (*this).add (t2); }
+
+            inline Time
+            operator+ (const time_t t2) const NOEXCEPT
+            { return Time (*this).add (t2); }
+
+            inline Time &
+            operator+= (const Time &t2) NOEXCEPT
+            { return this->add (t2); }
+
+            inline Time &
+            operator+= (const time_t t2) NOEXCEPT
+            { return this->add (t2); }
+
+            inline Time
+            operator- (const Time &t2) const NOEXCEPT
+            { return Time (*this).subtract (t2); }
+
+            inline Time
+            operator- (const time_t t2) const NOEXCEPT
+            { return Time (*this).subtract (t2); }
+
+            inline Time &
+            operator-= (const Time &t2) NOEXCEPT
+            { return this->subtract (t2); }
+
+            inline Time &
+            operator-= (const time_t t2) NOEXCEPT
+            { return this->subtract (t2); }
+
+            inline Time
+            operator* (const int64_t factor) const NOEXCEPT
+            { return Time (*this).scale (factor); }
+
+            inline Time &
+            operator*= (const int64_t factor) NOEXCEPT
+            { return this->scale (factor); }
+
+            inline Time
+            operator/ (const int64_t divisor) const NOEXCEPT
+            { return Time (*this).divide (divisor); }
+
+            inline Time &
+            operator/= (const int64_t divisor) NOEXCEPT
+            { return this->divide (divisor); }
+
+            friend CONSTEXPR bool
+            operator== (const Time &t1, const Time &t2) NOEXCEPT;
+
+            friend CONSTEXPR bool
+            operator< (const Time &t1, const Time &t2) NOEXCEPT;
+
+            friend CONSTEXPR bool
+            operator> (const Time &t1, const Time &t2) NOEXCEPT;
+
+            friend std::ostream &
+            operator<< (std::ostream &os, const Time &t);
+
+        /* formatting ********************************************************/
+        public:
+
+            boost::optional<std::string>
+            format_iso8601 (const bool utc  = true,
+                            const bool date = true,
+                            const bool time = true,
+                            const bool tz   = true) const;
+
+            std::string make_nice_time (void) const;
+            std::string format_full_time (void) const;
+            std::string format_date (void) const;
+
+            inline std::string
+            format_sec_msec (void) const
+            { return ::format_sec_msec (this->value); }
+
+            inline boost::optional<std::string>
+            format_min_sec_msec (void) const
+            { return ::format_min_sec_msec (this->value); }
+
+    }; /* [class Time] */
+
+    inline Time 
+    operator+ (const time_t t1, const Time &t2) NOEXCEPT
+    { return Time (t1) + t2; }
+
+    inline Time 
+    operator- (const time_t t1, const Time &t2) NOEXCEPT
+    { return Time (t1) - t2; }
+
+    inline Time
+    operator* (const time_t t1, const Time &t2) NOEXCEPT
+    { return t2 * t1; }
+
+    int compare (const Time &t1, const Time &t2) NOEXCEPT;
+
+    /*
+     * comparison for equality also considers the clock type;
+     */
+    inline CONSTEXPR bool
+    operator== (const Time &t1, const Time &t2) NOEXCEPT
+    {
+        return t1.id == t2.id && t1.variant == t2.variant
+            && t1.value.tv_sec  == t2.value.tv_sec
+            && t1.value.tv_nsec == t2.value.tv_nsec
+            ;
+    }
+
+    /* these ignore the *id* and *variant* fields */
+    inline CONSTEXPR bool
+    operator< (const Time &t1, const Time &t2) NOEXCEPT
+    {
+        return t1.value.tv_sec < t2.value.tv_sec
+            || (   t1.value.tv_sec  == t2.value.tv_sec
+                && t1.value.tv_nsec <  t2.value.tv_nsec)
+            ;
+    }
+
+    /* these ignore the *id* and *variant* fields */
+    inline CONSTEXPR bool
+    operator> (const Time &t1, const Time &t2) NOEXCEPT
+    {
+        return t1.value.tv_sec > t2.value.tv_sec
+            || (   t1.value.tv_sec  == t2.value.tv_sec
+                && t1.value.tv_nsec >  t2.value.tv_nsec)
+            ;
+    }
+
+    inline std::ostream &
+    operator<< (std::ostream &os, const Time &t)
+    {
+        os << I2n::to_string (t.value.tv_sec)  << "s, "
+           << I2n::to_string (t.value.tv_nsec) << "ns"
+           ;
+
+        return os;
+    }
+
+    boost::optional<Time>
+    now (const enum type::id      id  = type::mono,
+         const enum type::variant var = type::dflt) NOEXCEPT;
+    Time
+    zero (const enum type::id      id  = type::mono,
+          const enum type::variant var = type::dflt) NOEXCEPT;
+
+    boost::optional<Time>
+    time_of_iso8601 (const std::string        &s,
+                     const bool                date = true,
+                     const bool                time = true,
+                     const bool                tz   = true,
+                     const enum type::id       id   = type::real,
+                     const enum type::variant  var  = type::dflt) NOEXCEPT;
+
+    template <typename ContT>
+    Time
+    mean (const ContT &data)
+    {
+        Time sum (0, 0);
+
+        if (data.size () == 0) {
+            return sum;
+        }
+
+        for (typename ContT::const_iterator it = data.begin ();
+             it != data.end (); ++it)
+        {
+            sum += *it;
+        };
+
+        return sum.divide (static_cast<int64_t> (data.size ()));
+    };
+
+    template <typename ContT>
+    Time
+    median (const ContT &data)
+    {
+        if (data.size () == 0) {
+            return zero ();
+        }
+        if (data.size () == 1) {
+            return *data.begin ();
+        }
+
+        std::vector<typename ContT::value_type> sorted;
+        std::copy (data.begin (), data.end (), std::back_inserter (sorted));
+        std::sort (sorted.begin (), sorted.end ());
+
+        return sorted [data.size () / 2];
+    };
+
+} /* [namespace clock] */
+
+} /* [namespace I2n] */
 
 
 #endif
index 58dcaf5..885bb67 100644 (file)
@@ -32,6 +32,7 @@ on this file might be covered by the GNU General Public License.
 
 #include <unistd.h>
 #include <set>
+#include <iostream>
 
 using namespace std;
 using namespace I2n;
@@ -42,6 +43,7 @@ class TestTimeFuncFixture
 protected:
    typedef std::list< std::string > StringList;
    std::set<std::string>  used_check_files;
+   std::string tz; /* save and restore TZ from envp */
 
    std::string get_check_file_path(std::string tag)
    {
@@ -67,14 +69,43 @@ protected:
       used_check_files.clear();
    } // eo remove_check_files
 
+   void set_utc (void)
+   {
+        errno = 0;
+        if (setenv ("TZ", "UTC", 1) == -1)
+        {
+            std::cerr
+                << "error setting environment 'TZ': [" << this->tz << "]"
+                << std::endl
+                ;
+            return;
+        }
+
+        tzset ();
+   }
+
 public:
     TestTimeFuncFixture()
+        : tz (secure_getenv ("TZ") ?: "")
     {
     }
 
     ~TestTimeFuncFixture()
     {
         remove_check_files();
+
+        errno = 0;
+        if (setenv ("TZ", this->tz.c_str (), 1) == -1)
+        {
+            std::cerr
+                << "error cleaning up environment 'TZ': [" << this->tz << "]"
+                << std::endl
+                ;
+        }
+        else
+        {
+            tzset();
+        }
     }
 };
 
@@ -461,5 +492,905 @@ BOOST_AUTO_TEST_CASE(DateToSeconds2)
     BOOST_CHECK_EQUAL(1341093600, date_to_seconds("2012-07-01"));
 }
 
+BOOST_AUTO_TEST_CASE(FormatISO8601_T)
+{
+    const time_t moment = 1515492684;
+    BOOST_CHECK_EQUAL("10:11:24",
+                      format_iso8601 (moment, true, false, true, false));
+}
+
+BOOST_AUTO_TEST_CASE(FormatISO8601_TZ_local)
+{
+    const time_t moment = 1515492684;
+    BOOST_CHECK_EQUAL("11:11:24Z+0100",
+                      format_iso8601 (moment, false, false, true, true));
+}
+
+BOOST_AUTO_TEST_CASE(FormatISO8601_TZ)
+{
+    const time_t moment = 1515492684;
+    BOOST_CHECK_EQUAL("10:11:24Z+0000",
+                      format_iso8601 (moment, true, false, true, true));
+}
+
+BOOST_AUTO_TEST_CASE(FormatISO8601_DTZ_local)
+{
+    const time_t moment = 1515492684;
+    BOOST_CHECK_EQUAL("2018-01-09T11:11:24Z+0100",
+                      format_iso8601 (moment, false, true, true, true));
+}
+
+BOOST_AUTO_TEST_CASE(FormatISO8601_DTZ)
+{
+    const time_t moment = 1515492684;
+    BOOST_CHECK_EQUAL("2018-01-09T10:11:24Z+0000",
+                      format_iso8601 (moment, true, true, true, true));
+}
+
+BOOST_AUTO_TEST_CASE(FormatISO8601_DT)
+{
+    const time_t moment = 1515492684;
+    BOOST_CHECK_EQUAL("2018-01-09T10:11:24",
+                      format_iso8601 (moment, true, true, true, false));
+}
+
+BOOST_AUTO_TEST_CASE(FormatISO8601_D)
+{
+    const time_t moment = 1515492684;
+    BOOST_CHECK_EQUAL("2018-01-09",
+                      format_iso8601 (moment, true, true, false, false));
+}
+
+BOOST_AUTO_TEST_CASE(FormatISO8601_DTZ_struct_tm)
+{
+    struct tm helau;
+    helau.tm_sec    = 11;
+    helau.tm_min    = 11;
+    helau.tm_hour   = 11;
+    helau.tm_mday   = 11;
+    helau.tm_mon    = 10;
+    helau.tm_year   = 2018 - 1900;
+    helau.tm_wday   = 0;
+    helau.tm_yday   = 315;
+    helau.tm_isdst  = 0;
+    helau.tm_gmtoff = 0;
+    helau.tm_zone   = NULL;
+
+    BOOST_CHECK_EQUAL("2018-11-11T11:11:11Z+0000",
+                      format_iso8601 (helau, true, true, true));
+}
+
+BOOST_AUTO_TEST_CASE(FormatISO8601_DTZ_struct_timespec)
+{
+    struct timespec ts = { 1541934671, 11 };
+
+    BOOST_CHECK_EQUAL("2018-11-11T11:11:11Z+0000",
+                      format_iso8601 (ts, true, true, true, true));
+}
+
+BOOST_AUTO_TEST_SUITE(Clock)
+
+    BOOST_AUTO_TEST_CASE(ctor_simple)
+    {
+        I2n::clock::Time t;
+
+        BOOST_CHECK_EQUAL(t.get_sec  (), 0);
+        BOOST_CHECK_EQUAL(t.get_nsec (), 0);
+    }
+
+    BOOST_AUTO_TEST_CASE(ctor_type)
+    {
+        I2n::clock::Time t1 (I2n::clock::type::real);
+        I2n::clock::Time t2 (I2n::clock::type::mono);
+        I2n::clock::Time t3 (I2n::clock::type::boot);
+        I2n::clock::Time t4 (I2n::clock::type::cpu);
+
+        BOOST_CHECK_EQUAL(t1.get_sec  (), 0);
+        BOOST_CHECK_EQUAL(t1.get_nsec (), 0);
+
+        BOOST_CHECK_EQUAL(t2.get_sec  (), 0);
+        BOOST_CHECK_EQUAL(t2.get_nsec (), 0);
+
+        BOOST_CHECK_EQUAL(t3.get_sec  (), 0);
+        BOOST_CHECK_EQUAL(t3.get_nsec (), 0);
+
+        BOOST_CHECK_EQUAL(t4.get_sec  (), 0);
+        BOOST_CHECK_EQUAL(t4.get_nsec (), 0);
+    }
+
+    BOOST_AUTO_TEST_CASE(ctor_variant)
+    {
+        I2n::clock::Time tmc (I2n::clock::type::mono);
+        I2n::clock::Time tmr (I2n::clock::type::mono, I2n::clock::type::raw);
+        I2n::clock::Time tme (I2n::clock::type::mono, I2n::clock::type::exact);
+
+        I2n::clock::Time trc (I2n::clock::type::real);
+        I2n::clock::Time tre (I2n::clock::type::real, I2n::clock::type::exact);
+
+        I2n::clock::Time tb (I2n::clock::type::boot);
+
+        I2n::clock::Time tcp (I2n::clock::type::cpu);
+        I2n::clock::Time tct (I2n::clock::type::cpu, I2n::clock::type::thread);
+
+        BOOST_CHECK_EQUAL(tmc.get_sec  (), 0); /* MONO */
+        BOOST_CHECK_EQUAL(tmc.get_nsec (), 0);
+
+        BOOST_CHECK_EQUAL(tmr.get_sec  (), 0);
+        BOOST_CHECK_EQUAL(tmr.get_nsec (), 0);
+
+        BOOST_CHECK_EQUAL(tme.get_sec  (), 0);
+        BOOST_CHECK_EQUAL(tme.get_nsec (), 0);
+
+        BOOST_CHECK_EQUAL(trc.get_sec  (), 0); /* REAL */
+        BOOST_CHECK_EQUAL(trc.get_nsec (), 0);
+
+        BOOST_CHECK_EQUAL(tre.get_sec  (), 0);
+        BOOST_CHECK_EQUAL(tre.get_nsec (), 0);
+
+        BOOST_CHECK_EQUAL(tb.get_sec  (), 0); /* BOOT */
+        BOOST_CHECK_EQUAL(tb.get_nsec (), 0);
+
+        BOOST_CHECK_EQUAL(tcp.get_sec  (), 0); /* CPU */
+        BOOST_CHECK_EQUAL(tcp.get_nsec (), 0);
+
+        BOOST_CHECK_EQUAL(tct.get_sec  (), 0);
+        BOOST_CHECK_EQUAL(tct.get_nsec (), 0);
+    }
+
+    BOOST_AUTO_TEST_CASE(initializer_now)
+    {
+        boost::optional<I2n::clock::Time> t = I2n::clock::now ();
+
+        BOOST_CHECK(t);
+        BOOST_CHECK_GT(t->get_sec (), 0);
+        BOOST_CHECK_EQUAL(t->err, 0);
+    }
+
+    BOOST_AUTO_TEST_CASE(initializer_zero)
+    {
+        I2n::clock::Time stundenull = I2n::clock::zero ();
+
+        BOOST_CHECK_EQUAL(stundenull.get_sec  (), 0);
+        BOOST_CHECK_EQUAL(stundenull.get_nsec (), 0);
+        BOOST_CHECK_EQUAL(stundenull.err, 0);
+    }
+
+    BOOST_AUTO_TEST_CASE(member_set_now)
+    {
+        I2n::clock::Time t;
+
+        BOOST_CHECK(t.set ());
+
+        BOOST_CHECK_NE(t.get_sec (), 0);
+    }
+
+    BOOST_AUTO_TEST_CASE(member_set_value)
+    {
+        I2n::clock::Time t;
+
+        t.set (42, 42);
+
+        BOOST_CHECK_EQUAL(t.get_sec  (), 42);
+        BOOST_CHECK_EQUAL(t.get_nsec (), 42);
+    }
+
+    BOOST_AUTO_TEST_CASE(member_set_value_type)
+    {
+        I2n::clock::Time t;
+
+        t.set (42, 42, I2n::clock::type::real, I2n::clock::type::exact);
+
+        BOOST_CHECK_EQUAL(t.get_sec  (), 42);
+        BOOST_CHECK_EQUAL(t.get_nsec (), 42);
+    }
+
+    BOOST_AUTO_TEST_CASE(member_add_parts)
+    {
+        I2n::clock::Time t;
+
+        t.set (42, 42);
+        t.add (2187, 2187);
+
+        BOOST_CHECK_EQUAL(t.get_sec  (), 2229);
+        BOOST_CHECK_EQUAL(t.get_nsec (), 2229);
+    }
+
+    BOOST_AUTO_TEST_CASE(member_sub_parts)
+    {
+        I2n::clock::Time t;
+
+        t.set (2, 0L);
+        t.subtract (1, 1L);
+
+        BOOST_CHECK_EQUAL(t.get_sec  (), 0);
+        BOOST_CHECK_EQUAL(t.get_nsec (), 999999999);
+    }
+
+    BOOST_AUTO_TEST_CASE(member_sub_Time)
+    {
+        I2n::clock::Time t1;
+        I2n::clock::Time t2;
+
+        t1.set (42, 42L);
+        t2.set (42,  0L);
+
+        t1.subtract (t2);
+
+        BOOST_CHECK_EQUAL(t1.get_sec  (),  0);
+        BOOST_CHECK_EQUAL(t1.get_nsec (), 42L);
+    }
+
+    BOOST_AUTO_TEST_CASE(member_diff)
+    {
+        static const time_t five = 5 * 365 * 24 * 3600;
+
+        I2n::clock::Time t1 (42, 1337);
+        I2n::clock::Time t2 = t1 + five;;
+        I2n::clock::Time t3 = t1 - five;;
+
+        BOOST_CHECK_EQUAL(t2, I2n::clock::Time ((time_t)42 + five, 1337));
+        BOOST_CHECK_EQUAL(t3, I2n::clock::Time ((time_t)42 - five, 1337));
+        BOOST_CHECK_EQUAL(t1.difference (t3), t3.difference (t1));
+        BOOST_CHECK_EQUAL(t3.difference (t3), t3.difference (t3));
+    }
+
+    BOOST_AUTO_TEST_CASE(op_copyassign)
+    {
+        I2n::clock::Time t1;
+        I2n::clock::Time t2;
+
+        BOOST_CHECK(t1.set ());
+
+        t2 = t1;
+
+        BOOST_CHECK_EQUAL(t1.get_sec  (), t2.get_sec  ());
+        BOOST_CHECK_EQUAL(t1.get_nsec (), t2.get_nsec ());
+    }
+
+    BOOST_AUTO_TEST_CASE(op_equal)
+    {
+        I2n::clock::Time t1;
+        I2n::clock::Time t2;
+
+        BOOST_CHECK(t1.set ());
+        t2 = t1;
+
+        BOOST_CHECK_EQUAL(t1, t2);
+    }
+
+    BOOST_AUTO_TEST_CASE(op_add_Time)
+    {
+        I2n::clock::Time t1;
+        I2n::clock::Time t2;
+        I2n::clock::Time tsum;
+
+        t1.set (2187, 2187);
+        t2.set (1337, 1337);
+        tsum = t1 + t2;
+
+        BOOST_CHECK_EQUAL(tsum.get_sec  (), 3524);
+        BOOST_CHECK_EQUAL(tsum.get_nsec (), 3524);
+    }
+
+    BOOST_AUTO_TEST_CASE(op_add_Time_carry)
+    {
+        I2n::clock::Time t1;
+        I2n::clock::Time t2;
+        I2n::clock::Time tsum;
+
+        t1.set (2187, 2187);
+# if LONG_BIT == 32
+        t2.set (1300,  2L * 1000 * 1000 * 1000 + 1337);
+# else
+        t2.set (1300, 37L * 1000 * 1000 * 1000 + 1337);
+# endif
+
+        tsum = t1 + t2;
+
+# if LONG_BIT == 32
+        BOOST_CHECK_EQUAL(tsum.get_sec  (), 3489);
+        BOOST_CHECK_EQUAL(tsum.get_nsec (), 3524);
+# else
+        BOOST_CHECK_EQUAL(tsum.get_sec  (), 3524);
+        BOOST_CHECK_EQUAL(tsum.get_nsec (), 3524);
+# endif
+    }
+
+    BOOST_AUTO_TEST_CASE(op_add_time_t)
+    {
+        I2n::clock::Time t1 (2187, 2187);
+        time_t           t2 = 1337;
+        I2n::clock::Time tsum;
+
+        tsum = t1 + t2;
+
+        BOOST_CHECK_EQUAL(tsum.get_sec  (), 3524);
+        BOOST_CHECK_EQUAL(tsum.get_nsec (), 2187);
+    }
+
+    BOOST_AUTO_TEST_CASE(op_add_time_t_external)
+    {
+        time_t           t1 = 1337;
+        I2n::clock::Time t2 (2187, 2187);
+        I2n::clock::Time tsum;
+
+        tsum = t1 + t2;
+
+        BOOST_CHECK_EQUAL(tsum.get_sec  (), 3524);
+        BOOST_CHECK_EQUAL(tsum.get_nsec (), 2187);
+    }
+
+    BOOST_AUTO_TEST_CASE(op_incr_Time)
+    {
+        I2n::clock::Time t1 (2187, 2187);
+        I2n::clock::Time t2 (1337, 1337);
+
+        t1 += t2;
+
+        BOOST_CHECK_EQUAL(t1.get_sec  (), 3524);
+        BOOST_CHECK_EQUAL(t1.get_nsec (), 3524);
+    }
+
+    BOOST_AUTO_TEST_CASE(op_incr_time_t)
+    {
+        I2n::clock::Time t1 (2187, 2187);
+        time_t           t2 = 1337;
+
+        t1 += t2;
+
+        BOOST_CHECK_EQUAL(t1.get_sec  (), 3524);
+        BOOST_CHECK_EQUAL(t1.get_nsec (), 2187);
+    }
+
+    BOOST_AUTO_TEST_CASE(op_subtract_Time)
+    {
+        I2n::clock::Time t1;
+        I2n::clock::Time t2;
+        I2n::clock::Time tdiff;
+
+        t1.set (2187, 2187);
+        t2.set (1337, 1337);
+        tdiff = t1 - t2;
+
+        BOOST_CHECK_EQUAL(tdiff.get_sec  (), 850);
+        BOOST_CHECK_EQUAL(tdiff.get_nsec (), 850);
+    }
+
+    BOOST_AUTO_TEST_CASE(op_subtract_time_t)
+    {
+        I2n::clock::Time t1 (2187, 2187);
+        time_t           t2 = 1337;
+        I2n::clock::Time tdiff;
+
+        tdiff = t1 - t2;
+
+        BOOST_CHECK_EQUAL(tdiff.get_sec  (), 850);
+        BOOST_CHECK_EQUAL(tdiff.get_nsec (), 2187);
+    }
+
+    BOOST_AUTO_TEST_CASE(op_subtract_time_t_external)
+    {
+        time_t           t1 = 1337;
+        I2n::clock::Time t2 (2187, 2187);
+        I2n::clock::Time tdiff;
+
+        tdiff = t1 - t2;
+
+        BOOST_CHECK_EQUAL(tdiff.get_sec  (), -851);
+        BOOST_CHECK_EQUAL(tdiff.get_nsec (), 999997813);
+    }
+
+    BOOST_AUTO_TEST_CASE(op_decr_Time)
+    {
+        I2n::clock::Time t1 (2187, 2187);
+        I2n::clock::Time t2 (1337, 1337);
+
+        t1 -= t2;
+
+        BOOST_CHECK_EQUAL(t1.get_sec  (), 850);
+        BOOST_CHECK_EQUAL(t1.get_nsec (), 850);
+    }
+
+    BOOST_AUTO_TEST_CASE(op_decr_time_t)
+    {
+        I2n::clock::Time t1 (2187, 2187);
+        time_t           t2 = 1337;
+
+        t1 -= t2;
+
+        BOOST_CHECK_EQUAL(t1.get_sec  (), 850);
+        BOOST_CHECK_EQUAL(t1.get_nsec (), 2187);
+    }
+
+    BOOST_AUTO_TEST_CASE(op_mult_scale)
+    {
+        I2n::clock::Time t1;
+        I2n::clock::Time t2;
+
+        t1.set (1, 1);
+        t2 = t1 * 42;
+
+        BOOST_CHECK_EQUAL(t2.get_sec  (), 42);
+        BOOST_CHECK_EQUAL(t2.get_nsec (), 42);
+    }
+
+    BOOST_AUTO_TEST_CASE(op_mult_mutate)
+    {
+        I2n::clock::Time t1 (  42, 42);
+        I2n::clock::Time t2 (1337,  0);
+
+        t1 *=   2;
+        t2 *= -10;
+
+        BOOST_CHECK_EQUAL(t1.get_sec  (),     84);
+        BOOST_CHECK_EQUAL(t1.get_nsec (),     84);
+        BOOST_CHECK_EQUAL(t2.get_sec  (), -13370);
+    }
+
+    BOOST_AUTO_TEST_CASE(op_mult_scale_carry)
+    {
+        I2n::clock::Time t1;
+        I2n::clock::Time t2;
+
+        t1.set (1, 500 * 1000 * 1000);
+        t2 = t1 * 3;
+
+        BOOST_CHECK_EQUAL(t2.get_sec  (),  4);
+        BOOST_CHECK_EQUAL(t2.get_nsec (),  500 * 1000 * 1000);
+    }
+
+    BOOST_AUTO_TEST_CASE(op_equals)
+    {
+        I2n::clock::Time t1;
+        I2n::clock::Time t2;
+
+        t1.set (50, 50);
+        t2.set (50, 50);
+
+        BOOST_CHECK_EQUAL(t1, t2);
+    }
+
+    BOOST_AUTO_TEST_CASE(compare_equal)
+    {
+        I2n::clock::Time t1;
+        I2n::clock::Time t2;
+
+        t1.set (42, 42);
+        t2.set (42, 42);
+
+        BOOST_CHECK_EQUAL(I2n::clock::compare (t1, t2), 0);
+    }
+
+    BOOST_AUTO_TEST_CASE(compare_equal_type)
+    {
+        I2n::clock::Time t1 (42, 42, I2n::clock::type::real);
+        I2n::clock::Time t2 (42, 42, I2n::clock::type::cpu);
+        I2n::clock::Time t3 (42,  0, I2n::clock::type::real);
+        I2n::clock::Time t4 (42, 42, I2n::clock::type::real);
+
+        BOOST_CHECK_NE(t1, t2);
+        BOOST_CHECK_NE(t1, t3);
+        BOOST_CHECK_EQUAL(t1, t4);
+    }
+
+    BOOST_AUTO_TEST_CASE(compare_ne_sec)
+    {
+        I2n::clock::Time t1;
+        I2n::clock::Time t2;
+
+        t1.set (  42, 42);
+        t2.set (1337, 42);
+
+        BOOST_CHECK_EQUAL(I2n::clock::compare (t1, t2), -1);
+        BOOST_CHECK_EQUAL(I2n::clock::compare (t2, t1),  1);
+    }
+
+    BOOST_AUTO_TEST_CASE(compare_ne_nsec)
+    {
+        I2n::clock::Time t1;
+        I2n::clock::Time t2;
+
+        t1.set (42,   42);
+        t2.set (42, 1337);
+
+        BOOST_CHECK_EQUAL(I2n::clock::compare (t1, t2), -1);
+        BOOST_CHECK_EQUAL(I2n::clock::compare (t2, t1),  1);
+    }
+
+    BOOST_AUTO_TEST_CASE(compare_ne_both)
+    {
+        I2n::clock::Time t1;
+        I2n::clock::Time t2;
+
+        t1.set (42, 2187);
+        t2.set (23, 1337);
+
+        BOOST_CHECK_EQUAL(I2n::clock::compare (t1, t2),  1);
+        BOOST_CHECK_EQUAL(I2n::clock::compare (t2, t1), -1);
+    }
+
+    BOOST_AUTO_TEST_CASE(op_ineq_sec)
+    {
+        I2n::clock::Time t1 (1337);
+        I2n::clock::Time t2 (2187);
+
+        BOOST_CHECK_LT(t1, t2);
+        BOOST_CHECK_GT(t2, t1);
+    }
+
+    BOOST_AUTO_TEST_CASE(op_ineq_nsec)
+    {
+        I2n::clock::Time t1 (1337, 23);
+        I2n::clock::Time t2 (1337, 42);
+
+        BOOST_CHECK_LT(t1, t2);
+        BOOST_CHECK_GT(t2, t1);
+    }
+
+    BOOST_AUTO_TEST_CASE(op_ineq_both)
+    {
+        I2n::clock::Time t1 (2187, 23);
+        I2n::clock::Time t2 (1337, 42);
+
+        BOOST_CHECK_LT(t2, t1);
+        BOOST_CHECK_GT(t1, t2);
+    }
+
+    BOOST_AUTO_TEST_CASE(op_eq_time_t)
+    {
+        boost::optional<I2n::clock::Time> t1  = I2n::clock::now ();
+        const time_t                      t2  = time (NULL); /* race here */
+
+        *t1 -= (time_t)42;
+
+        BOOST_CHECK_NE(*t1,  t2);
+        BOOST_CHECK_LT(*t1,  t2);
+        BOOST_CHECK_GT( t2, *t1);
+    }
+
+    BOOST_AUTO_TEST_CASE(Format_sec_msec)
+    {
+        I2n::clock::Time t1 (42, 42);
+        I2n::clock::Time t2 ( 4, 242424242);
+        I2n::clock::Time t3 ( 0, 133713371);
+        I2n::clock::Time t4 ( 0, 0);
+
+        std::string s1 = t1.format_sec_msec ();
+        std::string s2 = t2.format_sec_msec ();
+        std::string s3 = t3.format_sec_msec ();
+        std::string s4 = t4.format_sec_msec ();
+
+        BOOST_CHECK_EQUAL("42s 0ms"  , s1);
+        BOOST_CHECK_EQUAL( "4s 242ms", s2);
+        BOOST_CHECK_EQUAL( "0s 133ms", s3);
+        BOOST_CHECK_EQUAL( "0s 0ms"  , s4);
+    }
+
+    BOOST_AUTO_TEST_CASE(Format_min_sec_msec)
+    {
+        I2n::clock::Time t1 (42*60 + 42, 42);
+        I2n::clock::Time t2 ( 4*60 + 42, 242424242);
+        I2n::clock::Time t3 ( 0*60 + 42, 133713371);
+        I2n::clock::Time t4 (    0 +  0,  0);
+
+        std::string s1 = *t1.format_min_sec_msec ();
+        std::string s2 = *t2.format_min_sec_msec ();
+        std::string s3 = *t3.format_min_sec_msec ();
+        std::string s4 = *t4.format_min_sec_msec ();
+
+        BOOST_CHECK_EQUAL("42m42.000s", s1);
+        BOOST_CHECK_EQUAL( "4m42.242s", s2);
+        BOOST_CHECK_EQUAL( "0m42.133s", s3);
+        BOOST_CHECK_EQUAL(  "0m0.000s", s4);
+    }
+
+    BOOST_AUTO_TEST_CASE(FormatISO8601_T)
+    {
+        I2n::clock::Time t (42, 42);
+        boost::optional<std::string> s = t.format_iso8601 (true, false, true, false);
+
+        BOOST_CHECK_EQUAL("00:00:42", *s);
+    }
+
+    BOOST_AUTO_TEST_CASE(FormatISO8601_DT)
+    {
+        I2n::clock::Time t (1541934671, 0);
+        boost::optional<std::string> s = t.format_iso8601 (true, true, true, false);
+
+        BOOST_CHECK_EQUAL("2018-11-11T11:11:11", *s);
+    }
+
+    BOOST_AUTO_TEST_CASE(FormatISO8601_DTZ)
+    {
+        I2n::clock::Time t (1541934671, 0);
+        boost::optional<std::string> s = t.format_iso8601 (true, true, true, true);
+
+        BOOST_CHECK_EQUAL("2018-11-11T11:11:11Z+0000", *s);
+    }
+
+    BOOST_AUTO_TEST_CASE(Format_make_nice_time)
+    {
+        I2n::clock::Time t (111111, 0);
+        boost::optional<std::string> s = t.make_nice_time ();
+
+        BOOST_CHECK_EQUAL("1 day, 06:51:51", *s);
+    }
+
+    BOOST_AUTO_TEST_CASE(Format_format_full_time)
+    {
+        I2n::clock::Time t (1541934671, 0);
+        /*
+         * brr, the old formatters use localtime without a way to opt out of
+         * it!
+         */
+        this->set_utc ();
+        boost::optional<std::string> s = t.format_full_time ();
+
+        BOOST_CHECK_EQUAL("11.11.2018 11:11", *s);
+    }
+
+    BOOST_AUTO_TEST_CASE(Format_format_date)
+    {
+        I2n::clock::Time t (1541934671, 0);
+        boost::optional<std::string> s = t.format_date ();
+
+        BOOST_CHECK_EQUAL("11.11.2018", *s);
+    }
+
+    BOOST_AUTO_TEST_CASE(FromString_iso8601_full)
+    {
+        const std::string in1 ("0001-01-01T00:00:00Z+0000");
+        const std::string in2 ("2018-11-11T11:11:11Z+0000");
+
+        this->set_utc ();
+
+        boost::optional<I2n::clock::Time> t1 = I2n::clock::time_of_iso8601 (in1);
+        boost::optional<I2n::clock::Time> t2 = I2n::clock::time_of_iso8601 (in2);
+
+# if LONG_BIT == 32
+        BOOST_CHECK(!t1);
+# else
+        BOOST_CHECK(t1);
+        BOOST_CHECK_EQUAL(*t1->format_iso8601 (), in1);
+# endif
+
+        BOOST_CHECK(t2);
+        BOOST_CHECK_EQUAL(*t2->format_iso8601 (), in2);
+    }
+
+    BOOST_AUTO_TEST_CASE(FromString_iso8601_full_negyear)
+    {
+        const std::string in1 ("-0001-01-01T00:00:00Z+0000");
+        const std::string in2 ("-2018-11-11T11:11:11Z+0000");
+
+        this->set_utc ();
+
+        boost::optional<I2n::clock::Time> t1 = I2n::clock::time_of_iso8601 (in1);
+        boost::optional<I2n::clock::Time> t2 = I2n::clock::time_of_iso8601 (in2);
+
+# if LONG_BIT == 32
+        BOOST_CHECK(!t1);
+        BOOST_CHECK(!t2);
+# else
+        BOOST_CHECK(t1);
+        BOOST_CHECK(t2);
+        BOOST_CHECK_EQUAL(*t1->format_iso8601 (), in1);
+        BOOST_CHECK_EQUAL(*t2->format_iso8601 (), in2);
+# endif
+    }
+
+    BOOST_AUTO_TEST_CASE(FromString_iso8601_partial)
+    {
+        const std::string in1 ("2018-11-11T11:11:11");
+        const std::string in2 ("2018-11-11");
+
+        this->set_utc ();
+
+        boost::optional<I2n::clock::Time> t1 =
+            I2n::clock::time_of_iso8601 (in1, true, true, false);
+        boost::optional<I2n::clock::Time> t2 =
+            I2n::clock::time_of_iso8601 (in2, true, false, false);
+
+        BOOST_CHECK(t1);
+        BOOST_CHECK(t2);
+        /*
+         * We test for the difference here which is zero if the number is
+         * correct but causes the difference from the expected value to be
+         * printed in case the test fails.
+         */
+        BOOST_CHECK_EQUAL(*t1->format_iso8601 (true,  true,  true, false), in1);
+        BOOST_CHECK_EQUAL(*t2->format_iso8601 (true,  true, false, false), in2);
+    }
+
+    BOOST_AUTO_TEST_CASE(FromString_iso8601_32bit_time_t_err)
+    {
+        const std::string timeless ("11:11:11");
+        boost::optional<I2n::clock::Time> untimely = boost::none;
+
+        this->set_utc ();
+
+        untimely = I2n::clock::time_of_iso8601 (timeless, false, true, false);
+
+# if LONG_BIT == 32
+        BOOST_CHECK(!untimely);
+# else
+        BOOST_CHECK(untimely);
+        BOOST_CHECK_EQUAL(*untimely->format_iso8601 (true, false,  true, false),
+                          timeless);
+# endif
+    }
+
+    BOOST_AUTO_TEST_CASE(Ctor_32bit_time_t_err)
+    {
+        boost::optional<std::string> threw = boost::none;
+
+        struct tm tm;
+        memset (&tm, 0, sizeof (tm));
+
+        tm.tm_sec    = 11;
+        tm.tm_min    = 11;
+        tm.tm_hour   = 11;
+        tm.tm_mday   = 11;
+        tm.tm_mon    = 10;
+        tm.tm_year   = -789;
+        tm.tm_gmtoff = 0;
+
+        try {
+            I2n::clock::Time untimely (tm);
+        } catch (I2n::clock::conversion_error &exn) {
+            threw = std::string (exn);
+        }
+
+
+# if LONG_BIT == 32
+        BOOST_CHECK_EQUAL(*threw,
+                          "errno=0 [mktime: from struct tm {Sun Nov 11 11:11:11 1111}]");
+# else
+        BOOST_CHECK(!threw);
+# endif
+    }
+
+    BOOST_AUTO_TEST_CASE(containers_list)
+    {
+        std::list<I2n::clock::Time> ts;
+
+        ts.push_back (I2n::clock::zero ());
+        ts.push_back (I2n::clock::zero ());
+
+        BOOST_CHECK_EQUAL(ts.size (), 2);
+    }
+
+    BOOST_AUTO_TEST_CASE(containers_vec)
+    {
+        std::vector<I2n::clock::Time> ts;
+
+        ts.push_back (I2n::clock::zero ());
+        ts.push_back (I2n::clock::zero ());
+
+        BOOST_CHECK_EQUAL(ts.size (), 2);
+    }
+
+    BOOST_AUTO_TEST_CASE(containers_set)
+    {
+        std::set<I2n::clock::Time> ts;
+
+        ts.insert (I2n::clock::zero ());
+        ts.insert (I2n::clock::Time (42, 2187));
+        ts.insert (I2n::clock::zero ());
+
+        BOOST_CHECK_EQUAL(ts.size (), 2);
+    }
+
+    BOOST_AUTO_TEST_CASE(containers_list_mean)
+    {
+        std::list<I2n::clock::Time> ts;
+
+        ts.push_back (I2n::clock::Time (42, 42));
+        ts.push_back (I2n::clock::Time (1337, 1337));
+
+        BOOST_CHECK_EQUAL(I2n::clock::mean (ts),
+                          I2n::clock::Time (689, 500000689));
+    }
+
+    BOOST_AUTO_TEST_CASE(containers_list_mean_zero)
+    {
+        std::list<I2n::clock::Time> ts;
+
+        ts.push_back (I2n::clock::Time (0, 0));
+        ts.push_back (I2n::clock::Time (0, 0));
+        ts.push_back (I2n::clock::Time (0, 0));
+        ts.push_back (I2n::clock::Time (0, 0));
+
+        BOOST_CHECK_EQUAL(I2n::clock::mean (ts),
+                          I2n::clock::zero ());
+    }
+
+    BOOST_AUTO_TEST_CASE(containers_list_mean_empty)
+    {
+        std::list<I2n::clock::Time> ts;
+
+        BOOST_CHECK_EQUAL(I2n::clock::mean (ts), I2n::clock::Time (0, 0));
+    }
+
+    BOOST_AUTO_TEST_CASE(containers_set_mean)
+    {
+        std::set<I2n::clock::Time> ts;
+
+        ts.insert (I2n::clock::Time (42));
+        ts.insert (I2n::clock::Time (1337));
+        ts.insert (I2n::clock::Time (2187));
+
+        BOOST_CHECK_EQUAL(I2n::clock::mean (ts),
+                          I2n::clock::Time (1188, 666666666));
+    }
+
+    BOOST_AUTO_TEST_CASE(containers_set_median_empty)
+    {
+        std::set<I2n::clock::Time> ts;
+
+        BOOST_CHECK_EQUAL(I2n::clock::median (ts), I2n::clock::Time (0, 0));
+    }
+
+    BOOST_AUTO_TEST_CASE(containers_set_median_one)
+    {
+        std::set<I2n::clock::Time> ts;
+
+        ts.insert (I2n::clock::Time (42, 0));
+
+        BOOST_CHECK_EQUAL(I2n::clock::median (ts),
+                          I2n::clock::Time (42, 0));
+    }
+
+    BOOST_AUTO_TEST_CASE(containers_set_median_multi)
+    {
+        std::set<I2n::clock::Time> ts;
+
+        ts.insert (I2n::clock::Time (42));
+        ts.insert (I2n::clock::Time (1337));
+        ts.insert (I2n::clock::Time (2187));
+
+        BOOST_CHECK_EQUAL(I2n::clock::median (ts), I2n::clock::Time (1337));
+    }
+
+    BOOST_AUTO_TEST_CASE(containers_vec_median_multi)
+    {
+        std::vector<I2n::clock::Time> ts;
+
+        ts.push_back (I2n::clock::Time (42));
+        ts.push_back (I2n::clock::Time (1337));
+        ts.push_back (I2n::clock::Time (2187));
+
+        BOOST_CHECK_EQUAL(I2n::clock::median (ts), I2n::clock::Time (1337));
+    }
+
+    BOOST_AUTO_TEST_CASE(containers_list_median_multi)
+    {
+        std::list<I2n::clock::Time> ts;
+
+        ts.push_back (I2n::clock::Time (42));
+        ts.push_back (I2n::clock::Time (1337));
+        ts.push_back (I2n::clock::Time (2187));
+        ts.push_back (I2n::clock::Time (0xdead));
+        ts.push_back (I2n::clock::Time (0xbeef));
+
+        BOOST_CHECK_EQUAL(I2n::clock::median (ts),
+                          I2n::clock::Time (2187));
+    }
+
+    BOOST_AUTO_TEST_CASE(containers_list_median_multi_evensize)
+    {
+        std::list<I2n::clock::Time> ts;
+
+        ts.push_back (I2n::clock::Time (42));
+        ts.push_back (I2n::clock::Time (1337));
+        ts.push_back (I2n::clock::Time (2187));
+        ts.push_back (I2n::clock::Time (0xf00d));
+        ts.push_back (I2n::clock::Time (0xfeed));
+        ts.push_back (I2n::clock::Time (0xdeadf0e));
+
+        BOOST_CHECK_EQUAL(I2n::clock::median (ts), I2n::clock::Time (0xf00d));
+    }
+
+BOOST_AUTO_TEST_SUITE_END() /* [Clock] */
 
 BOOST_AUTO_TEST_SUITE_END()