diff --git a/include/fmt/format.h b/include/fmt/format.h
index cdb9f91a..3fcf3b20 100644
--- a/include/fmt/format.h
+++ b/include/fmt/format.h
@@ -2803,24 +2803,6 @@ constexpr auto operator"" _format(const char* s, size_t n)
 }
 }  // namespace literals
 
-template <typename Char, FMT_ENABLE_IF(!std::is_same<Char, char>::value)>
-auto vformat(basic_string_view<Char> format_str,
-             basic_format_args<buffer_context<type_identity_t<Char>>> args)
-    -> std::basic_string<Char> {
-  basic_memory_buffer<Char> buffer;
-  detail::vformat_to(buffer, format_str, args);
-  return to_string(buffer);
-}
-
-// Pass char_t as a default template parameter instead of using
-// std::basic_string<char_t<S>> to reduce the symbol size.
-template <typename S, typename... Args, typename Char = char_t<S>,
-          FMT_ENABLE_IF(!std::is_same<Char, char>::value)>
-auto format(const S& format_str, Args&&... args) -> std::basic_string<Char> {
-  const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...);
-  return vformat(to_string_view(format_str), vargs);
-}
-
 template <typename Locale, FMT_ENABLE_IF(detail::is_locale<Locale>::value)>
 inline auto vformat(const Locale& loc, string_view fmt, format_args args)
     -> std::string {
diff --git a/include/fmt/xchar.h b/include/fmt/xchar.h
index f316ad17..69f44864 100644
--- a/include/fmt/xchar.h
+++ b/include/fmt/xchar.h
@@ -73,6 +73,24 @@ auto join(std::initializer_list<T> list, wstring_view sep)
   return join(std::begin(list), std::end(list), sep);
 }
 
+template <typename Char, FMT_ENABLE_IF(!std::is_same<Char, char>::value)>
+auto vformat(basic_string_view<Char> format_str,
+             basic_format_args<buffer_context<type_identity_t<Char>>> args)
+    -> std::basic_string<Char> {
+  basic_memory_buffer<Char> buffer;
+  detail::vformat_to(buffer, format_str, args);
+  return to_string(buffer);
+}
+
+// Pass char_t as a default template parameter instead of using
+// std::basic_string<char_t<S>> to reduce the symbol size.
+template <typename S, typename... Args, typename Char = char_t<S>,
+          FMT_ENABLE_IF(!std::is_same<Char, char>::value)>
+auto format(const S& format_str, Args&&... args) -> std::basic_string<Char> {
+  const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...);
+  return vformat(to_string_view(format_str), vargs);
+}
+
 template <typename Locale, typename S, typename Char = char_t<S>,
           FMT_ENABLE_IF(detail::is_locale<Locale>::value&&
                             detail::is_exotic_char<Char>::value)>
diff --git a/test/chrono-test.cc b/test/chrono-test.cc
index 98e603ab..6bc2cdb0 100644
--- a/test/chrono-test.cc
+++ b/test/chrono-test.cc
@@ -48,8 +48,6 @@ TEST(chrono_test, format_tm) {
   tm.tm_sec = 33;
   EXPECT_EQ(fmt::format("The date is {:%Y-%m-%d %H:%M:%S}.", tm),
             "The date is 2016-04-25 11:22:33.");
-  EXPECT_EQ(fmt::format(L"The date is {:%Y-%m-%d %H:%M:%S}.", tm),
-            L"The date is 2016-04-25 11:22:33.");
 }
 
 TEST(chrono_test, grow_buffer) {
@@ -151,10 +149,6 @@ TEST(chrono_test, format_default) {
       fmt::format("{}", std::chrono::duration<int, std::ratio<15, 4>>(42)));
 }
 
-TEST(chrono_test, format_wide) {
-  EXPECT_EQ(L"42s", fmt::format(L"{}", std::chrono::seconds(42)));
-}
-
 TEST(chrono_test, align) {
   auto s = std::chrono::seconds(42);
   EXPECT_EQ("42s  ", fmt::format("{:5}", s));
diff --git a/test/format-test.cc b/test/format-test.cc
index 84d503ec..13e2ffc1 100644
--- a/test/format-test.cc
+++ b/test/format-test.cc
@@ -634,13 +634,6 @@ TEST(format_test, space_sign) {
                    format_error, "format specifier requires numeric argument");
 }
 
