From 1e018e65cb74785d6b500bbf8fce82d3e807bf36 Mon Sep 17 00:00:00 2001 From: codicodi Date: Sun, 9 Oct 2016 23:57:52 +0200 Subject: [PATCH] Thread-safe time formatting This adds thread-safe (at least on platforms that provide necessary extensions) replacement functions for std::localtime and std::gmtime. Alternatively they could be placed in a new source file time.cc, but time.h seems so empty right now... --- fmt/time.h | 90 +++++++++++++++++++++++++++++++++++++++++++++++ test/time-test.cc | 24 +++++++++++++ 2 files changed, 114 insertions(+) diff --git a/fmt/time.h b/fmt/time.h index ccdad77f..20a5be7a 100644 --- a/fmt/time.h +++ b/fmt/time.h @@ -13,6 +13,12 @@ #include "fmt/format.h" #include +#ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable: 4702) // unreachable code +# pragma warning(disable: 4996) // "deprecated" functions +#endif + namespace fmt { template void format_arg(BasicFormatter &f, @@ -48,6 +54,90 @@ void format_arg(BasicFormatter &f, } format_str = end + 1; } + +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(fmt::FormatError("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(fmt::FormatError("time_t value out of range")); + return std::tm(); +} +} //namespace fmt + +#ifdef _MSC_VER +# pragma warning(pop) +#endif + #endif // FMT_TIME_H_ diff --git a/test/time-test.cc b/test/time-test.cc index 48df56a0..d66f0cbe 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))); +}