document timefunc api
[libi2ncommon] / src / timefunc.cpp
index bd1049a..0883055 100644 (file)
@@ -1,24 +1,60 @@
-/***************************************************************************
-                          timecvt.cpp  -  description
-                             -------------------
-    begin                : Fri May 11 2001
-    copyright            : (C) 2001 by STYLETEC
-    email                : info@styletec.de
- ***************************************************************************/
+/*
+The software in this package is distributed under the GNU General
+Public License version 2 (with a special exception described below).
 
+A copy of GNU General Public License (GPL) is included in this distribution,
+in the file COPYING.GPL.
+
+As a special exception, if other files instantiate templates or use macros
+or inline functions from this file, or you compile this file and link it
+with other works to produce a work based on this file, this file
+does not by itself cause the resulting work to be covered
+by the GNU General Public License.
+
+However the source code for this file must still be made available
+in accordance with section (3) of the GNU General Public License.
+
+This exception does not invalidate any other reasons why a work based
+on this file might be covered by the GNU General Public License.
+*/
+/** @file
+ * @brief time related functions.
+ *
+ * @copyright Copyright © 2001-2008 by Intra2net AG
+ *
+ */
+
+#include <cstdio>
+#include <errno.h>
 #include <string>
 #include <sstream>
 #include <iostream>
 #include <iomanip>
 #include <bitset>
 #include <stdexcept>
+#include <iterator>
+#include <algorithm>
 
 #include <time.h>
+#include <unistd.h>
+#include <string.h>
 #include <sys/timeb.h>
 
 #include <timefunc.hxx>
 #include <i18n.h>
 
+
+// define missing POSIX.1b constants...
+
+#ifndef CLOCK_REALTIME
+#define CLOCK_REALTIME 0
+#endif
+#ifndef CLOCK_MONOTONIC
+#define CLOCK_MONOTONIC 1
+#endif
+
+
+
 using namespace std;
 
 double prec_time(void)
@@ -34,9 +70,10 @@ double prec_time(void)
 }
 
 // converts ISO-DATE: 2003-06-13
-int date_to_seconds(const std::string &date)
+time_t date_to_seconds(const std::string &date)
 {
-    int rtn = -1, year = -1, month = -1, day = -1;
+    time_t rtn = 0;
+    int year = -1, month = -1, day = -1;
     
     string::size_type pos = date.find("-");
     if (pos == string::npos)
@@ -61,9 +98,9 @@ int date_to_seconds(const std::string &date)
     
     if (year < 0 || month == -1 || day == -1)
         return rtn;
-    
+
     struct tm tm_struct;
-    bzero (&tm_struct, sizeof(struct tm));
+    memset(&tm_struct, 0, sizeof(struct tm));
     tm_struct.tm_year = year;
     tm_struct.tm_mon = month;
     tm_struct.tm_mday = day;
@@ -80,16 +117,11 @@ string make_nice_time(int seconds)
     int days=seconds/86400;
     seconds%=86400;
 
-    int hours=seconds/3600;
-    seconds%=3600;
-
-    int minutes=seconds/60;
-    seconds%=60;
-
-    if (days==1)
-        out << i18n("1 day") << ", ";
-    else if (days>1)
-        out << days << ' ' << i18n("days") << ", ";
+    int hours,minutes;
+    split_daysec(seconds,&hours,&minutes,&seconds);
+    
+    if (days>0)
+        out << days << " " << i18n_plural("day", "days", days) << ", ";
 
     out << setfill('0');
     out << setw(2) << hours << ':' << setw(2) << minutes << ':' << setw(2) << seconds;
@@ -97,13 +129,27 @@ string make_nice_time(int seconds)
     return out.str();
 }
 
-string format_full_time(int seconds)
+string format_full_time(time_t seconds)
 {
     char buf[50];
     memset (buf, 0, 50);
-    struct tm *ta = localtime ((time_t *)&seconds);
+    struct tm ta;
+    if (localtime_r((time_t *)&seconds, &ta) == NULL)
+        memset (&ta, 0, sizeof(struct tm));
 
-    strftime (buf, 49, "%d.%m.%Y %H:%M", ta);
+    strftime (buf, 49, "%d.%m.%Y %H:%M", &ta);
+    return string(buf);
+}
+
+string format_date(time_t seconds)
+{
+    char buf[50];
+    memset (buf, 0, 50);
+    struct tm ta;
+    if (localtime_r((time_t *)&seconds, &ta) == NULL)
+        memset (&ta, 0, sizeof(struct tm));
+
+    strftime (buf, 49, "%d.%m.%Y", &ta);
     return string(buf);
 }
 
