From d70f7269cc1d4b389d82af625e092c0adab68ef0 Mon Sep 17 00:00:00 2001 From: Philipp Gesang Date: Tue, 9 Jan 2018 11:52:53 +0100 Subject: [PATCH] add ISO-8601 formatters to timefunc Adds format_iso8601() to create conforming timestamps from broken-down time. Includes overload wrappers for time_t and struct timespec. --- src/timefunc.cpp | 81 +++++++++++++++++++++++++++++++++++++++++++++++- src/timefunc.hxx | 12 ++++++- test/test_timefunc.cpp | 76 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 167 insertions(+), 2 deletions(-) diff --git a/src/timefunc.cpp b/src/timefunc.cpp index 30df7a6..6d4b365 100644 --- a/src/timefunc.cpp +++ b/src/timefunc.cpp @@ -24,6 +24,7 @@ on this file might be covered by the GNU General Public License. * */ +#include #include #include #include @@ -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] */ - diff --git a/src/timefunc.hxx b/src/timefunc.hxx index 09e4e5c..74c16e4 100644 --- a/src/timefunc.hxx +++ b/src/timefunc.hxx @@ -20,7 +20,7 @@ on this file might be covered by the GNU General Public License. /** @file * @brief time related functions. * - * @copyright Copyright © 2001-2008 by Intra2net AG + * @copyright Copyright © 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); diff --git a/test/test_timefunc.cpp b/test/test_timefunc.cpp index 6752042..658950d 100644 --- a/test/test_timefunc.cpp +++ b/test/test_timefunc.cpp @@ -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) -- 1.7.1