Replacing strftime with std::time_put (#2550)
* Fix unicode test * Add xchar support to chrono formatter * Replace strftime with std::time_put * Add std::locale support to std::tm formatter * Use predefined names and formats for C-locale * Performance improvement * Make locale-independent and C locale formats consistent among platforms
This commit is contained in:
parent
90034e4c4b
commit
1031eedf27
@ -289,84 +289,95 @@ inline null<> localtime_s(...) { return null<>(); }
|
||||
inline null<> gmtime_r(...) { return null<>(); }
|
||||
inline null<> gmtime_s(...) { return null<>(); }
|
||||
|
||||
template <typename Char>
|
||||
inline auto do_write(const std::tm& time, const std::locale& loc, char format,
|
||||
char modifier) -> std::string {
|
||||
auto&& os = std::ostringstream();
|
||||
char modifier) -> std::basic_string<Char> {
|
||||
auto&& os = std::basic_ostringstream<Char>();
|
||||
os.imbue(loc);
|
||||
using iterator = std::ostreambuf_iterator<char>;
|
||||
const auto& facet = std::use_facet<std::time_put<char, iterator>>(loc);
|
||||
auto end = facet.put(os, os, ' ', &time, format, modifier);
|
||||
using iterator = std::ostreambuf_iterator<Char>;
|
||||
const auto& facet = std::use_facet<std::time_put<Char, iterator>>(loc);
|
||||
auto end = facet.put(os, os, Char(' '), &time, format, modifier);
|
||||
if (end.failed()) FMT_THROW(format_error("failed to format time"));
|
||||
auto str = os.str();
|
||||
if (!detail::is_utf8() || loc == std::locale::classic()) return str;
|
||||
return std::move(os).str();
|
||||
}
|
||||
|
||||
template <typename Char, typename OutputIt,
|
||||
FMT_ENABLE_IF(!std::is_same<Char, char>::value)>
|
||||
auto write(OutputIt out, const std::tm& time, const std::locale& loc,
|
||||
char format, char modifier = 0) -> OutputIt {
|
||||
auto str = do_write<Char>(time, loc, format, modifier);
|
||||
return std::copy(str.begin(), str.end(), out);
|
||||
}
|
||||
|
||||
template <typename Char, typename OutputIt,
|
||||
FMT_ENABLE_IF(std::is_same<Char, char>::value)>
|
||||
auto write(OutputIt out, const std::tm& time, const std::locale& loc,
|
||||
char format, char modifier = 0) -> OutputIt {
|
||||
auto str = do_write<char>(time, loc, format, modifier);
|
||||
if (detail::is_utf8() && loc != std::locale::classic()) {
|
||||
// char16_t and char32_t codecvts are broken in MSVC (linkage errors) and
|
||||
// gcc-4.
|
||||
#if FMT_MSC_VER != 0 || \
|
||||
(defined(__GLIBCXX__) && !defined(_GLIBCXX_USE_DUAL_ABI))
|
||||
// The _GLIBCXX_USE_DUAL_ABI macro is always defined in libstdc++ from gcc-5
|
||||
// and newer.
|
||||
using code_unit = wchar_t;
|
||||
// The _GLIBCXX_USE_DUAL_ABI macro is always defined in libstdc++ from gcc-5
|
||||
// and newer.
|
||||
using code_unit = wchar_t;
|
||||
#else
|
||||
using code_unit = char32_t;
|
||||
using code_unit = char32_t;
|
||||
#endif
|
||||
|
||||
using codecvt = std::codecvt<code_unit, char, std::mbstate_t>;
|
||||
using codecvt = std::codecvt<code_unit, char, std::mbstate_t>;
|
||||
#if FMT_CLANG_VERSION
|
||||
# pragma clang diagnostic push
|
||||
# pragma clang diagnostic ignored "-Wdeprecated"
|
||||
auto& f = std::use_facet<codecvt>(loc);
|
||||
auto& f = std::use_facet<codecvt>(loc);
|
||||
# pragma clang diagnostic pop
|
||||
#else
|
||||
auto& f = std::use_facet<codecvt>(loc);
|
||||
auto& f = std::use_facet<codecvt>(loc);
|
||||
#endif
|
||||
|
||||
auto mb = std::mbstate_t();
|
||||
const char* from_next = nullptr;
|
||||
code_unit* to_next = nullptr;
|
||||
constexpr size_t buf_size = 32;
|
||||
code_unit buf[buf_size] = {};
|
||||
auto result = f.in(mb, str.data(), str.data() + str.size(), from_next, buf,
|
||||
buf + buf_size, to_next);
|
||||
if (result != std::codecvt_base::ok)
|
||||
FMT_THROW(format_error("failed to format time"));
|
||||
str.clear();
|
||||
for (code_unit* p = buf; p != to_next; ++p) {
|
||||
uint32_t c = static_cast<uint32_t>(*p);
|
||||
if (sizeof(code_unit) == 2 && c >= 0xd800 && c <= 0xdfff) {
|
||||
// surrogate pair
|
||||
++p;
|
||||
if (p == to_next || (c & 0xfc00) != 0xd800 || (*p & 0xfc00) != 0xdc00) {
|
||||
auto mb = std::mbstate_t();
|
||||
const char* from_next = nullptr;
|
||||
code_unit* to_next = nullptr;
|
||||
constexpr size_t buf_size = 32;
|
||||
code_unit buf[buf_size] = {};
|
||||
auto result = f.in(mb, str.data(), str.data() + str.size(), from_next, buf,
|
||||
buf + buf_size, to_next);
|
||||
if (result != std::codecvt_base::ok)
|
||||
FMT_THROW(format_error("failed to format time"));
|
||||
str.clear();
|
||||
for (code_unit* p = buf; p != to_next; ++p) {
|
||||
uint32_t c = static_cast<uint32_t>(*p);
|
||||
if (sizeof(code_unit) == 2 && c >= 0xd800 && c <= 0xdfff) {
|
||||
// surrogate pair
|
||||
++p;
|
||||
if (p == to_next || (c & 0xfc00) != 0xd800 || (*p & 0xfc00) != 0xdc00) {
|
||||
FMT_THROW(format_error("failed to format time"));
|
||||
}
|
||||
c = (c << 10) + static_cast<uint32_t>(*p) - 0x35fdc00;
|
||||
}
|
||||
if (c < 0x80) {
|
||||
str.push_back(static_cast<char>(c));
|
||||
} else if (c < 0x800) {
|
||||
str.push_back(static_cast<char>(0xc0 | (c >> 6)));
|
||||
str.push_back(static_cast<char>(0x80 | (c & 0x3f)));
|
||||
} else if ((c >= 0x800 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0xffff)) {
|
||||
str.push_back(static_cast<char>(0xe0 | (c >> 12)));
|
||||
str.push_back(static_cast<char>(0x80 | ((c & 0xfff) >> 6)));
|
||||
str.push_back(static_cast<char>(0x80 | (c & 0x3f)));
|
||||
} else if (c >= 0x10000 && c <= 0x10ffff) {
|
||||
str.push_back(static_cast<char>(0xf0 | (c >> 18)));
|
||||
str.push_back(static_cast<char>(0x80 | ((c & 0x3ffff) >> 12)));
|
||||
str.push_back(static_cast<char>(0x80 | ((c & 0xfff) >> 6)));
|
||||
str.push_back(static_cast<char>(0x80 | (c & 0x3f)));
|
||||
} else {
|
||||
FMT_THROW(format_error("failed to format time"));
|
||||
}
|
||||
c = (c << 10) + static_cast<uint32_t>(*p) - 0x35fdc00;
|
||||
}
|
||||
if (c < 0x80) {
|
||||
str.push_back(static_cast<char>(c));
|
||||
} else if (c < 0x800) {
|
||||
str.push_back(static_cast<char>(0xc0 | (c >> 6)));
|
||||
str.push_back(static_cast<char>(0x80 | (c & 0x3f)));
|
||||
} else if ((c >= 0x800 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0xffff)) {
|
||||
str.push_back(static_cast<char>(0xe0 | (c >> 12)));
|
||||
str.push_back(static_cast<char>(0x80 | ((c & 0xfff) >> 6)));
|
||||
str.push_back(static_cast<char>(0x80 | (c & 0x3f)));
|
||||
} else if (c >= 0x10000 && c <= 0x10ffff) {
|
||||
str.push_back(static_cast<char>(0xf0 | (c >> 18)));
|
||||
str.push_back(static_cast<char>(0x80 | ((c & 0x3ffff) >> 12)));
|
||||
str.push_back(static_cast<char>(0x80 | ((c & 0xfff) >> 6)));
|
||||
str.push_back(static_cast<char>(0x80 | (c & 0x3f)));
|
||||
} else {
|
||||
FMT_THROW(format_error("failed to format time"));
|
||||
}
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
template <typename OutputIt>
|
||||
auto write(OutputIt out, const std::tm& time, const std::locale& loc,
|
||||
char format, char modifier = 0) -> OutputIt {
|
||||
auto str = do_write(time, loc, format, modifier);
|
||||
return std::copy(str.begin(), str.end(), out);
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
FMT_MODULE_EXPORT_BEGIN
|
||||
@ -464,25 +475,6 @@ inline std::tm gmtime(
|
||||
|
||||
FMT_BEGIN_DETAIL_NAMESPACE
|
||||
|
||||
inline size_t strftime(char* str, size_t count, const char* format,
|
||||
const std::tm* time) {
|
||||
// Assign to a pointer to suppress GCCs -Wformat-nonliteral
|
||||
// First assign the nullptr to suppress -Wsuggest-attribute=format
|
||||
std::size_t (*strftime)(char*, std::size_t, const char*, const std::tm*) =
|
||||
nullptr;
|
||||
strftime = std::strftime;
|
||||
return strftime(str, count, format, time);
|
||||
}
|
||||
|
||||
inline size_t strftime(wchar_t* str, size_t count, const wchar_t* format,
|
||||
const std::tm* time) {
|
||||
// See above
|
||||
std::size_t (*wcsftime)(wchar_t*, std::size_t, const wchar_t*,
|
||||
const std::tm*) = nullptr;
|
||||
wcsftime = std::wcsftime;
|
||||
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/.
|
||||
@ -1067,7 +1059,7 @@ struct chrono_formatter {
|
||||
if (isnan(val)) return write_nan();
|
||||
const auto& loc = localized ? context.locale().template get<std::locale>()
|
||||
: std::locale::classic();
|
||||
out = detail::write(out, time, loc, format, modifier);
|
||||
out = detail::write<char_type>(out, time, loc, format, modifier);
|
||||
}
|
||||
|
||||
void on_text(const char_type* begin, const char_type* end) {
|
||||
@ -1215,12 +1207,13 @@ class year_month_day {};
|
||||
#endif
|
||||
|
||||
// A rudimentary weekday formatter.
|
||||
template <> struct formatter<weekday> {
|
||||
template <typename Char> struct formatter<weekday, Char> {
|
||||
private:
|
||||
bool localized = false;
|
||||
|
||||
public:
|
||||
FMT_CONSTEXPR auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
|
||||
FMT_CONSTEXPR auto parse(basic_format_parse_context<Char>& ctx)
|
||||
-> decltype(ctx.begin()) {
|
||||
auto begin = ctx.begin(), end = ctx.end();
|
||||
if (begin != end && *begin == 'L') {
|
||||
++begin;
|
||||
@ -1229,12 +1222,13 @@ template <> struct formatter<weekday> {
|
||||
return begin;
|
||||
}
|
||||
|
||||
auto format(weekday wd, format_context& ctx) -> decltype(ctx.out()) {
|
||||
template <typename FormatContext>
|
||||
auto format(weekday wd, FormatContext& ctx) const -> decltype(ctx.out()) {
|
||||
auto time = std::tm();
|
||||
time.tm_wday = static_cast<int>(wd.c_encoding());
|
||||
const auto& loc = localized ? ctx.locale().template get<std::locale>()
|
||||
: std::locale::classic();
|
||||
return detail::write(ctx.out(), time, loc, 'a');
|
||||
return detail::write<Char>(ctx.out(), time, loc, 'a');
|
||||
}
|
||||
};
|
||||
|
||||
@ -1399,10 +1393,38 @@ struct tm_format_checker : null_chrono_spec_handler<tm_format_checker> {
|
||||
FMT_CONSTEXPR void on_tz_name() {}
|
||||
};
|
||||
|
||||
inline const char* tm_wday_full_name(int wday) {
|
||||
static constexpr const char* full_name_list[] = {
|
||||
"Sunday", "Monday", "Tuesday", "Wednesday",
|
||||
"Thursday", "Friday", "Saturday"};
|
||||
return wday >= 0 && wday <= 6 ? full_name_list[wday] : "?";
|
||||
}
|
||||
inline const char* tm_wday_short_name(int wday) {
|
||||
static constexpr const char* short_name_list[] = {"Sun", "Mon", "Tue", "Wed",
|
||||
"Thu", "Fri", "Sat"};
|
||||
return wday >= 0 && wday <= 6 ? short_name_list[wday] : "???";
|
||||
}
|
||||
|
||||
inline const char* tm_mon_full_name(int mon) {
|
||||
static constexpr const char* full_name_list[] = {
|
||||
"January", "February", "March", "April", "May", "June",
|
||||
"July", "August", "September", "October", "November", "December"};
|
||||
return mon >= 0 && mon <= 11 ? full_name_list[mon] : "?";
|
||||
}
|
||||
inline const char* tm_mon_short_name(int mon) {
|
||||
static constexpr const char* short_name_list[] = {
|
||||
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
|
||||
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
|
||||
};
|
||||
return mon >= 0 && mon <= 11 ? short_name_list[mon] : "???";
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename Char> class tm_writer {
|
||||
private:
|
||||
static constexpr int days_per_week = 7;
|
||||
|
||||
const std::locale& loc_;
|
||||
const bool is_classic_;
|
||||
OutputIt out_;
|
||||
const std::tm& tm_;
|
||||
|
||||
@ -1437,8 +1459,9 @@ template <typename OutputIt, typename Char> class tm_writer {
|
||||
}
|
||||
|
||||
auto tm_hour12() const noexcept -> int {
|
||||
auto hour = tm_hour() % 12;
|
||||
return hour == 0 ? 12 : hour;
|
||||
const auto h = tm_hour();
|
||||
const auto z = h < 12 ? h : h - 12;
|
||||
return z == 0 ? 12 : z;
|
||||
}
|
||||
|
||||
// POSIX and the C Standard are unclear or inconsistent about what %C and %y
|
||||
@ -1514,34 +1537,15 @@ template <typename OutputIt, typename Char> class tm_writer {
|
||||
}
|
||||
|
||||
void format_localized(char format, char modifier = 0) {
|
||||
// By prepending an extra space we can distinguish an empty result that
|
||||
// indicates insufficient buffer size from a guaranteed non-empty result
|
||||
// https://github.com/fmtlib/fmt/issues/2238
|
||||
Char tm_format[5] = {' ', '%', 'x', '\0', '\0'};
|
||||
if (modifier) {
|
||||
tm_format[2] = modifier;
|
||||
tm_format[3] = format;
|
||||
} else {
|
||||
tm_format[2] = format;
|
||||
}
|
||||
|
||||
basic_memory_buffer<Char> buf;
|
||||
for (;;) {
|
||||
size_t size = buf.capacity();
|
||||
size_t count = detail::strftime(buf.data(), size, tm_format, &tm_);
|
||||
if (count != 0) {
|
||||
buf.resize(count);
|
||||
break;
|
||||
}
|
||||
const size_t min_growth = 10;
|
||||
buf.reserve(buf.capacity() + (size > min_growth ? size : min_growth));
|
||||
}
|
||||
// Remove the extra space.
|
||||
out_ = copy_str<Char>(buf.begin() + 1, buf.end(), out_);
|
||||
out_ = write<Char>(out_, tm_, loc_, format, modifier);
|
||||
}
|
||||
|
||||
public:
|
||||
explicit tm_writer(OutputIt out, const std::tm& tm) : out_(out), tm_(tm) {}
|
||||
explicit tm_writer(const std::locale& loc, OutputIt out, const std::tm& tm)
|
||||
: loc_(loc),
|
||||
is_classic_(loc_ == std::locale::classic()),
|
||||
out_(out),
|
||||
tm_(tm) {}
|
||||
|
||||
OutputIt out() const { return out_; }
|
||||
|
||||
@ -1549,8 +1553,18 @@ template <typename OutputIt, typename Char> class tm_writer {
|
||||
out_ = copy_str<Char>(begin, end, out_);
|
||||
}
|
||||
|
||||
void on_abbr_weekday() { format_localized('a'); }
|
||||
void on_full_weekday() { format_localized('A'); }
|
||||
void on_abbr_weekday() {
|
||||
if (is_classic_)
|
||||
out_ = write(out_, tm_wday_short_name(tm_wday()));
|
||||
else
|
||||
format_localized('a');
|
||||
}
|
||||
void on_full_weekday() {
|
||||
if (is_classic_)
|
||||
out_ = write(out_, tm_wday_full_name(tm_wday()));
|
||||
else
|
||||
format_localized('A');
|
||||
}
|
||||
void on_dec0_weekday(numeric_system ns) {
|
||||
if (ns != numeric_system::standard) return format_localized('w', 'O');
|
||||
write1(tm_wday());
|
||||
@ -1561,17 +1575,45 @@ template <typename OutputIt, typename Char> class tm_writer {
|
||||
write1(wday == 0 ? days_per_week : wday);
|
||||
}
|
||||
|
||||
void on_abbr_month() { format_localized('b'); }
|
||||
void on_full_month() { format_localized('B'); }
|
||||
void on_abbr_month() {
|
||||
if (is_classic_)
|
||||
out_ = write(out_, tm_mon_short_name(tm_mon()));
|
||||
else
|
||||
format_localized('b');
|
||||
}
|
||||
void on_full_month() {
|
||||
if (is_classic_)
|
||||
out_ = write(out_, tm_mon_full_name(tm_mon()));
|
||||
else
|
||||
format_localized('B');
|
||||
}
|
||||
|
||||
void on_datetime(numeric_system ns) {
|
||||
format_localized('c', ns == numeric_system::standard ? '\0' : 'E');
|
||||
if (is_classic_) {
|
||||
on_abbr_weekday();
|
||||
*out_++ = ' ';
|
||||
on_abbr_month();
|
||||
*out_++ = ' ';
|
||||
on_day_of_month_space(numeric_system::standard);
|
||||
*out_++ = ' ';
|
||||
on_iso_time();
|
||||
*out_++ = ' ';
|
||||
on_year(numeric_system::standard);
|
||||
} else {
|
||||
format_localized('c', ns == numeric_system::standard ? '\0' : 'E');
|
||||
}
|
||||
}
|
||||
void on_loc_date(numeric_system ns) {
|
||||
format_localized('x', ns == numeric_system::standard ? '\0' : 'E');
|
||||
if (is_classic_)
|
||||
on_us_date();
|
||||
else
|
||||
format_localized('x', ns == numeric_system::standard ? '\0' : 'E');
|
||||
}
|
||||
void on_loc_time(numeric_system ns) {
|
||||
format_localized('X', ns == numeric_system::standard ? '\0' : 'E');
|
||||
if (is_classic_)
|
||||
on_iso_time();
|
||||
else
|
||||
format_localized('X', ns == numeric_system::standard ? '\0' : 'E');
|
||||
}
|
||||
void on_us_date() {
|
||||
char buf[8];
|
||||
@ -1685,7 +1727,18 @@ template <typename OutputIt, typename Char> class tm_writer {
|
||||
write2(tm_sec());
|
||||
}
|
||||
|
||||
void on_12_hour_time() { format_localized('r'); }
|
||||
void on_12_hour_time() {
|
||||
if (is_classic_) {
|
||||
char buf[8];
|
||||
write_digit2_separated(buf, to_unsigned(tm_hour12()),
|
||||
to_unsigned(tm_min()), to_unsigned(tm_sec()), ':');
|
||||
out_ = copy_str<Char>(std::begin(buf), std::end(buf), out_);
|
||||
*out_++ = ' ';
|
||||
on_am_pm();
|
||||
} else {
|
||||
format_localized('r');
|
||||
}
|
||||
}
|
||||
void on_24_hour_time() {
|
||||
write2(tm_hour());
|
||||
*out_++ = ':';
|
||||
@ -1698,7 +1751,14 @@ template <typename OutputIt, typename Char> class tm_writer {
|
||||
out_ = copy_str<Char>(std::begin(buf), std::end(buf), out_);
|
||||
}
|
||||
|
||||
void on_am_pm() { format_localized('p'); }
|
||||
void on_am_pm() {
|
||||
if (is_classic_) {
|
||||
*out_++ = tm_hour() < 12 ? 'A' : 'P';
|
||||
*out_++ = 'M';
|
||||
} else {
|
||||
format_localized('p');
|
||||
}
|
||||
}
|
||||
|
||||
// These apply to chrono durations but not tm.
|
||||
void on_duration_value() {}
|
||||
@ -1771,7 +1831,10 @@ template <typename Char> struct formatter<std::tm, Char> {
|
||||
template <typename FormatContext>
|
||||
auto format(const std::tm& tm, FormatContext& ctx) const
|
||||
-> decltype(ctx.out()) {
|
||||
auto w = detail::tm_writer<decltype(ctx.out()), Char>(ctx.out(), tm);
|
||||
const auto& loc_ref = ctx.locale();
|
||||
const auto& loc =
|
||||
loc_ref ? loc_ref.template get<std::locale>() : std::locale::classic();
|
||||
auto w = detail::tm_writer<decltype(ctx.out()), Char>(loc, ctx.out(), tm);
|
||||
if (spec_ == spec::year_month_day)
|
||||
w.on_iso_date();
|
||||
else if (spec_ == spec::hh_mm_ss)
|
||||
|
@ -42,11 +42,14 @@ auto make_second(int s) -> std::tm {
|
||||
}
|
||||
|
||||
std::string system_strftime(const std::string& format, const std::tm* timeptr,
|
||||
size_t maxsize = 1024) {
|
||||
std::vector<char> output(maxsize);
|
||||
auto size =
|
||||
std::strftime(output.data(), output.size(), format.c_str(), timeptr);
|
||||
return std::string(output.data(), size);
|
||||
std::locale* locptr = nullptr) {
|
||||
auto loc = locptr ? *locptr : std::locale::classic();
|
||||
auto& facet = std::use_facet<std::time_put<char>>(loc);
|
||||
std::ostringstream os;
|
||||
os.imbue(loc);
|
||||
facet.put(os, os, ' ', timeptr, format.c_str(),
|
||||
format.c_str() + format.size());
|
||||
return os.str();
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR std::tm make_tm(int year, int mon, int mday, int hour, int min,
|
||||
@ -245,13 +248,19 @@ TEST(chrono_test, time_point) {
|
||||
EXPECT_EQ(strftime_full(t2), fmt::format("{:%Y-%m-%d %H:%M:%S}", t2));
|
||||
|
||||
std::vector<std::string> spec_list = {
|
||||
"%%", "%n", "%t", "%Y", "%EY", "%y", "%Oy", "%Ey", "%C", "%EC",
|
||||
"%G", "%g", "%b", "%h", "%B", "%m", "%Om", "%U", "%OU", "%W",
|
||||
"%OW", "%V", "%OV", "%j", "%d", "%Od", "%e", "%Oe", "%a", "%A",
|
||||
"%w", "%Ow", "%u", "%Ou", "%H", "%OH", "%I", "%OI", "%M", "%OM",
|
||||
"%S", "%OS", "%c", "%Ec", "%x", "%Ex", "%X", "%EX", "%D", "%F",
|
||||
"%r", "%R", "%T", "%p", "%z", "%Z"};
|
||||
"%%", "%n", "%t", "%Y", "%EY", "%y", "%Oy", "%Ey", "%C",
|
||||
"%EC", "%G", "%g", "%b", "%h", "%B", "%m", "%Om", "%U",
|
||||
"%OU", "%W", "%OW", "%V", "%OV", "%j", "%d", "%Od", "%e",
|
||||
"%Oe", "%a", "%A", "%w", "%Ow", "%u", "%Ou", "%H", "%OH",
|
||||
"%I", "%OI", "%M", "%OM", "%S", "%OS", "%x", "%Ex", "%X",
|
||||
"%EX", "%D", "%F", "%R", "%T", "%p", "%z", "%Z"};
|
||||
spec_list.push_back("%Y-%m-%d %H:%M:%S");
|
||||
#ifndef _WIN32
|
||||
// Disabled on Windows, because these formats is not consistent among
|
||||
// platforms.
|
||||
spec_list.insert(spec_list.end(), {"%c", "%Ec", "%r"});
|
||||
#endif
|
||||
|
||||
for (const auto& spec : spec_list) {
|
||||
auto t = std::chrono::system_clock::to_time_t(t1);
|
||||
auto tm = *std::localtime(&t);
|
||||
@ -533,10 +542,18 @@ TEST(chrono_test, weekday) {
|
||||
auto loc = get_locale("ru_RU.UTF-8");
|
||||
std::locale::global(loc);
|
||||
auto mon = fmt::weekday(1);
|
||||
|
||||
auto tm = std::tm();
|
||||
tm.tm_wday = static_cast<int>(mon.c_encoding());
|
||||
|
||||
EXPECT_EQ(fmt::format("{}", mon), "Mon");
|
||||
EXPECT_EQ(fmt::format("{:%a}", tm), "Mon");
|
||||
|
||||
if (loc != std::locale::classic()) {
|
||||
EXPECT_THAT((std::vector<std::string>{"пн", "Пн", "пнд", "Пнд"}),
|
||||
Contains(fmt::format(loc, "{:L}", mon)));
|
||||
EXPECT_THAT((std::vector<std::string>{"пн", "Пн", "пнд", "Пнд"}),
|
||||
Contains(fmt::format(loc, "{:%a}", tm)));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -18,7 +18,7 @@ using testing::Contains;
|
||||
TEST(unicode_test, is_utf8) { EXPECT_TRUE(fmt::detail::is_utf8()); }
|
||||
|
||||
TEST(unicode_test, legacy_locale) {
|
||||
auto loc = get_locale("ru_RU.CP1251", "Russian.1251");
|
||||
auto loc = get_locale("ru_RU.CP1251", "Russian_Russia.1251");
|
||||
if (loc == std::locale::classic()) return;
|
||||
|
||||
auto s = std::string();
|
||||
|
@ -15,9 +15,11 @@
|
||||
#include "fmt/color.h"
|
||||
#include "fmt/ostream.h"
|
||||
#include "fmt/ranges.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "gtest-extra.h" // Contains
|
||||
#include "util.h" // get_locale
|
||||
|
||||
using fmt::detail::max_value;
|
||||
using testing::Contains;
|
||||
|
||||
namespace test_ns {
|
||||
template <typename Char> class test_string {
|
||||
@ -268,25 +270,33 @@ TEST(xchar_test, chrono) {
|
||||
}
|
||||
|
||||
std::wstring system_wcsftime(const std::wstring& format, const std::tm* timeptr,
|
||||
size_t maxsize = 1024) {
|
||||
std::vector<wchar_t> output(maxsize);
|
||||
auto size =
|
||||
std::wcsftime(output.data(), output.size(), format.c_str(), timeptr);
|
||||
return std::wstring(output.data(), size);
|
||||
std::locale* locptr = nullptr) {
|
||||
auto loc = locptr ? *locptr : std::locale::classic();
|
||||
auto& facet = std::use_facet<std::time_put<wchar_t>>(loc);
|
||||
std::wostringstream os;
|
||||
os.imbue(loc);
|
||||
facet.put(os, os, L' ', timeptr, format.c_str(),
|
||||
format.c_str() + format.size());
|
||||
return os.str();
|
||||
}
|
||||
|
||||
TEST(chrono_test, time_point) {
|
||||
auto t1 = std::chrono::system_clock::now();
|
||||
|
||||
std::vector<std::wstring> spec_list = {
|
||||
L"%%", L"%n", L"%t", L"%Y", L"%EY", L"%y", L"%Oy", L"%Ey",
|
||||
L"%C", L"%EC", L"%G", L"%g", L"%b", L"%h", L"%B", L"%m",
|
||||
L"%Om", L"%U", L"%OU", L"%W", L"%OW", L"%V", L"%OV", L"%j",
|
||||
L"%d", L"%Od", L"%e", L"%Oe", L"%a", L"%A", L"%w", L"%Ow",
|
||||
L"%u", L"%Ou", L"%H", L"%OH", L"%I", L"%OI", L"%M", L"%OM",
|
||||
L"%S", L"%OS", L"%c", L"%Ec", L"%x", L"%Ex", L"%X", L"%EX",
|
||||
L"%D", L"%F", L"%r", L"%R", L"%T", L"%p", L"%z", L"%Z"};
|
||||
L"%%", L"%n", L"%t", L"%Y", L"%EY", L"%y", L"%Oy", L"%Ey", L"%C",
|
||||
L"%EC", L"%G", L"%g", L"%b", L"%h", L"%B", L"%m", L"%Om", L"%U",
|
||||
L"%OU", L"%W", L"%OW", L"%V", L"%OV", L"%j", L"%d", L"%Od", L"%e",
|
||||
L"%Oe", L"%a", L"%A", L"%w", L"%Ow", L"%u", L"%Ou", L"%H", L"%OH",
|
||||
L"%I", L"%OI", L"%M", L"%OM", L"%S", L"%OS", L"%x", L"%Ex", L"%X",
|
||||
L"%EX", L"%D", L"%F", L"%R", L"%T", L"%p", L"%z", L"%Z"};
|
||||
spec_list.push_back(L"%Y-%m-%d %H:%M:%S");
|
||||
#ifndef _WIN32
|
||||
// Disabled on Windows, because these formats is not consistent among
|
||||
// platforms.
|
||||
spec_list.insert(spec_list.end(), {L"%c", L"%Ec", L"%r"});
|
||||
#endif
|
||||
|
||||
for (const auto& spec : spec_list) {
|
||||
auto t = std::chrono::system_clock::to_time_t(t1);
|
||||
auto tm = *std::localtime(&t);
|
||||
@ -466,4 +476,20 @@ TEST(locale_test, complex) {
|
||||
EXPECT_EQ(fmt::format("{:8}", std::complex<double>(1, 2)), " (1+2i)");
|
||||
}
|
||||
|
||||
TEST(locale_test, chrono_weekday) {
|
||||
auto loc = get_locale("ru_RU.UTF-8", "Russian_Russia.1251");
|
||||
auto loc_old = std::locale::global(loc);
|
||||
auto mon = fmt::weekday(1);
|
||||
EXPECT_EQ(fmt::format(L"{}", mon), L"Mon");
|
||||
if (loc != std::locale::classic()) {
|
||||
// {L"\x43F\x43D", L"\x41F\x43D", L"\x43F\x43D\x434", L"\x41F\x43D\x434"}
|
||||
// {L"пн", L"Пн", L"пнд", L"Пнд"}
|
||||
EXPECT_THAT(
|
||||
(std::vector<std::wstring>{L"\x43F\x43D", L"\x41F\x43D",
|
||||
L"\x43F\x43D\x434", L"\x41F\x43D\x434"}),
|
||||
Contains(fmt::format(loc, L"{:L}", mon)));
|
||||
}
|
||||
std::locale::global(loc_old);
|
||||
}
|
||||
|
||||
#endif // FMT_STATIC_THOUSANDS_SEPARATOR
|
||||
|
Loading…
Reference in New Issue
Block a user