diff options
Diffstat (limited to 'src/common/iso_8601.cc')
-rw-r--r-- | src/common/iso_8601.cc | 205 |
1 files changed, 205 insertions, 0 deletions
diff --git a/src/common/iso_8601.cc b/src/common/iso_8601.cc new file mode 100644 index 000000000..21601379c --- /dev/null +++ b/src/common/iso_8601.cc @@ -0,0 +1,205 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include <iomanip> +#include <sstream> + +#include "iso_8601.h" +#include "include/timegm.h" +#include "include/ceph_assert.h" + +namespace ceph { +using std::chrono::duration_cast; +using std::chrono::nanoseconds; +using std::chrono::seconds; +using std::setw; +using std::size_t; +using std::stringstream; +using std::string; +using std::uint16_t; + +using boost::none; +using boost::optional; +using std::string_view; + +using ceph::real_clock; +using ceph::real_time; + +using sriter = string_view::const_iterator; + +namespace { +// This assumes a contiguous block of numbers in the correct order. +uint16_t digit(char c) { + if (!(c >= '0' && c <= '9')) { + throw std::invalid_argument("Not a digit."); + } + return static_cast<uint16_t>(c - '0'); +} + +optional<real_time> calculate(const tm& t, uint32_t n = 0) { + ceph_assert(n < 1000000000); + time_t tt = internal_timegm(&t); + if (tt == static_cast<time_t>(-1)) { + return none; + } + + return boost::make_optional<real_time>(real_clock::from_time_t(tt) + + nanoseconds(n)); +} +} + +optional<real_time> from_iso_8601(const string_view s, + const bool ws_terminates) noexcept { + auto end = s.cend(); + auto read_digit = [end](sriter& c) mutable { + if (c == end) { + throw std::invalid_argument("End of input."); + } + auto f = digit(*c); + ++c; + return f; + }; + + auto read_digits = [&read_digit](sriter& c, std::size_t n) { + auto v = 0ULL; + for (auto i = 0U; i < n; ++i) { + auto d = read_digit(c); + v = (10ULL * v) + d; + } + return v; + }; + auto partial_date = [end, ws_terminates](sriter& c) { + return (c == end || (ws_terminates && std::isspace(*c))); + }; + auto time_end = [end, ws_terminates](sriter& c) { + return (c != end && *c == 'Z' && + ((c + 1) == end || + (ws_terminates && std::isspace(*(c + 1))))); + }; + auto consume_delimiter = [end](sriter& c, char q) { + if (c == end || *c != q) { + throw std::invalid_argument("Expected delimiter not found."); + } else { + ++c; + } + }; + + tm t = { 0, // tm_sec + 0, // tm_min + 0, // tm_hour + 1, // tm_mday + 0, // tm_mon + 70, // tm_year + 0, // tm_wday + 0, // tm_yday + 0, // tm_isdst + }; + try { + auto c = s.cbegin(); + { + auto y = read_digits(c, 4); + if (y < 1970) { + return none; + } + t.tm_year = y - 1900; + } + if (partial_date(c)) { + return calculate(t, 0); + } + + consume_delimiter(c, '-'); + t.tm_mon = (read_digits(c, 2) - 1); + if (partial_date(c)) { + return calculate(t); + } + consume_delimiter(c, '-'); + t.tm_mday = read_digits(c, 2); + if (partial_date(c)) { + return calculate(t); + } + consume_delimiter(c, 'T'); + t.tm_hour = read_digits(c, 2); + if (time_end(c)) { + return calculate(t); + } + consume_delimiter(c, ':'); + t.tm_min = read_digits(c, 2); + if (time_end(c)) { + return calculate(t); + } + consume_delimiter(c, ':'); + t.tm_sec = read_digits(c, 2); + if (time_end(c)) { + return calculate(t); + } + consume_delimiter(c, '.'); + + auto n = 0UL; + auto multiplier = 100000000UL; + for (auto i = 0U; i < 9U; ++i) { + auto d = read_digit(c); + n += d * multiplier; + multiplier /= 10; + if (time_end(c)) { + return calculate(t, n); + } + } + } catch (std::invalid_argument& e) { + // fallthrough + } + return none; +} + +string to_iso_8601(const real_time t, + const iso_8601_format f, + std::string_view date_separator, + std::string_view time_separator) noexcept { + ceph_assert(f >= iso_8601_format::Y && + f <= iso_8601_format::YMDhmsn); + stringstream out(std::ios_base::out); + + auto sec = real_clock::to_time_t(t); + auto nsec = duration_cast<nanoseconds>(t.time_since_epoch() % + seconds(1)).count(); + + struct tm bt; + gmtime_r(&sec, &bt); + out.fill('0'); + + out << 1900 + bt.tm_year; + if (f == iso_8601_format::Y) { + return out.str(); + } + + out << date_separator << setw(2) << bt.tm_mon + 1; + if (f == iso_8601_format::YM) { + return out.str(); + } + + out << date_separator << setw(2) << bt.tm_mday; + if (f == iso_8601_format::YMD) { + return out.str(); + } + + out << 'T' << setw(2) << bt.tm_hour; + if (f == iso_8601_format::YMDh) { + out << 'Z'; + return out.str(); + } + + out << time_separator << setw(2) << bt.tm_min; + if (f == iso_8601_format::YMDhm) { + out << 'Z'; + return out.str(); + } + + out << time_separator << setw(2) << bt.tm_sec; + if (f == iso_8601_format::YMDhms) { + out << 'Z'; + return out.str(); + } + out << '.' << setw(9) << nsec << 'Z'; + return out.str(); +} + +} |