From 3f24a388407438ff25e5b42bdde61e66beedf0d0 Mon Sep 17 00:00:00 2001 From: codicodi Date: Sun, 9 Oct 2016 23:57:52 +0200 Subject: [PATCH] Thread-safe time formatting (#396) --- include/fmt/time.h | 80 ++++++++++++++++++++++++++++++++++++++++++++++ test/time-test.cc | 24 ++++++++++++++ 2 files changed, 104 insertions(+) diff --git a/include/fmt/time.h b/include/fmt/time.h index 05a503b09fe3..a33c0b3043ea 100644 --- a/include/fmt/time.h +++ b/include/fmt/time.h @@ -13,6 +13,86 @@ namespace fmt { +namespace internal{ +inline null<> localtime_r(...) { return null<>(); } +inline null<> localtime_s(...) { return null<>(); } +inline null<> gmtime_r(...) { return null<>(); } +inline null<> gmtime_s(...) { return null<>(); } +} + +// Thread-safe replacement for std::localtime +inline std::tm localtime(std::time_t time) { + struct LocalTime { + std::time_t time_; + std::tm tm_; + + LocalTime(std::time_t t): time_(t) {} + + bool run() { + using namespace fmt::internal; + return handle(localtime_r(&time_, &tm_)); + } + + bool handle(std::tm* tm) { return tm != 0; } + + bool handle(internal::null<>) { + using namespace fmt::internal; + return fallback(localtime_s(&tm_, &time_)); + } + + bool fallback(int res) { return res == 0; } + + bool fallback(internal::null<>) { + using namespace fmt::internal; + std::tm* tm = std::localtime(&time_); + if (tm != 0) tm_ = *tm; + return tm != 0; + } + }; + LocalTime lt(time); + if (lt.run()) + return lt.tm_; + // Too big time values may be unsupported. + FMT_THROW(format_error("time_t value out of range")); + return std::tm(); +} + +// Thread-safe replacement for std::gmtime +inline std::tm gmtime(std::time_t time) { + struct GMTime { + std::time_t time_; + std::tm tm_; + + GMTime(std::time_t t): time_(t) {} + + bool run() { + using namespace fmt::internal; + return handle(gmtime_r(&time_, &tm_)); + } + + bool handle(std::tm* tm) { return tm != 0; } + + bool handle(internal::null<>) { + using namespace fmt::internal; + return fallback(gmtime_s(&tm_, &time_)); + } + + bool fallback(int res) { return res == 0; } + + bool fallback(internal::null<>) { + std::tm* tm = std::gmtime(&time_); + if (tm != 0) tm_ = *tm; + return tm != 0; + } + }; + GMTime gt(time); + if (gt.run()) + return gt.tm_; + // Too big time values may be unsupported. + FMT_THROW(format_error("time_t value out of range")); + return std::tm(); +} + template <> struct formatter { template diff --git a/test/time-test.cc b/test/time-test.cc index 48df56a05dc4..d66f0cbe4855 100644 --- a/test/time-test.cc +++ b/test/time-test.cc @@ -31,3 +31,27 @@ TEST(TimeTest, GrowBuffer) { TEST(TimeTest, EmptyResult) { EXPECT_EQ("", fmt::format("{}", std::tm())); } + +bool EqualTime(const std::tm &lhs, const std::tm &rhs) { + return lhs.tm_sec == rhs.tm_sec && + lhs.tm_min == rhs.tm_min && + lhs.tm_hour == rhs.tm_hour && + lhs.tm_mday == rhs.tm_mday && + lhs.tm_mon == rhs.tm_mon && + lhs.tm_year == rhs.tm_year && + lhs.tm_wday == rhs.tm_wday && + lhs.tm_yday == rhs.tm_yday && + lhs.tm_isdst == rhs.tm_isdst; +} + +TEST(TimeTest, LocalTime) { + std::time_t t = std::time(0); + std::tm tm = *std::localtime(&t); + EXPECT_TRUE(EqualTime(tm, fmt::localtime(t))); +} + +TEST(TimeTest, GMTime) { + std::time_t t = std::time(0); + std::tm tm = *std::gmtime(&t); + EXPECT_TRUE(EqualTime(tm, fmt::gmtime(t))); +}