// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- // vim: ts=8 sw=2 smarttab #include "iso_8601.h" #include "include/timegm.h" #include 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 boost::string_ref; using ceph::real_clock; using ceph::real_time; using sriter = string_ref::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(c - '0'); } optional calculate(const tm& t, uint32_t n = 0) { ceph_assert(n < 1000000000); time_t tt = internal_timegm(&t); if (tt == static_cast(-1)) { return none; } return boost::make_optional(real_clock::from_time_t(tt) + nanoseconds(n)); } } optional from_iso_8601(const string_ref 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 = [end, &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) 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(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 << '-' << setw(2) << bt.tm_mon + 1; if (f == iso_8601_format::YM) { return out.str(); } out << '-' << 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 << ':' << setw(2) << bt.tm_min; if (f == iso_8601_format::YMDhm) { out << 'Z'; return out.str(); } out << ':' << setw(2) << bt.tm_sec; if (f == iso_8601_format::YMDhms) { out << 'Z'; return out.str(); } out << '.' << setw(9) << nsec << 'Z'; return out.str(); } }