Optimize %F in tm formatting

This commit is contained in:
Victor Zverovich 2021-09-10 07:33:45 -07:00
parent 1aa98f8b93
commit 67cb2dad37
2 changed files with 51 additions and 3 deletions

View File

@ -482,6 +482,32 @@ inline size_t strftime(wchar_t* str, size_t count, const wchar_t* format,
return wcsftime(str, count, format, time); return wcsftime(str, count, format, time);
} }
// Writes two-digit numbers a, b and c separated by sep to buf.
// The method by Pavel Novikov based on
// https://johnnylee-sde.github.io/Fast-unsigned-integer-to-time-string/.
void write_digit2_separated(char* buf, unsigned a, unsigned b, unsigned c,
char sep) {
unsigned long long digits =
a | (b << 24) | (static_cast<unsigned long long>(c) << 48);
// Convert each value to BCD.
// We have x = a * 10 + b and we want to convert it to BCD y = a * 16 + b.
// The difference is
// y - x = a * 6
// a can be found from x:
// a = floor(x / 10)
// then
// y = x + a * 6 = x + floor(x / 10) * 6
// floor(x / 10) is (x * 205) >> 11 (needs 16 bits).
digits += (((digits * 205) >> 11) & 0x000f00000f00000f) * 6;
// Put low nibbles to high bytes and high nibbles to low bytes.
digits = ((digits & 0x00f00000f00000f0) >> 4) |
((digits & 0x000f00000f00000f) << 8);
auto usep = static_cast<unsigned long long>(sep);
// Add ASCII '0' to each digit byte and insert separators.
digits |= 0x3030003030003030 | (usep << 16) | (usep << 40);
memcpy(buf, &digits, 8);
}
FMT_END_DETAIL_NAMESPACE FMT_END_DETAIL_NAMESPACE
template <typename Char, typename Duration> template <typename Char, typename Duration>
@ -519,19 +545,40 @@ constexpr Char
Char>::default_specs[]; Char>::default_specs[];
template <typename Char> struct formatter<std::tm, Char> { template <typename Char> struct formatter<std::tm, Char> {
private:
enum class spec {
unknown,
year_month_day,
};
spec spec_ = spec::unknown;
public:
basic_string_view<Char> specs;
template <typename ParseContext> template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
auto it = ctx.begin(); auto it = ctx.begin();
if (it != ctx.end() && *it == ':') ++it; if (it != ctx.end() && *it == ':') ++it;
auto end = it; auto end = it;
while (end != ctx.end() && *end != '}') ++end; while (end != ctx.end() && *end != '}') ++end;
specs = {it, detail::to_unsigned(end - it)}; auto size = detail::to_unsigned(end - it);
specs = {it, size};
if (specs == string_view("%F", 2)) spec_ = spec::year_month_day;
return end; return end;
} }
template <typename FormatContext> template <typename FormatContext>
auto format(const std::tm& tm, FormatContext& ctx) const auto format(const std::tm& tm, FormatContext& ctx) const
-> decltype(ctx.out()) { -> decltype(ctx.out()) {
auto year = 1900 + tm.tm_year;
if (spec_ == spec::year_month_day && year >= 0 && year < 10000) {
char buf[10];
detail::copy2(buf, detail::data::digits[year / 100]);
detail::write_digit2_separated(buf + 2, year % 100,
detail::to_unsigned(tm.tm_mon + 1),
detail::to_unsigned(tm.tm_mday), '-');
return std::copy_n(buf, sizeof(buf), ctx.out());
}
basic_memory_buffer<Char> tm_format; basic_memory_buffer<Char> tm_format;
tm_format.append(specs.begin(), specs.end()); tm_format.append(specs.begin(), specs.end());
// By appending an extra space we can distinguish an empty result that // By appending an extra space we can distinguish an empty result that
@ -554,8 +601,6 @@ template <typename Char> struct formatter<std::tm, Char> {
// Remove the extra space. // Remove the extra space.
return std::copy(buf.begin(), buf.end() - 1, ctx.out()); return std::copy(buf.begin(), buf.end() - 1, ctx.out());
} }
basic_string_view<Char> specs;
}; };
FMT_BEGIN_DETAIL_NAMESPACE FMT_BEGIN_DETAIL_NAMESPACE
@ -1170,6 +1215,8 @@ class weekday {
: value(static_cast<unsigned char>(wd != 7 ? wd : 0)) {} : value(static_cast<unsigned char>(wd != 7 ? wd : 0)) {}
constexpr unsigned c_encoding() const noexcept { return value; } constexpr unsigned c_encoding() const noexcept { return value; }
}; };
class year_month_day {};
#endif #endif
// A rudimentary weekday formatter. // A rudimentary weekday formatter.

View File

@ -48,6 +48,7 @@ TEST(chrono_test, format_tm) {
tm.tm_sec = 33; tm.tm_sec = 33;
EXPECT_EQ(fmt::format("The date is {:%Y-%m-%d %H:%M:%S}.", tm), EXPECT_EQ(fmt::format("The date is {:%Y-%m-%d %H:%M:%S}.", tm),
"The date is 2016-04-25 11:22:33."); "The date is 2016-04-25 11:22:33.");
EXPECT_EQ(fmt::format("{:%F}", tm), "2016-04-25");
} }
TEST(chrono_test, grow_buffer) { TEST(chrono_test, grow_buffer) {