-TEST(format_test, sign_not_truncated) {
-  wchar_t format_str[] = {
-      L'{', L':',
-      '+' | static_cast<wchar_t>(1 << fmt::detail::num_bits<char>()), L'}', 0};
-  EXPECT_THROW(fmt::format(format_str, 42), format_error);
-}
-
 TEST(format_test, hash_flag) {
   EXPECT_EQ("42", fmt::format("{0:#}", 42));
   EXPECT_EQ("-42", fmt::format("{0:#}", -42));
@@ -1019,7 +1012,6 @@ TEST(format_test, format_bool) {
   EXPECT_EQ("false", fmt::format("{}", false));
   EXPECT_EQ("1", fmt::format("{:d}", true));
   EXPECT_EQ("true ", fmt::format("{:5}", true));
-  EXPECT_EQ(L"true", fmt::format(L"{}", true));
   EXPECT_EQ("true", fmt::format("{:s}", true));
   EXPECT_EQ("false", fmt::format("{:s}", false));
   EXPECT_EQ("false ", fmt::format("{:6s}", false));
@@ -1330,7 +1322,6 @@ TEST(format_test, format_char) {
   check_unknown_types('a', types, "char");
   EXPECT_EQ("a", fmt::format("{0}", 'a'));
   EXPECT_EQ("z", fmt::format("{0:c}", 'z'));
-  EXPECT_EQ(L"a", fmt::format(L"{0}", 'a'));
   int n = 'x';
   for (const char* type = types + 1; *type; ++type) {
     std::string format_str = fmt::format("{{:{}}}", *type);
@@ -1351,12 +1342,6 @@ TEST(format_test, format_unsigned_char) {
   EXPECT_EQ("42", fmt::format("{}", static_cast<uint8_t>(42)));
 }
 
-TEST(format_test, format_wchar) {
-  EXPECT_EQ(L"a", fmt::format(L"{0}", L'a'));
-  // This shouldn't compile:
-  // format("{}", L'a');
-}
-
 TEST(format_test, format_cstring) {
   check_unknown_types("test", "sp", "string");
   EXPECT_EQ("test", fmt::format("{0}", "test"));
@@ -1451,28 +1436,6 @@ TEST(format_test, format_explicitly_convertible_to_std_string_view) {
 }
 #endif
 
-namespace fake_qt {
-class QString {
- public:
-  QString(const wchar_t* s) : s_(s) {}
-  const wchar_t* utf16() const FMT_NOEXCEPT { return s_.data(); }
-  int size() const FMT_NOEXCEPT { return static_cast<int>(s_.size()); }
-
- private:
-  std::wstring s_;
-};
-
-fmt::basic_string_view<wchar_t> to_string_view(const QString& s) FMT_NOEXCEPT {
-  return {s.utf16(), static_cast<size_t>(s.size())};
-}
-}  // namespace fake_qt
-
-TEST(format_test, format_foreign_strings) {
-  using fake_qt::QString;
-  EXPECT_EQ(fmt::format(QString(L"{}"), 42), L"42");
-  EXPECT_EQ(fmt::format(QString(L"{}"), QString(L"42")), L"42");
-}
-
 class Answer {};
 
 FMT_BEGIN_NAMESPACE
@@ -1513,14 +1476,6 @@ TEST(format_test, format_to_custom) {
   EXPECT_STREQ(buf, "42");
 }
 
-TEST(format_test, wide_format_string) {
-  EXPECT_EQ(L"42", fmt::format(L"{}", 42));
-  EXPECT_EQ(L"4.2", fmt::format(L"{}", 4.2));
-  EXPECT_EQ(L"abc", fmt::format(L"{}", L"abc"));
-  EXPECT_EQ(L"z", fmt::format(L"{}", L'z'));
-  EXPECT_THROW(fmt::format(L"{:*\x343E}", 42), fmt::format_error);
-}
-
 TEST(format_test, format_string_from_speed_test) {
   EXPECT_EQ("1.2340000000:0042:+3.13:str:0x3e8:X:%",
             fmt::format("{0:0.10f}:{1:04}:{2:+g}:{3}:{4}:{5}:%", 1.234, 42,
@@ -1587,9 +1542,6 @@ TEST(format_test, format_examples) {
   EXPECT_THROW_MSG(fmt::format(runtime("The answer is {:d}"), "forty-two"),
                    format_error, "invalid type specifier");
 
-  EXPECT_EQ(L"Cyrillic letter \x42e",
-            fmt::format(L"Cyrillic letter {}", L'\x42e'));
-
   EXPECT_WRITE(
       stdout, fmt::print("{}", std::numeric_limits<double>::infinity()), "inf");
 }
@@ -1602,7 +1554,6 @@ TEST(format_test, print) {
 
 TEST(format_test, variadic) {
   EXPECT_EQ("abc1", fmt::format("{}c{}", "ab", 1));
-  EXPECT_EQ(L"abc1", fmt::format(L"{}c{}", L"ab", 1));
 }
 
 TEST(format_test, dynamic) {
@@ -1698,15 +1649,11 @@ fmt::string_view to_string_view(string_like) { return "foo"; }
 constexpr char with_null[3] = {'{', '}', '\0'};
 constexpr char no_null[2] = {'{', '}'};
 static FMT_CONSTEXPR_DECL const char static_with_null[3] = {'{', '}', '\0'};
-static FMT_CONSTEXPR_DECL const wchar_t static_with_null_wide[3] = {'{', '}',
-                                                                    '\0'};
 static FMT_CONSTEXPR_DECL const char static_no_null[2] = {'{', '}'};
-static FMT_CONSTEXPR_DECL const wchar_t static_no_null_wide[2] = {'{', '}'};
 
 TEST(format_test, compile_time_string) {
   EXPECT_EQ("foo", fmt::format(FMT_STRING("foo")));
   EXPECT_EQ("42", fmt::format(FMT_STRING("{}"), 42));
-  EXPECT_EQ(L"42", fmt::format(FMT_STRING(L"{}"), 42));
   EXPECT_EQ("foo", fmt::format(FMT_STRING("{}"), string_like()));
 
 #if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS
@@ -1718,14 +1665,10 @@ TEST(format_test, compile_time_string) {
 #endif
 
   (void)static_with_null;
-  (void)static_with_null_wide;
   (void)static_no_null;
-  (void)static_no_null_wide;
 #ifndef _MSC_VER
   EXPECT_EQ("42", fmt::format(FMT_STRING(static_with_null), 42));
-  EXPECT_EQ(L"42", fmt::format(FMT_STRING(static_with_null_wide), 42));
   EXPECT_EQ("42", fmt::format(FMT_STRING(static_no_null), 42));
-  EXPECT_EQ(L"42", fmt::format(FMT_STRING(static_no_null_wide), 42));
 #endif
 
   (void)with_null;
@@ -1736,7 +1679,6 @@ TEST(format_test, compile_time_string) {
 #endif
 #if defined(FMT_USE_STRING_VIEW) && __cplusplus >= 201703L
   EXPECT_EQ("42", fmt::format(FMT_STRING(std::string_view("{}")), 42));
-  EXPECT_EQ(L"42", fmt::format(FMT_STRING(std::wstring_view(L"{}")), 42));
 #endif
 }
 
@@ -2119,49 +2061,6 @@ TEST(format_test, char_traits_is_not_ambiguous) {
 #endif
 }
 
-#if __cplusplus > 201103L
-struct custom_char {
-  int value;
-  custom_char() = default;
-
-  template <typename T>
-  constexpr custom_char(T val) : value(static_cast<int>(val)) {}
-
-  operator int() const { return value; }
-};
-
-int to_ascii(custom_char c) { return c; }
-
-FMT_BEGIN_NAMESPACE
-template <> struct is_char<custom_char> : std::true_type {};
-FMT_END_NAMESPACE
-
-TEST(format_test, format_custom_char) {
-  const custom_char format[] = {'{', '}', 0};
-  auto result = fmt::format(format, custom_char('x'));
-  EXPECT_EQ(result.size(), 1);
-  EXPECT_EQ(result[0], custom_char('x'));
-}
-#endif
-
-// Convert a char8_t string to std::string. Otherwise GTest will insist on
-// inserting `char8_t` NTBS into a `char` stream which is disabled by P1423.
-template <typename S> std::string from_u8str(const S& str) {
-  return std::string(str.begin(), str.end());
-}
-
-TEST(format_test, format_utf8_precision) {
-  using str_type = std::basic_string<fmt::detail::char8_type>;
-  auto format =
-      str_type(reinterpret_cast<const fmt::detail::char8_type*>(u8"{:.4}"));
-  auto str = str_type(reinterpret_cast<const fmt::detail::char8_type*>(
-      u8"caf\u00e9s"));  // cafés
-  auto result = fmt::format(format, str);
-  EXPECT_EQ(fmt::detail::compute_width(result), 4);
-  EXPECT_EQ(result.size(), 5);
-  EXPECT_EQ(from_u8str(result), from_u8str(str.substr(0, 5)));
-}
-
 struct check_back_appender {};
 
 FMT_BEGIN_NAMESPACE
diff --git a/test/os-test.cc b/test/os-test.cc
index cc23e6b8..a34f96e6 100644
--- a/test/os-test.cc
+++ b/test/os-test.cc
@@ -80,18 +80,6 @@ TEST(os_test, format_std_error_code) {
                         std::error_code(-42, fmt::system_category())));
 }
 
-TEST(os_test, format_std_error_code_wide) {
-  EXPECT_EQ(L"generic:42",
-            fmt::format(FMT_STRING(L"{0}"),
-                        std::error_code(42, std::generic_category())));
-  EXPECT_EQ(L"system:42",
-            fmt::format(FMT_STRING(L"{0}"),
-                        std::error_code(42, fmt::system_category())));
-  EXPECT_EQ(L"system:-42",
-            fmt::format(FMT_STRING(L"{0}"),
-                        std::error_code(-42, fmt::system_category())));
-}
-
 TEST(os_test, format_windows_error) {
   LPWSTR message = 0;
   auto result = FormatMessageW(
diff --git a/test/ostream-test.cc b/test/ostream-test.cc
index 99bc6e5b..62e45f5d 100644
--- a/test/ostream-test.cc
+++ b/test/ostream-test.cc
@@ -45,29 +45,22 @@ template <typename T> void operator,(type_with_comma_op, const T&);
 template <typename T> type_with_comma_op operator<<(T&, const date&);
 
 enum streamable_enum {};
+
 std::ostream& operator<<(std::ostream& os, streamable_enum) {
   return os << "streamable_enum";
 }
 
-std::wostream& operator<<(std::wostream& os, streamable_enum) {
-  return os << L"streamable_enum";
-}
-
 enum unstreamable_enum {};
 
 TEST(ostream_test, enum) {
   EXPECT_EQ("streamable_enum", fmt::format("{}", streamable_enum()));
   EXPECT_EQ("0", fmt::format("{}", unstreamable_enum()));
-  EXPECT_EQ(L"streamable_enum", fmt::format(L"{}", streamable_enum()));
-  EXPECT_EQ(L"0", fmt::format(L"{}", unstreamable_enum()));
 }
 
 TEST(ostream_test, format) {
   EXPECT_EQ("a string", fmt::format("{0}", test_string("a string")));
   EXPECT_EQ("The date is 2012-12-9",
             fmt::format("The date is {0}", date(2012, 12, 9)));
-  EXPECT_EQ(L"The date is 2012-12-9",
-            fmt::format(L"The date is {0}", date(2012, 12, 9)));
 }
 
 TEST(ostream_test, format_specs) {
@@ -220,7 +213,6 @@ template <typename T> struct convertible {
 
 TEST(ostream_test, disable_builtin_ostream_operators) {
   EXPECT_EQ("42", fmt::format("{:d}", convertible<unsigned short>(42)));
-  EXPECT_EQ(L"42", fmt::format(L"{:d}", convertible<unsigned short>(42)));
   EXPECT_EQ("foo", fmt::format("{}", convertible<const char*>("foo")));
 }
 
diff --git a/test/printf-test.cc b/test/printf-test.cc
index 5aaa306e..e1b606a1 100644
--- a/test/printf-test.cc
+++ b/test/printf-test.cc
@@ -11,8 +11,8 @@
 #include <climits>
 #include <cstring>
 
-#include "fmt/core.h"
 #include "fmt/ostream.h"
+#include "fmt/xchar.h"
 #include "gtest-extra.h"
 #include "util.h"
 
diff --git a/test/ranges-test.cc b/test/ranges-test.cc
index 64f65e46..4932fe21 100644
--- a/test/ranges-test.cc
+++ b/test/ranges-test.cc
@@ -219,11 +219,6 @@ TEST(ranges_test, join_tuple) {
   EXPECT_EQ(fmt::format("{}", fmt::join(t4, "/")), "4");
 }
 
-TEST(ranges_test, wide_string_join_tuple) {
-  auto t = std::tuple<wchar_t, int, float>('a', 1, 2.0f);
-  EXPECT_EQ(fmt::format(L"({})", fmt::join(t, L", ")), L"(a, 1, 2)");
-}
-
 TEST(ranges_test, join_initializer_list) {
   EXPECT_EQ(fmt::format("{}", fmt::join({1, 2, 3}, ", ")), "1, 2, 3");
   EXPECT_EQ(fmt::format("{}", fmt::join({"fmt", "rocks", "!"}, " ")),
diff --git a/test/wchar-test.cc b/test/wchar-test.cc
index 4569a11a..bf512629 100644
--- a/test/wchar-test.cc
+++ b/test/wchar-test.cc
@@ -7,6 +7,9 @@
 
 #include <complex>
 
+#include "fmt/chrono.h"
+#include "fmt/ostream.h"
+#include "fmt/ranges.h"
 #include "fmt/xchar.h"
 #include "gtest/gtest.h"
 
@@ -24,6 +27,69 @@ TEST(wchar_test, format_explicitly_convertible_to_wstring_view) {
 }
 #endif
 
+TEST(wchar_test, format) {
+  EXPECT_EQ(L"42", fmt::format(L"{}", 42));
+  EXPECT_EQ(L"4.2", fmt::format(L"{}", 4.2));
+  EXPECT_EQ(L"abc", fmt::format(L"{}", L"abc"));
+  EXPECT_EQ(L"z", fmt::format(L"{}", L'z'));
+  EXPECT_THROW(fmt::format(L"{:*\x343E}", 42), fmt::format_error);
+  EXPECT_EQ(L"true", fmt::format(L"{}", true));
+  EXPECT_EQ(L"a", fmt::format(L"{0}", 'a'));
+  EXPECT_EQ(L"a", fmt::format(L"{0}", L'a'));
+  EXPECT_EQ(L"Cyrillic letter \x42e",
+            fmt::format(L"Cyrillic letter {}", L'\x42e'));
+  EXPECT_EQ(L"abc1", fmt::format(L"{}c{}", L"ab", 1));
+}
+
+TEST(wchar_test, compile_time_string) {
+#if defined(FMT_USE_STRING_VIEW) && __cplusplus >= 201703L
+  EXPECT_EQ(L"42", fmt::format(FMT_STRING(std::wstring_view(L"{}")), 42));
+#endif
+}
+
+#if __cplusplus > 201103L
+struct custom_char {
+  int value;
+  custom_char() = default;
+
+  template <typename T>
+  constexpr custom_char(T val) : value(static_cast<int>(val)) {}
+
+  operator int() const { return value; }
+};
+
+int to_ascii(custom_char c) { return c; }
+
+FMT_BEGIN_NAMESPACE
+template <> struct is_char<custom_char> : std::true_type {};
+FMT_END_NAMESPACE
+
+TEST(wchar_test, format_custom_char) {
+  const custom_char format[] = {'{', '}', 0};
+  auto result = fmt::format(format, custom_char('x'));
+  EXPECT_EQ(result.size(), 1);
+  EXPECT_EQ(result[0], custom_char('x'));
+}
+#endif
+
+// Convert a char8_t string to std::string. Otherwise GTest will insist on
+// inserting `char8_t` NTBS into a `char` stream which is disabled by P1423.
+template <typename S> std::string from_u8str(const S& str) {
+  return std::string(str.begin(), str.end());
+}
+
+TEST(wchar_test, format_utf8_precision) {
+  using str_type = std::basic_string<fmt::detail::char8_type>;
+  auto format =
+      str_type(reinterpret_cast<const fmt::detail::char8_type*>(u8"{:.4}"));
+  auto str = str_type(reinterpret_cast<const fmt::detail::char8_type*>(
+      u8"caf\u00e9s"));  // cafés
+  auto result = fmt::format(format, str);
+  EXPECT_EQ(fmt::detail::compute_width(result), 4);
+  EXPECT_EQ(result.size(), 5);
+  EXPECT_EQ(from_u8str(result), from_u8str(str.substr(0, 5)));
+}
+
 TEST(wchar_test, format_to) {
   auto buf = std::vector<wchar_t>();
   fmt::format_to(std::back_inserter(buf), L"{}{}", 42, L'\0');
@@ -88,6 +154,63 @@ TEST(wchar_test, print) {
 TEST(wchar_test, join) {
   int v[3] = {1, 2, 3};
   EXPECT_EQ(fmt::format(L"({})", fmt::join(v, v + 3, L", ")), L"(1, 2, 3)");
+  auto t = std::tuple<wchar_t, int, float>('a', 1, 2.0f);
+  EXPECT_EQ(fmt::format(L"({})", fmt::join(t, L", ")), L"(a, 1, 2)");
+}
+
+enum streamable_enum {};
+
+std::wostream& operator<<(std::wostream& os, streamable_enum) {
+  return os << L"streamable_enum";
+}
+
+enum unstreamable_enum {};
+
+TEST(wchar_test, enum) {
+  EXPECT_EQ(L"streamable_enum", fmt::format(L"{}", streamable_enum()));
+  EXPECT_EQ(L"0", fmt::format(L"{}", unstreamable_enum()));
+}
+
+TEST(wchar_test, sign_not_truncated) {
+  wchar_t format_str[] = {
+      L'{', L':',
+      '+' | static_cast<wchar_t>(1 << fmt::detail::num_bits<char>()), L'}', 0};
+  EXPECT_THROW(fmt::format(format_str, 42), fmt::format_error);
+}
+
+namespace fake_qt {
+class QString {
+ public:
+  QString(const wchar_t* s) : s_(s) {}
+  const wchar_t* utf16() const FMT_NOEXCEPT { return s_.data(); }
+  int size() const FMT_NOEXCEPT { return static_cast<int>(s_.size()); }
+
+ private:
+  std::wstring s_;
+};
+
+fmt::basic_string_view<wchar_t> to_string_view(const QString& s) FMT_NOEXCEPT {
+  return {s.utf16(), static_cast<size_t>(s.size())};
+}
+}  // namespace fake_qt
+
+TEST(format_test, format_foreign_strings) {
+  using fake_qt::QString;
+  EXPECT_EQ(fmt::format(QString(L"{}"), 42), L"42");
+  EXPECT_EQ(fmt::format(QString(L"{}"), QString(L"42")), L"42");
+}
+
+TEST(wchar_test, chrono) {
+  auto tm = std::tm();
+  tm.tm_year = 116;
+  tm.tm_mon = 3;
+  tm.tm_mday = 25;
+  tm.tm_hour = 11;
+  tm.tm_min = 22;
+  tm.tm_sec = 33;
+  EXPECT_EQ(fmt::format("The date is {:%Y-%m-%d %H:%M:%S}.", tm),
+            "The date is 2016-04-25 11:22:33.");
+  EXPECT_EQ(L"42s", fmt::format(L"{}", std::chrono::seconds(42)));
 }
 
 TEST(wchar_test, to_wstring) { EXPECT_EQ(L"42", fmt::to_wstring(42)); }