@@ -126,7 +172,32 @@ void seconds_to_hour_minute(int seconds, int *hour, int *minute)
     }
 }
 
-std::string output_hour_minute(int hour, int minute, bool h_for_00)
+/**
+    * Split seconds into hours, minutes and seconds
+    * @param [in] daysec Seconds since start of day
+    * @param [out] outhours hours
+    * @param [out] outminutes minutes
+    * @param [out] outseconds seconds
+    */
+void split_daysec(int daysec, int *outhours, int *outminutes, int *outseconds)
+{
+    int hours=daysec/3600;
+    daysec%=3600;
+
+    int minutes=daysec/60;
+    daysec%=60;
+
+    if (outhours)
+        *outhours=hours;
+
+    if (outminutes)
+        *outminutes=minutes;
+
+    if (outseconds)
+        *outseconds=daysec;
+}
+
+std::string output_hour_minute(int hour, int minute, bool h_for_00, int seconds)
 {
     ostringstream out;
     
@@ -134,169 +205,1137 @@ std::string output_hour_minute(int hour, int minute, bool h_for_00)
         out << '0';
     out << hour;
     
-    if (!h_for_00 || minute != 0)
+    if (!h_for_00 || minute != 0 || seconds > 0)
     {
         out << ':';
-        if (minute > 0 && minute < 10)
+        if (minute >= 0 && minute < 10)
             out << '0';
         out << minute;
     }
     else
         out << 'h';
 
+    if (seconds > 0)
+    {
+        out << ':';
+        if (seconds > 0 && seconds < 10)
+            out << '0';
+        out << seconds;
+    }
+
     return out.str();
 }
 
-WEEK::WEEK(const std::string& daystring)
+string get_month_name(unsigned char month)
 {
-    int len=daystring.length();
-    for (int p=0; p < len; p++)
-    {
-        char nr=daystring[p];
-        istringstream c(&nr);
-        int wnr=-1;
-        if (!(c >> wnr) || wnr<0 || wnr >6)
-            throw out_of_range("illegal weekday in "+daystring);
-            
-        days.set(wnr);
+    string rtn;
+    switch(month) {
+        case 1:
+            rtn = i18n("January");
+            break;
+        case 2:
+            rtn = i18n("February");
+            break;
+        case 3:
+            rtn = i18n("March");
+            break;
+        case 4:
+            rtn = i18n("April");
+            break;
+        case 5:
+            rtn = i18n("May");
+            break;
+        case 6:
+            rtn = i18n("June");
+            break;
+        case 7:
+            rtn = i18n("July");
+            break;
+        case 8:
+            rtn = i18n("August");
+            break;
+        case 9:
+            rtn = i18n("September");
+            break;
+        case 10:
+            rtn = i18n("October");
+            break;
+        case 11:
+            rtn = i18n("November");
+            break;
+        case 12:
+            rtn = i18n("December");
+            break;
+        default:
+            {
+                ostringstream out;
+                out << i18n("Illegal month:") << " " << month;
+                rtn = out.str();
+            }
     }
+
+    return rtn;
 }
 
-std::string WEEK::get_daystring() const
+
+/*
+** implementaion of Interval
+*/
+
+
+/**
+ * @brief clears the interval (make it empty).
+ */
+void Interval::clear()
 {
-    ostringstream out;
-    for (int i = 0; i < 7; i++)
-        if (days[i])
-            out << i;
+    m_lower_bound = m_upper_bound = 0;
+} // eo Interval::clear()
+
+
+/**
+ * @brief tests if there is some overlapping with another interval
+ * @param other the other interval
+ * @return @a true if the two intervals have a non empty intersection.
+ */
+bool Interval::intersects(const Interval& other) const
+{
+    return
+        //  // other start within this:
+        (other.m_lower_bound >= m_lower_bound and other.m_lower_bound < m_upper_bound )
+        // // other end within this:
+        or (other.m_upper_bound > m_lower_bound and other.m_upper_bound <= m_upper_bound )
+        //  // other contains this
+        or (other.m_lower_bound <= m_lower_bound and other.m_upper_bound >= m_upper_bound )
+    ;
+} // eo Interval::intersects(const Interval&)
+
+
+/**
+ * @brief tests if the current interval (fully) contains another one.
+ * @param other the other interval.
+ * @return @a true if the current interval fully contains the other interval.
+ */
+bool Interval::contains(const Interval& other) const
+{
+    return  (other.m_lower_bound >= m_lower_bound)
+        and (other.m_upper_bound <= m_upper_bound)
+    ;
+} // eo Interval::contains(const Interval& other) const
+
+
+/*
+** implementation of Intervals:
+*/
+
+
+Intervals::Intervals()
+{
+} // eo Intervals::Intervals
 
-    return out.str();
-}
 
