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 | 42 +++++++++++++-- butl/timestamp.cxx | 154 ++++++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 162 insertions(+), 34 deletions(-) diff --git a/butl/timestamp b/butl/timestamp index 77af4f1..e68a59f 100644 --- a/butl/timestamp +++ b/butl/timestamp @@ -30,7 +30,7 @@ namespace butl // using std::chrono::system_clock; - // Note that uninitialized timestamp has the timestamp_nonexistent + // Note that the default-initialized timestamp has the timestamp_nonexistent // value. // using timestamp = system_clock::time_point; @@ -41,11 +41,45 @@ namespace butl const timestamp timestamp_unknown {duration {-1}}; const timestamp timestamp_nonexistent {duration {0}}; - // Human-readable representation. Note that these operators - // may throw std::system_error. + // Human-readable representation. By default the timestamp is printed by + // localtime_r() in the local timezone, so tzset() from should be + // called prior to using the corresponding operator or the to_stream() + // function (normally from main() or equivalent). + // + // The format argument in the to_stream() function is the put_time() format + // string except that it also supports the nanoseconds conversion specifier + // in the form %[N] where is the optional single delimiter character, + // for example '.'. If the nanoseconds part is 0, then it is not printed (nor + // the delimiter character). + // + // The special argument in the to_stream() function indicates whether the + // special timestamp_unknown and timestamp_nonexistent values should be + // printed as '' and '', respectively. + // + // The local argument in the to_stream() function indicates whether to use + // localtime_r() or gmtime_r(). + // + // Note also that these operators/function may throw std::system_error. + // + // Potential improvements: + // - add flag to to_stream() to use + // - support %[U] (microseconds) and %[M] (milliseconds). + // - make to_stream() a manipulator, similar to put_time() + // - support %(N) version for non-optional printing + // - support for suffix %[N], for example %[N nsec] // std::ostream& - operator<< (std::ostream&, const timestamp&); + to_stream (std::ostream&, + const timestamp&, + const char* format, + bool special, + bool local); + + inline std::ostream& + operator<< (std::ostream& os, const timestamp& ts) + { + return to_stream (os, ts, "%Y-%m-%d %H:%M:%S%[.N]", true, true); + } std::ostream& operator<< (std::ostream&, const duration&); 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