From 24e05cd878553ad2379d513951f06ec984b08594 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Tue, 5 Jan 2016 10:00:32 +0200 Subject: Generalize timestamp printing code --- butl/timestamp.cxx | 154 ++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 124 insertions(+), 30 deletions(-) (limited to 'butl/timestamp.cxx') diff --git a/butl/timestamp.cxx b/butl/timestamp.cxx index f9f5455..8c53f60 100644 --- a/butl/timestamp.cxx +++ b/butl/timestamp.cxx @@ -4,49 +4,149 @@ #include -#include // localtime, gmtime, strftime +#include // localtime_r(), gmtime_r() +#include // EINVAL +#include // tm, strftime() +#include // put_time() +#include // strlen(), memcpy() #include #include using namespace std; +// libstdc++ prior to GCC 5 does not have std::put_time() so we have to invent +// our own. Detecting the "prior to GCC 5" condition, however, is not easy: +// libstdc++ is used by other compilers (e.g., Clang) so we cannot just use +// __GNUC__. There is __GLIBCXX__ but it is a date which is updated with +// every release, including bugfixes (so, there can be some 4.7.X release with +// a date greater than 5.0.0). +// +// So what we going to do here is "offer" our implementation and let the ADL +// pick one. If there is std::put_time(), then it will be preferred because +// of the std::tm argument. +// +#ifdef __GLIBCXX__ +namespace details +{ + struct put_time_data + { + const std::tm* tm; + const char* fmt; + }; + + inline put_time_data + put_time (const std::tm* tm, const char* fmt) + { + return put_time_data {tm, fmt}; + } + + inline ostream& + operator<< (ostream& os, const put_time_data& d) + { + char buf[256]; + if (strftime (buf, sizeof (buf), d.fmt, d.tm) != 0) + os << buf; + else + os.setstate (ostream::badbit); + return os; + } +} + +using namespace details; +#endif + namespace butl { ostream& - operator<< (ostream& os, const timestamp& ts) + to_stream (ostream& os, + const timestamp& ts, + const char* format, + bool special, + bool local) { - // @@ replace with put_time() - // + if (special) + { + if (ts == timestamp_unknown) + return os << ""; - time_t t (system_clock::to_time_t (ts)); + if (ts == timestamp_nonexistent) + return os << ""; + } - if (t == 0) - return os << ""; + time_t t (system_clock::to_time_t (ts)); std::tm tm; - if (localtime_r (&t, &tm) == nullptr) + if ((local ? localtime_r (&t, &tm) : gmtime_r (&t, &tm)) == nullptr) throw system_error (errno, system_category ()); - // If year is greater than 9999, we will overflow. - // - char buf[20]; // YYYY-MM-DD HH:MM:SS\0 - if (strftime (buf, sizeof (buf), "%Y-%m-%d %H:%M:%S", &tm) == 0) - return os << ""; - - os << buf; - using namespace chrono; timestamp sec (system_clock::from_time_t (t)); nanoseconds ns (duration_cast (ts - sec)); - if (ns != nanoseconds::zero ()) + char fmt[256]; + size_t n (strlen (format)); + if (n + 1 > sizeof (fmt)) + throw system_error (EINVAL, system_category ()); + memcpy (fmt, format, n + 1); + + // Chunk the format string into fragments that we feed to put_time() and + // those that we handle ourselves. Watch out for the escapes (%%). + // + size_t i (0), j (0); // put_time()'s range. + for (; j != n; ++j) { - os << '.'; - os.width (9); - os.fill ('0'); - os << ns.count (); + if (fmt[j] == '%' && j + 1 != n) + { + if (fmt[j + 1] == '[') + { + // Our fragment. First see if we need to call put_time(). + // + if (i != j) + { + fmt[j] = '\0'; + if (!(os << put_time (&tm, fmt + i))) + return os; + } + + j += 2; // Character after '['. + if (j == n) + throw system_error (EINVAL, system_category ()); + + char d ('\0'); + if (fmt[j] != 'N') + { + d = fmt[j]; + if (++j == n || fmt[j] != 'N') + throw system_error (EINVAL, system_category ()); + } + + if (++j == n || fmt[j] != ']') + throw system_error (EINVAL, system_category ()); + + if (ns != nanoseconds::zero ()) + { + if (d != '\0') + os << d; + os.width (9); + os.fill ('0'); + os << ns.count (); + } + + i = j + 1; // j is incremented in the for-loop header. + } + else + ++j; // Skip % and the next character to handle %%. + } + } + + // Do we need to call put_time() one last time? + // + if (i != j) + { + if (!(os << put_time (&tm, fmt + i))) + return os; } return os; @@ -55,9 +155,6 @@ namespace butl ostream& operator<< (ostream& os, const duration& d) { - // @@ replace with put_time() - // - timestamp ts; // Epoch. ts += d; @@ -102,11 +199,8 @@ namespace butl if (gmtime_r (&t, &tm) == nullptr) throw system_error (errno, system_category ()); - char buf[20]; // YYYY-MM-DD HH:MM:SS\0 - if (strftime (buf, sizeof (buf), fmt, &tm) == 0) - return os << ""; - - os << buf; + if (!(os << put_time (&tm, fmt))) + return os; } using namespace chrono; @@ -125,7 +219,7 @@ namespace butl os << ns.count () << ' ' << unt; } - else if (fmt == 0) + else if (fmt == nullptr) os << '0'; return os; -- cgit v1.1