Optimize %F in tm formatting
This commit is contained in:
parent
1aa98f8b93
commit
67cb2dad37
@ -482,6 +482,32 @@ inline size_t strftime(wchar_t* str, size_t count, const wchar_t* format,
|
||||
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
|
||||
|
||||
template <typename Char, typename Duration>
|
||||
@ -519,19 +545,40 @@ constexpr Char
|
||||
Char>::default_specs[];
|
||||
|
||||
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>
|
||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||
auto it = ctx.begin();
|
||||
if (it != ctx.end() && *it == ':') ++it;
|
||||
auto end = it;
|
||||
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;
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(const std::tm& tm, FormatContext& ctx) const
|
||||
-> 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;
|
||||
tm_format.append(specs.begin(), specs.end());
|
||||
// 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.
|
||||
return std::copy(buf.begin(), buf.end() - 1, ctx.out());
|
||||
}
|
||||
|
||||
basic_string_view<Char> specs;
|
||||
};
|
||||
|
||||
FMT_BEGIN_DETAIL_NAMESPACE
|
||||
@ -1170,6 +1215,8 @@ class weekday {
|
||||
: value(static_cast<unsigned char>(wd != 7 ? wd : 0)) {}
|
||||
constexpr unsigned c_encoding() const noexcept { return value; }
|
||||
};
|
||||
|
||||
class year_month_day {};
|
||||
#endif
|
||||
|
||||
// A rudimentary weekday formatter.
|
||||
|
@ -48,6 +48,7 @@ TEST(chrono_test, format_tm) {
|
||||
tm.tm_sec = 33;
|
||||
EXPECT_EQ(fmt::format("The date is {:%Y-%m-%d %H:%M:%S}.", tm),
|
||||
"The date is 2016-04-25 11:22:33.");
|
||||
EXPECT_EQ(fmt::format("{:%F}", tm), "2016-04-25");
|
||||
}
|
||||
|
||||
TEST(chrono_test, grow_buffer) {
|
||||
|
Loading…
Reference in New Issue
Block a user