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