diff --git a/doc/basic-bootstrap/layout.html b/doc/basic-bootstrap/layout.html
index 845aa35f..5519c4b5 100644
--- a/doc/basic-bootstrap/layout.html
+++ b/doc/basic-bootstrap/layout.html
@@ -97,7 +97,7 @@
};
{%- for scriptfile in script_files %}
-
+ {{ js_tag(scriptfile) }}
{%- endfor %}
{%- endmacro %}
diff --git a/include/fmt/chrono.h b/include/fmt/chrono.h
index 79ceaba4..55d590b5 100644
--- a/include/fmt/chrono.h
+++ b/include/fmt/chrono.h
@@ -289,84 +289,95 @@ inline null<> localtime_s(...) { return null<>(); }
inline null<> gmtime_r(...) { return null<>(); }
inline null<> gmtime_s(...) { return null<>(); }
+template
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 {
+ auto&& os = std::basic_ostringstream();
os.imbue(loc);
- using iterator = std::ostreambuf_iterator;
- const auto& facet = std::use_facet>(loc);
- auto end = facet.put(os, os, ' ', &time, format, modifier);
+ using iterator = std::ostreambuf_iterator;
+ const auto& facet = std::use_facet>(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 ::value)>
+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);
+}
+
+template ::value)>
+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);
+ 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;
+ using codecvt = std::codecvt;
#if FMT_CLANG_VERSION
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wdeprecated"
- auto& f = std::use_facet(loc);
+ auto& f = std::use_facet(loc);
# pragma clang diagnostic pop
#else
- auto& f = std::use_facet(loc);
+ auto& f = std::use_facet(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(*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(*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(*p) - 0x35fdc00;
+ }
+ if (c < 0x80) {
+ str.push_back(static_cast(c));
+ } else if (c < 0x800) {
+ str.push_back(static_cast(0xc0 | (c >> 6)));
+ str.push_back(static_cast(0x80 | (c & 0x3f)));
+ } else if ((c >= 0x800 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0xffff)) {
+ str.push_back(static_cast(0xe0 | (c >> 12)));
+ str.push_back(static_cast(0x80 | ((c & 0xfff) >> 6)));
+ str.push_back(static_cast(0x80 | (c & 0x3f)));
+ } else if (c >= 0x10000 && c <= 0x10ffff) {
+ str.push_back(static_cast(0xf0 | (c >> 18)));
+ str.push_back(static_cast(0x80 | ((c & 0x3ffff) >> 12)));
+ str.push_back(static_cast(0x80 | ((c & 0xfff) >> 6)));
+ str.push_back(static_cast(0x80 | (c & 0x3f)));
+ } else {
FMT_THROW(format_error("failed to format time"));
}
- c = (c << 10) + static_cast(*p) - 0x35fdc00;
- }
- if (c < 0x80) {
- str.push_back(static_cast(c));
- } else if (c < 0x800) {
- str.push_back(static_cast(0xc0 | (c >> 6)));
- str.push_back(static_cast(0x80 | (c & 0x3f)));
- } else if ((c >= 0x800 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0xffff)) {
- str.push_back(static_cast(0xe0 | (c >> 12)));
- str.push_back(static_cast(0x80 | ((c & 0xfff) >> 6)));
- str.push_back(static_cast(0x80 | (c & 0x3f)));
- } else if (c >= 0x10000 && c <= 0x10ffff) {
- str.push_back(static_cast(0xf0 | (c >> 18)));
- str.push_back(static_cast(0x80 | ((c & 0x3ffff) >> 12)));
- str.push_back(static_cast(0x80 | ((c & 0xfff) >> 6)));
- str.push_back(static_cast(0x80 | (c & 0x3f)));
- } else {
- FMT_THROW(format_error("failed to format time"));
}
}
- return str;
-}
-
-template
-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/.
@@ -1131,7 +1123,7 @@ struct chrono_formatter {
if (isnan(val)) return write_nan();
const auto& loc = localized ? context.locale().template get()
: std::locale::classic();
- out = detail::write(out, time, loc, format, modifier);
+ out = detail::write(out, time, loc, format, modifier);
}
void on_text(const char_type* begin, const char_type* end) {
@@ -1288,12 +1280,13 @@ class year_month_day {};
#endif
// A rudimentary weekday formatter.
-template <> struct formatter {
+template struct formatter {
private:
bool localized = false;
public:
- FMT_CONSTEXPR auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
+ FMT_CONSTEXPR auto parse(basic_format_parse_context& ctx)
+ -> decltype(ctx.begin()) {
auto begin = ctx.begin(), end = ctx.end();
if (begin != end && *begin == 'L') {
++begin;
@@ -1302,12 +1295,13 @@ template <> struct formatter {
return begin;
}
- auto format(weekday wd, format_context& ctx) -> decltype(ctx.out()) {
+ template
+ auto format(weekday wd, FormatContext& ctx) const -> decltype(ctx.out()) {
auto time = std::tm();
time.tm_wday = static_cast(wd.c_encoding());
const auto& loc = localized ? ctx.locale().template get()
: std::locale::classic();
- return detail::write(ctx.out(), time, loc, 'a');
+ return detail::write(ctx.out(), time, loc, 'a');
}
};
@@ -1472,118 +1466,159 @@ struct tm_format_checker : null_chrono_spec_handler {
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 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_;
- auto tm_year() const noexcept -> int { return 1900 + tm_.tm_year; }
+ auto tm_sec() const noexcept -> int {
+ FMT_ASSERT(tm_.tm_sec >= 0 && tm_.tm_sec <= 61, "");
+ return tm_.tm_sec;
+ }
+ auto tm_min() const noexcept -> int {
+ FMT_ASSERT(tm_.tm_min >= 0 && tm_.tm_min <= 59, "");
+ return tm_.tm_min;
+ }
+ auto tm_hour() const noexcept -> int {
+ FMT_ASSERT(tm_.tm_hour >= 0 && tm_.tm_hour <= 23, "");
+ return tm_.tm_hour;
+ }
+ auto tm_mday() const noexcept -> int {
+ FMT_ASSERT(tm_.tm_mday >= 1 && tm_.tm_mday <= 31, "");
+ return tm_.tm_mday;
+ }
+ auto tm_mon() const noexcept -> int {
+ FMT_ASSERT(tm_.tm_mon >= 0 && tm_.tm_mon <= 11, "");
+ return tm_.tm_mon;
+ }
+ auto tm_year() const noexcept -> long long { return 1900ll + tm_.tm_year; }
+ auto tm_wday() const noexcept -> int {
+ FMT_ASSERT(tm_.tm_wday >= 0 && tm_.tm_wday <= 6, "");
+ return tm_.tm_wday;
+ }
+ auto tm_yday() const noexcept -> int {
+ FMT_ASSERT(tm_.tm_yday >= 0 && tm_.tm_yday <= 365, "");
+ return tm_.tm_yday;
+ }
+
+ auto tm_hour12() const noexcept -> int {
+ 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
// do if the year is negative or exceeds 9999. Use the convention that %C
// concatenated with %y yields the same output as %Y, and that %Y contains at
// least 4 characters, with more only if necessary.
- auto split_year_lower(int year) const noexcept -> int {
+ auto split_year_lower(long long year) const noexcept -> int {
auto l = year % 100;
if (l < 0) l = -l; // l in [0, 99]
- return l;
+ return static_cast(l);
}
// Algorithm:
// https://en.wikipedia.org/wiki/ISO_week_date#Calculating_the_week_number_from_a_month_and_day_of_the_month_or_ordinal_date
- auto iso_year_weeks(const int year) const noexcept -> int {
- const long long curr_year = year;
- const long long prev_year = curr_year - 1;
- const int curr_p = static_cast(
+ auto iso_year_weeks(long long curr_year) const noexcept -> int {
+ const auto prev_year = curr_year - 1;
+ const auto curr_p =
(curr_year + curr_year / 4 - curr_year / 100 + curr_year / 400) %
- days_per_week);
- const int prev_p = static_cast(
+ days_per_week;
+ const auto prev_p =
(prev_year + prev_year / 4 - prev_year / 100 + prev_year / 400) %
- days_per_week);
+ days_per_week;
return 52 + ((curr_p == 4 || prev_p == 3) ? 1 : 0);
}
auto iso_week_num(int tm_yday, int tm_wday) const noexcept -> int {
return (tm_yday + 11 - (tm_wday == 0 ? days_per_week : tm_wday)) /
days_per_week;
}
- auto tm_iso_week_year() const noexcept -> int {
+ auto tm_iso_week_year() const noexcept -> long long {
const auto year = tm_year();
- const int w = iso_week_num(tm_.tm_yday, tm_.tm_wday);
+ const auto w = iso_week_num(tm_yday(), tm_wday());
if (w < 1) return year - 1;
if (w > iso_year_weeks(year)) return year + 1;
return year;
}
auto tm_iso_week_of_year() const noexcept -> int {
const auto year = tm_year();
- const int w = iso_week_num(tm_.tm_yday, tm_.tm_wday);
+ const auto w = iso_week_num(tm_yday(), tm_wday());
if (w < 1) return iso_year_weeks(year - 1);
if (w > iso_year_weeks(year)) return 1;
return w;
}
- auto tm_hour12() const noexcept -> int {
- auto hour = tm_.tm_hour % 12;
- return hour == 0 ? 12 : hour;
+ void write1(int value) {
+ *out_++ = static_cast('0' + to_unsigned(value) % 10);
}
-
- void write1(int value) { *out_++ = static_cast('0' + value % 10); }
void write2(int value) {
- const char* d = digits2(to_unsigned(value));
+ const char* d = digits2(to_unsigned(value) % 100);
*out_++ = *d++;
*out_++ = *d;
}
- void write_year(int year) {
+ void write_year_extended(long long year) {
+ // At least 4 characters.
+ int width = 4;
+ if (year < 0) {
+ *out_++ = '-';
+ year = 0 - year;
+ --width;
+ }
+ uint32_or_64_or_128_t n = to_unsigned(year);
+ const int num_digits = count_digits(n);
+ if (width > num_digits) out_ = std::fill_n(out_, width - num_digits, '0');
+ out_ = format_decimal(out_, n, num_digits).end;
+ }
+ void write_year(long long year) {
if (year >= 0 && year < 10000) {
- write2(year / 100);
- write2(year % 100);
+ write2(static_cast(year / 100));
+ write2(static_cast(year % 100));
} else {
- // At least 4 characters.
- int width = 4;
- if (year < 0) {
- *out_++ = '-';
- year = 0 - year;
- --width;
- }
- uint32_or_64_or_128_t n =
- to_unsigned(to_nonnegative_int(year, max_value()));
- const int num_digits = count_digits(n);
- if (width > num_digits) out_ = std::fill_n(out_, width - num_digits, '0');
- out_ = format_decimal(out_, n, num_digits).end;
+ write_year_extended(year);
}
}
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 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(buf.begin() + 1, buf.end(), out_);
+ out_ = write(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_; }
@@ -1591,33 +1626,72 @@ template class tm_writer {
out_ = copy_str(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_.tm_wday);
+ write1(tm_wday());
}
void on_dec1_weekday(numeric_system ns) {
if (ns != numeric_system::standard) return format_localized('u', 'O');
- write1(tm_.tm_wday == 0 ? days_per_week : tm_.tm_wday);
+ auto wday = tm_wday();
+ 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];
- write_digit2_separated(buf, to_unsigned(tm_.tm_mon + 1),
- to_unsigned(tm_.tm_mday),
+ write_digit2_separated(buf, to_unsigned(tm_mon() + 1),
+ to_unsigned(tm_mday()),
to_unsigned(split_year_lower(tm_year())), '/');
out_ = copy_str(std::begin(buf), std::end(buf), out_);
}
@@ -1629,11 +1703,12 @@ template class tm_writer {
copy2(buf, digits2(to_unsigned(year / 100)));
} else {
offset = 4;
- write_year(year);
+ write_year_extended(year);
year = 0;
}
- write_digit2_separated(buf + 2, year % 100, to_unsigned(tm_.tm_mon + 1),
- to_unsigned(tm_.tm_mday), '-');
+ write_digit2_separated(buf + 2, static_cast(year % 100),
+ to_unsigned(tm_mon() + 1), to_unsigned(tm_mday()),
+ '-');
out_ = copy_str(std::begin(buf) + offset, std::end(buf), out_);
}
@@ -1659,7 +1734,7 @@ template class tm_writer {
*out_++ = '-';
*out_++ = '0';
} else if (upper >= 0 && upper < 100) {
- write2(upper);
+ write2(static_cast(upper));
} else {
out_ = write(out_, upper);
}
@@ -1667,17 +1742,18 @@ template class tm_writer {
void on_dec_month(numeric_system ns) {
if (ns != numeric_system::standard) return format_localized('m', 'O');
- write2(tm_.tm_mon + 1);
+ write2(tm_mon() + 1);
}
void on_dec0_week_of_year(numeric_system ns) {
if (ns != numeric_system::standard) return format_localized('U', 'O');
- write2((tm_.tm_yday + days_per_week - tm_.tm_wday) / days_per_week);
+ write2((tm_yday() + days_per_week - tm_wday()) / days_per_week);
}
void on_dec1_week_of_year(numeric_system ns) {
if (ns != numeric_system::standard) return format_localized('W', 'O');
- write2((tm_.tm_yday + days_per_week -
- (tm_.tm_wday == 0 ? (days_per_week - 1) : (tm_.tm_wday - 1))) /
+ auto wday = tm_wday();
+ write2((tm_yday() + days_per_week -
+ (wday == 0 ? (days_per_week - 1) : (wday - 1))) /
days_per_week);
}
void on_iso_week_of_year(numeric_system ns) {
@@ -1691,24 +1767,25 @@ template class tm_writer {
}
void on_day_of_year() {
- auto yday = tm_.tm_yday + 1;
+ auto yday = tm_yday() + 1;
write1(yday / 100);
write2(yday % 100);
}
void on_day_of_month(numeric_system ns) {
if (ns != numeric_system::standard) return format_localized('d', 'O');
- write2(tm_.tm_mday);
+ write2(tm_mday());
}
void on_day_of_month_space(numeric_system ns) {
if (ns != numeric_system::standard) return format_localized('e', 'O');
- const char* d2 = digits2(to_unsigned(tm_.tm_mday));
- *out_++ = tm_.tm_mday < 10 ? ' ' : d2[0];
+ auto mday = to_unsigned(tm_mday()) % 100;
+ const char* d2 = digits2(mday);
+ *out_++ = mday < 10 ? ' ' : d2[0];
*out_++ = d2[1];
}
void on_24_hour(numeric_system ns) {
if (ns != numeric_system::standard) return format_localized('H', 'O');
- write2(tm_.tm_hour);
+ write2(tm_hour());
}
void on_12_hour(numeric_system ns) {
if (ns != numeric_system::standard) return format_localized('I', 'O');
@@ -1716,28 +1793,45 @@ template class tm_writer {
}
void on_minute(numeric_system ns) {
if (ns != numeric_system::standard) return format_localized('M', 'O');
- write2(tm_.tm_min);
+ write2(tm_min());
}
void on_second(numeric_system ns) {
if (ns != numeric_system::standard) return format_localized('S', 'O');
- write2(tm_.tm_sec);
+ 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(std::begin(buf), std::end(buf), out_);
+ *out_++ = ' ';
+ on_am_pm();
+ } else {
+ format_localized('r');
+ }
+ }
void on_24_hour_time() {
- write2(tm_.tm_hour);
+ write2(tm_hour());
*out_++ = ':';
- write2(tm_.tm_min);
+ write2(tm_min());
}
void on_iso_time() {
char buf[8];
- write_digit2_separated(buf, to_unsigned(tm_.tm_hour),
- to_unsigned(tm_.tm_min), to_unsigned(tm_.tm_sec),
- ':');
+ write_digit2_separated(buf, to_unsigned(tm_hour()), to_unsigned(tm_min()),
+ to_unsigned(tm_sec()), ':');
out_ = copy_str(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() {}
@@ -1810,7 +1904,10 @@ template struct formatter {
template
auto format(const std::tm& tm, FormatContext& ctx) const
-> decltype(ctx.out()) {
- auto w = detail::tm_writer(ctx.out(), tm);
+ const auto& loc_ref = ctx.locale();
+ const auto& loc =
+ loc_ref ? loc_ref.template get() : std::locale::classic();
+ auto w = detail::tm_writer(loc, ctx.out(), tm);
if (spec_ == spec::year_month_day)
w.on_iso_date();
else if (spec_ == spec::hh_mm_ss)
diff --git a/include/fmt/core.h b/include/fmt/core.h
index 5dc4e352..8e8d2f1c 100644
--- a/include/fmt/core.h
+++ b/include/fmt/core.h
@@ -18,7 +18,7 @@
// The fmt library version in the form major * 10000 + minor * 100 + patch.
#define FMT_VERSION 80001
-#ifdef __clang__
+#if defined (__clang__ ) && !defined(__ibmxl__)
# define FMT_CLANG_VERSION (__clang_major__ * 100 + __clang_minor__)
#else
# define FMT_CLANG_VERSION 0
@@ -258,8 +258,8 @@
#ifndef FMT_CONSTEVAL
# if ((FMT_GCC_VERSION >= 1000 || FMT_CLANG_VERSION >= 1101) && \
__cplusplus > 201703L && !defined(__apple_build_version__)) || \
- (defined(__cpp_consteval) && \
- !FMT_MSC_VER) // consteval is broken in MSVC and Apple clang 13.
+ (defined(__cpp_consteval) && (!FMT_MSC_VER || _MSC_FULL_VER >= 193030704))
+ // consteval is broken in MSVC before VS2022 and Apple clang 13.
# define FMT_CONSTEVAL consteval
# define FMT_HAS_CONSTEVAL
# else
diff --git a/test/chrono-test.cc b/test/chrono-test.cc
index be502423..2105db12 100644
--- a/test/chrono-test.cc
+++ b/test/chrono-test.cc
@@ -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 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>(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,
@@ -93,6 +96,13 @@ TEST(chrono_test, format_tm) {
EXPECT_EQ(fmt::format("{:%Y}", tm), "0027");
EXPECT_EQ(fmt::format("{:%C%y}", tm), "0027");
+ // Overflow year
+ tm.tm_year = 2147483647;
+ EXPECT_EQ(fmt::format("{:%Y}", tm), "2147485547");
+
+ tm.tm_year = -2147483648;
+ EXPECT_EQ(fmt::format("{:%Y}", tm), "-2147481748");
+
// for week on the year
// https://www.cl.cam.ac.uk/~mgk25/iso-time.html
std::vector tm_list = {
@@ -238,13 +248,19 @@ TEST(chrono_test, time_point) {
EXPECT_EQ(strftime_full(t2), fmt::format("{:%Y-%m-%d %H:%M:%S}", t2));
std::vector 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);
@@ -523,10 +539,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(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{"пн", "Пн", "пнд", "Пнд"}),
Contains(fmt::format(loc, "{:L}", mon)));
+ EXPECT_THAT((std::vector{"пн", "Пн", "пнд", "Пнд"}),
+ Contains(fmt::format(loc, "{:%a}", tm)));
}
}
diff --git a/test/module-test.cc b/test/module-test.cc
index 7b09ddce..39c83983 100644
--- a/test/module-test.cc
+++ b/test/module-test.cc
@@ -76,8 +76,10 @@ bool oops_detail_namespace_is_visible;
namespace fmt {
bool namespace_detail_invisible() {
#if defined(FMT_HIDE_MODULE_BUGS) && defined(_MSC_FULL_VER) && \
- _MSC_FULL_VER <= 192930129
- // bug in msvc up to 16.11-pre1:
+ ((_MSC_VER == 1929 && _MSC_FULL_VER <= 192930136) || \
+ (_MSC_VER == 1930 && _MSC_FULL_VER <= 193030704))
+ // bug in msvc up to 16.11.5 / 17.0-pre5:
+
// the namespace is visible even when it is neither
// implicitly nor explicitly exported
return true;
@@ -97,8 +99,8 @@ TEST(module_test, detail_namespace) {
// macros must not be imported from a *named* module [cpp.import]/5.1
TEST(module_test, macros) {
#if defined(FMT_HIDE_MODULE_BUGS) && defined(_MSC_FULL_VER) && \
- _MSC_FULL_VER <= 192930129
- // bug in msvc up to 16.11-pre1:
+ _MSC_FULL_VER <= 192930130
+ // bug in msvc up to 16.11-pre2:
// include-guard macros leak from BMI
// and even worse: they cannot be #undef-ined
macro_leaked = false;
@@ -456,8 +458,7 @@ TEST(module_test, time_duration) {
}
TEST(module_test, weekday) {
- EXPECT_EQ("Monday",
- std::format(std::locale::classic(), "{:%A}", fmt::weekday(1)));
+ EXPECT_EQ("Mon", fmt::format(std::locale::classic(), "{}", fmt::weekday(1)));
}
TEST(module_test, to_string_view) {
diff --git a/test/unicode-test.cc b/test/unicode-test.cc
index 73cac58c..63c828dd 100644
--- a/test/unicode-test.cc
+++ b/test/unicode-test.cc
@@ -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();
diff --git a/test/xchar-test.cc b/test/xchar-test.cc
index 67010aac..8f98c050 100644
--- a/test/xchar-test.cc
+++ b/test/xchar-test.cc
@@ -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 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 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>(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 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(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{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