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:
Vladislav Shchapov
2021-10-30 20:25:45 +05:00
committed by GitHub
parent 90034e4c4b
commit 1031eedf27
4 changed files with 247 additions and 141 deletions

View File

@@ -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)));
}
}

View File

@@ -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();

View File

@@ -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