diff --git a/include/fmt/chrono.h b/include/fmt/chrono.h index 7507e32f..c024fd71 100644 --- a/include/fmt/chrono.h +++ b/include/fmt/chrono.h @@ -298,8 +298,16 @@ inline auto do_write(const std::tm& time, const std::locale& loc, char format, 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; - // char16_t and char32_t codecvts are broken in MSVC (linkage errors). - using code_unit = conditional_t; + // 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; +#else + using code_unit = char32_t; +#endif auto& f = std::use_facet>(loc); auto mb = std::mbstate_t(); const char* from_next = nullptr; @@ -312,12 +320,29 @@ inline auto do_write(const std::tm& time, const std::locale& loc, char format, FMT_THROW(format_error("failed to format time")); str.clear(); for (code_unit* p = buf; p != to_next; ++p) { - auto c = *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")); }