-std::string WEEK::get_displaystring() const
+void Intervals::clear()
 {
-    string weekdays_str;
+    m_intervals.clear();
+} // eo Intervals::clear()
 
-    // From Monday to Saturday
-    int j;
-    for (int i = 1; i < 7; i++)
+/**
+ * @brief tests if one of the intervals of the list intersects with the given interval.
+ * @param other the interval to check for intersection.
+ * @return @a true if there is an intersection.
+ */
+bool Intervals::intersects(const Interval& other) const
+{
+    for(const_iterator it= begin();
+        it != end();
+        ++it)
     {
-        if (days[i])
+        if ( it->intersects(other) )
         {
-            if (!weekdays_str.empty())
-                weekdays_str += ", ";
+            return true;
+        }
+    }
+    return false;
+} // eo Intervals::intersects(const Interval&) const
+
 
-            weekdays_str += get_day_display(static_cast<WEEKDAY>(i));
+/**
+ * @brief tests if we have at least one intersection with another Intervals instance.
+ * @param other the other instance.
+ * @return @a true if there is an intersection.
+ */
+bool Intervals::intersects(const Intervals& other) const
+{
+    for(const_iterator it= begin();
+        it != end();
+        ++it)
+    {
+        if ( other.intersects( *it ) )
+        {
+            return true;
+        }
+    }
+    return false;
+} // eo Intervals::intersects(const Intervals&) const
 
-            // check if we can group two or more days
-            j = i;
-            while (days[j] && j < 7)
-                j++;
-            j--;
 
