mirror of
https://github.com/fmtlib/fmt.git
synced 2025-07-30 10:47:35 +02:00
Add initial support for weekday formatting
This commit is contained in:
2
.github/workflows/windows.yml
vendored
2
.github/workflows/windows.yml
vendored
@ -53,6 +53,6 @@ jobs:
|
|||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
working-directory: ${{runner.workspace}}/build
|
working-directory: ${{runner.workspace}}/build
|
||||||
run: ctest -C ${{matrix.build_type}}
|
run: ctest -C ${{matrix.build_type}} -V
|
||||||
env:
|
env:
|
||||||
CTEST_OUTPUT_ON_FAILURE: True
|
CTEST_OUTPUT_ON_FAILURE: True
|
||||||
|
@ -683,34 +683,50 @@ FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin,
|
|||||||
return ptr;
|
return ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct chrono_format_checker {
|
template <typename Derived> struct null_chrono_spec_handler {
|
||||||
FMT_NORETURN void report_no_date() { FMT_THROW(format_error("no date")); }
|
FMT_CONSTEXPR void unsupported() {
|
||||||
|
static_cast<Derived*>(this)->unsupported();
|
||||||
|
}
|
||||||
|
FMT_CONSTEXPR void on_abbr_weekday() { unsupported(); }
|
||||||
|
FMT_CONSTEXPR void on_full_weekday() { unsupported(); }
|
||||||
|
FMT_CONSTEXPR void on_dec0_weekday(numeric_system) { unsupported(); }
|
||||||
|
FMT_CONSTEXPR void on_dec1_weekday(numeric_system) { unsupported(); }
|
||||||
|
FMT_CONSTEXPR void on_abbr_month() { unsupported(); }
|
||||||
|
FMT_CONSTEXPR void on_full_month() { unsupported(); }
|
||||||
|
FMT_CONSTEXPR void on_24_hour(numeric_system) { unsupported(); }
|
||||||
|
FMT_CONSTEXPR void on_12_hour(numeric_system) { unsupported(); }
|
||||||
|
FMT_CONSTEXPR void on_minute(numeric_system) { unsupported(); }
|
||||||
|
FMT_CONSTEXPR void on_second(numeric_system) { unsupported(); }
|
||||||
|
FMT_CONSTEXPR void on_datetime(numeric_system) { unsupported(); }
|
||||||
|
FMT_CONSTEXPR void on_loc_date(numeric_system) { unsupported(); }
|
||||||
|
FMT_CONSTEXPR void on_loc_time(numeric_system) { unsupported(); }
|
||||||
|
FMT_CONSTEXPR void on_us_date() { unsupported(); }
|
||||||
|
FMT_CONSTEXPR void on_iso_date() { unsupported(); }
|
||||||
|
FMT_CONSTEXPR void on_12_hour_time() { unsupported(); }
|
||||||
|
FMT_CONSTEXPR void on_24_hour_time() { unsupported(); }
|
||||||
|
FMT_CONSTEXPR void on_iso_time() { unsupported(); }
|
||||||
|
FMT_CONSTEXPR void on_am_pm() { unsupported(); }
|
||||||
|
FMT_CONSTEXPR void on_duration_value() { unsupported(); }
|
||||||
|
FMT_CONSTEXPR void on_duration_unit() { unsupported(); }
|
||||||
|
FMT_CONSTEXPR void on_utc_offset() { unsupported(); }
|
||||||
|
FMT_CONSTEXPR void on_tz_name() { unsupported(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct chrono_format_checker : null_chrono_spec_handler<chrono_format_checker> {
|
||||||
|
FMT_NORETURN void unsupported() { FMT_THROW(format_error("no date")); }
|
||||||
|
|
||||||
template <typename Char>
|
template <typename Char>
|
||||||
FMT_CONSTEXPR void on_text(const Char*, const Char*) {}
|
FMT_CONSTEXPR void on_text(const Char*, const Char*) {}
|
||||||
FMT_NORETURN void on_abbr_weekday() { report_no_date(); }
|
|
||||||
FMT_NORETURN void on_full_weekday() { report_no_date(); }
|
|
||||||
FMT_NORETURN void on_dec0_weekday(numeric_system) { report_no_date(); }
|
|
||||||
FMT_NORETURN void on_dec1_weekday(numeric_system) { report_no_date(); }
|
|
||||||
FMT_NORETURN void on_abbr_month() { report_no_date(); }
|
|
||||||
FMT_NORETURN void on_full_month() { report_no_date(); }
|
|
||||||
FMT_CONSTEXPR void on_24_hour(numeric_system) {}
|
FMT_CONSTEXPR void on_24_hour(numeric_system) {}
|
||||||
FMT_CONSTEXPR void on_12_hour(numeric_system) {}
|
FMT_CONSTEXPR void on_12_hour(numeric_system) {}
|
||||||
FMT_CONSTEXPR void on_minute(numeric_system) {}
|
FMT_CONSTEXPR void on_minute(numeric_system) {}
|
||||||
FMT_CONSTEXPR void on_second(numeric_system) {}
|
FMT_CONSTEXPR void on_second(numeric_system) {}
|
||||||
FMT_NORETURN void on_datetime(numeric_system) { report_no_date(); }
|
|
||||||
FMT_NORETURN void on_loc_date(numeric_system) { report_no_date(); }
|
|
||||||
FMT_NORETURN void on_loc_time(numeric_system) { report_no_date(); }
|
|
||||||
FMT_NORETURN void on_us_date() { report_no_date(); }
|
|
||||||
FMT_NORETURN void on_iso_date() { report_no_date(); }
|
|
||||||
FMT_CONSTEXPR void on_12_hour_time() {}
|
FMT_CONSTEXPR void on_12_hour_time() {}
|
||||||
FMT_CONSTEXPR void on_24_hour_time() {}
|
FMT_CONSTEXPR void on_24_hour_time() {}
|
||||||
FMT_CONSTEXPR void on_iso_time() {}
|
FMT_CONSTEXPR void on_iso_time() {}
|
||||||
FMT_CONSTEXPR void on_am_pm() {}
|
FMT_CONSTEXPR void on_am_pm() {}
|
||||||
FMT_CONSTEXPR void on_duration_value() {}
|
FMT_CONSTEXPR void on_duration_value() {}
|
||||||
FMT_CONSTEXPR void on_duration_unit() {}
|
FMT_CONSTEXPR void on_duration_unit() {}
|
||||||
FMT_NORETURN void on_utc_offset() { report_no_date(); }
|
|
||||||
FMT_NORETURN void on_tz_name() { report_no_date(); }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
|
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
|
||||||
@ -1080,6 +1096,81 @@ struct chrono_formatter {
|
|||||||
|
|
||||||
FMT_END_DETAIL_NAMESPACE
|
FMT_END_DETAIL_NAMESPACE
|
||||||
|
|
||||||
|
#if defined(__cpp_lib_chrono) && __cpp_lib_chrono >= 201907
|
||||||
|
using weekday = std::chrono::weekday;
|
||||||
|
#else
|
||||||
|
// A fallback version of weekday.
|
||||||
|
class weekday {
|
||||||
|
private:
|
||||||
|
unsigned char value;
|
||||||
|
|
||||||
|
public:
|
||||||
|
weekday() = default;
|
||||||
|
explicit constexpr weekday(unsigned wd) noexcept
|
||||||
|
: value(static_cast<unsigned char>(wd != 7 ? wd : 0)) {}
|
||||||
|
constexpr unsigned c_encoding() const noexcept { return value; }
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// A rudimentary weekday formatter.
|
||||||
|
template <> struct formatter<weekday> {
|
||||||
|
private:
|
||||||
|
bool localized = false;
|
||||||
|
|
||||||
|
public:
|
||||||
|
FMT_CONSTEXPR auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
|
||||||
|
auto begin = ctx.begin(), end = ctx.end();
|
||||||
|
if (begin != end && *begin == 'L') {
|
||||||
|
++begin;
|
||||||
|
localized = true;
|
||||||
|
}
|
||||||
|
return begin;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto format(weekday wd, format_context& ctx) -> decltype(ctx.out()) {
|
||||||
|
auto tm = std::tm();
|
||||||
|
tm.tm_wday = static_cast<int>(wd.c_encoding());
|
||||||
|
auto&& os = std::ostringstream();
|
||||||
|
using iterator = std::ostreambuf_iterator<char>;
|
||||||
|
auto& loc = localized ? ctx.locale().template get<std::locale>()
|
||||||
|
: std::locale::classic();
|
||||||
|
const auto& tp = std::use_facet<std::time_put<char, iterator>>(loc);
|
||||||
|
auto fmt = string_view("%a");
|
||||||
|
auto end =
|
||||||
|
tp.put(iterator(os.rdbuf()), os, ' ', &tm, fmt.begin(), fmt.end());
|
||||||
|
if (end.failed()) FMT_THROW(format_error("failed to format time"));
|
||||||
|
auto s = os.str();
|
||||||
|
if (detail::is_utf8() && localized) {
|
||||||
|
// char16_t codecvt is broken in MSVC.
|
||||||
|
using code_unit = conditional_t<FMT_MSC_VER, wchar_t, char16_t>;
|
||||||
|
auto& f =
|
||||||
|
std::use_facet<std::codecvt<code_unit, char, std::mbstate_t>>(loc);
|
||||||
|
auto mb = std::mbstate_t();
|
||||||
|
const char* from_next = nullptr;
|
||||||
|
code_unit* to_next = nullptr;
|
||||||
|
constexpr size_t buf_size = 100;
|
||||||
|
code_unit buf[buf_size] = {};
|
||||||
|
auto result = f.in(mb, s.data(), s.data() + s.size(), from_next, buf,
|
||||||
|
buf + buf_size, to_next);
|
||||||
|
if (result != std::codecvt_base::ok)
|
||||||
|
FMT_THROW(format_error("failed to format time"));
|
||||||
|
s.clear();
|
||||||
|
for (code_unit* p = buf; p != to_next; ++p) {
|
||||||
|
code_unit c = *p;
|
||||||
|
if (c < 0x80) {
|
||||||
|
s.push_back(static_cast<char>(c));
|
||||||
|
} else if (c < 0x800) {
|
||||||
|
s.push_back(static_cast<char>(0xc0 | (c >> 6)));
|
||||||
|
s.push_back(static_cast<char>(0x80 | (c & 0x3f)));
|
||||||
|
} else {
|
||||||
|
FMT_THROW(format_error("failed to format time"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return std::copy(s.begin(), s.end(), ctx.out());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
template <typename Rep, typename Period, typename Char>
|
template <typename Rep, typename Period, typename Char>
|
||||||
struct formatter<std::chrono::duration<Rep, Period>, Char> {
|
struct formatter<std::chrono::duration<Rep, Period>, Char> {
|
||||||
private:
|
private:
|
||||||
|
@ -8,9 +8,12 @@
|
|||||||
#include "fmt/chrono.h"
|
#include "fmt/chrono.h"
|
||||||
|
|
||||||
#include "gtest-extra.h" // EXPECT_THROW_MSG
|
#include "gtest-extra.h" // EXPECT_THROW_MSG
|
||||||
|
#include "util.h" // get_locale
|
||||||
|
|
||||||
using fmt::runtime;
|
using fmt::runtime;
|
||||||
|
|
||||||
|
using testing::Contains;
|
||||||
|
|
||||||
auto make_tm() -> std::tm {
|
auto make_tm() -> std::tm {
|
||||||
auto time = std::tm();
|
auto time = std::tm();
|
||||||
time.tm_mday = 1;
|
time.tm_mday = 1;
|
||||||
@ -246,26 +249,15 @@ auto format_tm(const std::tm& time, fmt::string_view spec,
|
|||||||
return os.str();
|
return os.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(chrono_test, locale) {
|
||||||
|
auto loc = get_locale("ja_JP.utf8");
|
||||||
|
if (loc == std::locale::classic()) return;
|
||||||
# define EXPECT_TIME(spec, time, duration) \
|
# define EXPECT_TIME(spec, time, duration) \
|
||||||
{ \
|
{ \
|
||||||
auto jp_loc = std::locale("ja_JP.utf8"); \
|
auto jp_loc = std::locale("ja_JP.utf8"); \
|
||||||
EXPECT_EQ(format_tm(time, spec, jp_loc), \
|
EXPECT_EQ(format_tm(time, spec, jp_loc), \
|
||||||
fmt::format(jp_loc, "{:L" spec "}", duration)); \
|
fmt::format(jp_loc, "{:L" spec "}", duration)); \
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(chrono_test, locale) {
|
|
||||||
auto loc_name = "ja_JP.utf8";
|
|
||||||
bool has_locale = false;
|
|
||||||
auto loc = std::locale();
|
|
||||||
try {
|
|
||||||
loc = std::locale(loc_name);
|
|
||||||
has_locale = true;
|
|
||||||
} catch (const std::runtime_error&) {
|
|
||||||
}
|
|
||||||
if (!has_locale) {
|
|
||||||
fmt::print("{} locale is missing.\n", loc_name);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
EXPECT_TIME("%OH", make_hour(14), std::chrono::hours(14));
|
EXPECT_TIME("%OH", make_hour(14), std::chrono::hours(14));
|
||||||
EXPECT_TIME("%OI", make_hour(14), std::chrono::hours(14));
|
EXPECT_TIME("%OI", make_hour(14), std::chrono::hours(14));
|
||||||
EXPECT_TIME("%OM", make_minute(42), std::chrono::minutes(42));
|
EXPECT_TIME("%OM", make_minute(42), std::chrono::minutes(42));
|
||||||
@ -384,4 +376,14 @@ TEST(chrono_test, unsigned_duration) {
|
|||||||
EXPECT_EQ("42s", fmt::format("{}", std::chrono::duration<unsigned>(42)));
|
EXPECT_EQ("42s", fmt::format("{}", std::chrono::duration<unsigned>(42)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(chrono_test, format_weekday) {
|
||||||
|
auto loc = get_locale("ru_RU.UTF-8");
|
||||||
|
std::locale::global(loc);
|
||||||
|
EXPECT_EQ(fmt::format("{}", fmt::weekday(1)), "Mon");
|
||||||
|
if (loc != std::locale::classic()) {
|
||||||
|
EXPECT_THAT((std::vector<std::string>{"пн", "Пн"}),
|
||||||
|
Contains(fmt::format(loc, "{:L}", fmt::weekday(1))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#endif // FMT_STATIC_THOUSANDS_SEPARATOR
|
#endif // FMT_STATIC_THOUSANDS_SEPARATOR
|
||||||
|
@ -5,7 +5,25 @@
|
|||||||
//
|
//
|
||||||
// For the license information refer to format.h.
|
// For the license information refer to format.h.
|
||||||
|
|
||||||
#include "fmt/core.h"
|
#include <vector>
|
||||||
#include "gtest/gtest.h"
|
|
||||||
|
|
||||||
TEST(unicode_test, is_utf8) { EXPECT_TRUE(fmt::detail::is_utf8()); }
|
#include "fmt/chrono.h"
|
||||||
|
#include "gmock/gmock.h"
|
||||||
|
#include "util.h" // get_locale
|
||||||
|
|
||||||
|
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");
|
||||||
|
if (loc == std::locale::classic()) return;
|
||||||
|
try {
|
||||||
|
EXPECT_THAT(
|
||||||
|
(std::vector<std::string>{"День недели: пн", "День недели: Пн"}),
|
||||||
|
Contains(fmt::format(loc, "День недели: {:L}", fmt::weekday(1))));
|
||||||
|
} catch (const fmt::format_error& e) {
|
||||||
|
// Formatting can fail due to unsupported encoding.
|
||||||
|
fmt::print("Format error: {}\n", e.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -26,3 +26,12 @@ fmt::buffered_file open_buffered_file(FILE** fp) {
|
|||||||
#endif
|
#endif
|
||||||
return f;
|
return f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::locale get_locale(const char* name) {
|
||||||
|
try {
|
||||||
|
return std::locale(name);
|
||||||
|
} catch (const std::runtime_error&) {
|
||||||
|
fmt::print(stderr, "{} locale is missing.\n", name);
|
||||||
|
}
|
||||||
|
return std::locale::classic();
|
||||||
|
}
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
#include <cstdarg>
|
#include <cstdarg>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
|
#include <locale>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include "fmt/os.h"
|
#include "fmt/os.h"
|
||||||
@ -75,3 +76,6 @@ class date {
|
|||||||
int month() const { return month_; }
|
int month() const { return month_; }
|
||||||
int day() const { return day_; }
|
int day() const { return day_; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Returns a locale with the given name if available or classic locale othewise.
|
||||||
|
std::locale get_locale(const char* name);
|
||||||
|
Reference in New Issue
Block a user