aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2016-01-05 10:00:32 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2016-01-05 10:00:32 +0200
commit24e05cd878553ad2379d513951f06ec984b08594 (patch)
tree28c683ef950e39faeeabbf1cacf458dd87ecd9e0
parentb21868670b31121548d6c6df4f4d3221531ebada (diff)
Generalize timestamp printing code
-rw-r--r--butl/timestamp42
-rw-r--r--butl/timestamp.cxx154
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 <time.h> 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 %[<d>N] where <d> 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 '<unknown>' and '<nonexistent>', 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 %[<d>U] (microseconds) and %[<d>M] (milliseconds).
+ // - make to_stream() a manipulator, similar to put_time()
+ // - support %(N) version for non-optional printing
+ // - support for suffix %[<d>N<s>], 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 <butl/timestamp>
-#include <time.h> // localtime, gmtime, strftime
+#include <time.h> // localtime_r(), gmtime_r()
+#include <errno.h> // EINVAL
+#include <ctime> // tm, strftime()
+#include <iomanip> // put_time()
+#include <cstring> // strlen(), memcpy()
#include <ostream>
#include <system_error>
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 << "<unknown>";
- time_t t (system_clock::to_time_t (ts));
+ if (ts == timestamp_nonexistent)
+ return os << "<nonexistent>";
+ }
- if (t == 0)
- return os << "<nonexistent>";
+ 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 << "<beyond year 9999>";
-
- os << buf;
-
using namespace chrono;
timestamp sec (system_clock::from_time_t (t));
nanoseconds ns (duration_cast<nanoseconds> (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 << "<beyond 9999>";
-
- 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;