-            // Sunday end of week? j -> 7
-            if (j-i > 0 && j == 6 && days[0])
-                j++;
+/**
+ * @brief adds a new interval to the list.
+ * @param new_frame the new interval.
+ *
+ * Adds the interval to the list and joins overlapping intervals.
+ *
+ * @internal complexity O(n).
+ */
+void Intervals::add(const Interval& new_frame)
+{
+    if (not new_frame.is_valid() or new_frame.empty())
+    {
+        // well... we will not insert invalid or empty frames!
+        return;
+    }
+    for (IntervalList::iterator it= m_intervals.begin();
+         it != m_intervals.end();
+         ++it)
+    {
+        Interval& current_frame = *it;
+        if ( new_frame.m_lower_bound > current_frame.m_upper_bound )
+        {
+            // new_frame begins later than current end; go on:
+            continue;
+        }
+        // at this point: the begin of the new frame is less then the current end.
+        // now let's determine how we can insert the new frame:
 
-            if (j-i > 1) 
+        if ( new_frame.m_upper_bound < current_frame.m_lower_bound )
+        {
+            // new disjoint frame; insert it before the current frame:
+            m_intervals.insert( it, new_frame );
+            // and we are done.
+            return;
+        }
+        // at this point: the end of the new frame is >= current begin. 
+        if ( new_frame.m_upper_bound <= current_frame.m_upper_bound )
+        {
+            // the end of the new frame is within our current frame; we need to combine
+            if (new_frame.m_lower_bound < current_frame.m_lower_bound)
             {
-                if (j == 7)
-                    weekdays_str += "-" + get_day_display(SU);
-                else
-                    weekdays_str += "-" + get_day_display(static_cast<WEEKDAY>(j));
+                // the new interval starts earlier; we need to adjust our current frame:
+                current_frame.m_lower_bound = new_frame.m_lower_bound;
+                current_frame.m_changed = true;
+            }
+            // NOTE no "else" part needed since in that case our current frame already
+            // contains the new one!
 
-                i = j;
+            // we are done:
+            return;
+        }
+        // at this point: end of new frame > end of current frame
+        // so we need to extend the current frame; at least the end.
+        // But we need to deal with intersects of following frames... *sigh*
+
+        // first the simple part: let's see if we need to move the start:
+        if ( new_frame.m_lower_bound < current_frame.m_lower_bound)
+        {
+            // yes, we need to move the start:
+            current_frame.m_lower_bound = new_frame.m_lower_bound;
+            current_frame.m_changed= true;
+        }
+
+        // now let's extend the end:
+        current_frame.m_upper_bound = new_frame.m_upper_bound;
+        current_frame.m_changed = true;
+
+        // well... let's walk through the following frames; looking for more joins...:
+        IntervalList::iterator it2 = it;
+        while(      ++(it2=it) != m_intervals.end()
+                and current_frame.m_upper_bound >= it2->m_lower_bound
+           )
+        {
+            Interval next_frame= *it2;
+            if ( current_frame.m_upper_bound < next_frame.m_upper_bound )
+            {
+                // in this case our end is within the next frame.
+                // adjust our end.
+                current_frame.m_upper_bound = next_frame.m_upper_bound;
+            }
+            // and remove the next frame since the current frame contains it (now):
+            m_intervals.erase(it2);
+        }
+        // we are done!
+        return;
+    }
+    // at this point: new frame starts later than the last frame ends
+    // append the new frame:
+    m_intervals.push_back( new_frame );
+} // eo Intervals::add(const Interval&)
+
+
+/**
+ * @brief subtracts a time interval from the list.
+ * @param del_frame the time interval to subtract.
+ *
+ * removes the time interval from the list; cut off parts from or remove existing
+ * intervals if they overlap.
+ *
+ * @internal complexity O(n).
+ */
+void Intervals::sub(const Interval& del_frame)
+{
+    if (not del_frame.is_valid() or del_frame.empty() )
+    {
+        return;
+    }
+    for (IntervalList::iterator it= m_intervals.begin();
+         it != m_intervals.end();
+         )
+    {
+        Interval& current_frame = *it;
+        if ( del_frame.m_lower_bound >= current_frame.m_upper_bound )
+        {
+            // del_frame begins later than current end; go on:
+            ++it;
+            continue;
+        }
+        // at this point: the begin of the del frame is less then the current end.
+        if ( del_frame.m_upper_bound < current_frame.m_lower_bound )
+        {
+            // end is before our start; nothing to do.
+            return;
+        }
+        // at this point: the end of the del frame is >= current begin.
+        if ( del_frame.m_upper_bound < current_frame.m_upper_bound )
+        {
+            // del frame end point is within our interval.
+            if ( del_frame.m_lower_bound > current_frame.m_lower_bound)
+            {
+                // the del frame is within our interval... we need to split:
+                m_intervals.insert(it, Interval( current_frame.m_lower_bound, del_frame.m_lower_bound ) );
             }
+            // adjust start of current frame:
+            if (current_frame.m_lower_bound < del_frame.m_upper_bound)
+            {
+                current_frame.m_lower_bound= del_frame.m_upper_bound;
+                current_frame.m_changed= true;
+            }
+            // and we are done!
+            return;
+        }
+        // at this point the end of the del frame is >= current end
+        if ( del_frame.m_lower_bound > current_frame.m_lower_bound )
+        {
+            // a part of the current interval needs to be preserved..
+            // move the end.
+            current_frame.m_upper_bound= del_frame.m_lower_bound;
+            current_frame.m_changed= true;
+            // and continue with the next interval:
+            ++it;
+            continue;
+        }
+        // at this point; the whole frame needs to be deleted..
+        if ( it == m_intervals.begin())
+        {
+            m_intervals.erase(it);
+            it= m_intervals.begin();
+        }
+        else
+        {
+            IntervalList::iterator it2= it++;
+            m_intervals.erase(it2);
         }
     }
+} // eo Intervals::sub(const Interval&)
+
 
