} // eo realtime_clock_gettime(long int&,long int&)
-static const char *const iso8601_fmt_d = "%F";
+/*
+ * 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.
+ */
+static const char *const iso8601_fmt_d = "%4Y-%m-%d";
static const char *const iso8601_fmt_t = "%T";
static const char *const iso8601_fmt_tz = "%TZ%z";
-static const char *const iso8601_fmt_dt = "%FT%T";
-static const char *const iso8601_fmt_dtz = "%FT%TZ%z";
+static const char *const iso8601_fmt_dt = "%4Y-%m-%dT%T";
+static const char *const iso8601_fmt_dtz = "%4Y-%m-%dT%TZ%z";
-/**
- * @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)
+static inline const char *
+pick_iso8601_fmt (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) {
format = iso8601_fmt_t; /* default to %T */
}
- const size_t n = strftime (buf, ISO8601_BUFSIZE, format, &tm);
+ return format;
+}
+
+namespace {
+
+ /*
+ * max: -YYYYYYYYYY-MM-DDThh:mm:ssZ+zzzz ⇒ 32
+ * That is assuming the year in broken down time is an int we
+ * need to reserve ten decimal places.
+ */
+// static_assert (sizeof (((struct tm *)NULL)->tm_year) == 4);
+ static const size_t iso8601_bufsize = 33;
+
+ 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 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)
+{
+ struct tm tmp;
+ char buf [iso8601_bufsize] = { 0 };
+ char *start = &buf [0];
+ const char *format = pick_iso8601_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 her
+ * is always one character shorter.
+ * */
+ const size_t n = strftime (start, iso8601_bufsize-1, format, &tmp);
- buf [n] = '\0';
+ buf [n+1] = '\0';
return std::string (buf);
}
-
typedef struct tm * (*time_breakdown_fn) (const time_t *, struct tm *);
/**
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 = pick_iso8601_fmt (date, time, tz);
+ 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;
+}
+
namespace I2n {
, err (0)
{ }
+ Time::Time (const struct tm &tm,
+ const enum type::id id,
+ const enum type::variant var) NOEXCEPT
+ {
+ struct tm tmp_tm; /* dummy for mktime(3) */
+ Time tmp_time;
+
+ memcpy (&tmp_tm, &tm, sizeof (tmp_tm));
+ tmp_time = Time (mktime (&tmp_tm), 0l, id, var);
+
+ this->swap (tmp_time);
+ }
+
int64_t
Time::as_nanosec (void) const NOEXCEPT
{
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;
+ }
+
+ return Time (*tm, id, var);
+ }
+
} /* [namespace clock] */
} /* [namespace I2n] */
const bool tz=true)
{ return format_iso8601 (ts.tv_sec, utc, date, time, tz); }
+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);
, err (err)
{ this->carry_nsec (); }
+ Time (const struct tm &tm,
+ const enum type::id id = type::mono,
+ const enum type::variant var = type::dflt) NOEXCEPT;
+
/* value read access *************************************************/
public:
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;
+
} /* [namespace clock] */
} /* [namespace I2n] */
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");
+
+ boost::optional<I2n::clock::Time> t1 = I2n::clock::time_of_iso8601 (in1);
+ boost::optional<I2n::clock::Time> t2 = I2n::clock::time_of_iso8601 (in2);
+
+ BOOST_CHECK(t1);
+ BOOST_CHECK(t2);
+
+ BOOST_CHECK_EQUAL(*t1->format_iso8601 (), in1);
+ 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");
+
+ boost::optional<I2n::clock::Time> t1 = I2n::clock::time_of_iso8601 (in1);
+ boost::optional<I2n::clock::Time> t2 = I2n::clock::time_of_iso8601 (in2);
+
+ BOOST_CHECK(t1);
+ BOOST_CHECK(t2);
+
+ BOOST_CHECK_EQUAL(*t1->format_iso8601 (), in1);
+ BOOST_CHECK_EQUAL(*t2->format_iso8601 (), in2);
+ }
+
+ BOOST_AUTO_TEST_CASE(FromString_iso8601_partial)
+ {
+ const std::string in1 ("2018-11-11T11:11:11");
+ const std::string in2 ("2018-11-11");
+ const std::string in3 ("11:11:11");
+
+ 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::optional<I2n::clock::Time> t3 =
+ I2n::clock::time_of_iso8601 (in3, false, true, false);
+
+ BOOST_CHECK(t1);
+ BOOST_CHECK(t2);
+ BOOST_CHECK(t3);
+ /*
+ * 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_CHECK_EQUAL(*t3->format_iso8601 (true, false, true, false), in3);
+ }
+
BOOST_AUTO_TEST_SUITE_END() /* [Clock] */
BOOST_AUTO_TEST_SUITE_END()