add ISO-8601 formatters to timefunc
authorPhilipp Gesang <philipp.gesang@intra2net.com>
Tue, 9 Jan 2018 10:52:53 +0000 (11:52 +0100)
committerThomas Jarosch <thomas.jarosch@intra2net.com>
Wed, 27 Mar 2019 09:31:38 +0000 (10:31 +0100)
Adds format_iso8601() to create conforming timestamps from
broken-down time. Includes overload wrappers for time_t and
struct timespec.

src/timefunc.cpp
src/timefunc.hxx
test/test_timefunc.cpp

index 30df7a6..6d4b365 100644 (file)
@@ -24,6 +24,7 @@ on this file might be covered by the GNU General Public License.
  *
  */
 
+#include <errno.h>
 #include <string>
 #include <sstream>
 #include <iostream>
@@ -771,6 +772,85 @@ bool realtime_clock_gettime(long int& seconds, long int& nano_seconds)
 } // eo realtime_clock_gettime(long int&,long int&)
 
 
+static const char *const iso8601_fmt_d    = "%F";
+static const char *const iso8601_fmt_t    = "%T";
+static const char *const iso8601_fmt_tz   = "%TZ%z";
+static const char *const iso8601_fmt_dt   = "%FT%T";
+static const char *const iso8601_fmt_dtz  = "%FT%TZ%z";
+
+/**
+ * @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 heeded 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)
+{
+#   define ISO8601_BUFSIZE 27   /* max: -YYYY-MM-DDThh:mm:ssZ+zzzz ⇒ 26 */
+    char buf [ISO8601_BUFSIZE] = { 0 };
+    const char *format = NULL;
+
+    if (date) {
+        if (time) {
+            if (tz) {
+                format = iso8601_fmt_dtz;
+            } else {
+                format = iso8601_fmt_dt;
+            }
+        } else {
+            format = iso8601_fmt_d;
+        }
+    } else if (time && tz) {
+        format = iso8601_fmt_tz;
+    } else {
+        format = iso8601_fmt_t; /* default to %T */
+    }
+
+    const size_t n = strftime (buf, ISO8601_BUFSIZE, format, &tm);
+
+    buf [n] = '\0';
+
+    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);
+}
+
+
 namespace I2n {
 
 namespace clock {
@@ -972,4 +1052,3 @@ namespace clock {
 } /* [namespace clock] */
 
 } /* [namespace I2n] */
-
index 09e4e5c..74c16e4 100644 (file)
@@ -20,7 +20,7 @@ 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
@@ -54,6 +54,16 @@ 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); }
+
 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);
index 6752042..658950d 100644 (file)
@@ -461,6 +461,82 @@ 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)