-    // special: sunday
-    if (days[0] && j != 7) 
+/**
+ * @brief returns if we contain an interval.
+ * @param other the interval to check.
+ * @return @a true if we cover the given interval, too.
+ */
+bool Intervals::contains(const Interval& other) const
+{
+    for(const_iterator it= begin();
+        it != end();
+        ++it)
     {
-        if (!weekdays_str.empty())
-            weekdays_str += ", ";
+        if ( it->contains( other ))
+        {
+            return true;
+        }
+    }
+    return false;
+} // eo Intervals::contains(const Interval&) const
+
 
-        weekdays_str += get_day_display(SU);
+/**
+ * @brief returns if we contain an exact interval.
+ * @param other the interval to check.
+ * @return @a true if we axactly contains the given interval.
+ *
+ * @note thsi differs from contain in the way, that we return only @a true
+ * iff we have the given interval in our list; not only cover it.
+ */
+bool Intervals::contains_exact(const Interval& other) const
+{
+    for(const_iterator it= begin();
+        it != end();
+        ++it)
+    {
+        if ( *it == other)
+        {
+            return true;
+        }
     }
+    return false;
+} // eo Intervals::contains_exact(const Interval&)const
 
-    return weekdays_str;
-}
 
-std::string WEEK::get_netfilterstring() const
+/**
+ * @brief returns if we contain another interval combination.
+ * @param other the intervals to check.
+ * @return @a true if we cover the given intervals, too.
+ *
+ * @internal we rely on the fact that the lists are sorted and contain
+ * disjoint intervals.
+ *
+ * So this method has a complexity of O(n).
+ */
+bool Intervals::contains(const Intervals& other) const
 {
-    string out;
-    for (int i = 0; i < 7; i++)
-        if (days[i])
+    const_iterator my_it= begin();
+    const_iterator other_it= other.begin();
+    while( my_it != end() and other_it!= other.end() )
+    {
+        // seek the first interval which contains the lower bound of the current other interval
+        while (my_it != end()
+               and my_it->m_lower_bound > other_it->m_lower_bound
+               and other_it->m_lower_bound >= my_it->m_upper_bound
+              )
         {
-            if (!out.empty())
-                out+=","; 
-            out+=get_english_display(static_cast<WEEKDAY>(i));;
+            ++my_it;
         }
-            
-    return out;
+        if (my_it == end())
+        {
+            break;
+        }
+        if (not my_it->contains( *other_it ))
+        {
+            // if we don't contain the current other; we're done:
+            return false;
+        }
+        //else check the next other interval:
+        ++other_it;
+    }
+    return (other_it == other.end());
+} // eo Intervals::contains(const Intervals&) const
+
+
+/**
+ * @brief combines to interval combinates for equality
+ * @param other the other instance.
+ * @return @a true if the other is equal to the current.
+ *
+ * @internal since the lists are sorted, we compare the interval lists.
+ * Thus we have a complexity of O(n).
+ */
+bool Intervals::operator==(const Intervals& other) const
+{
+    // since we keep sorted lists: just compare the lists :-)
+    return m_intervals == other.m_intervals;
+} // eo Intervals::operator==(const Intervals&)
+
+
+Intervals& Intervals::operator+=(const Interval& other)
+{
+    add(other);
+    return *this;
+} // eo operator+=(const Interval&)
+
+
+Intervals& Intervals::operator-=(const Interval& other)
+{
+    sub(other);
+    return *this;
+} // eo operator-=(const Interval&)
+
+
+/**
+ * @brief adds the intervals of a second instance to us.
+ * @param other the other instance.
+ * @return self reference (allow chaining).
+ *
+ * @internal since we do simple loops over the other and our intervals
+ * we have a complexity of O(n^2).
+ *
+ * @todo optimize if complexity becomes a problem.
+ */
+Intervals& Intervals::operator+=(const Intervals& other)
+{
+    for(const_iterator it= other.begin();
+        it != other.end();
+        ++it)
+    {
+        add( *it );
+    }
+    return *this;
+} // eo operator+=(const Intervals&)
+
+
+/**
+ * @brief subtracts the intervals of a second instance from us.
+ * @param other the other instance.
+ * @return self reference (allow chaining).
+ *
+ * @internal since we do simple loops over the other and our intervals
+ * we have a complexity of O(n^2).
+ *
+ * @todo optimize if complexity becomes a problem.
+ */
+Intervals& Intervals::operator-=(const Intervals& other)
+{
+    if (&other == this)
+    {
+        m_intervals.clear();
+    }
+    else
+    {
+        for(const_iterator it= other.begin();
+            it != other.end();
+            ++it)
+        {
+            sub( *it );
+        }
+    }
+    return *this;
+} // eo operator-=(const Intervals&)
+
+
+
+/*
+** clock funcs:
+*/
+
+
+/**
+ * @brief fetches the value from the monotonic clock source.
+ * @param[out] seconds the seconds.
+ * @param[out] nano_seconds the nano seconds.
+ * @return @a true if the clock was successfully read.
+ */
+bool monotonic_clock_gettime(long int& seconds, long int& nano_seconds)
+{
+    struct timespec tp[1];
+    int res= clock_gettime (CLOCK_MONOTONIC, tp);
+    if (0 == res)
+    {
+        seconds= tp->tv_sec;
+        nano_seconds= tp->tv_nsec;
+    }
+    return (res==0);
+} // eo monotonic_clock_gettime(long int&,long int&)
+
+
+/**
+ * @brief fetches the value from the monotonic clock source.
+ * @return the time since system start in nanoseconds, 0 if read was unsuccessful
+ */
+long long monotonic_clock_gettime_nano()
+{
+    long int seconds;
+    long int nano_seconds;
+    long long nano=0;
+
+    if (monotonic_clock_gettime(seconds,nano_seconds))
+    {
+        nano=seconds;
+        nano*=1000000000LL;
+        nano+=nano_seconds;
+    }
+
+    return nano;
 }
 
-std::string WEEK::get_day_display(WEEKDAY day)
+/**
+ * @brief fetches the value from the monotonic clock source.
+ * @param[out] seconds the seconds.
+ * @param[out] nano_seconds the nano seconds.
+ * @return @a true if the clock was successfully read.
+ */
+bool realtime_clock_gettime(long int& seconds, long int& nano_seconds)
 {
-    string weekday_str;
+    struct timespec tp[1];
+    int res= clock_gettime(CLOCK_REALTIME, tp);
+    if (0 == res)
+    {
+        seconds= tp->tv_sec;
+        nano_seconds= tp->tv_nsec;
+    }
+    return (res==0);
+} // eo realtime_clock_gettime(long int&,long int&)
 
-    switch (day) {
-        case MO:
-            weekday_str = i18n("Mon");
-            break;
-        case TU:
-            weekday_str = i18n("Tue");
-            break;
-        case WE:
-            weekday_str = i18n("Wed");
-            break;
-        case TH:
-            weekday_str = i18n("Thu");
-            break;
-        case FR:
-            weekday_str = i18n("Fri");
-            break;
-        case SA:
-            weekday_str = i18n("Sat");
-            break;
-        case SU:
-            weekday_str = i18n("Sun");
-            break;
-        default:
-            break;
+
+/*
+ * 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);
     }
 
-    return weekday_str;
+    /*
+     * 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);
 }
 
-std::string WEEK::get_english_display(WEEKDAY day)
+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)
 {
-    string weekday_str;
+    time_breakdown_fn breakdown = utc ? gmtime_r : localtime_r;
+    struct tm tm;
 
-    switch (day) {
-        case MO:
-            weekday_str = "Mon";
-            break;
-        case TU:
-            weekday_str = "Tue";
-            break;
-        case WE:
-            weekday_str = "Wed";
-            break;
-        case TH:
-            weekday_str = "Thu";
-            break;
-        case FR:
-            weekday_str = "Fri";
-            break;
-        case SA:
-            weekday_str = "Sat";
+    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;
-        case SU:
-            weekday_str = "Sun";
+        }
+        /*
+         * 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:
+        }
+        default: {
             break;
+        }
     }
 
-    return weekday_str;
+    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;
+    }
+
+    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); }
+
+    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); }
+
+    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;
+    }
+
+    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] */