Snap for 11541002 from 3b8b60f70480ead65ad864cb3311fc52ec122907 to sdk-release

Change-Id: Icc3710924873571a2f4447c9b32d4bb64f63a95b
diff --git a/.github/workflows/cifuzz.yml b/.github/workflows/cifuzz.yml
index f87899d..25250e8 100644
--- a/.github/workflows/cifuzz.yml
+++ b/.github/workflows/cifuzz.yml
@@ -10,20 +10,20 @@
     steps:
     - name: Build Fuzzers
       id: build
-      uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master
+      uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@061583ebb5a96653e42feb3a97ee513eedc18078 # master
       with:
         oss-fuzz-project-name: 'fmt'
         dry-run: false
         language: c++
     - name: Run Fuzzers
-      uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master
+      uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@061583ebb5a96653e42feb3a97ee513eedc18078 # master
       with:
         oss-fuzz-project-name: 'fmt'
         fuzz-seconds: 300
         dry-run: false
         language: c++
     - name: Upload Crash
-      uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
+      uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # v4.0.0
       if: failure() && steps.build.outcome == 'success'
       with:
         name: artifacts
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
new file mode 100644
index 0000000..b906fac
--- /dev/null
+++ b/.github/workflows/lint.yml
@@ -0,0 +1,26 @@
+name: lint
+
+on:
+  pull_request:
+    paths:
+      - '**.h'
+      - '**.cc'
+
+permissions:
+  contents: read
+
+jobs:
+  format_code:
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@v4
+
+    - name: Install clang-format
+      uses: aminya/setup-cpp@v1
+      with:
+        clangformat: 17.0.5
+
+    - name: Run clang-format
+      run: |
+        find include src -name '*.h' -o -name '*.cc' |  xargs clang-format -i -style=file -fallback-style=none
+        git diff --exit-code
diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml
index 33aecd3..c5f2ea9 100644
--- a/.github/workflows/scorecard.yml
+++ b/.github/workflows/scorecard.yml
@@ -52,7 +52,7 @@
       # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
       # format to the repository Actions tab.
       - name: "Upload artifact"
-        uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
+        uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # v4.0.0
         with:
           name: SARIF file
           path: results.sarif
diff --git a/.gitignore b/.gitignore
index 8a37cb9..1406ac3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,37 +1,24 @@
-.vscode/
-.vs/
-
-*.iml
-.idea/
-.externalNativeBuild/
-.gradle/
-gradle/
-gradlew*
-local.properties
-build/
-support/.cxx
-
-bin/
-/_CPack_Packages
-/CMakeScripts
-/doc/doxyxml
-/doc/html
-/doc/node_modules
-virtualenv
-/Testing
-/install_manifest.txt
-*~
 *.a
 *.so*
 *.xcodeproj
-*.zip
-cmake_install.cmake
-CPack*.cmake
-fmt-*.cmake
-CTestTestfile.cmake
+*~
+.vscode/
+/CMakeScripts
+/Testing
+/_CPack_Packages
+/doc/doxyxml
+/doc/html
+/doc/node_modules
+/install_manifest.txt
 CMakeCache.txt
 CMakeFiles
+CPack*.cmake
+CTestTestfile.cmake
 FMT.build
 Makefile
-run-msbuild.bat
+bin/
+build/
+cmake_install.cmake
+fmt-*.cmake
 fmt.pc
+virtualenv
diff --git a/Android.bp b/Android.bp
index 2d6c6b3..e1fb5cb 100644
--- a/Android.bp
+++ b/Android.bp
@@ -133,7 +133,11 @@
     host_supported: true,
     test_suites: ["general-tests"],
     // The tests require exceptions and RTTI.
-    cflags: ["-fexceptions"],
+    cflags: [
+        "-fexceptions",
+        // https://github.com/fmtlib/fmt/issues/3884
+        "-Wno-non-virtual-dtor",
+    ],
     rtti: true,
     // The usual "gtest *and* gmock, please" dance...
     gtest: false,
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 485eb86..4b928ae 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,9 +1,9 @@
 cmake_minimum_required(VERSION 3.8...3.26)
 
 # Fallback for using newer policies on CMake <3.12.
-if(${CMAKE_VERSION} VERSION_LESS 3.12)
+if (${CMAKE_VERSION} VERSION_LESS 3.12)
   cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
-endif()
+endif ()
 
 # Determine if fmt is built as a subproject (using add_subdirectory)
 # or if it is the master project.
@@ -162,10 +162,10 @@
 if (FMT_SYSTEM_HEADERS)
   set(FMT_SYSTEM_HEADERS_ATTRIBUTE SYSTEM)
 endif ()
-if(CMAKE_SYSTEM_NAME STREQUAL "MSDOS")
+if (CMAKE_SYSTEM_NAME STREQUAL "MSDOS")
   set(FMT_TEST OFF)
   message(STATUS "MSDOS is incompatible with gtest")
-endif()
+endif ()
 
 # Get version from core.h
 file(READ include/fmt/core.h core_h)
@@ -283,7 +283,7 @@
 endif ()
 
 add_module_library(fmt src/fmt.cc FALLBACK
-                   ${FMT_SOURCES} ${FMT_HEADERS} README.rst ChangeLog.md
+                   ${FMT_SOURCES} ${FMT_HEADERS} README.md ChangeLog.md
                    IF FMT_MODULE)
 add_library(fmt::fmt ALIAS fmt)
 if (FMT_MODULE)
@@ -334,7 +334,7 @@
 endif ()
 if (FMT_SAFE_DURATION_CAST)
   target_compile_definitions(fmt PUBLIC FMT_SAFE_DURATION_CAST)
-endif()
+endif ()
 
 add_library(fmt-header-only INTERFACE)
 add_library(fmt::fmt-header-only ALIAS fmt-header-only)
@@ -342,7 +342,8 @@
 target_compile_definitions(fmt-header-only INTERFACE FMT_HEADER_ONLY=1)
 target_compile_features(fmt-header-only INTERFACE cxx_std_11)
 
-target_include_directories(fmt-header-only ${FMT_SYSTEM_HEADERS_ATTRIBUTE} INTERFACE
+target_include_directories(fmt-header-only
+  ${FMT_SYSTEM_HEADERS_ATTRIBUTE} INTERFACE
   $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
   $<INSTALL_INTERFACE:${FMT_INC_DIR}>)
 
@@ -447,6 +448,6 @@
   set(CPACK_SOURCE_IGNORE_FILES ${ignored_files})
   set(CPACK_SOURCE_PACKAGE_FILE_NAME fmt-${FMT_VERSION})
   set(CPACK_PACKAGE_NAME fmt)
-  set(CPACK_RESOURCE_FILE_README ${PROJECT_SOURCE_DIR}/README.rst)
+  set(CPACK_RESOURCE_FILE_README ${PROJECT_SOURCE_DIR}/README.md)
   include(CPack)
 endif ()
diff --git a/ChangeLog.md b/ChangeLog.md
index 2e51d10..8f8075a 100644
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,3 +1,248 @@
+# 10.2.0 - 2024-01-01
+
+-   Added support for the `%j` specifier (the number of days) for
+    `std::chrono::duration` (https://github.com/fmtlib/fmt/issues/3643,
+    https://github.com/fmtlib/fmt/pull/3732). Thanks @intelfx.
+
+-   Added support for the chrono suffix for days and changed
+    the suffix for minutes from "m" to the correct "min"
+    (https://github.com/fmtlib/fmt/issues/3662,
+    https://github.com/fmtlib/fmt/pull/3664).
+    For example ([godbolt](https://godbolt.org/z/9KhMnq9ba)):
+
+    ```c++
+    #include <fmt/chrono.h>
+
+    int main() {
+      fmt::print("{}\n", std::chrono::days(42)); // prints "42d"
+    }
+    ```
+
+    Thanks @Richardk2n.
+
+-   Fixed an overflow in `std::chrono::time_point` formatting with large dates
+    (https://github.com/fmtlib/fmt/issues/3725,
+    https://github.com/fmtlib/fmt/pull/3727). Thanks @cschreib.
+
+-   Added a formatter for `std::source_location`
+    (https://github.com/fmtlib/fmt/pull/3730).
+    For example ([godbolt](https://godbolt.org/z/YajfKjhhr)):
+
+    ```c++
+    #include <source_location>
+    #include <fmt/std.h>
+
+    int main() {
+      fmt::print("{}\n", std::source_location::current());
+    }
+    ```
+
+    prints
+
+    ```
+    /app/example.cpp:5:51: int main()
+    ```
+
+    Thanks @felix642.
+
+-   Added a formatter for `std::bitset`
+    (https://github.com/fmtlib/fmt/pull/3660).
+    For example ([godbolt](https://godbolt.org/z/bdEaGeYxe)):
+
+    ```c++
+    #include <bitset>
+    #include <fmt/std.h>
+
+    int main() {
+      fmt::print("{}\n", std::bitset<6>(42)); // prints "101010"
+    }
+    ```
+
+    Thanks @muggenhor.
+
+-   Added an experimental `nested_formatter` that provides an easy way of
+    applying a formatter to one or more subobjects while automatically handling
+    width, fill and alignment. For example:
+
+    ```c++
+    #include <fmt/format.h>
+
+    struct point {
+      double x, y;
+    };
+
+    template <>
+    struct fmt::formatter<point> : nested_formatter<double> {
+      auto format(point p, format_context& ctx) const {
+        return write_padded(ctx, [=](auto out) {
+          return format_to(out, "({}, {})", nested(p.x), nested(p.y));
+        });
+      }
+    };
+
+    int main() {
+      fmt::print("[{:>20.2f}]", point{1, 2});
+    }
+    ```
+
+    prints
+
+    ```
+    [          (1.00, 2.00)]
+    ```
+
+-   Added the generic representation (`g`) to `std::filesystem::path`
+    (https://github.com/fmtlib/fmt/issues/3715,
+    https://github.com/fmtlib/fmt/pull/3729). For example:
+
+    ```c++
+    #include <filesystem>
+    #include <fmt/std.h>
+
+    int main() {
+      fmt::print("{:g}\n", std::filesystem::path("C:\\foo"));
+    }
+    ```
+
+    prints `"C:/foo"` on Windows.
+
+    Thanks @js324.
+
+-   Made `format_as` work with references
+    (https://github.com/fmtlib/fmt/pull/3739). Thanks @tchaikov.
+
+-   Fixed formatting of invalid UTF-8 with precision
+    (https://github.com/fmtlib/fmt/issues/3284).
+
+-   Fixed an inconsistency between `fmt::to_string` and `fmt::format`
+    (https://github.com/fmtlib/fmt/issues/3684).
+
+-   Disallowed unsafe uses of `fmt::styled`
+    (https://github.com/fmtlib/fmt/issues/3625):
+
+    ```c++
+    auto s = fmt::styled(std::string("dangle"), fmt::emphasis::bold);
+    fmt::print("{}\n", s); // compile error
+    ```
+
+    Pass `fmt::styled(...)` as a parameter instead.
+
+-   Added a null check when formatting a C string with the `s` specifier
+    (https://github.com/fmtlib/fmt/issues/3706).
+
+-   Disallowed the `c` specifier for `bool`
+    (https://github.com/fmtlib/fmt/issues/3726,
+    https://github.com/fmtlib/fmt/pull/3734). Thanks @js324.
+
+-   Made the default formatting unlocalized in `fmt::ostream_formatter` for
+    consistency with the rest of the library
+    (https://github.com/fmtlib/fmt/issues/3460).
+
+-   Fixed localized formatting in bases other than decimal
+    (https://github.com/fmtlib/fmt/issues/3693,
+    https://github.com/fmtlib/fmt/pull/3750). Thanks @js324.
+
+-   Fixed a performance regression in experimental `fmt::ostream::print`
+    (https://github.com/fmtlib/fmt/issues/3674).
+
+-   Added synchronization with the underlying output stream when writing to
+    the Windows console
+    (https://github.com/fmtlib/fmt/pull/3668,
+    https://github.com/fmtlib/fmt/issues/3688,
+    https://github.com/fmtlib/fmt/pull/3689).
+    Thanks @Roman-Koshelev and @dimztimz.
+
+-   Changed to only export `format_error` when {fmt} is built as a shared
+    library (https://github.com/fmtlib/fmt/issues/3626,
+    https://github.com/fmtlib/fmt/pull/3627). Thanks @phprus.
+
+-   Made `fmt::streamed` `constexpr`.
+    (https://github.com/fmtlib/fmt/pull/3650). Thanks @muggenhor.
+
+-   Enabled `consteval` on older versions of MSVC
+    (https://github.com/fmtlib/fmt/pull/3757). Thanks @phprus.
+
+-   Added an option to build without `wchar_t` support on Windows
+    (https://github.com/fmtlib/fmt/issues/3631,
+    https://github.com/fmtlib/fmt/pull/3636). Thanks @glebm.
+
+-   Improved build and CI configuration
+    (https://github.com/fmtlib/fmt/pull/3679,
+    https://github.com/fmtlib/fmt/issues/3701,
+    https://github.com/fmtlib/fmt/pull/3702,
+    https://github.com/fmtlib/fmt/pull/3749).
+    Thanks @jcar87, @pklima and @tchaikov.
+
+-   Fixed various warnings, compilation and test issues
+    (https://github.com/fmtlib/fmt/issues/3607,
+    https://github.com/fmtlib/fmt/pull/3610,
+    https://github.com/fmtlib/fmt/pull/3624,
+    https://github.com/fmtlib/fmt/pull/3630,
+    https://github.com/fmtlib/fmt/pull/3634,
+    https://github.com/fmtlib/fmt/pull/3638,
+    https://github.com/fmtlib/fmt/issues/3645,
+    https://github.com/fmtlib/fmt/issues/3646,
+    https://github.com/fmtlib/fmt/pull/3647,
+    https://github.com/fmtlib/fmt/pull/3652,
+    https://github.com/fmtlib/fmt/issues/3654,
+    https://github.com/fmtlib/fmt/pull/3663,
+    https://github.com/fmtlib/fmt/issues/3670,
+    https://github.com/fmtlib/fmt/pull/3680,
+    https://github.com/fmtlib/fmt/issues/3694,
+    https://github.com/fmtlib/fmt/pull/3695,
+    https://github.com/fmtlib/fmt/pull/3699,
+    https://github.com/fmtlib/fmt/issues/3705,
+    https://github.com/fmtlib/fmt/issues/3710,
+    https://github.com/fmtlib/fmt/issues/3712,
+    https://github.com/fmtlib/fmt/pull/3713,
+    https://github.com/fmtlib/fmt/issues/3714,
+    https://github.com/fmtlib/fmt/pull/3716,
+    https://github.com/fmtlib/fmt/pull/3723,
+    https://github.com/fmtlib/fmt/issues/3738,
+    https://github.com/fmtlib/fmt/issues/3740,
+    https://github.com/fmtlib/fmt/pull/3741,
+    https://github.com/fmtlib/fmt/pull/3743,
+    https://github.com/fmtlib/fmt/issues/3745,
+    https://github.com/fmtlib/fmt/pull/3747,
+    https://github.com/fmtlib/fmt/pull/3748,
+    https://github.com/fmtlib/fmt/pull/3751,
+    https://github.com/fmtlib/fmt/pull/3754,
+    https://github.com/fmtlib/fmt/pull/3755,
+    https://github.com/fmtlib/fmt/issues/3760,
+    https://github.com/fmtlib/fmt/pull/3762,
+    https://github.com/fmtlib/fmt/issues/3763,
+    https://github.com/fmtlib/fmt/pull/3764,
+    https://github.com/fmtlib/fmt/issues/3774,
+    https://github.com/fmtlib/fmt/pull/3779).
+    Thanks @danakj, @vinayyadav3016, @cyyever, @phprus, @qimiko, @saschasc,
+    @gsjaardema, @lazka, @Zhaojun-Liu, @carlsmedstad, @hotwatermorning,
+    @cptFracassa, @kuguma, @PeterJohnson, @H1X4Dev, @asantoni, @eltociear,
+    @msimberg, @tchaikov, @waywardmonkeys.
+
+-   Improved documentation and README
+    (https://github.com/fmtlib/fmt/issues/2086,
+    https://github.com/fmtlib/fmt/issues/3637,
+    https://github.com/fmtlib/fmt/pull/3642,
+    https://github.com/fmtlib/fmt/pull/3653,
+    https://github.com/fmtlib/fmt/pull/3655,
+    https://github.com/fmtlib/fmt/pull/3661,
+    https://github.com/fmtlib/fmt/issues/3673,
+    https://github.com/fmtlib/fmt/pull/3677,
+    https://github.com/fmtlib/fmt/pull/3737,
+    https://github.com/fmtlib/fmt/issues/3742,
+    https://github.com/fmtlib/fmt/pull/3744).
+    Thanks @idzm, @perlun, @joycebrum, @fennewald, @reinhardt1053, @GeorgeLS.
+
+-   Updated CI dependencies
+    (https://github.com/fmtlib/fmt/pull/3615,
+    https://github.com/fmtlib/fmt/pull/3622,
+    https://github.com/fmtlib/fmt/pull/3623,
+    https://github.com/fmtlib/fmt/pull/3666,
+    https://github.com/fmtlib/fmt/pull/3696,
+    https://github.com/fmtlib/fmt/pull/3697,
+    https://github.com/fmtlib/fmt/pull/3759,
+    https://github.com/fmtlib/fmt/pull/3782).
+
 # 10.1.1 - 2023-08-28
 
 -   Added formatters for `std::atomic` and `atomic_flag`
@@ -13,8 +258,7 @@
     https://github.com/fmtlib/fmt/pull/3605).
     Thanks @MathewBensonCode.
 -   Made `fmt::to_string` work with types that have `format_as`
-    overloads (https://github.com/fmtlib/fmt/pull/3575).
-    Thanks @phprus.
+    overloads (https://github.com/fmtlib/fmt/pull/3575). Thanks @phprus.
 -   Made `formatted_size` work with integral format specifiers at
     compile time (https://github.com/fmtlib/fmt/pull/3591).
     Thanks @elbeno.
@@ -388,7 +632,9 @@
 
     Thanks @ShawnZhong.
 
--   Added a formatter for `std::optional` to `fmt/std.h`.
+-   Added a formatter for `std::optional` to `fmt/std.h`
+    (https://github.com/fmtlib/fmt/issues/1367,
+    https://github.com/fmtlib/fmt/pull/3303).
     Thanks @tom-huntington.
 
 -   Fixed formatting of valueless by exception variants
diff --git a/METADATA b/METADATA
index f9ea867..b4da998 100644
--- a/METADATA
+++ b/METADATA
@@ -1,19 +1,20 @@
+# This project was upgraded with external_updater.
+# Usage: tools/external_updater/updater.sh update external/fmtlib
+# For more info, check https://cs.android.com/android/platform/superproject/+/main:tools/external_updater/README.md
+
 name: "fmtlib"
 description: "{fmt} is an open-source formatting library providing a fast and safe alternative to C stdio and C++ iostreams."
 third_party {
-  url {
-    type: HOMEPAGE
-    value: "https://github.com/fmtlib/fmt"
-  }
-  url {
-    type: GIT
-    value: "https://github.com/fmtlib/fmt.git"
-  }
-  version: "10.1.1"
   license_type: NOTICE
   last_upgrade_date {
-    year: 2023
-    month: 12
-    day: 7
+    year: 2024
+    month: 3
+    day: 5
+  }
+  homepage: "https://github.com/fmtlib/fmt"
+  identifier {
+    type: "Git"
+    value: "https://github.com/fmtlib/fmt.git"
+    version: "10.2.0"
   }
 }
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..dcfb16e
--- /dev/null
+++ b/README.md
@@ -0,0 +1,490 @@
+<img src="https://user-images.githubusercontent.com/576385/156254208-f5b743a9-88cf-439d-b0c0-923d53e8d551.png" alt="{fmt}" width="25%"/>
+
+[![image](https://github.com/fmtlib/fmt/workflows/linux/badge.svg)](https://github.com/fmtlib/fmt/actions?query=workflow%3Alinux)
+[![image](https://github.com/fmtlib/fmt/workflows/macos/badge.svg)](https://github.com/fmtlib/fmt/actions?query=workflow%3Amacos)
+[![image](https://github.com/fmtlib/fmt/workflows/windows/badge.svg)](https://github.com/fmtlib/fmt/actions?query=workflow%3Awindows)
+[![fmt is continuously fuzzed at oss-fuzz](https://oss-fuzz-build-logs.storage.googleapis.com/badges/fmt.svg)](https://bugs.chromium.org/p/oss-fuzz/issues/list?\%0Acolspec=ID%20Type%20Component%20Status%20Proj%20Reported%20Owner%20\%0ASummary&q=proj%3Dfmt&can=1)
+[![Ask questions at StackOverflow with the tag fmt](https://img.shields.io/badge/stackoverflow-fmt-blue.svg)](https://stackoverflow.com/questions/tagged/fmt)
+[![image](https://api.securityscorecards.dev/projects/github.com/fmtlib/fmt/badge)](https://securityscorecards.dev/viewer/?uri=github.com/fmtlib/fmt)
+
+**{fmt}** is an open-source formatting library providing a fast and safe
+alternative to C stdio and C++ iostreams.
+
+If you like this project, please consider donating to one of the funds
+that help victims of the war in Ukraine: <https://www.stopputin.net/>.
+
+[Documentation](https://fmt.dev)
+
+[Cheat Sheets](https://hackingcpp.com/cpp/libs/fmt.html)
+
+Q&A: ask questions on [StackOverflow with the tag
+fmt](https://stackoverflow.com/questions/tagged/fmt).
+
+Try {fmt} in [Compiler Explorer](https://godbolt.org/z/Eq5763).
+
+# Features
+
+- Simple [format API](https://fmt.dev/latest/api.html) with positional
+  arguments for localization
+- Implementation of [C++20
+  std::format](https://en.cppreference.com/w/cpp/utility/format) and
+  [C++23 std::print](https://en.cppreference.com/w/cpp/io/print)
+- [Format string syntax](https://fmt.dev/latest/syntax.html) similar
+  to Python\'s
+  [format](https://docs.python.org/3/library/stdtypes.html#str.format)
+- Fast IEEE 754 floating-point formatter with correct rounding,
+  shortness and round-trip guarantees using the
+  [Dragonbox](https://github.com/jk-jeon/dragonbox) algorithm
+- Portable Unicode support
+- Safe [printf
+  implementation](https://fmt.dev/latest/api.html#printf-formatting)
+  including the POSIX extension for positional arguments
+- Extensibility: [support for user-defined
+  types](https://fmt.dev/latest/api.html#formatting-user-defined-types)
+- High performance: faster than common standard library
+  implementations of `(s)printf`, iostreams, `to_string` and
+  `to_chars`, see [Speed tests](#speed-tests) and [Converting a
+  hundred million integers to strings per
+  second](http://www.zverovich.net/2020/06/13/fast-int-to-string-revisited.html)
+- Small code size both in terms of source code with the minimum
+  configuration consisting of just three files, `core.h`, `format.h`
+  and `format-inl.h`, and compiled code; see [Compile time and code
+  bloat](#compile-time-and-code-bloat)
+- Reliability: the library has an extensive set of
+  [tests](https://github.com/fmtlib/fmt/tree/master/test) and is
+  [continuously fuzzed](https://bugs.chromium.org/p/oss-fuzz/issues/list?colspec=ID%20Type%20Component%20Status%20Proj%20Reported%20Owner%20Summary&q=proj%3Dfmt&can=1)
+- Safety: the library is fully type-safe, errors in format strings can
+  be reported at compile time, automatic memory management prevents
+  buffer overflow errors
+- Ease of use: small self-contained code base, no external
+  dependencies, permissive MIT
+  [license](https://github.com/fmtlib/fmt/blob/master/LICENSE.rst)
+- [Portability](https://fmt.dev/latest/index.html#portability) with
+  consistent output across platforms and support for older compilers
+- Clean warning-free codebase even on high warning levels such as
+  `-Wall -Wextra -pedantic`
+- Locale independence by default
+- Optional header-only configuration enabled with the
+  `FMT_HEADER_ONLY` macro
+
+See the [documentation](https://fmt.dev) for more details.
+
+# Examples
+
+**Print to stdout** ([run](https://godbolt.org/z/Tevcjh))
+
+``` c++
+#include <fmt/core.h>
+
+int main() {
+  fmt::print("Hello, world!\n");
+}
+```
+
+**Format a string** ([run](https://godbolt.org/z/oK8h33))
+
+``` c++
+std::string s = fmt::format("The answer is {}.", 42);
+// s == "The answer is 42."
+```
+
+**Format a string using positional arguments**
+([run](https://godbolt.org/z/Yn7Txe))
+
+``` c++
+std::string s = fmt::format("I'd rather be {1} than {0}.", "right", "happy");
+// s == "I'd rather be happy than right."
+```
+
+**Print dates and times** ([run](https://godbolt.org/z/c31ExdY3W))
+
+``` c++
+#include <fmt/chrono.h>
+
+int main() {
+  auto now = std::chrono::system_clock::now();
+  fmt::print("Date and time: {}\n", now);
+  fmt::print("Time: {:%H:%M}\n", now);
+}
+```
+
+Output:
+
+    Date and time: 2023-12-26 19:10:31.557195597
+    Time: 19:10
+
+**Print a container** ([run](https://godbolt.org/z/MxM1YqjE7))
+
+``` c++
+#include <vector>
+#include <fmt/ranges.h>
+
+int main() {
+  std::vector<int> v = {1, 2, 3};
+  fmt::print("{}\n", v);
+}
+```
+
+Output:
+
+    [1, 2, 3]
+
+**Check a format string at compile time**
+
+``` c++
+std::string s = fmt::format("{:d}", "I am not a number");
+```
+
+This gives a compile-time error in C++20 because `d` is an invalid
+format specifier for a string.
+
+**Write a file from a single thread**
+
+``` c++
+#include <fmt/os.h>
+
+int main() {
+  auto out = fmt::output_file("guide.txt");
+  out.print("Don't {}", "Panic");
+}
+```
+
+This can be [5 to 9 times faster than
+fprintf](http://www.zverovich.net/2020/08/04/optimal-file-buffer-size.html).
+
+**Print with colors and text styles**
+
+``` c++
+#include <fmt/color.h>
+
+int main() {
+  fmt::print(fg(fmt::color::crimson) | fmt::emphasis::bold,
+             "Hello, {}!\n", "world");
+  fmt::print(fg(fmt::color::floral_white) | bg(fmt::color::slate_gray) |
+             fmt::emphasis::underline, "Olá, {}!\n", "Mundo");
+  fmt::print(fg(fmt::color::steel_blue) | fmt::emphasis::italic,
+             "你好{}!\n", "世界");
+}
+```
+
+Output on a modern terminal with Unicode support:
+
+![image](https://github.com/fmtlib/fmt/assets/%0A576385/2a93c904-d6fa-4aa6-b453-2618e1c327d7)
+
+# Benchmarks
+
+## Speed tests
+
+| Library           | Method        | Run Time, s |
+|-------------------|---------------|-------------|
+| libc              | printf        |   0.91      |
+| libc++            | std::ostream  |   2.49      |
+| {fmt} 9.1         | fmt::print    |   0.74      |
+| Boost Format 1.80 | boost::format |   6.26      |
+| Folly Format      | folly::format |   1.87      |
+
+{fmt} is the fastest of the benchmarked methods, \~20% faster than
+`printf`.
+
+The above results were generated by building `tinyformat_test.cpp` on
+macOS 12.6.1 with `clang++ -O3 -DNDEBUG -DSPEED_TEST -DHAVE_FORMAT`, and
+taking the best of three runs. In the test, the format string
+`"%0.10f:%04d:%+g:%s:%p:%c:%%\n"` or equivalent is filled 2,000,000
+times with output sent to `/dev/null`; for further details refer to the
+[source](https://github.com/fmtlib/format-benchmark/blob/master/src/tinyformat-test.cc).
+
+{fmt} is up to 20-30x faster than `std::ostringstream` and `sprintf` on
+IEEE754 `float` and `double` formatting
+([dtoa-benchmark](https://github.com/fmtlib/dtoa-benchmark)) and faster
+than [double-conversion](https://github.com/google/double-conversion)
+and [ryu](https://github.com/ulfjack/ryu):
+
+[![image](https://user-images.githubusercontent.com/576385/95684665-11719600-0ba8-11eb-8e5b-972ff4e49428.png)](https://fmt.dev/unknown_mac64_clang12.0.html)
+
+## Compile time and code bloat
+
+The script
+[bloat-test.py](https://github.com/fmtlib/format-benchmark/blob/master/bloat-test.py)
+from [format-benchmark](https://github.com/fmtlib/format-benchmark)
+tests compile time and code bloat for nontrivial projects. It generates
+100 translation units and uses `printf()` or its alternative five times
+in each to simulate a medium-sized project. The resulting executable
+size and compile time (Apple LLVM version 8.1.0 (clang-802.0.42), macOS
+Sierra, best of three) is shown in the following tables.
+
+**Optimized build (-O3)**
+
+| Method        | Compile Time, s | Executable size, KiB | Stripped size, KiB |
+|---------------|-----------------|----------------------|--------------------|
+| printf        |   2.6           |   29                 |   26               |
+| printf+string |   16.4          |   29                 |   26               |
+| iostreams     |   31.1          |   59                 |   55               |
+| {fmt}         |   19.0          |   37                 |   34               |
+| Boost Format  |   91.9          |   226                |   203              |
+| Folly Format  |   115.7         |   101                |   88               |
+
+As you can see, {fmt} has 60% less overhead in terms of resulting binary
+code size compared to iostreams and comes pretty close to `printf`.
+Boost Format and Folly Format have the largest overheads.
+
+`printf+string` is the same as `printf` but with an extra `<string>`
+include to measure the overhead of the latter.
+
+**Non-optimized build**
+
+| Method        | Compile Time, s | Executable size, KiB | Stripped size, KiB |
+|---------------|-----------------|----------------------|--------------------|
+| printf        |   2.2           |   33                 |   30               |
+| printf+string |   16.0          |   33                 |   30               |
+| iostreams     |   28.3          |   56                 |   52               |
+| {fmt}         |   18.2          |   59                 |   50               |
+| Boost Format  |   54.1          |   365                |   303              |
+| Folly Format  |   79.9          |   445                |   430              |
+
+`libc`, `lib(std)c++`, and `libfmt` are all linked as shared libraries
+to compare formatting function overhead only. Boost Format is a
+header-only library so it doesn\'t provide any linkage options.
+
+## Running the tests
+
+Please refer to [Building the
+library](https://fmt.dev/latest/usage.html#building-the-library) for
+instructions on how to build the library and run the unit tests.
+
+Benchmarks reside in a separate repository,
+[format-benchmarks](https://github.com/fmtlib/format-benchmark), so to
+run the benchmarks you first need to clone this repository and generate
+Makefiles with CMake:
+
+    $ git clone --recursive https://github.com/fmtlib/format-benchmark.git
+    $ cd format-benchmark
+    $ cmake .
+
+Then you can run the speed test:
+
+    $ make speed-test
+
+or the bloat test:
+
+    $ make bloat-test
+
+# Migrating code
+
+[clang-tidy](https://clang.llvm.org/extra/clang-tidy/) v17 (not yet
+released) provides the
+[modernize-use-std-print](https://clang.llvm.org/extra/clang-tidy/checks/modernize/use-std-print.html)
+check that is capable of converting occurrences of `printf` and
+`fprintf` to `fmt::print` if configured to do so. (By default it
+converts to `std::print`.)
+
+# Notable projects using this library
+
+- [0 A.D.](https://play0ad.com/): a free, open-source, cross-platform
+  real-time strategy game
+- [AMPL/MP](https://github.com/ampl/mp): an open-source library for
+  mathematical programming
+- [Apple's FoundationDB](https://github.com/apple/foundationdb): an open-source,
+  distributed, transactional key-value store
+- [Aseprite](https://github.com/aseprite/aseprite): animated sprite
+  editor & pixel art tool
+- [AvioBook](https://www.aviobook.aero/en): a comprehensive aircraft
+  operations suite
+- [Blizzard Battle.net](https://battle.net/): an online gaming
+  platform
+- [Celestia](https://celestia.space/): real-time 3D visualization of
+  space
+- [Ceph](https://ceph.com/): a scalable distributed storage system
+- [ccache](https://ccache.dev/): a compiler cache
+- [ClickHouse](https://github.com/ClickHouse/ClickHouse): an
+  analytical database management system
+- [Contour](https://github.com/contour-terminal/contour/): a modern
+  terminal emulator
+- [CUAUV](https://cuauv.org/): Cornell University\'s autonomous
+  underwater vehicle
+- [Drake](https://drake.mit.edu/): a planning, control, and analysis
+  toolbox for nonlinear dynamical systems (MIT)
+- [Envoy](https://lyft.github.io/envoy/): C++ L7 proxy and
+  communication bus (Lyft)
+- [FiveM](https://fivem.net/): a modification framework for GTA V
+- [fmtlog](https://github.com/MengRao/fmtlog): a performant
+  fmtlib-style logging library with latency in nanoseconds
+- [Folly](https://github.com/facebook/folly): Facebook open-source
+  library
+- [GemRB](https://gemrb.org/): a portable open-source implementation
+  of Bioware's Infinity Engine
+- [Grand Mountain
+  Adventure](https://store.steampowered.com/app/1247360/Grand_Mountain_Adventure/):
+  a beautiful open-world ski & snowboarding game
+- [HarpyWar/pvpgn](https://github.com/pvpgn/pvpgn-server): Player vs
+  Player Gaming Network with tweaks
+- [KBEngine](https://github.com/kbengine/kbengine): an open-source
+  MMOG server engine
+- [Keypirinha](https://keypirinha.com/): a semantic launcher for
+  Windows
+- [Kodi](https://kodi.tv/) (formerly xbmc): home theater software
+- [Knuth](https://kth.cash/): high-performance Bitcoin full-node
+- [libunicode](https://github.com/contour-terminal/libunicode/): a
+  modern C++17 Unicode library
+- [MariaDB](https://mariadb.org/): relational database management
+  system
+- [Microsoft Verona](https://github.com/microsoft/verona): research
+  programming language for concurrent ownership
+- [MongoDB](https://mongodb.com/): distributed document database
+- [MongoDB Smasher](https://github.com/duckie/mongo_smasher): a small
+  tool to generate randomized datasets
+- [OpenSpace](https://openspaceproject.com/): an open-source
+  astrovisualization framework
+- [PenUltima Online (POL)](https://www.polserver.com/): an MMO server,
+  compatible with most Ultima Online clients
+- [PyTorch](https://github.com/pytorch/pytorch): an open-source
+  machine learning library
+- [quasardb](https://www.quasardb.net/): a distributed,
+  high-performance, associative database
+- [Quill](https://github.com/odygrd/quill): asynchronous low-latency
+  logging library
+- [QKW](https://github.com/ravijanjam/qkw): generalizing aliasing to
+  simplify navigation, and executing complex multi-line terminal
+  command sequences
+- [redis-cerberus](https://github.com/HunanTV/redis-cerberus): a Redis
+  cluster proxy
+- [redpanda](https://vectorized.io/redpanda): a 10x faster Kafka®
+  replacement for mission-critical systems written in C++
+- [rpclib](http://rpclib.net/): a modern C++ msgpack-RPC server and
+  client library
+- [Salesforce Analytics
+  Cloud](https://www.salesforce.com/analytics-cloud/overview/):
+  business intelligence software
+- [Scylla](https://www.scylladb.com/): a Cassandra-compatible NoSQL
+  data store that can handle 1 million transactions per second on a
+  single server
+- [Seastar](http://www.seastar-project.org/): an advanced, open-source
+  C++ framework for high-performance server applications on modern
+  hardware
+- [spdlog](https://github.com/gabime/spdlog): super fast C++ logging
+  library
+- [Stellar](https://www.stellar.org/): financial platform
+- [Touch Surgery](https://www.touchsurgery.com/): surgery simulator
+- [TrinityCore](https://github.com/TrinityCore/TrinityCore):
+  open-source MMORPG framework
+- [🐙 userver framework](https://userver.tech/): open-source
+  asynchronous framework with a rich set of abstractions and database
+  drivers
+- [Windows Terminal](https://github.com/microsoft/terminal): the new
+  Windows terminal
+
+[More\...](https://github.com/search?q=fmtlib&type=Code)
+
+If you are aware of other projects using this library, please let me
+know by [email](mailto:victor.zverovich@gmail.com) or by submitting an
+[issue](https://github.com/fmtlib/fmt/issues).
+
+# Motivation
+
+So why yet another formatting library?
+
+There are plenty of methods for doing this task, from standard ones like
+the printf family of function and iostreams to Boost Format and
+FastFormat libraries. The reason for creating a new library is that
+every existing solution that I found either had serious issues or
+didn\'t provide all the features I needed.
+
+## printf
+
+The good thing about `printf` is that it is pretty fast and readily
+available being a part of the C standard library. The main drawback is
+that it doesn\'t support user-defined types. `printf` also has safety
+issues although they are somewhat mitigated with [\_\_attribute\_\_
+((format (printf,
+\...))](https://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html) in
+GCC. There is a POSIX extension that adds positional arguments required
+for
+[i18n](https://en.wikipedia.org/wiki/Internationalization_and_localization)
+to `printf` but it is not a part of C99 and may not be available on some
+platforms.
+
+## iostreams
+
+The main issue with iostreams is best illustrated with an example:
+
+``` c++
+std::cout << std::setprecision(2) << std::fixed << 1.23456 << "\n";
+```
+
+which is a lot of typing compared to printf:
+
+``` c++
+printf("%.2f\n", 1.23456);
+```
+
+Matthew Wilson, the author of FastFormat, called this \"chevron hell\".
+iostreams don\'t support positional arguments by design.
+
+The good part is that iostreams support user-defined types and are safe
+although error handling is awkward.
+
+## Boost Format
+
+This is a very powerful library that supports both `printf`-like format
+strings and positional arguments. Its main drawback is performance.
+According to various benchmarks, it is much slower than other methods
+considered here. Boost Format also has excessive build times and severe
+code bloat issues (see [Benchmarks](#benchmarks)).
+
+## FastFormat
+
+This is an interesting library that is fast, safe, and has positional
+arguments. However, it has significant limitations, citing its author:
+
+> Three features that have no hope of being accommodated within the
+> current design are:
+>
+> - Leading zeros (or any other non-space padding)
+> - Octal/hexadecimal encoding
+> - Runtime width/alignment specification
+
+It is also quite big and has a heavy dependency, STLSoft, which might be
+too restrictive for using it in some projects.
+
+## Boost Spirit.Karma
+
+This is not a formatting library but I decided to include it here for
+completeness. As iostreams, it suffers from the problem of mixing
+verbatim text with arguments. The library is pretty fast, but slower on
+integer formatting than `fmt::format_to` with format string compilation
+on Karma\'s own benchmark, see [Converting a hundred million integers to
+strings per
+second](http://www.zverovich.net/2020/06/13/fast-int-to-string-revisited.html).
+
+# License
+
+{fmt} is distributed under the MIT
+[license](https://github.com/fmtlib/fmt/blob/master/LICENSE).
+
+# Documentation License
+
+The [Format String Syntax](https://fmt.dev/latest/syntax.html) section
+in the documentation is based on the one from Python [string module
+documentation](https://docs.python.org/3/library/string.html#module-string).
+For this reason, the documentation is distributed under the Python
+Software Foundation license available in
+[doc/python-license.txt](https://raw.github.com/fmtlib/fmt/master/doc/python-license.txt).
+It only applies if you distribute the documentation of {fmt}.
+
+# Maintainers
+
+The {fmt} library is maintained by Victor Zverovich
+([vitaut](https://github.com/vitaut)) with contributions from many other
+people. See
+[Contributors](https://github.com/fmtlib/fmt/graphs/contributors) and
+[Releases](https://github.com/fmtlib/fmt/releases) for some of the
+names. Let us know if your contribution is not listed or mentioned
+incorrectly and we\'ll make it right.
+
+# Security Policy
+
+To report a security issue, please disclose it at [security
+advisory](https://github.com/fmtlib/fmt/security/advisories/new).
+
+This project is maintained by a team of volunteers on a
+reasonable-effort basis. As such, please give us at least 90 days to
+work on a fix before public exposure.
diff --git a/README.rst b/README.rst
deleted file mode 100644
index 81948bd..0000000
--- a/README.rst
+++ /dev/null
@@ -1,553 +0,0 @@
-.. image:: https://user-images.githubusercontent.com/
-           576385/156254208-f5b743a9-88cf-439d-b0c0-923d53e8d551.png
-   :width: 25%
-   :alt: {fmt}
-
-.. image:: https://github.com/fmtlib/fmt/workflows/linux/badge.svg
-   :target: https://github.com/fmtlib/fmt/actions?query=workflow%3Alinux
-
-.. image:: https://github.com/fmtlib/fmt/workflows/macos/badge.svg
-   :target: https://github.com/fmtlib/fmt/actions?query=workflow%3Amacos
-
-.. image:: https://github.com/fmtlib/fmt/workflows/windows/badge.svg
-   :target: https://github.com/fmtlib/fmt/actions?query=workflow%3Awindows
-
-.. image:: https://oss-fuzz-build-logs.storage.googleapis.com/badges/fmt.svg
-   :alt: fmt is continuously fuzzed at oss-fuzz
-   :target: https://bugs.chromium.org/p/oss-fuzz/issues/list?\
-            colspec=ID%20Type%20Component%20Status%20Proj%20Reported%20Owner%20\
-            Summary&q=proj%3Dfmt&can=1
-
-.. image:: https://img.shields.io/badge/stackoverflow-fmt-blue.svg
-   :alt: Ask questions at StackOverflow with the tag fmt
-   :target: https://stackoverflow.com/questions/tagged/fmt
-
-.. image:: https://api.securityscorecards.dev/projects/github.com/fmtlib/fmt/badge
-   :target: https://securityscorecards.dev/viewer/?uri=github.com/fmtlib/fmt
-
-**{fmt}** is an open-source formatting library providing a fast and safe
-alternative to C stdio and C++ iostreams.
-
-If you like this project, please consider donating to one of the funds that
-help victims of the war in Ukraine: https://www.stopputin.net/.
-
-`Documentation <https://fmt.dev>`__
-
-`Cheat Sheets <https://hackingcpp.com/cpp/libs/fmt.html>`__
-
-Q&A: ask questions on `StackOverflow with the tag fmt
-<https://stackoverflow.com/questions/tagged/fmt>`_.
-
-Try {fmt} in `Compiler Explorer <https://godbolt.org/z/Eq5763>`_.
-
-Features
---------
-
-* Simple `format API <https://fmt.dev/latest/api.html>`_ with positional arguments
-  for localization
-* Implementation of `C++20 std::format
-  <https://en.cppreference.com/w/cpp/utility/format>`__ and `C++23 std::print
-  <https://en.cppreference.com/w/cpp/io/print>`__
-* `Format string syntax <https://fmt.dev/latest/syntax.html>`_ similar to Python's
-  `format <https://docs.python.org/3/library/stdtypes.html#str.format>`_
-* Fast IEEE 754 floating-point formatter with correct rounding, shortness and
-  round-trip guarantees using the `Dragonbox <https://github.com/jk-jeon/dragonbox>`_
-  algorithm
-* Portable Unicode support
-* Safe `printf implementation
-  <https://fmt.dev/latest/api.html#printf-formatting>`_ including the POSIX
-  extension for positional arguments
-* Extensibility: `support for user-defined types
-  <https://fmt.dev/latest/api.html#formatting-user-defined-types>`_
-* High performance: faster than common standard library implementations of
-  ``(s)printf``, iostreams, ``to_string`` and ``to_chars``, see `Speed tests`_
-  and `Converting a hundred million integers to strings per second
-  <http://www.zverovich.net/2020/06/13/fast-int-to-string-revisited.html>`_
-* Small code size both in terms of source code with the minimum configuration
-  consisting of just three files, ``core.h``, ``format.h`` and ``format-inl.h``,
-  and compiled code; see `Compile time and code bloat`_
-* Reliability: the library has an extensive set of `tests
-  <https://github.com/fmtlib/fmt/tree/master/test>`_ and is `continuously fuzzed
-  <https://bugs.chromium.org/p/oss-fuzz/issues/list?colspec=ID%20Type%20
-  Component%20Status%20Proj%20Reported%20Owner%20Summary&q=proj%3Dfmt&can=1>`_
-* Safety: the library is fully type-safe, errors in format strings can be
-  reported at compile time, automatic memory management prevents buffer overflow
-  errors
-* Ease of use: small self-contained code base, no external dependencies,
-  permissive MIT `license
-  <https://github.com/fmtlib/fmt/blob/master/LICENSE.rst>`_
-* `Portability <https://fmt.dev/latest/index.html#portability>`_ with
-  consistent output across platforms and support for older compilers
-* Clean warning-free codebase even on high warning levels such as
-  ``-Wall -Wextra -pedantic``
-* Locale independence by default
-* Optional header-only configuration enabled with the ``FMT_HEADER_ONLY`` macro
-
-See the `documentation <https://fmt.dev>`_ for more details.
-
-Examples
---------
-
-**Print to stdout** (`run <https://godbolt.org/z/Tevcjh>`_)
-
-.. code:: c++
-
-    #include <fmt/core.h>
-
-    int main() {
-      fmt::print("Hello, world!\n");
-    }
-
-**Format a string** (`run <https://godbolt.org/z/oK8h33>`_)
-
-.. code:: c++
-
-    std::string s = fmt::format("The answer is {}.", 42);
-    // s == "The answer is 42."
-
-**Format a string using positional arguments** (`run <https://godbolt.org/z/Yn7Txe>`_)
-
-.. code:: c++
-
-    std::string s = fmt::format("I'd rather be {1} than {0}.", "right", "happy");
-    // s == "I'd rather be happy than right."
-
-**Print chrono durations** (`run <https://godbolt.org/z/K8s4Mc>`_)
-
-.. code:: c++
-
-    #include <fmt/chrono.h>
-
-    int main() {
-      using namespace std::literals::chrono_literals;
-      fmt::print("Default format: {} {}\n", 42s, 100ms);
-      fmt::print("strftime-like format: {:%H:%M:%S}\n", 3h + 15min + 30s);
-    }
-
-Output::
-
-    Default format: 42s 100ms
-    strftime-like format: 03:15:30
-
-**Print a container** (`run <https://godbolt.org/z/MxM1YqjE7>`_)
-
-.. code:: c++
-
-    #include <vector>
-    #include <fmt/ranges.h>
-
-    int main() {
-      std::vector<int> v = {1, 2, 3};
-      fmt::print("{}\n", v);
-    }
-
-Output::
-
-    [1, 2, 3]
-
-**Check a format string at compile time**
-
-.. code:: c++
-
-    std::string s = fmt::format("{:d}", "I am not a number");
-
-This gives a compile-time error in C++20 because ``d`` is an invalid format
-specifier for a string.
-
-**Write a file from a single thread**
-
-.. code:: c++
-
-    #include <fmt/os.h>
-
-    int main() {
-      auto out = fmt::output_file("guide.txt");
-      out.print("Don't {}", "Panic");
-    }
-
-This can be `5 to 9 times faster than fprintf
-<http://www.zverovich.net/2020/08/04/optimal-file-buffer-size.html>`_.
-
-**Print with colors and text styles**
-
-.. code:: c++
-
-    #include <fmt/color.h>
-
-    int main() {
-      fmt::print(fg(fmt::color::crimson) | fmt::emphasis::bold,
-                 "Hello, {}!\n", "world");
-      fmt::print(fg(fmt::color::floral_white) | bg(fmt::color::slate_gray) |
-                 fmt::emphasis::underline, "Olá, {}!\n", "Mundo");
-      fmt::print(fg(fmt::color::steel_blue) | fmt::emphasis::italic,
-                 "你好{}!\n", "世界");
-    }
-
-Output on a modern terminal with Unicode support:
-
-.. image:: https://github.com/fmtlib/fmt/assets/
-           576385/2a93c904-d6fa-4aa6-b453-2618e1c327d7
-
-Benchmarks
-----------
-
-Speed tests
-~~~~~~~~~~~
-
-================= ============= ===========
-Library           Method        Run Time, s
-================= ============= ===========
-libc              printf          0.91
-libc++            std::ostream    2.49
-{fmt} 9.1         fmt::print      0.74
-Boost Format 1.80 boost::format   6.26
-Folly Format      folly::format   1.87
-================= ============= ===========
-
-{fmt} is the fastest of the benchmarked methods, ~20% faster than ``printf``.
-
-The above results were generated by building ``tinyformat_test.cpp`` on macOS
-12.6.1 with ``clang++ -O3 -DNDEBUG -DSPEED_TEST -DHAVE_FORMAT``, and taking the
-best of three runs. In the test, the format string ``"%0.10f:%04d:%+g:%s:%p:%c:%%\n"``
-or equivalent is filled 2,000,000 times with output sent to ``/dev/null``; for
-further details refer to the `source
-<https://github.com/fmtlib/format-benchmark/blob/master/src/tinyformat-test.cc>`_.
-
-{fmt} is up to 20-30x faster than ``std::ostringstream`` and ``sprintf`` on
-IEEE754 ``float`` and ``double`` formatting (`dtoa-benchmark <https://github.com/fmtlib/dtoa-benchmark>`_)
-and faster than `double-conversion <https://github.com/google/double-conversion>`_ and
-`ryu <https://github.com/ulfjack/ryu>`_:
-
-.. image:: https://user-images.githubusercontent.com/576385/
-           95684665-11719600-0ba8-11eb-8e5b-972ff4e49428.png
-   :target: https://fmt.dev/unknown_mac64_clang12.0.html
-
-Compile time and code bloat
-~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-The script `bloat-test.py
-<https://github.com/fmtlib/format-benchmark/blob/master/bloat-test.py>`_
-from `format-benchmark <https://github.com/fmtlib/format-benchmark>`_
-tests compile time and code bloat for nontrivial projects.
-It generates 100 translation units and uses ``printf()`` or its alternative
-five times in each to simulate a medium-sized project.  The resulting
-executable size and compile time (Apple LLVM version 8.1.0 (clang-802.0.42),
-macOS Sierra, best of three) is shown in the following tables.
-
-**Optimized build (-O3)**
-
-============= =============== ==================== ==================
-Method        Compile Time, s Executable size, KiB Stripped size, KiB
-============= =============== ==================== ==================
-printf                    2.6                   29                 26
-printf+string            16.4                   29                 26
-iostreams                31.1                   59                 55
-{fmt}                    19.0                   37                 34
-Boost Format             91.9                  226                203
-Folly Format            115.7                  101                 88
-============= =============== ==================== ==================
-
-As you can see, {fmt} has 60% less overhead in terms of resulting binary code
-size compared to iostreams and comes pretty close to ``printf``. Boost Format
-and Folly Format have the largest overheads.
-
-``printf+string`` is the same as ``printf`` but with an extra ``<string>``
-include to measure the overhead of the latter.
-
-**Non-optimized build**
-
-============= =============== ==================== ==================
-Method        Compile Time, s Executable size, KiB Stripped size, KiB
-============= =============== ==================== ==================
-printf                    2.2                   33                 30
-printf+string            16.0                   33                 30
-iostreams                28.3                   56                 52
-{fmt}                    18.2                   59                 50
-Boost Format             54.1                  365                303
-Folly Format             79.9                  445                430
-============= =============== ==================== ==================
-
-``libc``, ``lib(std)c++``, and ``libfmt`` are all linked as shared libraries to
-compare formatting function overhead only. Boost Format is a
-header-only library so it doesn't provide any linkage options.
-
-Running the tests
-~~~~~~~~~~~~~~~~~
-
-Please refer to `Building the library`__ for instructions on how to build
-the library and run the unit tests.
-
-__ https://fmt.dev/latest/usage.html#building-the-library
-
-Benchmarks reside in a separate repository,
-`format-benchmarks <https://github.com/fmtlib/format-benchmark>`_,
-so to run the benchmarks you first need to clone this repository and
-generate Makefiles with CMake::
-
-    $ git clone --recursive https://github.com/fmtlib/format-benchmark.git
-    $ cd format-benchmark
-    $ cmake .
-
-Then you can run the speed test::
-
-    $ make speed-test
-
-or the bloat test::
-
-    $ make bloat-test
-
-Migrating code
---------------
-
-`clang-tidy <https://clang.llvm.org/extra/clang-tidy/>`_ v17 (not yet
-released) provides the `modernize-use-std-print
-<https://clang.llvm.org/extra/clang-tidy/checks/modernize/use-std-print.html>`_
-check that is capable of converting occurrences of ``printf`` and
-``fprintf`` to ``fmt::print`` if configured to do so. (By default it
-converts to ``std::print``.)
-
-Projects using this library
----------------------------
-
-* `0 A.D. <https://play0ad.com/>`_: a free, open-source, cross-platform
-  real-time strategy game
-
-* `AMPL/MP <https://github.com/ampl/mp>`_:
-  an open-source library for mathematical programming
-
-* `Aseprite <https://github.com/aseprite/aseprite>`_:
-  animated sprite editor & pixel art tool
-
-* `AvioBook <https://www.aviobook.aero/en>`_: a comprehensive aircraft
-  operations suite
-
-* `Blizzard Battle.net <https://battle.net/>`_: an online gaming platform
-
-* `Celestia <https://celestia.space/>`_: real-time 3D visualization of space
-
-* `Ceph <https://ceph.com/>`_: a scalable distributed storage system
-
-* `ccache <https://ccache.dev/>`_: a compiler cache
-
-* `ClickHouse <https://github.com/ClickHouse/ClickHouse>`_: an analytical database
-  management system
-
-* `Contour <https://github.com/contour-terminal/contour/>`_: a modern terminal emulator
-
-* `CUAUV <https://cuauv.org/>`_: Cornell University's autonomous underwater
-  vehicle
-
-* `Drake <https://drake.mit.edu/>`_: a planning, control, and analysis toolbox
-  for nonlinear dynamical systems (MIT)
-
-* `Envoy <https://lyft.github.io/envoy/>`_: C++ L7 proxy and communication bus
-  (Lyft)
-
-* `FiveM <https://fivem.net/>`_: a modification framework for GTA V
-
-* `fmtlog <https://github.com/MengRao/fmtlog>`_: a performant fmtlib-style
-  logging library with latency in nanoseconds
-
-* `Folly <https://github.com/facebook/folly>`_: Facebook open-source library
-
-* `GemRB <https://gemrb.org/>`_: a portable open-source implementation of
-  Bioware’s Infinity Engine
-
-* `Grand Mountain Adventure
-  <https://store.steampowered.com/app/1247360/Grand_Mountain_Adventure/>`_:
-  a beautiful open-world ski & snowboarding game
-
-* `HarpyWar/pvpgn <https://github.com/pvpgn/pvpgn-server>`_:
-  Player vs Player Gaming Network with tweaks
-
-* `KBEngine <https://github.com/kbengine/kbengine>`_: an open-source MMOG server
-  engine
-
-* `Keypirinha <https://keypirinha.com/>`_: a semantic launcher for Windows
-
-* `Kodi <https://kodi.tv/>`_ (formerly xbmc): home theater software
-
-* `Knuth <https://kth.cash/>`_: high-performance Bitcoin full-node
-
-* `libunicode <https://github.com/contour-terminal/libunicode/>`_: a modern C++17 Unicode library
-
-* `MariaDB <https://mariadb.org/>`_: relational database management system
-
-* `Microsoft Verona <https://github.com/microsoft/verona>`_:
-  research programming language for concurrent ownership
-
-* `MongoDB <https://mongodb.com/>`_: distributed document database
-
-* `MongoDB Smasher <https://github.com/duckie/mongo_smasher>`_: a small tool to
-  generate randomized datasets
-
-* `OpenSpace <https://openspaceproject.com/>`_: an open-source
-  astrovisualization framework
-
-* `PenUltima Online (POL) <https://www.polserver.com/>`_:
-  an MMO server, compatible with most Ultima Online clients
-
-* `PyTorch <https://github.com/pytorch/pytorch>`_: an open-source machine
-  learning library
-
-* `quasardb <https://www.quasardb.net/>`_: a distributed, high-performance,
-  associative database
-
-* `Quill <https://github.com/odygrd/quill>`_: asynchronous low-latency logging library
-
-* `QKW <https://github.com/ravijanjam/qkw>`_: generalizing aliasing to simplify
-  navigation, and executing complex multi-line terminal command sequences
-
-* `redis-cerberus <https://github.com/HunanTV/redis-cerberus>`_: a Redis cluster
-  proxy
-
-* `redpanda <https://vectorized.io/redpanda>`_: a 10x faster Kafka® replacement
-  for mission-critical systems written in C++
-
-* `rpclib <http://rpclib.net/>`_: a modern C++ msgpack-RPC server and client
-  library
-
-* `Salesforce Analytics Cloud
-  <https://www.salesforce.com/analytics-cloud/overview/>`_:
-  business intelligence software
-
-* `Scylla <https://www.scylladb.com/>`_: a Cassandra-compatible NoSQL data store
-  that can handle 1 million transactions per second on a single server
-
-* `Seastar <http://www.seastar-project.org/>`_: an advanced, open-source C++
-  framework for high-performance server applications on modern hardware
-
-* `spdlog <https://github.com/gabime/spdlog>`_: super fast C++ logging library
-
-* `Stellar <https://www.stellar.org/>`_: financial platform
-
-* `Touch Surgery <https://www.touchsurgery.com/>`_: surgery simulator
-
-* `TrinityCore <https://github.com/TrinityCore/TrinityCore>`_: open-source
-  MMORPG framework
-
-* `🐙 userver framework <https://userver.tech/>`_: open-source asynchronous
-  framework with a rich set of abstractions and database drivers
-
-* `Windows Terminal <https://github.com/microsoft/terminal>`_: the new Windows
-  terminal
-
-`More... <https://github.com/search?q=fmtlib&type=Code>`_
-
-If you are aware of other projects using this library, please let me know
-by `email <mailto:victor.zverovich@gmail.com>`_ or by submitting an
-`issue <https://github.com/fmtlib/fmt/issues>`_.
-
-Motivation
-----------
-
-So why yet another formatting library?
-
-There are plenty of methods for doing this task, from standard ones like
-the printf family of function and iostreams to Boost Format and FastFormat
-libraries. The reason for creating a new library is that every existing
-solution that I found either had serious issues or didn't provide
-all the features I needed.
-
-printf
-~~~~~~
-
-The good thing about ``printf`` is that it is pretty fast and readily available
-being a part of the C standard library. The main drawback is that it
-doesn't support user-defined types. ``printf`` also has safety issues although
-they are somewhat mitigated with `__attribute__ ((format (printf, ...))
-<https://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html>`_ in GCC.
-There is a POSIX extension that adds positional arguments required for
-`i18n <https://en.wikipedia.org/wiki/Internationalization_and_localization>`_
-to ``printf`` but it is not a part of C99 and may not be available on some
-platforms.
-
-iostreams
-~~~~~~~~~
-
-The main issue with iostreams is best illustrated with an example:
-
-.. code:: c++
-
-    std::cout << std::setprecision(2) << std::fixed << 1.23456 << "\n";
-
-which is a lot of typing compared to printf:
-
-.. code:: c++
-
-    printf("%.2f\n", 1.23456);
-
-Matthew Wilson, the author of FastFormat, called this "chevron hell". iostreams
-don't support positional arguments by design.
-
-The good part is that iostreams support user-defined types and are safe although
-error handling is awkward.
-
-Boost Format
-~~~~~~~~~~~~
-
-This is a very powerful library that supports both ``printf``-like format
-strings and positional arguments. Its main drawback is performance. According to
-various benchmarks, it is much slower than other methods considered here. Boost
-Format also has excessive build times and severe code bloat issues (see
-`Benchmarks`_).
-
-FastFormat
-~~~~~~~~~~
-
-This is an interesting library that is fast, safe, and has positional arguments.
-However, it has significant limitations, citing its author:
-
-    Three features that have no hope of being accommodated within the
-    current design are:
-
-    * Leading zeros (or any other non-space padding)
-    * Octal/hexadecimal encoding
-    * Runtime width/alignment specification
-
-It is also quite big and has a heavy dependency, STLSoft, which might be too
-restrictive for using it in some projects.
-
-Boost Spirit.Karma
-~~~~~~~~~~~~~~~~~~
-
-This is not a formatting library but I decided to include it here for
-completeness. As iostreams, it suffers from the problem of mixing verbatim text
-with arguments. The library is pretty fast, but slower on integer formatting
-than ``fmt::format_to`` with format string compilation on Karma's own benchmark,
-see `Converting a hundred million integers to strings per second
-<http://www.zverovich.net/2020/06/13/fast-int-to-string-revisited.html>`_.
-
-License
--------
-
-{fmt} is distributed under the MIT `license
-<https://github.com/fmtlib/fmt/blob/master/LICENSE>`_.
-
-Documentation License
----------------------
-
-The `Format String Syntax <https://fmt.dev/latest/syntax.html>`_
-section in the documentation is based on the one from Python `string module
-documentation <https://docs.python.org/3/library/string.html#module-string>`_.
-For this reason, the documentation is distributed under the Python Software
-Foundation license available in `doc/python-license.txt
-<https://raw.github.com/fmtlib/fmt/master/doc/python-license.txt>`_.
-It only applies if you distribute the documentation of {fmt}.
-
-Maintainers
------------
-
-The {fmt} library is maintained by Victor Zverovich (`vitaut
-<https://github.com/vitaut>`_) with contributions from many other people.
-See `Contributors <https://github.com/fmtlib/fmt/graphs/contributors>`_ and
-`Releases <https://github.com/fmtlib/fmt/releases>`_ for some of the names.
-Let us know if your contribution is not listed or mentioned incorrectly and
-we'll make it right.
-
-Security Policy
----------------
-
-To report a security issue, please disclose it at `security advisory <https://github.com/fmtlib/fmt/security/advisories/new>`_.
-
-This project is maintained by a team of volunteers on a reasonable-effort basis. As such, please give us at least 90 days to work on a fix before public exposure.
diff --git a/doc/api.rst b/doc/api.rst
index 8b8b19c..31b0328 100644
--- a/doc/api.rst
+++ b/doc/api.rst
@@ -167,8 +167,8 @@
 
 will return ``"      blue"``.
 
-The experimental ``nested_formatter`` provides an easy way applying a formatter
-to one or more subobjects.
+The experimental ``nested_formatter`` provides an easy way of applying a
+formatter to one or more subobjects.
 
 For example::
 
@@ -600,7 +600,6 @@
    :members:
 
 .. doxygenfunction:: fmt::windows_error
-   :members:
 
 .. _ostream-api:
 
diff --git a/doc/build.py b/doc/build.py
index bd10aa8..a6ae8f8 100755
--- a/doc/build.py
+++ b/doc/build.py
@@ -4,7 +4,12 @@
 import errno, os, re, sys
 from subprocess import check_call, CalledProcessError, Popen, PIPE, STDOUT
 
-versions = ['1.0.0', '1.1.0', '2.0.0', '3.0.2', '4.0.0', '4.1.0', '5.0.0', '5.1.0', '5.2.0', '5.2.1', '5.3.0', '6.0.0', '6.1.0', '6.1.1', '6.1.2', '6.2.0', '6.2.1', '7.0.0', '7.0.1', '7.0.2', '7.0.3', '7.1.0', '7.1.1', '7.1.2', '7.1.3', '8.0.0', '8.0.1', '8.1.0', '8.1.1', '9.0.0', '9.1.0', '10.0.0', '10.1.0', '10.1.1', '10.1.1']
+versions = [
+  '1.0.0', '1.1.0', '2.0.0', '3.0.2', '4.0.0', '4.1.0', '5.0.0', '5.1.0',
+  '5.2.0', '5.2.1', '5.3.0', '6.0.0', '6.1.0', '6.1.1', '6.1.2', '6.2.0',
+  '6.2.1', '7.0.0', '7.0.1', '7.0.2', '7.0.3', '7.1.0', '7.1.1', '7.1.2',
+  '7.1.3', '8.0.0', '8.0.1', '8.1.0', '8.1.1', '9.0.0', '9.1.0']
+versions += ['10.0.0', '10.1.0', '10.1.1', '10.1.1', '10.2.0']
 
 class Pip:
   def __init__(self, venv_dir):
@@ -31,7 +36,7 @@
   # Jinja2 >= 3.1 incompatible with sphinx 3.3.0
   # See: https://github.com/sphinx-doc/sphinx/issues/10291
   pip.install('Jinja2<3.1')
-  pip.install('sphinx-doc/sphinx', 'v3.3.0')
+  pip.install('sphinx==3.3.0')
   pip.install('michaeljones/breathe', 'v4.25.0')
 
 def build_docs(version='dev', **kwargs):
@@ -50,7 +55,7 @@
       GENERATE_RTF      = NO
       CASE_SENSE_NAMES  = NO
       INPUT             = {0}/args.h {0}/chrono.h {0}/color.h {0}/core.h \
-                          {0}/compile.h  {0}/format.h {0}/os.h {0}/ostream.h \
+                          {0}/compile.h {0}/format.h {0}/os.h {0}/ostream.h \
                           {0}/printf.h {0}/xchar.h
       QUIET             = YES
       JAVADOC_AUTOBRIEF = YES
diff --git a/doc/syntax.rst b/doc/syntax.rst
index 74b64c5..3c21902 100644
--- a/doc/syntax.rst
+++ b/doc/syntax.rst
@@ -82,7 +82,7 @@
    width: `integer` | "{" [`arg_id`] "}"
    precision: `integer` | "{" [`arg_id`] "}"
    type: "a" | "A" | "b" | "B" | "c" | "d" | "e" | "E" | "f" | "F" | "g" | "G" |
-       : "o" | "p" | "s" | "x" | "X"
+       : "o" | "p" | "s" | "x" | "X" | "?"
 
 The *fill* character can be any Unicode code point other than ``'{'`` or
 ``'}'``. The presence of a fill character is signaled by the character following
@@ -177,6 +177,9 @@
 | ``'s'`` | String format. This is the default type for strings and  |
 |         | may be omitted.                                          |
 +---------+----------------------------------------------------------+
+| ``'?'`` | Debug format. The string is quoted and special           |
+|         | characters escaped.                                      |
++---------+----------------------------------------------------------+
 | none    | The same as ``'s'``.                                     |
 +---------+----------------------------------------------------------+
 
@@ -188,6 +191,9 @@
 | ``'c'`` | Character format. This is the default type for           |
 |         | characters and may be omitted.                           |
 +---------+----------------------------------------------------------+
+| ``'?'`` | Debug format. The character is quoted and special        |
+|         | characters escaped.                                      |
++---------+----------------------------------------------------------+
 | none    | The same as ``'c'``.                                     |
 +---------+----------------------------------------------------------+
 
@@ -223,9 +229,10 @@
 | none    | The same as ``'d'``.                                     |
 +---------+----------------------------------------------------------+
 
-Integer presentation types can also be used with character and Boolean values.
-Boolean values are formatted using textual representation, either ``true`` or
-``false``, if the presentation type is not specified.
+Integer presentation types can also be used with character and Boolean values
+with the only exception that ``'c'`` cannot be used with `bool`. Boolean values
+are formatted using textual representation, either ``true`` or ``false``, if the
+presentation type is not specified.
 
 The available presentation types for floating-point values are:
 
diff --git a/include/fmt/args.h b/include/fmt/args.h
index 2d684e7..ad1654b 100644
--- a/include/fmt/args.h
+++ b/include/fmt/args.h
@@ -22,8 +22,9 @@
 template <typename T>
 struct is_reference_wrapper<std::reference_wrapper<T>> : std::true_type {};
 
-template <typename T> const T& unwrap(const T& v) { return v; }
-template <typename T> const T& unwrap(const std::reference_wrapper<T>& v) {
+template <typename T> auto unwrap(const T& v) -> const T& { return v; }
+template <typename T>
+auto unwrap(const std::reference_wrapper<T>& v) -> const T& {
   return static_cast<const T&>(v);
 }
 
@@ -50,7 +51,7 @@
   std::unique_ptr<node<>> head_;
 
  public:
-  template <typename T, typename Arg> const T& push(const Arg& arg) {
+  template <typename T, typename Arg> auto push(const Arg& arg) -> const T& {
     auto new_node = std::unique_ptr<typed_node<T>>(new typed_node<T>(arg));
     auto& value = new_node->value;
     new_node->next = std::move(head_);
@@ -110,14 +111,14 @@
 
   friend class basic_format_args<Context>;
 
-  unsigned long long get_types() const {
+  auto get_types() const -> unsigned long long {
     return detail::is_unpacked_bit | data_.size() |
            (named_info_.empty()
                 ? 0ULL
                 : static_cast<unsigned long long>(detail::has_named_args_bit));
   }
 
-  const basic_format_arg<Context>* data() const {
+  auto data() const -> const basic_format_arg<Context>* {
     return named_info_.empty() ? data_.data() : data_.data() + 1;
   }
 
diff --git a/include/fmt/chrono.h b/include/fmt/chrono.h
index 7bd4206..9d54574 100644
--- a/include/fmt/chrono.h
+++ b/include/fmt/chrono.h
@@ -18,7 +18,7 @@
 #include <ostream>
 #include <type_traits>
 
-#include "format.h"
+#include "ostream.h"  // formatbuf
 
 FMT_BEGIN_NAMESPACE
 
@@ -72,7 +72,8 @@
           FMT_ENABLE_IF(!std::is_same<From, To>::value &&
                         std::numeric_limits<From>::is_signed ==
                             std::numeric_limits<To>::is_signed)>
-FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) {
+FMT_CONSTEXPR auto lossless_integral_conversion(const From from, int& ec)
+    -> To {
   ec = 0;
   using F = std::numeric_limits<From>;
   using T = std::numeric_limits<To>;
@@ -101,7 +102,8 @@
           FMT_ENABLE_IF(!std::is_same<From, To>::value &&
                         std::numeric_limits<From>::is_signed !=
                             std::numeric_limits<To>::is_signed)>
-FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) {
+FMT_CONSTEXPR auto lossless_integral_conversion(const From from, int& ec)
+    -> To {
   ec = 0;
   using F = std::numeric_limits<From>;
   using T = std::numeric_limits<To>;
@@ -133,7 +135,8 @@
 
 template <typename To, typename From,
           FMT_ENABLE_IF(std::is_same<From, To>::value)>
-FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) {
+FMT_CONSTEXPR auto lossless_integral_conversion(const From from, int& ec)
+    -> To {
   ec = 0;
   return from;
 }  // function
@@ -154,7 +157,7 @@
 // clang-format on
 template <typename To, typename From,
           FMT_ENABLE_IF(!std::is_same<From, To>::value)>
-FMT_CONSTEXPR To safe_float_conversion(const From from, int& ec) {
+FMT_CONSTEXPR auto safe_float_conversion(const From from, int& ec) -> To {
   ec = 0;
   using T = std::numeric_limits<To>;
   static_assert(std::is_floating_point<From>::value, "From must be floating");
@@ -176,7 +179,7 @@
 
 template <typename To, typename From,
           FMT_ENABLE_IF(std::is_same<From, To>::value)>
-FMT_CONSTEXPR To safe_float_conversion(const From from, int& ec) {
+FMT_CONSTEXPR auto safe_float_conversion(const From from, int& ec) -> To {
   ec = 0;
   static_assert(std::is_floating_point<From>::value, "From must be floating");
   return from;
@@ -188,8 +191,8 @@
 template <typename To, typename FromRep, typename FromPeriod,
           FMT_ENABLE_IF(std::is_integral<FromRep>::value),
           FMT_ENABLE_IF(std::is_integral<typename To::rep>::value)>
-To safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from,
-                      int& ec) {
+auto safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from,
+                        int& ec) -> To {
   using From = std::chrono::duration<FromRep, FromPeriod>;
   ec = 0;
   // the basic idea is that we need to convert from count() in the from type
@@ -240,8 +243,8 @@
 template <typename To, typename FromRep, typename FromPeriod,
           FMT_ENABLE_IF(std::is_floating_point<FromRep>::value),
           FMT_ENABLE_IF(std::is_floating_point<typename To::rep>::value)>
-To safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from,
-                      int& ec) {
+auto safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from,
+                        int& ec) -> To {
   using From = std::chrono::duration<FromRep, FromPeriod>;
   ec = 0;
   if (std::isnan(from.count())) {
@@ -321,12 +324,12 @@
 
 namespace detail {
 template <typename T = void> struct null {};
-inline null<> localtime_r FMT_NOMACRO(...) { return null<>(); }
-inline null<> localtime_s(...) { return null<>(); }
-inline null<> gmtime_r(...) { return null<>(); }
-inline null<> gmtime_s(...) { return null<>(); }
+inline auto localtime_r FMT_NOMACRO(...) -> null<> { return null<>(); }
+inline auto localtime_s(...) -> null<> { return null<>(); }
+inline auto gmtime_r(...) -> null<> { return null<>(); }
+inline auto gmtime_s(...) -> null<> { return null<>(); }
 
-inline const std::locale& get_classic_locale() {
+inline auto get_classic_locale() -> const std::locale& {
   static const auto& locale = std::locale::classic();
   return locale;
 }
@@ -406,8 +409,7 @@
   auto&& format_buf = formatbuf<std::basic_streambuf<Char>>(buf);
   auto&& os = std::basic_ostream<Char>(&format_buf);
   os.imbue(loc);
-  using iterator = std::ostreambuf_iterator<Char>;
-  const auto& facet = std::use_facet<std::time_put<Char, iterator>>(loc);
+  const auto& facet = std::use_facet<std::time_put<Char>>(loc);
   auto end = facet.put(os, os, Char(' '), &time, format, modifier);
   if (end.failed()) FMT_THROW(format_error("failed to format time"));
 }
@@ -430,6 +432,51 @@
   return write_encoded_tm_str(out, string_view(buf.data(), buf.size()), loc);
 }
 
+template <typename Rep1, typename Rep2>
+struct is_same_arithmetic_type
+    : public std::integral_constant<bool,
+                                    (std::is_integral<Rep1>::value &&
+                                     std::is_integral<Rep2>::value) ||
+                                        (std::is_floating_point<Rep1>::value &&
+                                         std::is_floating_point<Rep2>::value)> {
+};
+
+template <
+    typename To, typename FromRep, typename FromPeriod,
+    FMT_ENABLE_IF(is_same_arithmetic_type<FromRep, typename To::rep>::value)>
+auto fmt_duration_cast(std::chrono::duration<FromRep, FromPeriod> from) -> To {
+#if FMT_SAFE_DURATION_CAST
+  // Throwing version of safe_duration_cast is only available for
+  // integer to integer or float to float casts.
+  int ec;
+  To to = safe_duration_cast::safe_duration_cast<To>(from, ec);
+  if (ec) FMT_THROW(format_error("cannot format duration"));
+  return to;
+#else
+  // Standard duration cast, may overflow.
+  return std::chrono::duration_cast<To>(from);
+#endif
+}
+
+template <
+    typename To, typename FromRep, typename FromPeriod,
+    FMT_ENABLE_IF(!is_same_arithmetic_type<FromRep, typename To::rep>::value)>
+auto fmt_duration_cast(std::chrono::duration<FromRep, FromPeriod> from) -> To {
+  // Mixed integer <-> float cast is not supported by safe_duration_cast.
+  return std::chrono::duration_cast<To>(from);
+}
+
+template <typename Duration>
+auto to_time_t(
+    std::chrono::time_point<std::chrono::system_clock, Duration> time_point)
+    -> std::time_t {
+  // Cannot use std::chrono::system_clock::to_time_t since this would first
+  // require a cast to std::chrono::system_clock::time_point, which could
+  // overflow.
+  return fmt_duration_cast<std::chrono::duration<std::time_t>>(
+             time_point.time_since_epoch())
+      .count();
+}
 }  // namespace detail
 
 FMT_BEGIN_EXPORT
@@ -439,29 +486,29 @@
   expressed in local time. Unlike ``std::localtime``, this function is
   thread-safe on most platforms.
  */
-inline std::tm localtime(std::time_t time) {
+inline auto localtime(std::time_t time) -> std::tm {
   struct dispatcher {
     std::time_t time_;
     std::tm tm_;
 
     dispatcher(std::time_t t) : time_(t) {}
 
-    bool run() {
+    auto run() -> bool {
       using namespace fmt::detail;
       return handle(localtime_r(&time_, &tm_));
     }
 
-    bool handle(std::tm* tm) { return tm != nullptr; }
+    auto handle(std::tm* tm) -> bool { return tm != nullptr; }
 
-    bool handle(detail::null<>) {
+    auto handle(detail::null<>) -> bool {
       using namespace fmt::detail;
       return fallback(localtime_s(&tm_, &time_));
     }
 
-    bool fallback(int res) { return res == 0; }
+    auto fallback(int res) -> bool { return res == 0; }
 
 #if !FMT_MSC_VERSION
-    bool fallback(detail::null<>) {
+    auto fallback(detail::null<>) -> bool {
       using namespace fmt::detail;
       std::tm* tm = std::localtime(&time_);
       if (tm) tm_ = *tm;
@@ -478,8 +525,8 @@
 #if FMT_USE_LOCAL_TIME
 template <typename Duration>
 inline auto localtime(std::chrono::local_time<Duration> time) -> std::tm {
-  return localtime(std::chrono::system_clock::to_time_t(
-      std::chrono::current_zone()->to_sys(time)));
+  return localtime(
+      detail::to_time_t(std::chrono::current_zone()->to_sys(time)));
 }
 #endif
 
@@ -488,29 +535,29 @@
   expressed in Coordinated Universal Time (UTC). Unlike ``std::gmtime``, this
   function is thread-safe on most platforms.
  */
-inline std::tm gmtime(std::time_t time) {
+inline auto gmtime(std::time_t time) -> std::tm {
   struct dispatcher {
     std::time_t time_;
     std::tm tm_;
 
     dispatcher(std::time_t t) : time_(t) {}
 
-    bool run() {
+    auto run() -> bool {
       using namespace fmt::detail;
       return handle(gmtime_r(&time_, &tm_));
     }
 
-    bool handle(std::tm* tm) { return tm != nullptr; }
+    auto handle(std::tm* tm) -> bool { return tm != nullptr; }
 
-    bool handle(detail::null<>) {
+    auto handle(detail::null<>) -> bool {
       using namespace fmt::detail;
       return fallback(gmtime_s(&tm_, &time_));
     }
 
-    bool fallback(int res) { return res == 0; }
+    auto fallback(int res) -> bool { return res == 0; }
 
 #if !FMT_MSC_VERSION
-    bool fallback(detail::null<>) {
+    auto fallback(detail::null<>) -> bool {
       std::tm* tm = std::gmtime(&time_);
       if (tm) tm_ = *tm;
       return tm != nullptr;
@@ -523,9 +570,11 @@
   return gt.tm_;
 }
 
-inline std::tm gmtime(
-    std::chrono::time_point<std::chrono::system_clock> time_point) {
-  return gmtime(std::chrono::system_clock::to_time_t(time_point));
+template <typename Duration>
+inline auto gmtime(
+    std::chrono::time_point<std::chrono::system_clock, Duration> time_point)
+    -> std::tm {
+  return gmtime(detail::to_time_t(time_point));
 }
 
 namespace detail {
@@ -564,7 +613,8 @@
   }
 }
 
-template <typename Period> FMT_CONSTEXPR inline const char* get_units() {
+template <typename Period>
+FMT_CONSTEXPR inline auto get_units() -> const char* {
   if (std::is_same<Period, std::atto>::value) return "as";
   if (std::is_same<Period, std::femto>::value) return "fs";
   if (std::is_same<Period, std::pico>::value) return "ps";
@@ -620,9 +670,8 @@
 
 // Parses a put_time-like format string and invokes handler actions.
 template <typename Char, typename Handler>
-FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin,
-                                              const Char* end,
-                                              Handler&& handler) {
+FMT_CONSTEXPR auto parse_chrono_format(const Char* begin, const Char* end,
+                                       Handler&& handler) -> const Char* {
   if (begin == end || *begin == '}') return begin;
   if (*begin != '%') FMT_THROW(format_error("invalid format"));
   auto ptr = begin;
@@ -953,25 +1002,25 @@
   FMT_CONSTEXPR void on_tz_name() {}
 };
 
-inline const char* tm_wday_full_name(int wday) {
+inline auto tm_wday_full_name(int wday) -> const char* {
   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) {
+inline auto tm_wday_short_name(int wday) -> const char* {
   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) {
+inline auto tm_mon_full_name(int mon) -> const char* {
   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) {
+inline auto tm_mon_short_name(int mon) -> const char* {
   static constexpr const char* short_name_list[] = {
       "Jan", "Feb", "Mar", "Apr", "May", "Jun",
       "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
@@ -1003,21 +1052,21 @@
 
 // Converts value to Int and checks that it's in the range [0, upper).
 template <typename T, typename Int, FMT_ENABLE_IF(std::is_integral<T>::value)>
-inline Int to_nonnegative_int(T value, Int upper) {
-  FMT_ASSERT(std::is_unsigned<Int>::value ||
-                 (value >= 0 && to_unsigned(value) <= to_unsigned(upper)),
-             "invalid value");
-  (void)upper;
+inline auto to_nonnegative_int(T value, Int upper) -> Int {
+  if (!std::is_unsigned<Int>::value &&
+      (value < 0 || to_unsigned(value) > to_unsigned(upper))) {
+    FMT_THROW(fmt::format_error("chrono value is out of range"));
+  }
   return static_cast<Int>(value);
 }
 template <typename T, typename Int, FMT_ENABLE_IF(!std::is_integral<T>::value)>
-inline Int to_nonnegative_int(T value, Int upper) {
+inline auto to_nonnegative_int(T value, Int upper) -> Int {
   if (value < 0 || value > static_cast<T>(upper))
     FMT_THROW(format_error("invalid value"));
   return static_cast<Int>(value);
 }
 
-constexpr long long pow10(std::uint32_t n) {
+constexpr auto pow10(std::uint32_t n) -> long long {
   return n == 0 ? 1 : 10 * pow10(n - 1);
 }
 
@@ -1051,13 +1100,12 @@
                                 std::chrono::seconds::rep>::type,
       std::ratio<1, detail::pow10(num_fractional_digits)>>;
 
-  const auto fractional =
-      d - std::chrono::duration_cast<std::chrono::seconds>(d);
+  const auto fractional = d - fmt_duration_cast<std::chrono::seconds>(d);
   const auto subseconds =
       std::chrono::treat_as_floating_point<
           typename subsecond_precision::rep>::value
           ? fractional.count()
-          : std::chrono::duration_cast<subsecond_precision>(fractional).count();
+          : fmt_duration_cast<subsecond_precision>(fractional).count();
   auto n = static_cast<uint32_or_64_or_128_t<long long>>(subseconds);
   const int num_digits = detail::count_digits(n);
 
@@ -1108,11 +1156,11 @@
       num_fractional_digits = 6;
   }
 
-  format_to(std::back_inserter(buf), FMT_STRING("{:.{}f}"),
-            std::fmod(val * static_cast<rep>(Duration::period::num) /
-                          static_cast<rep>(Duration::period::den),
-                      static_cast<rep>(60)),
-            num_fractional_digits);
+  fmt::format_to(std::back_inserter(buf), FMT_STRING("{:.{}f}"),
+                 std::fmod(val * static_cast<rep>(Duration::period::num) /
+                               static_cast<rep>(Duration::period::den),
+                           static_cast<rep>(60)),
+                 num_fractional_digits);
 }
 
 template <typename OutputIt, typename Char,
@@ -1173,8 +1221,7 @@
     return static_cast<int>(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
+  // Algorithm: https://en.wikipedia.org/wiki/ISO_week_date.
   auto iso_year_weeks(long long curr_year) const noexcept -> int {
     const auto prev_year = curr_year - 1;
     const auto curr_p =
@@ -1314,7 +1361,7 @@
         subsecs_(subsecs),
         tm_(tm) {}
 
-  OutputIt out() const { return out_; }
+  auto out() const -> OutputIt { return out_; }
 
   FMT_CONSTEXPR void on_text(const Char* begin, const Char* end) {
     out_ = copy_str<Char>(begin, end, out_);
@@ -1578,6 +1625,7 @@
 
   template <typename Char>
   FMT_CONSTEXPR void on_text(const Char*, const Char*) {}
+  FMT_CONSTEXPR void on_day_of_year() {}
   FMT_CONSTEXPR void on_24_hour(numeric_system, pad_type) {}
   FMT_CONSTEXPR void on_12_hour(numeric_system, pad_type) {}
   FMT_CONSTEXPR void on_minute(numeric_system, pad_type) {}
@@ -1596,16 +1644,16 @@
 
 template <typename T,
           FMT_ENABLE_IF(std::is_integral<T>::value&& has_isfinite<T>::value)>
-inline bool isfinite(T) {
+inline auto isfinite(T) -> bool {
   return true;
 }
 
 template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
-inline T mod(T x, int y) {
+inline auto mod(T x, int y) -> T {
   return x % static_cast<T>(y);
 }
 template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)>
-inline T mod(T x, int y) {
+inline auto mod(T x, int y) -> T {
   return std::fmod(x, static_cast<T>(y));
 }
 
@@ -1620,49 +1668,38 @@
   using type = typename std::make_unsigned<T>::type;
 };
 
-#if FMT_SAFE_DURATION_CAST
-// throwing version of safe_duration_cast
-template <typename To, typename FromRep, typename FromPeriod>
-To fmt_safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from) {
-  int ec;
-  To to = safe_duration_cast::safe_duration_cast<To>(from, ec);
-  if (ec) FMT_THROW(format_error("cannot format duration"));
-  return to;
-}
-#endif
-
 template <typename Rep, typename Period,
           FMT_ENABLE_IF(std::is_integral<Rep>::value)>
-inline std::chrono::duration<Rep, std::milli> get_milliseconds(
-    std::chrono::duration<Rep, Period> d) {
+inline auto get_milliseconds(std::chrono::duration<Rep, Period> d)
+    -> std::chrono::duration<Rep, std::milli> {
   // this may overflow and/or the result may not fit in the
   // target type.
 #if FMT_SAFE_DURATION_CAST
   using CommonSecondsType =
       typename std::common_type<decltype(d), std::chrono::seconds>::type;
-  const auto d_as_common = fmt_safe_duration_cast<CommonSecondsType>(d);
+  const auto d_as_common = fmt_duration_cast<CommonSecondsType>(d);
   const auto d_as_whole_seconds =
-      fmt_safe_duration_cast<std::chrono::seconds>(d_as_common);
+      fmt_duration_cast<std::chrono::seconds>(d_as_common);
   // this conversion should be nonproblematic
   const auto diff = d_as_common - d_as_whole_seconds;
   const auto ms =
-      fmt_safe_duration_cast<std::chrono::duration<Rep, std::milli>>(diff);
+      fmt_duration_cast<std::chrono::duration<Rep, std::milli>>(diff);
   return ms;
 #else
-  auto s = std::chrono::duration_cast<std::chrono::seconds>(d);
-  return std::chrono::duration_cast<std::chrono::milliseconds>(d - s);
+  auto s = fmt_duration_cast<std::chrono::seconds>(d);
+  return fmt_duration_cast<std::chrono::milliseconds>(d - s);
 #endif
 }
 
 template <typename Char, typename Rep, typename OutputIt,
           FMT_ENABLE_IF(std::is_integral<Rep>::value)>
-OutputIt format_duration_value(OutputIt out, Rep val, int) {
+auto format_duration_value(OutputIt out, Rep val, int) -> OutputIt {
   return write<Char>(out, val);
 }
 
 template <typename Char, typename Rep, typename OutputIt,
           FMT_ENABLE_IF(std::is_floating_point<Rep>::value)>
-OutputIt format_duration_value(OutputIt out, Rep val, int precision) {
+auto format_duration_value(OutputIt out, Rep val, int precision) -> OutputIt {
   auto specs = format_specs<Char>();
   specs.precision = precision;
   specs.type = precision >= 0 ? presentation_type::fixed_lower
@@ -1671,12 +1708,12 @@
 }
 
 template <typename Char, typename OutputIt>
-OutputIt copy_unit(string_view unit, OutputIt out, Char) {
+auto copy_unit(string_view unit, OutputIt out, Char) -> OutputIt {
   return std::copy(unit.begin(), unit.end(), out);
 }
 
 template <typename OutputIt>
-OutputIt copy_unit(string_view unit, OutputIt out, wchar_t) {
+auto copy_unit(string_view unit, OutputIt out, wchar_t) -> OutputIt {
   // This works when wchar_t is UTF-32 because units only contain characters
   // that have the same representation in UTF-16 and UTF-32.
   utf8_to_utf16 u(unit);
@@ -1684,7 +1721,7 @@
 }
 
 template <typename Char, typename Period, typename OutputIt>
-OutputIt format_duration_unit(OutputIt out) {
+auto format_duration_unit(OutputIt out) -> OutputIt {
   if (const char* unit = get_units<Period>())
     return copy_unit(string_view(unit), out, Char());
   *out++ = '[';
@@ -1751,18 +1788,12 @@
 
     // this may overflow and/or the result may not fit in the
     // target type.
-#if FMT_SAFE_DURATION_CAST
     // might need checked conversion (rep!=Rep)
-    auto tmpval = std::chrono::duration<rep, Period>(val);
-    s = fmt_safe_duration_cast<seconds>(tmpval);
-#else
-    s = std::chrono::duration_cast<seconds>(
-        std::chrono::duration<rep, Period>(val));
-#endif
+    s = fmt_duration_cast<seconds>(std::chrono::duration<rep, Period>(val));
   }
 
   // returns true if nan or inf, writes to out.
-  bool handle_nan_inf() {
+  auto handle_nan_inf() -> bool {
     if (isfinite(val)) {
       return false;
     }
@@ -1779,17 +1810,22 @@
     return true;
   }
 
-  Rep hour() const { return static_cast<Rep>(mod((s.count() / 3600), 24)); }
+  auto days() const -> Rep { return static_cast<Rep>(s.count() / 86400); }
+  auto hour() const -> Rep {
+    return static_cast<Rep>(mod((s.count() / 3600), 24));
+  }
 
-  Rep hour12() const {
+  auto hour12() const -> Rep {
     Rep hour = static_cast<Rep>(mod((s.count() / 3600), 12));
     return hour <= 0 ? 12 : hour;
   }
 
-  Rep minute() const { return static_cast<Rep>(mod((s.count() / 60), 60)); }
-  Rep second() const { return static_cast<Rep>(mod(s.count(), 60)); }
+  auto minute() const -> Rep {
+    return static_cast<Rep>(mod((s.count() / 60), 60));
+  }
+  auto second() const -> Rep { return static_cast<Rep>(mod(s.count(), 60)); }
 
-  std::tm time() const {
+  auto time() const -> std::tm {
     auto time = std::tm();
     time.tm_hour = to_nonnegative_int(hour(), 24);
     time.tm_min = to_nonnegative_int(minute(), 60);
@@ -1857,10 +1893,14 @@
   void on_dec0_week_of_year(numeric_system) {}
   void on_dec1_week_of_year(numeric_system) {}
   void on_iso_week_of_year(numeric_system) {}
-  void on_day_of_year() {}
   void on_day_of_month(numeric_system) {}
   void on_day_of_month_space(numeric_system) {}
 
+  void on_day_of_year() {
+    if (handle_nan_inf()) return;
+    write(days(), 0);
+  }
+
   void on_24_hour(numeric_system ns, pad_type pad) {
     if (handle_nan_inf()) return;
 
@@ -1967,7 +2007,7 @@
   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; }
+  constexpr auto c_encoding() const noexcept -> unsigned { return value; }
 };
 
 class year_month_day {};
@@ -2082,25 +2122,22 @@
             period::num != 1 || period::den != 1 ||
             std::is_floating_point<typename Duration::rep>::value)) {
       const auto epoch = val.time_since_epoch();
-      auto subsecs = std::chrono::duration_cast<Duration>(
-          epoch - std::chrono::duration_cast<std::chrono::seconds>(epoch));
+      auto subsecs = detail::fmt_duration_cast<Duration>(
+          epoch - detail::fmt_duration_cast<std::chrono::seconds>(epoch));
 
       if (subsecs.count() < 0) {
         auto second =
-            std::chrono::duration_cast<Duration>(std::chrono::seconds(1));
+            detail::fmt_duration_cast<Duration>(std::chrono::seconds(1));
         if (epoch.count() < ((Duration::min)() + second).count())
           FMT_THROW(format_error("duration is too small"));
         subsecs += second;
         val -= second;
       }
 
-      return formatter<std::tm, Char>::do_format(
-          gmtime(std::chrono::time_point_cast<std::chrono::seconds>(val)), ctx,
-          &subsecs);
+      return formatter<std::tm, Char>::do_format(gmtime(val), ctx, &subsecs);
     }
 
-    return formatter<std::tm, Char>::format(
-        gmtime(std::chrono::time_point_cast<std::chrono::seconds>(val)), ctx);
+    return formatter<std::tm, Char>::format(gmtime(val), ctx);
   }
 };
 
@@ -2119,17 +2156,13 @@
     if (period::num != 1 || period::den != 1 ||
         std::is_floating_point<typename Duration::rep>::value) {
       const auto epoch = val.time_since_epoch();
-      const auto subsecs = std::chrono::duration_cast<Duration>(
-          epoch - std::chrono::duration_cast<std::chrono::seconds>(epoch));
+      const auto subsecs = detail::fmt_duration_cast<Duration>(
+          epoch - detail::fmt_duration_cast<std::chrono::seconds>(epoch));
 
-      return formatter<std::tm, Char>::do_format(
-          localtime(std::chrono::time_point_cast<std::chrono::seconds>(val)),
-          ctx, &subsecs);
+      return formatter<std::tm, Char>::do_format(localtime(val), ctx, &subsecs);
     }
 
-    return formatter<std::tm, Char>::format(
-        localtime(std::chrono::time_point_cast<std::chrono::seconds>(val)),
-        ctx);
+    return formatter<std::tm, Char>::format(localtime(val), ctx);
   }
 };
 #endif
diff --git a/include/fmt/color.h b/include/fmt/color.h
index 57b7f57..367849a 100644
--- a/include/fmt/color.h
+++ b/include/fmt/color.h
@@ -233,7 +233,7 @@
   FMT_CONSTEXPR text_style(emphasis em = emphasis()) noexcept
       : set_foreground_color(), set_background_color(), ems(em) {}
 
-  FMT_CONSTEXPR text_style& operator|=(const text_style& rhs) {
+  FMT_CONSTEXPR auto operator|=(const text_style& rhs) -> text_style& {
     if (!set_foreground_color) {
       set_foreground_color = rhs.set_foreground_color;
       foreground_color = rhs.foreground_color;
@@ -257,29 +257,29 @@
     return *this;
   }
 
-  friend FMT_CONSTEXPR text_style operator|(text_style lhs,
-                                            const text_style& rhs) {
+  friend FMT_CONSTEXPR auto operator|(text_style lhs, const text_style& rhs)
+      -> text_style {
     return lhs |= rhs;
   }
 
-  FMT_CONSTEXPR bool has_foreground() const noexcept {
+  FMT_CONSTEXPR auto has_foreground() const noexcept -> bool {
     return set_foreground_color;
   }
-  FMT_CONSTEXPR bool has_background() const noexcept {
+  FMT_CONSTEXPR auto has_background() const noexcept -> bool {
     return set_background_color;
   }
-  FMT_CONSTEXPR bool has_emphasis() const noexcept {
+  FMT_CONSTEXPR auto has_emphasis() const noexcept -> bool {
     return static_cast<uint8_t>(ems) != 0;
   }
-  FMT_CONSTEXPR detail::color_type get_foreground() const noexcept {
+  FMT_CONSTEXPR auto get_foreground() const noexcept -> detail::color_type {
     FMT_ASSERT(has_foreground(), "no foreground specified for this style");
     return foreground_color;
   }
-  FMT_CONSTEXPR detail::color_type get_background() const noexcept {
+  FMT_CONSTEXPR auto get_background() const noexcept -> detail::color_type {
     FMT_ASSERT(has_background(), "no background specified for this style");
     return background_color;
   }
-  FMT_CONSTEXPR emphasis get_emphasis() const noexcept {
+  FMT_CONSTEXPR auto get_emphasis() const noexcept -> emphasis {
     FMT_ASSERT(has_emphasis(), "no emphasis specified for this style");
     return ems;
   }
@@ -297,9 +297,11 @@
     }
   }
 
-  friend FMT_CONSTEXPR text_style fg(detail::color_type foreground) noexcept;
+  friend FMT_CONSTEXPR auto fg(detail::color_type foreground) noexcept
+      -> text_style;
 
-  friend FMT_CONSTEXPR text_style bg(detail::color_type background) noexcept;
+  friend FMT_CONSTEXPR auto bg(detail::color_type background) noexcept
+      -> text_style;
 
   detail::color_type foreground_color;
   detail::color_type background_color;
@@ -309,16 +311,19 @@
 };
 
 /** Creates a text style from the foreground (text) color. */
-FMT_CONSTEXPR inline text_style fg(detail::color_type foreground) noexcept {
+FMT_CONSTEXPR inline auto fg(detail::color_type foreground) noexcept
+    -> text_style {
   return text_style(true, foreground);
 }
 
 /** Creates a text style from the background color. */
-FMT_CONSTEXPR inline text_style bg(detail::color_type background) noexcept {
+FMT_CONSTEXPR inline auto bg(detail::color_type background) noexcept
+    -> text_style {
   return text_style(false, background);
 }
 
-FMT_CONSTEXPR inline text_style operator|(emphasis lhs, emphasis rhs) noexcept {
+FMT_CONSTEXPR inline auto operator|(emphasis lhs, emphasis rhs) noexcept
+    -> text_style {
   return text_style(lhs) | rhs;
 }
 
@@ -384,8 +389,8 @@
   }
   FMT_CONSTEXPR operator const Char*() const noexcept { return buffer; }
 
-  FMT_CONSTEXPR const Char* begin() const noexcept { return buffer; }
-  FMT_CONSTEXPR_CHAR_TRAITS const Char* end() const noexcept {
+  FMT_CONSTEXPR auto begin() const noexcept -> const Char* { return buffer; }
+  FMT_CONSTEXPR_CHAR_TRAITS auto end() const noexcept -> const Char* {
     return buffer + std::char_traits<Char>::length(buffer);
   }
 
@@ -400,25 +405,27 @@
     out[2] = static_cast<Char>('0' + c % 10);
     out[3] = static_cast<Char>(delimiter);
   }
-  static FMT_CONSTEXPR bool has_emphasis(emphasis em, emphasis mask) noexcept {
+  static FMT_CONSTEXPR auto has_emphasis(emphasis em, emphasis mask) noexcept
+      -> bool {
     return static_cast<uint8_t>(em) & static_cast<uint8_t>(mask);
   }
 };
 
 template <typename Char>
-FMT_CONSTEXPR ansi_color_escape<Char> make_foreground_color(
-    detail::color_type foreground) noexcept {
+FMT_CONSTEXPR auto make_foreground_color(detail::color_type foreground) noexcept
+    -> ansi_color_escape<Char> {
   return ansi_color_escape<Char>(foreground, "\x1b[38;2;");
 }
 
 template <typename Char>
-FMT_CONSTEXPR ansi_color_escape<Char> make_background_color(
-    detail::color_type background) noexcept {
+FMT_CONSTEXPR auto make_background_color(detail::color_type background) noexcept
+    -> ansi_color_escape<Char> {
   return ansi_color_escape<Char>(background, "\x1b[48;2;");
 }
 
 template <typename Char>
-FMT_CONSTEXPR ansi_color_escape<Char> make_emphasis(emphasis em) noexcept {
+FMT_CONSTEXPR auto make_emphasis(emphasis em) noexcept
+    -> ansi_color_escape<Char> {
   return ansi_color_escape<Char>(em);
 }
 
@@ -511,9 +518,10 @@
 }
 
 template <typename S, typename Char = char_t<S>>
-inline std::basic_string<Char> vformat(
+inline auto vformat(
     const text_style& ts, const S& format_str,
-    basic_format_args<buffer_context<type_identity_t<Char>>> args) {
+    basic_format_args<buffer_context<type_identity_t<Char>>> args)
+    -> std::basic_string<Char> {
   basic_memory_buffer<Char> buf;
   detail::vformat_to(buf, ts, detail::to_string_view(format_str), args);
   return fmt::to_string(buf);
@@ -532,8 +540,8 @@
   \endrst
 */
 template <typename S, typename... Args, typename Char = char_t<S>>
-inline std::basic_string<Char> format(const text_style& ts, const S& format_str,
-                                      const Args&... args) {
+inline auto format(const text_style& ts, const S& format_str,
+                   const Args&... args) -> std::basic_string<Char> {
   return fmt::vformat(ts, detail::to_string_view(format_str),
                       fmt::make_format_args<buffer_context<Char>>(args...));
 }
@@ -543,9 +551,10 @@
  */
 template <typename OutputIt, typename Char,
           FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value)>
-OutputIt vformat_to(
-    OutputIt out, const text_style& ts, basic_string_view<Char> format_str,
-    basic_format_args<buffer_context<type_identity_t<Char>>> args) {
+auto vformat_to(OutputIt out, const text_style& ts,
+                basic_string_view<Char> format_str,
+                basic_format_args<buffer_context<type_identity_t<Char>>> args)
+    -> OutputIt {
   auto&& buf = detail::get_buffer<Char>(out);
   detail::vformat_to(buf, ts, format_str, args);
   return detail::get_iterator(buf, out);
@@ -563,9 +572,10 @@
                    fmt::emphasis::bold | fg(fmt::color::red), "{}", 42);
   \endrst
 */
-template <typename OutputIt, typename S, typename... Args,
-          bool enable = detail::is_output_iterator<OutputIt, char_t<S>>::value&&
-              detail::is_string<S>::value>
+template <
+    typename OutputIt, typename S, typename... Args,
+    bool enable = detail::is_output_iterator<OutputIt, char_t<S>>::value &&
+                  detail::is_string<S>::value>
 inline auto format_to(OutputIt out, const text_style& ts, const S& format_str,
                       Args&&... args) ->
     typename std::enable_if<enable, OutputIt>::type {
diff --git a/include/fmt/compile.h b/include/fmt/compile.h
index a4c7e49..3b3f166 100644
--- a/include/fmt/compile.h
+++ b/include/fmt/compile.h
@@ -14,8 +14,8 @@
 namespace detail {
 
 template <typename Char, typename InputIt>
-FMT_CONSTEXPR inline counting_iterator copy_str(InputIt begin, InputIt end,
-                                                counting_iterator it) {
+FMT_CONSTEXPR inline auto copy_str(InputIt begin, InputIt end,
+                                   counting_iterator it) -> counting_iterator {
   return it + (end - begin);
 }
 
@@ -57,7 +57,7 @@
 #endif
 
 template <typename T, typename... Tail>
-const T& first(const T& value, const Tail&...) {
+auto first(const T& value, const Tail&...) -> const T& {
   return value;
 }
 
@@ -488,18 +488,19 @@
 
 template <typename OutputIt, typename S, typename... Args,
           FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
-format_to_n_result<OutputIt> format_to_n(OutputIt out, size_t n,
-                                         const S& format_str, Args&&... args) {
+auto format_to_n(OutputIt out, size_t n, const S& format_str, Args&&... args)
+    -> format_to_n_result<OutputIt> {
   using traits = detail::fixed_buffer_traits;
   auto buf = detail::iterator_buffer<OutputIt, char, traits>(out, n);
-  format_to(std::back_inserter(buf), format_str, std::forward<Args>(args)...);
+  fmt::format_to(std::back_inserter(buf), format_str,
+                 std::forward<Args>(args)...);
   return {buf.out(), buf.count()};
 }
 
 template <typename S, typename... Args,
           FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
-FMT_CONSTEXPR20 size_t formatted_size(const S& format_str,
-                                      const Args&... args) {
+FMT_CONSTEXPR20 auto formatted_size(const S& format_str, const Args&... args)
+    -> size_t {
   return fmt::format_to(detail::counting_iterator(), format_str, args...)
       .count();
 }
diff --git a/include/fmt/core.h b/include/fmt/core.h
index 18ebada..94f3f22 100644
--- a/include/fmt/core.h
+++ b/include/fmt/core.h
@@ -18,7 +18,7 @@
 #include <type_traits>
 
 // The fmt library version in the form major * 10000 + minor * 100 + patch.
-#define FMT_VERSION 100101
+#define FMT_VERSION 100200
 
 #if defined(__clang__) && !defined(__ibmxl__)
 #  define FMT_CLANG_VERSION (__clang_major__ * 100 + __clang_minor__)
@@ -105,9 +105,12 @@
 #  define FMT_CONSTEXPR
 #endif
 
-#if ((FMT_CPLUSPLUS >= 202002L) &&                            \
-     (!defined(_GLIBCXX_RELEASE) || _GLIBCXX_RELEASE > 9)) || \
-    (FMT_CPLUSPLUS >= 201709L && FMT_GCC_VERSION >= 1002)
+#if (FMT_CPLUSPLUS >= 202002L ||                                \
+     (FMT_CPLUSPLUS >= 201709L && FMT_GCC_VERSION >= 1002)) &&  \
+    ((!defined(_GLIBCXX_RELEASE) || _GLIBCXX_RELEASE >= 10) &&  \
+     (!defined(_LIBCPP_VERSION) || _LIBCPP_VERSION >= 10000) && \
+     (!FMT_MSC_VERSION || FMT_MSC_VERSION >= 1928)) &&          \
+    defined(__cpp_lib_is_constant_evaluated)
 #  define FMT_CONSTEXPR20 constexpr
 #else
 #  define FMT_CONSTEXPR20
@@ -224,8 +227,9 @@
         __apple_build_version__ >= 14000029L) &&                 \
        FMT_CPLUSPLUS >= 202002L) ||                              \
       (defined(__cpp_consteval) &&                               \
-       (!FMT_MSC_VERSION || _MSC_FULL_VER >= 193030704))
-// consteval is broken in MSVC before VS2022 and Apple clang before 14.
+       (!FMT_MSC_VERSION || FMT_MSC_VERSION >= 1929))
+// consteval is broken in MSVC before VS2019 version 16.10 and Apple clang
+// before 14.
 #    define FMT_CONSTEVAL consteval
 #    define FMT_HAS_CONSTEVAL
 #  else
@@ -244,6 +248,15 @@
 #  endif
 #endif
 
+// GCC < 5 requires this-> in decltype
+#ifndef FMT_DECLTYPE_THIS
+#  if FMT_GCC_VERSION && FMT_GCC_VERSION < 500
+#    define FMT_DECLTYPE_THIS this->
+#  else
+#    define FMT_DECLTYPE_THIS
+#  endif
+#endif
+
 // Enable minimal optimizations for more compact code in debug mode.
 FMT_GCC_PRAGMA("GCC push_options")
 #if !defined(__OPTIMIZE__) && !defined(__NVCOMPILER) && !defined(__LCC__) && \
@@ -458,15 +471,15 @@
     size_ -= n;
   }
 
-  FMT_CONSTEXPR_CHAR_TRAITS bool starts_with(
-      basic_string_view<Char> sv) const noexcept {
+  FMT_CONSTEXPR_CHAR_TRAITS auto starts_with(
+      basic_string_view<Char> sv) const noexcept -> bool {
     return size_ >= sv.size_ &&
            std::char_traits<Char>::compare(data_, sv.data_, sv.size_) == 0;
   }
-  FMT_CONSTEXPR_CHAR_TRAITS bool starts_with(Char c) const noexcept {
+  FMT_CONSTEXPR_CHAR_TRAITS auto starts_with(Char c) const noexcept -> bool {
     return size_ >= 1 && std::char_traits<Char>::eq(*data_, c);
   }
-  FMT_CONSTEXPR_CHAR_TRAITS bool starts_with(const Char* s) const {
+  FMT_CONSTEXPR_CHAR_TRAITS auto starts_with(const Char* s) const -> bool {
     return starts_with(basic_string_view<Char>(s));
   }
 
@@ -604,10 +617,10 @@
 FMT_TYPE_CONSTANT(basic_string_view<Char>, string_type);
 FMT_TYPE_CONSTANT(const void*, pointer_type);
 
-constexpr bool is_integral_type(type t) {
+constexpr auto is_integral_type(type t) -> bool {
   return t > type::none_type && t <= type::last_integer_type;
 }
-constexpr bool is_arithmetic_type(type t) {
+constexpr auto is_arithmetic_type(type t) -> bool {
   return t > type::none_type && t <= type::last_numeric_type;
 }
 
@@ -631,6 +644,7 @@
   pointer_set = set(type::pointer_type)
 };
 
+// DEPRECATED!
 FMT_NORETURN FMT_API void throw_format_error(const char* message);
 
 struct error_handler {
@@ -808,7 +822,7 @@
  protected:
   // Don't initialize ptr_ since it is not accessed to save a few cycles.
   FMT_MSC_WARNING(suppress : 26495)
-  buffer(size_t sz) noexcept : size_(sz), capacity_(sz) {}
+  FMT_CONSTEXPR buffer(size_t sz) noexcept : size_(sz), capacity_(sz) {}
 
   FMT_CONSTEXPR20 buffer(T* p = nullptr, size_t sz = 0, size_t cap = 0) noexcept
       : ptr_(p), size_(sz), capacity_(cap) {}
@@ -823,6 +837,7 @@
   }
 
   /** Increases the buffer capacity to hold at least *capacity* elements. */
+  // DEPRECATED!
   virtual FMT_CONSTEXPR20 void grow(size_t capacity) = 0;
 
  public:
@@ -1309,6 +1324,7 @@
     parse_ctx.advance_to(f.parse(parse_ctx));
     using qualified_type =
         conditional_t<has_const_formatter<T, Context>(), const T, T>;
+    // Calling format through a mutable reference is deprecated.
     ctx.advance_to(f.format(*static_cast<qualified_type*>(arg), ctx));
   }
 };
@@ -1322,7 +1338,7 @@
 template <typename T> struct format_as_result {
   template <typename U,
             FMT_ENABLE_IF(std::is_enum<U>::value || std::is_class<U>::value)>
-  static auto map(U*) -> decltype(format_as(std::declval<U>()));
+  static auto map(U*) -> remove_cvref_t<decltype(format_as(std::declval<U>()))>;
   static auto map(...) -> void;
 
   using type = decltype(map(static_cast<T*>(nullptr)));
@@ -1439,7 +1455,8 @@
   // Only map owning types because mapping views can be unsafe.
   template <typename T, typename U = format_as_t<T>,
             FMT_ENABLE_IF(std::is_arithmetic<U>::value)>
-  FMT_CONSTEXPR FMT_INLINE auto map(const T& val) -> decltype(this->map(U())) {
+  FMT_CONSTEXPR FMT_INLINE auto map(const T& val)
+      -> decltype(FMT_DECLTYPE_THIS map(U())) {
     return map(format_as(val));
   }
 
@@ -1463,13 +1480,14 @@
                           !is_string<U>::value && !is_char<U>::value &&
                           !is_named_arg<U>::value &&
                           !std::is_arithmetic<format_as_t<U>>::value)>
-  FMT_CONSTEXPR FMT_INLINE auto map(T& val) -> decltype(this->do_map(val)) {
+  FMT_CONSTEXPR FMT_INLINE auto map(T& val)
+      -> decltype(FMT_DECLTYPE_THIS do_map(val)) {
     return do_map(val);
   }
 
   template <typename T, FMT_ENABLE_IF(is_named_arg<T>::value)>
   FMT_CONSTEXPR FMT_INLINE auto map(const T& named_arg)
-      -> decltype(this->map(named_arg.value)) {
+      -> decltype(FMT_DECLTYPE_THIS map(named_arg.value)) {
     return map(named_arg.value);
   }
 
@@ -1604,8 +1622,8 @@
 }  // namespace detail
 FMT_BEGIN_EXPORT
 
-// A formatting argument. It is a trivially copyable/constructible type to
-// allow storage in basic_memory_buffer.
+// A formatting argument. Context is a template parameter for the compiled API
+// where output can be unbuffered.
 template <typename Context> class basic_format_arg {
  private:
   detail::value<Context> value_;
@@ -1657,6 +1675,15 @@
   auto is_arithmetic() const -> bool {
     return detail::is_arithmetic_type(type_);
   }
+
+  FMT_INLINE auto format_custom(const char_type* parse_begin,
+                                typename Context::parse_context_type& parse_ctx,
+                                Context& ctx) -> bool {
+    if (type_ != detail::type::custom_type) return false;
+    parse_ctx.advance_to(parse_begin);
+    value_.custom.format(value_.custom.value, parse_ctx, ctx);
+    return true;
+  }
 };
 
 /**
@@ -1745,6 +1772,7 @@
   }
   auto args() const -> const format_args& { return args_; }
 
+  // DEPRECATED!
   FMT_CONSTEXPR auto error_handler() -> detail::error_handler { return {}; }
   void on_error(const char* message) { error_handler().on_error(message); }
 
@@ -2417,6 +2445,8 @@
     case 'G':
       return parse_presentation_type(pres::general_upper, float_set);
     case 'c':
+      if (arg_type == type::bool_type)
+        throw_format_error("invalid format specifier");
       return parse_presentation_type(pres::chr, integral_set);
     case 's':
       return parse_presentation_type(pres::string,
diff --git a/include/fmt/format-inl.h b/include/fmt/format-inl.h
index 5f8c83a..e9a4ca4 100644
--- a/include/fmt/format-inl.h
+++ b/include/fmt/format-inl.h
@@ -58,8 +58,8 @@
   error_code_size += detail::to_unsigned(detail::count_digits(abs_value));
   auto it = buffer_appender<char>(out);
   if (message.size() <= inline_buffer_size - error_code_size)
-    format_to(it, FMT_STRING("{}{}"), message, SEP);
-  format_to(it, FMT_STRING("{}{}"), ERROR_STR, error_code);
+    fmt::format_to(it, FMT_STRING("{}{}"), message, SEP);
+  fmt::format_to(it, FMT_STRING("{}{}"), ERROR_STR, error_code);
   FMT_ASSERT(out.size() <= inline_buffer_size, "");
 }
 
@@ -85,7 +85,7 @@
   static_assert(std::is_same<Locale, std::locale>::value, "");
 }
 
-template <typename Locale> Locale locale_ref::get() const {
+template <typename Locale> auto locale_ref::get() const -> Locale {
   static_assert(std::is_same<Locale, std::locale>::value, "");
   return locale_ ? *static_cast<const std::locale*>(locale_) : std::locale();
 }
@@ -97,7 +97,8 @@
   auto thousands_sep = grouping.empty() ? Char() : facet.thousands_sep();
   return {std::move(grouping), thousands_sep};
 }
-template <typename Char> FMT_FUNC Char decimal_point_impl(locale_ref loc) {
+template <typename Char>
+FMT_FUNC auto decimal_point_impl(locale_ref loc) -> Char {
   return std::use_facet<std::numpunct<Char>>(loc.get<std::locale>())
       .decimal_point();
 }
@@ -143,24 +144,25 @@
 }
 #endif
 
-FMT_FUNC std::system_error vsystem_error(int error_code, string_view fmt,
-                                         format_args args) {
+FMT_FUNC auto vsystem_error(int error_code, string_view fmt, format_args args)
+    -> std::system_error {
   auto ec = std::error_code(error_code, std::generic_category());
   return std::system_error(ec, vformat(fmt, args));
 }
 
 namespace detail {
 
-template <typename F> inline bool operator==(basic_fp<F> x, basic_fp<F> y) {
+template <typename F>
+inline auto operator==(basic_fp<F> x, basic_fp<F> y) -> bool {
   return x.f == y.f && x.e == y.e;
 }
 
 // Compilers should be able to optimize this into the ror instruction.
-FMT_CONSTEXPR inline uint32_t rotr(uint32_t n, uint32_t r) noexcept {
+FMT_CONSTEXPR inline auto rotr(uint32_t n, uint32_t r) noexcept -> uint32_t {
   r &= 31;
   return (n >> r) | (n << (32 - r));
 }
-FMT_CONSTEXPR inline uint64_t rotr(uint64_t n, uint32_t r) noexcept {
+FMT_CONSTEXPR inline auto rotr(uint64_t n, uint32_t r) noexcept -> uint64_t {
   r &= 63;
   return (n >> r) | (n << (64 - r));
 }
@@ -169,14 +171,14 @@
 namespace dragonbox {
 // Computes upper 64 bits of multiplication of a 32-bit unsigned integer and a
 // 64-bit unsigned integer.
-inline uint64_t umul96_upper64(uint32_t x, uint64_t y) noexcept {
+inline auto umul96_upper64(uint32_t x, uint64_t y) noexcept -> uint64_t {
   return umul128_upper64(static_cast<uint64_t>(x) << 32, y);
 }
 
 // Computes lower 128 bits of multiplication of a 64-bit unsigned integer and a
 // 128-bit unsigned integer.
-inline uint128_fallback umul192_lower128(uint64_t x,
-                                         uint128_fallback y) noexcept {
+inline auto umul192_lower128(uint64_t x, uint128_fallback y) noexcept
+    -> uint128_fallback {
   uint64_t high = x * y.high();
   uint128_fallback high_low = umul128(x, y.low());
   return {high + high_low.high(), high_low.low()};
@@ -184,12 +186,12 @@
 
 // Computes lower 64 bits of multiplication of a 32-bit unsigned integer and a
 // 64-bit unsigned integer.
-inline uint64_t umul96_lower64(uint32_t x, uint64_t y) noexcept {
+inline auto umul96_lower64(uint32_t x, uint64_t y) noexcept -> uint64_t {
   return x * y;
 }
 
 // Various fast log computations.
-inline int floor_log10_pow2_minus_log10_4_over_3(int e) noexcept {
+inline auto floor_log10_pow2_minus_log10_4_over_3(int e) noexcept -> int {
   FMT_ASSERT(e <= 2936 && e >= -2985, "too large exponent");
   return (e * 631305 - 261663) >> 21;
 }
@@ -203,7 +205,7 @@
 // divisible by pow(10, N).
 // Precondition: n <= pow(10, N + 1).
 template <int N>
-bool check_divisibility_and_divide_by_pow10(uint32_t& n) noexcept {
+auto check_divisibility_and_divide_by_pow10(uint32_t& n) noexcept -> bool {
   // The numbers below are chosen such that:
   //   1. floor(n/d) = floor(nm / 2^k) where d=10 or d=100,
   //   2. nm mod 2^k < m if and only if n is divisible by d,
@@ -228,7 +230,7 @@
 
 // Computes floor(n / pow(10, N)) for small n and N.
 // Precondition: n <= pow(10, N + 1).
-template <int N> uint32_t small_division_by_pow10(uint32_t n) noexcept {
+template <int N> auto small_division_by_pow10(uint32_t n) noexcept -> uint32_t {
   constexpr auto info = div_small_pow10_infos[N - 1];
   FMT_ASSERT(n <= info.divisor * 10, "n is too large");
   constexpr uint32_t magic_number =
@@ -237,12 +239,12 @@
 }
 
 // Computes floor(n / 10^(kappa + 1)) (float)
-inline uint32_t divide_by_10_to_kappa_plus_1(uint32_t n) noexcept {
+inline auto divide_by_10_to_kappa_plus_1(uint32_t n) noexcept -> uint32_t {
   // 1374389535 = ceil(2^37/100)
   return static_cast<uint32_t>((static_cast<uint64_t>(n) * 1374389535) >> 37);
 }
 // Computes floor(n / 10^(kappa + 1)) (double)
-inline uint64_t divide_by_10_to_kappa_plus_1(uint64_t n) noexcept {
+inline auto divide_by_10_to_kappa_plus_1(uint64_t n) noexcept -> uint64_t {
   // 2361183241434822607 = ceil(2^(64+7)/1000)
   return umul128_upper64(n, 2361183241434822607ull) >> 7;
 }
@@ -254,7 +256,7 @@
   using carrier_uint = float_info<float>::carrier_uint;
   using cache_entry_type = uint64_t;
 
-  static uint64_t get_cached_power(int k) noexcept {
+  static auto get_cached_power(int k) noexcept -> uint64_t {
     FMT_ASSERT(k >= float_info<float>::min_k && k <= float_info<float>::max_k,
                "k is out of range");
     static constexpr const uint64_t pow10_significands[] = {
@@ -296,20 +298,23 @@
     bool is_integer;
   };
 
-  static compute_mul_result compute_mul(
-      carrier_uint u, const cache_entry_type& cache) noexcept {
+  static auto compute_mul(carrier_uint u,
+                          const cache_entry_type& cache) noexcept
+      -> compute_mul_result {
     auto r = umul96_upper64(u, cache);
     return {static_cast<carrier_uint>(r >> 32),
             static_cast<carrier_uint>(r) == 0};
   }
 
-  static uint32_t compute_delta(const cache_entry_type& cache,
-                                int beta) noexcept {
+  static auto compute_delta(const cache_entry_type& cache, int beta) noexcept
+      -> uint32_t {
     return static_cast<uint32_t>(cache >> (64 - 1 - beta));
   }
 
-  static compute_mul_parity_result compute_mul_parity(
-      carrier_uint two_f, const cache_entry_type& cache, int beta) noexcept {
+  static auto compute_mul_parity(carrier_uint two_f,
+                                 const cache_entry_type& cache,
+                                 int beta) noexcept
+      -> compute_mul_parity_result {
     FMT_ASSERT(beta >= 1, "");
     FMT_ASSERT(beta < 64, "");
 
@@ -318,22 +323,22 @@
             static_cast<uint32_t>(r >> (32 - beta)) == 0};
   }
 
-  static carrier_uint compute_left_endpoint_for_shorter_interval_case(
-      const cache_entry_type& cache, int beta) noexcept {
+  static auto compute_left_endpoint_for_shorter_interval_case(
+      const cache_entry_type& cache, int beta) noexcept -> carrier_uint {
     return static_cast<carrier_uint>(
         (cache - (cache >> (num_significand_bits<float>() + 2))) >>
         (64 - num_significand_bits<float>() - 1 - beta));
   }
 
-  static carrier_uint compute_right_endpoint_for_shorter_interval_case(
-      const cache_entry_type& cache, int beta) noexcept {
+  static auto compute_right_endpoint_for_shorter_interval_case(
+      const cache_entry_type& cache, int beta) noexcept -> carrier_uint {
     return static_cast<carrier_uint>(
         (cache + (cache >> (num_significand_bits<float>() + 1))) >>
         (64 - num_significand_bits<float>() - 1 - beta));
   }
 
-  static carrier_uint compute_round_up_for_shorter_interval_case(
-      const cache_entry_type& cache, int beta) noexcept {
+  static auto compute_round_up_for_shorter_interval_case(
+      const cache_entry_type& cache, int beta) noexcept -> carrier_uint {
     return (static_cast<carrier_uint>(
                 cache >> (64 - num_significand_bits<float>() - 2 - beta)) +
             1) /
@@ -345,7 +350,7 @@
   using carrier_uint = float_info<double>::carrier_uint;
   using cache_entry_type = uint128_fallback;
 
-  static uint128_fallback get_cached_power(int k) noexcept {
+  static auto get_cached_power(int k) noexcept -> uint128_fallback {
     FMT_ASSERT(k >= float_info<double>::min_k && k <= float_info<double>::max_k,
                "k is out of range");
 
@@ -984,7 +989,7 @@
       {0xe0accfa875af45a7, 0x93eb1b80a33b8606},
       {0x8c6c01c9498d8b88, 0xbc72f130660533c4},
       {0xaf87023b9bf0ee6a, 0xeb8fad7c7f8680b5},
-      {0xdb68c2ca82ed2a05, 0xa67398db9f6820e2}
+      {0xdb68c2ca82ed2a05, 0xa67398db9f6820e2},
 #else
       {0xff77b1fcbebcdc4f, 0x25e8e89c13bb0f7b},
       {0xce5d73ff402d98e3, 0xfb0a3d212dc81290},
@@ -1069,19 +1074,22 @@
     bool is_integer;
   };
 
-  static compute_mul_result compute_mul(
-      carrier_uint u, const cache_entry_type& cache) noexcept {
+  static auto compute_mul(carrier_uint u,
+                          const cache_entry_type& cache) noexcept
+      -> compute_mul_result {
     auto r = umul192_upper128(u, cache);
     return {r.high(), r.low() == 0};
   }
 
-  static uint32_t compute_delta(cache_entry_type const& cache,
-                                int beta) noexcept {
+  static auto compute_delta(cache_entry_type const& cache, int beta) noexcept
+      -> uint32_t {
     return static_cast<uint32_t>(cache.high() >> (64 - 1 - beta));
   }
 
-  static compute_mul_parity_result compute_mul_parity(
-      carrier_uint two_f, const cache_entry_type& cache, int beta) noexcept {
+  static auto compute_mul_parity(carrier_uint two_f,
+                                 const cache_entry_type& cache,
+                                 int beta) noexcept
+      -> compute_mul_parity_result {
     FMT_ASSERT(beta >= 1, "");
     FMT_ASSERT(beta < 64, "");
 
@@ -1090,35 +1098,35 @@
             ((r.high() << beta) | (r.low() >> (64 - beta))) == 0};
   }
 
-  static carrier_uint compute_left_endpoint_for_shorter_interval_case(
-      const cache_entry_type& cache, int beta) noexcept {
+  static auto compute_left_endpoint_for_shorter_interval_case(
+      const cache_entry_type& cache, int beta) noexcept -> carrier_uint {
     return (cache.high() -
             (cache.high() >> (num_significand_bits<double>() + 2))) >>
            (64 - num_significand_bits<double>() - 1 - beta);
   }
 
-  static carrier_uint compute_right_endpoint_for_shorter_interval_case(
-      const cache_entry_type& cache, int beta) noexcept {
+  static auto compute_right_endpoint_for_shorter_interval_case(
+      const cache_entry_type& cache, int beta) noexcept -> carrier_uint {
     return (cache.high() +
             (cache.high() >> (num_significand_bits<double>() + 1))) >>
            (64 - num_significand_bits<double>() - 1 - beta);
   }
 
-  static carrier_uint compute_round_up_for_shorter_interval_case(
-      const cache_entry_type& cache, int beta) noexcept {
+  static auto compute_round_up_for_shorter_interval_case(
+      const cache_entry_type& cache, int beta) noexcept -> carrier_uint {
     return ((cache.high() >> (64 - num_significand_bits<double>() - 2 - beta)) +
             1) /
            2;
   }
 };
 
-FMT_FUNC uint128_fallback get_cached_power(int k) noexcept {
+FMT_FUNC auto get_cached_power(int k) noexcept -> uint128_fallback {
   return cache_accessor<double>::get_cached_power(k);
 }
 
 // Various integer checks
 template <typename T>
-bool is_left_endpoint_integer_shorter_interval(int exponent) noexcept {
+auto is_left_endpoint_integer_shorter_interval(int exponent) noexcept -> bool {
   const int case_shorter_interval_left_endpoint_lower_threshold = 2;
   const int case_shorter_interval_left_endpoint_upper_threshold = 3;
   return exponent >= case_shorter_interval_left_endpoint_lower_threshold &&
@@ -1232,7 +1240,7 @@
   return ret_value;
 }
 
-template <typename T> decimal_fp<T> to_decimal(T x) noexcept {
+template <typename T> auto to_decimal(T x) noexcept -> decimal_fp<T> {
   // Step 1: integer promotion & Schubfach multiplier calculation.
 
   using carrier_uint = typename float_info<T>::carrier_uint;
@@ -1371,15 +1379,15 @@
     for (auto i = n.bigits_.size(); i > 0; --i) {
       auto value = n.bigits_[i - 1u];
       if (first) {
-        out = format_to(out, FMT_STRING("{:x}"), value);
+        out = fmt::format_to(out, FMT_STRING("{:x}"), value);
         first = false;
         continue;
       }
-      out = format_to(out, FMT_STRING("{:08x}"), value);
+      out = fmt::format_to(out, FMT_STRING("{:08x}"), value);
     }
     if (n.exp_ > 0)
-      out = format_to(out, FMT_STRING("p{}"),
-                      n.exp_ * detail::bigint::bigit_bits);
+      out = fmt::format_to(out, FMT_STRING("p{}"),
+                           n.exp_ * detail::bigint::bigit_bits);
     return out;
   }
 };
@@ -1415,7 +1423,7 @@
   report_error(format_system_error, error_code, message);
 }
 
-FMT_FUNC std::string vformat(string_view fmt, format_args args) {
+FMT_FUNC auto vformat(string_view fmt, format_args args) -> std::string {
   // Don't optimize the "{}" case to keep the binary size small and because it
   // can be better optimized in fmt::format anyway.
   auto buffer = memory_buffer();
@@ -1425,7 +1433,7 @@
 
 namespace detail {
 #if !defined(_WIN32) || defined(FMT_WINDOWS_NO_WCHAR)
-FMT_FUNC bool write_console(int, string_view) { return false; }
+FMT_FUNC auto write_console(int, string_view) -> bool { return false; }
 #else
 using dword = conditional_t<sizeof(long) == 4, unsigned long, unsigned>;
 extern "C" __declspec(dllimport) int __stdcall WriteConsoleW(  //
diff --git a/include/fmt/format.h b/include/fmt/format.h
index c8e1c46..f0ca55c 100644
--- a/include/fmt/format.h
+++ b/include/fmt/format.h
@@ -43,7 +43,7 @@
 #include <system_error>      // std::system_error
 
 #ifdef __cpp_lib_bit_cast
-#  include <bit>  // std::bitcast
+#  include <bit>  // std::bit_cast
 #endif
 
 #include "core.h"
@@ -277,19 +277,6 @@
 #endif
 
 FMT_BEGIN_NAMESPACE
-
-template <typename...> struct disjunction : std::false_type {};
-template <typename P> struct disjunction<P> : P {};
-template <typename P1, typename... Pn>
-struct disjunction<P1, Pn...>
-    : conditional_t<bool(P1::value), P1, disjunction<Pn...>> {};
-
-template <typename...> struct conjunction : std::true_type {};
-template <typename P> struct conjunction<P> : P {};
-template <typename P1, typename... Pn>
-struct conjunction<P1, Pn...>
-    : conditional_t<bool(P1::value), conjunction<Pn...>, P1> {};
-
 namespace detail {
 
 FMT_CONSTEXPR inline void abort_fuzzing_if(bool condition) {
@@ -311,37 +298,6 @@
 constexpr CharT string_literal<CharT, C...>::value[sizeof...(C)];
 #endif
 
-template <typename Streambuf> class formatbuf : public Streambuf {
- private:
-  using char_type = typename Streambuf::char_type;
-  using streamsize = decltype(std::declval<Streambuf>().sputn(nullptr, 0));
-  using int_type = typename Streambuf::int_type;
-  using traits_type = typename Streambuf::traits_type;
-
-  buffer<char_type>& buffer_;
-
- public:
-  explicit formatbuf(buffer<char_type>& buf) : buffer_(buf) {}
-
- protected:
-  // The put area is always empty. This makes the implementation simpler and has
-  // the advantage that the streambuf and the buffer are always in sync and
-  // sputc never writes into uninitialized memory. A disadvantage is that each
-  // call to sputc always results in a (virtual) call to overflow. There is no
-  // disadvantage here for sputn since this always results in a call to xsputn.
-
-  auto overflow(int_type ch) -> int_type override {
-    if (!traits_type::eq_int_type(ch, traits_type::eof()))
-      buffer_.push_back(static_cast<char_type>(ch));
-    return ch;
-  }
-
-  auto xsputn(const char_type* s, streamsize count) -> streamsize override {
-    buffer_.append(s, s + count);
-    return count;
-  }
-};
-
 // Implementation of std::bit_cast for pre-C++20.
 template <typename To, typename From, FMT_ENABLE_IF(sizeof(To) == sizeof(From))>
 FMT_CONSTEXPR20 auto bit_cast(const From& from) -> To {
@@ -377,8 +333,8 @@
   constexpr uint128_fallback(uint64_t hi, uint64_t lo) : lo_(lo), hi_(hi) {}
   constexpr uint128_fallback(uint64_t value = 0) : lo_(value), hi_(0) {}
 
-  constexpr uint64_t high() const noexcept { return hi_; }
-  constexpr uint64_t low() const noexcept { return lo_; }
+  constexpr auto high() const noexcept -> uint64_t { return hi_; }
+  constexpr auto low() const noexcept -> uint64_t { return lo_; }
 
   template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
   constexpr explicit operator T() const {
@@ -454,7 +410,7 @@
     hi_ &= n.hi_;
   }
 
-  FMT_CONSTEXPR20 uint128_fallback& operator+=(uint64_t n) noexcept {
+  FMT_CONSTEXPR20 auto operator+=(uint64_t n) noexcept -> uint128_fallback& {
     if (is_constant_evaluated()) {
       lo_ += n;
       hi_ += (lo_ < n ? 1 : 0);
@@ -744,7 +700,7 @@
 }
 
 // Computes approximate display width of a UTF-8 string.
-FMT_CONSTEXPR inline size_t compute_width(string_view s) {
+FMT_CONSTEXPR inline auto compute_width(string_view s) -> size_t {
   size_t num_code_points = 0;
   // It is not a lambda for compatibility with C++14.
   struct count_code_points {
@@ -791,12 +747,17 @@
 
 // Calculates the index of the nth code point in a UTF-8 string.
 inline auto code_point_index(string_view s, size_t n) -> size_t {
-  const char* data = s.data();
-  size_t num_code_points = 0;
-  for (size_t i = 0, size = s.size(); i != size; ++i) {
-    if ((data[i] & 0xc0) != 0x80 && ++num_code_points > n) return i;
-  }
-  return s.size();
+  size_t result = s.size();
+  const char* begin = s.begin();
+  for_each_codepoint(s, [begin, &n, &result](uint32_t, string_view sv) {
+    if (n != 0) {
+      --n;
+      return true;
+    }
+    result = to_unsigned(sv.begin() - begin);
+    return false;
+  });
+  return result;
 }
 
 inline auto code_point_index(basic_string_view<char8_type> s, size_t n)
@@ -906,7 +867,7 @@
   **Example**::
 
      auto out = fmt::memory_buffer();
-     format_to(std::back_inserter(out), "The answer is {}.", 42);
+     fmt::format_to(std::back_inserter(out), "The answer is {}.", 42);
 
   This will append the following output to the ``out`` object:
 
@@ -1022,7 +983,6 @@
   /** Increases the buffer capacity to *new_capacity*. */
   void reserve(size_t new_capacity) { this->try_reserve(new_capacity); }
 
-  // Directly append data into the buffer
   using detail::buffer<T>::append;
   template <typename ContiguousRange>
   void append(const ContiguousRange& range) {
@@ -1038,7 +998,7 @@
 
 FMT_END_EXPORT
 namespace detail {
-FMT_API bool write_console(int fd, string_view text);
+FMT_API auto write_console(int fd, string_view text) -> bool;
 FMT_API void print(std::FILE*, string_view);
 }  // namespace detail
 
@@ -1157,13 +1117,13 @@
 template <typename T>
 using uint64_or_128_t = conditional_t<num_bits<T>() <= 64, uint64_t, uint128_t>;
 
-#define FMT_POWERS_OF_10(factor)                                             \
-  factor * 10, (factor)*100, (factor)*1000, (factor)*10000, (factor)*100000, \
-      (factor)*1000000, (factor)*10000000, (factor)*100000000,               \
-      (factor)*1000000000
+#define FMT_POWERS_OF_10(factor)                                  \
+  factor * 10, (factor) * 100, (factor) * 1000, (factor) * 10000, \
+      (factor) * 100000, (factor) * 1000000, (factor) * 10000000, \
+      (factor) * 100000000, (factor) * 1000000000
 
 // Converts value in the range [0, 100) to a string.
-constexpr const char* digits2(size_t value) {
+constexpr auto digits2(size_t value) -> const char* {
   // GCC generates slightly better code when value is pointer-size.
   return &"0001020304050607080910111213141516171819"
          "2021222324252627282930313233343536373839"
@@ -1173,7 +1133,7 @@
 }
 
 // Sign is a template parameter to workaround a bug in gcc 4.8.
-template <typename Char, typename Sign> constexpr Char sign(Sign s) {
+template <typename Char, typename Sign> constexpr auto sign(Sign s) -> Char {
 #if !FMT_GCC_VERSION || FMT_GCC_VERSION >= 604
   static_assert(std::is_same<Sign, sign_t>::value, "");
 #endif
@@ -1434,22 +1394,23 @@
                                                       : "invalid utf32"));
   }
   operator string_view() const { return string_view(&buffer_[0], size()); }
-  size_t size() const { return buffer_.size() - 1; }
-  const char* c_str() const { return &buffer_[0]; }
-  std::string str() const { return std::string(&buffer_[0], size()); }
+  auto size() const -> size_t { return buffer_.size() - 1; }
+  auto c_str() const -> const char* { return &buffer_[0]; }
+  auto str() const -> std::string { return std::string(&buffer_[0], size()); }
 
   // Performs conversion returning a bool instead of throwing exception on
   // conversion error. This method may still throw in case of memory allocation
   // error.
-  bool convert(basic_string_view<WChar> s,
-               to_utf8_error_policy policy = to_utf8_error_policy::abort) {
+  auto convert(basic_string_view<WChar> s,
+               to_utf8_error_policy policy = to_utf8_error_policy::abort)
+      -> bool {
     if (!convert(buffer_, s, policy)) return false;
     buffer_.push_back(0);
     return true;
   }
-  static bool convert(
-      Buffer& buf, basic_string_view<WChar> s,
-      to_utf8_error_policy policy = to_utf8_error_policy::abort) {
+  static auto convert(Buffer& buf, basic_string_view<WChar> s,
+                      to_utf8_error_policy policy = to_utf8_error_policy::abort)
+      -> bool {
     for (auto p = s.begin(); p != s.end(); ++p) {
       uint32_t c = static_cast<uint32_t>(*p);
       if (sizeof(WChar) == 2 && c >= 0xd800 && c <= 0xdfff) {
@@ -1485,7 +1446,7 @@
 };
 
 // Computes 128-bit result of multiplication of two 64-bit unsigned integers.
-inline uint128_fallback umul128(uint64_t x, uint64_t y) noexcept {
+inline auto umul128(uint64_t x, uint64_t y) noexcept -> uint128_fallback {
 #if FMT_USE_INT128
   auto p = static_cast<uint128_opt>(x) * static_cast<uint128_opt>(y);
   return {static_cast<uint64_t>(p >> 64), static_cast<uint64_t>(p)};
@@ -1516,19 +1477,19 @@
 namespace dragonbox {
 // Computes floor(log10(pow(2, e))) for e in [-2620, 2620] using the method from
 // https://fmt.dev/papers/Dragonbox.pdf#page=28, section 6.1.
-inline int floor_log10_pow2(int e) noexcept {
+inline auto floor_log10_pow2(int e) noexcept -> int {
   FMT_ASSERT(e <= 2620 && e >= -2620, "too large exponent");
   static_assert((-1 >> 1) == -1, "right shift is not arithmetic");
   return (e * 315653) >> 20;
 }
 
-inline int floor_log2_pow10(int e) noexcept {
+inline auto floor_log2_pow10(int e) noexcept -> int {
   FMT_ASSERT(e <= 1233 && e >= -1233, "too large exponent");
   return (e * 1741647) >> 19;
 }
 
 // Computes upper 64 bits of multiplication of two 64-bit unsigned integers.
-inline uint64_t umul128_upper64(uint64_t x, uint64_t y) noexcept {
+inline auto umul128_upper64(uint64_t x, uint64_t y) noexcept -> uint64_t {
 #if FMT_USE_INT128
   auto p = static_cast<uint128_opt>(x) * static_cast<uint128_opt>(y);
   return static_cast<uint64_t>(p >> 64);
@@ -1541,14 +1502,14 @@
 
 // Computes upper 128 bits of multiplication of a 64-bit unsigned integer and a
 // 128-bit unsigned integer.
-inline uint128_fallback umul192_upper128(uint64_t x,
-                                         uint128_fallback y) noexcept {
+inline auto umul192_upper128(uint64_t x, uint128_fallback y) noexcept
+    -> uint128_fallback {
   uint128_fallback r = umul128(x, y.high());
   r += umul128_upper64(x, y.low());
   return r;
 }
 
-FMT_API uint128_fallback get_cached_power(int k) noexcept;
+FMT_API auto get_cached_power(int k) noexcept -> uint128_fallback;
 
 // Type-specific information that Dragonbox uses.
 template <typename T, typename Enable = void> struct float_info;
@@ -1602,14 +1563,14 @@
 }  // namespace dragonbox
 
 // Returns true iff Float has the implicit bit which is not stored.
-template <typename Float> constexpr bool has_implicit_bit() {
+template <typename Float> constexpr auto has_implicit_bit() -> bool {
   // An 80-bit FP number has a 64-bit significand an no implicit bit.
   return std::numeric_limits<Float>::digits != 64;
 }
 
 // Returns the number of significand bits stored in Float. The implicit bit is
 // not counted since it is not stored.
-template <typename Float> constexpr int num_significand_bits() {
+template <typename Float> constexpr auto num_significand_bits() -> int {
   // std::numeric_limits may not support __float128.
   return is_float128<Float>() ? 112
                               : (std::numeric_limits<Float>::digits -
@@ -1702,7 +1663,7 @@
 
 // Normalizes the value converted from double and multiplied by (1 << SHIFT).
 template <int SHIFT = 0, typename F>
-FMT_CONSTEXPR basic_fp<F> normalize(basic_fp<F> value) {
+FMT_CONSTEXPR auto normalize(basic_fp<F> value) -> basic_fp<F> {
   // Handle subnormals.
   const auto implicit_bit = F(1) << num_significand_bits<double>();
   const auto shifted_implicit_bit = implicit_bit << SHIFT;
@@ -1719,7 +1680,7 @@
 }
 
 // Computes lhs * rhs / pow(2, 64) rounded to nearest with half-up tie breaking.
-FMT_CONSTEXPR inline uint64_t multiply(uint64_t lhs, uint64_t rhs) {
+FMT_CONSTEXPR inline auto multiply(uint64_t lhs, uint64_t rhs) -> uint64_t {
 #if FMT_USE_INT128
   auto product = static_cast<__uint128_t>(lhs) * rhs;
   auto f = static_cast<uint64_t>(product >> 64);
@@ -1736,7 +1697,7 @@
 #endif
 }
 
-FMT_CONSTEXPR inline fp operator*(fp x, fp y) {
+FMT_CONSTEXPR inline auto operator*(fp x, fp y) -> fp {
   return {multiply(x.f, y.f), x.e + y.e + 64};
 }
 
@@ -1962,8 +1923,9 @@
   *out++ = static_cast<Char>('\'');
   if ((needs_escape(static_cast<uint32_t>(v)) && v != static_cast<Char>('"')) ||
       v == static_cast<Char>('\'')) {
-    out = write_escaped_cp(
-        out, find_escape_result<Char>{v_array, v_array + 1, static_cast<uint32_t>(v)});
+    out = write_escaped_cp(out,
+                           find_escape_result<Char>{v_array, v_array + 1,
+                                                    static_cast<uint32_t>(v)});
   } else {
     *out++ = v;
   }
@@ -2052,10 +2014,10 @@
     std::string::const_iterator group;
     int pos;
   };
-  next_state initial_state() const { return {grouping_.begin(), 0}; }
+  auto initial_state() const -> next_state { return {grouping_.begin(), 0}; }
 
   // Returns the next digit group separator position.
-  int next(next_state& state) const {
+  auto next(next_state& state) const -> int {
     if (thousands_sep_.empty()) return max_value<int>();
     if (state.group == grouping_.end()) return state.pos += grouping_.back();
     if (*state.group <= 0 || *state.group == max_value<char>())
@@ -2074,9 +2036,9 @@
   digit_grouping(std::string grouping, std::basic_string<Char> sep)
       : grouping_(std::move(grouping)), thousands_sep_(std::move(sep)) {}
 
-  bool has_separator() const { return !thousands_sep_.empty(); }
+  auto has_separator() const -> bool { return !thousands_sep_.empty(); }
 
-  int count_separators(int num_digits) const {
+  auto count_separators(int num_digits) const -> int {
     int count = 0;
     auto state = initial_state();
     while (num_digits > next(state)) ++count;
@@ -2085,7 +2047,7 @@
 
   // Applies grouping to digits and write the output to out.
   template <typename Out, typename C>
-  Out apply(Out out, basic_string_view<C> digits) const {
+  auto apply(Out out, basic_string_view<C> digits) const -> Out {
     auto num_digits = static_cast<int>(digits.size());
     auto separators = basic_memory_buffer<int>();
     separators.push_back(0);
@@ -2108,24 +2070,66 @@
   }
 };
 
+FMT_CONSTEXPR inline void prefix_append(unsigned& prefix, unsigned value) {
+  prefix |= prefix != 0 ? value << 8 : value;
+  prefix += (1u + (value > 0xff ? 1 : 0)) << 24;
+}
+
 // Writes a decimal integer with digit grouping.
 template <typename OutputIt, typename UInt, typename Char>
 auto write_int(OutputIt out, UInt value, unsigned prefix,
                const format_specs<Char>& specs,
                const digit_grouping<Char>& grouping) -> OutputIt {
   static_assert(std::is_same<uint64_or_128_t<UInt>, UInt>::value, "");
-  int num_digits = count_digits(value);
-  char digits[40];
-  format_decimal(digits, value, num_digits);
-  unsigned size = to_unsigned((prefix != 0 ? 1 : 0) + num_digits +
-                              grouping.count_separators(num_digits));
+  int num_digits = 0;
+  auto buffer = memory_buffer();
+  switch (specs.type) {
+  case presentation_type::none:
+  case presentation_type::dec: {
+    num_digits = count_digits(value);
+    format_decimal<char>(appender(buffer), value, num_digits);
+    break;
+  }
+  case presentation_type::hex_lower:
+  case presentation_type::hex_upper: {
+    bool upper = specs.type == presentation_type::hex_upper;
+    if (specs.alt)
+      prefix_append(prefix, unsigned(upper ? 'X' : 'x') << 8 | '0');
+    num_digits = count_digits<4>(value);
+    format_uint<4, char>(appender(buffer), value, num_digits, upper);
+    break;
+  }
+  case presentation_type::bin_lower:
+  case presentation_type::bin_upper: {
+    bool upper = specs.type == presentation_type::bin_upper;
+    if (specs.alt)
+      prefix_append(prefix, unsigned(upper ? 'B' : 'b') << 8 | '0');
+    num_digits = count_digits<1>(value);
+    format_uint<1, char>(appender(buffer), value, num_digits);
+    break;
+  }
+  case presentation_type::oct: {
+    num_digits = count_digits<3>(value);
+    // Octal prefix '0' is counted as a digit, so only add it if precision
+    // is not greater than the number of digits.
+    if (specs.alt && specs.precision <= num_digits && value != 0)
+      prefix_append(prefix, '0');
+    format_uint<3, char>(appender(buffer), value, num_digits);
+    break;
+  }
+  case presentation_type::chr:
+    return write_char(out, static_cast<Char>(value), specs);
+  default:
+    throw_format_error("invalid format specifier");
+  }
+
+  unsigned size = (prefix != 0 ? prefix >> 24 : 0) + to_unsigned(num_digits) +
+                  to_unsigned(grouping.count_separators(num_digits));
   return write_padded<align::right>(
       out, specs, size, size, [&](reserve_iterator<OutputIt> it) {
-        if (prefix != 0) {
-          char sign = static_cast<char>(prefix);
-          *it++ = static_cast<Char>(sign);
-        }
-        return grouping.apply(it, string_view(digits, to_unsigned(num_digits)));
+        for (unsigned p = prefix & 0xffffff; p != 0; p >>= 8)
+          *it++ = static_cast<Char>(p & 0xff);
+        return grouping.apply(it, string_view(buffer.data(), buffer.size()));
       });
 }
 
@@ -2138,11 +2142,6 @@
   return false;
 }
 
-FMT_CONSTEXPR inline void prefix_append(unsigned& prefix, unsigned value) {
-  prefix |= prefix != 0 ? value << 8 : value;
-  prefix += (1u + (value > 0xff ? 1 : 0)) << 24;
-}
-
 template <typename UInt> struct write_int_arg {
   UInt abs_value;
   unsigned prefix;
@@ -2289,25 +2288,25 @@
 
   FMT_CONSTEXPR counting_iterator() : count_(0) {}
 
-  FMT_CONSTEXPR size_t count() const { return count_; }
+  FMT_CONSTEXPR auto count() const -> size_t { return count_; }
 
-  FMT_CONSTEXPR counting_iterator& operator++() {
+  FMT_CONSTEXPR auto operator++() -> counting_iterator& {
     ++count_;
     return *this;
   }
-  FMT_CONSTEXPR counting_iterator operator++(int) {
+  FMT_CONSTEXPR auto operator++(int) -> counting_iterator {
     auto it = *this;
     ++*this;
     return it;
   }
 
-  FMT_CONSTEXPR friend counting_iterator operator+(counting_iterator it,
-                                                   difference_type n) {
+  FMT_CONSTEXPR friend auto operator+(counting_iterator it, difference_type n)
+      -> counting_iterator {
     it.count_ += static_cast<size_t>(n);
     return it;
   }
 
-  FMT_CONSTEXPR value_type operator*() const { return {}; }
+  FMT_CONSTEXPR auto operator*() const -> value_type { return {}; }
 };
 
 template <typename Char, typename OutputIt>
@@ -2342,9 +2341,10 @@
 FMT_CONSTEXPR auto write(OutputIt out, const Char* s,
                          const format_specs<Char>& specs, locale_ref)
     -> OutputIt {
-  return specs.type != presentation_type::pointer
-             ? write(out, basic_string_view<Char>(s), specs, {})
-             : write_ptr<Char>(out, bit_cast<uintptr_t>(s), &specs);
+  if (specs.type == presentation_type::pointer)
+    return write_ptr<Char>(out, bit_cast<uintptr_t>(s), &specs);
+  if (!s) throw_format_error("string pointer is null");
+  return write(out, basic_string_view<Char>(s), specs, {});
 }
 
 template <typename Char, typename OutputIt, typename T,
@@ -2430,9 +2430,8 @@
   bool showpoint : 1;
 };
 
-template <typename ErrorHandler = error_handler, typename Char>
-FMT_CONSTEXPR auto parse_float_type_spec(const format_specs<Char>& specs,
-                                         ErrorHandler&& eh = {})
+template <typename Char>
+FMT_CONSTEXPR auto parse_float_type_spec(const format_specs<Char>& specs)
     -> float_specs {
   auto result = float_specs();
   result.showpoint = specs.alt;
@@ -2468,7 +2467,7 @@
     result.format = float_format::hex;
     break;
   default:
-    eh.on_error("invalid format specifier");
+    throw_format_error("invalid format specifier");
     break;
   }
   return result;
@@ -2707,12 +2706,12 @@
  public:
   constexpr fallback_digit_grouping(locale_ref, bool) {}
 
-  constexpr bool has_separator() const { return false; }
+  constexpr auto has_separator() const -> bool { return false; }
 
-  constexpr int count_separators(int) const { return 0; }
+  constexpr auto count_separators(int) const -> int { return 0; }
 
   template <typename Out, typename C>
-  constexpr Out apply(Out out, basic_string_view<C>) const {
+  constexpr auto apply(Out out, basic_string_view<C>) const -> Out {
     return out;
   }
 };
@@ -2731,7 +2730,7 @@
   }
 }
 
-template <typename T> constexpr bool isnan(T value) {
+template <typename T> constexpr auto isnan(T value) -> bool {
   return !(value >= value);  // std::isnan doesn't support __float128.
 }
 
@@ -2744,14 +2743,14 @@
 
 template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value&&
                                         has_isfinite<T>::value)>
-FMT_CONSTEXPR20 bool isfinite(T value) {
+FMT_CONSTEXPR20 auto isfinite(T value) -> bool {
   constexpr T inf = T(std::numeric_limits<double>::infinity());
   if (is_constant_evaluated(true))
     return !detail::isnan(value) && value < inf && value > -inf;
   return std::isfinite(value);
 }
 template <typename T, FMT_ENABLE_IF(!has_isfinite<T>::value)>
-FMT_CONSTEXPR bool isfinite(T value) {
+FMT_CONSTEXPR auto isfinite(T value) -> bool {
   T inf = T(std::numeric_limits<double>::infinity());
   // std::isfinite doesn't support __float128.
   return !detail::isnan(value) && value < inf && value > -inf;
@@ -2788,10 +2787,10 @@
   basic_memory_buffer<bigit, bigits_capacity> bigits_;
   int exp_;
 
-  FMT_CONSTEXPR20 bigit operator[](int index) const {
+  FMT_CONSTEXPR20 auto operator[](int index) const -> bigit {
     return bigits_[to_unsigned(index)];
   }
-  FMT_CONSTEXPR20 bigit& operator[](int index) {
+  FMT_CONSTEXPR20 auto operator[](int index) -> bigit& {
     return bigits_[to_unsigned(index)];
   }
 
@@ -2887,11 +2886,11 @@
     assign(uint64_or_128_t<Int>(n));
   }
 
-  FMT_CONSTEXPR20 int num_bigits() const {
+  FMT_CONSTEXPR20 auto num_bigits() const -> int {
     return static_cast<int>(bigits_.size()) + exp_;
   }
 
-  FMT_NOINLINE FMT_CONSTEXPR20 bigint& operator<<=(int shift) {
+  FMT_NOINLINE FMT_CONSTEXPR20 auto operator<<=(int shift) -> bigint& {
     FMT_ASSERT(shift >= 0, "");
     exp_ += shift / bigit_bits;
     shift %= bigit_bits;
@@ -2906,13 +2905,15 @@
     return *this;
   }
 
-  template <typename Int> FMT_CONSTEXPR20 bigint& operator*=(Int value) {
+  template <typename Int>
+  FMT_CONSTEXPR20 auto operator*=(Int value) -> bigint& {
     FMT_ASSERT(value > 0, "");
     multiply(uint32_or_64_or_128_t<Int>(value));
     return *this;
   }
 
-  friend FMT_CONSTEXPR20 int compare(const bigint& lhs, const bigint& rhs) {
+  friend FMT_CONSTEXPR20 auto compare(const bigint& lhs, const bigint& rhs)
+      -> int {
     int num_lhs_bigits = lhs.num_bigits(), num_rhs_bigits = rhs.num_bigits();
     if (num_lhs_bigits != num_rhs_bigits)
       return num_lhs_bigits > num_rhs_bigits ? 1 : -1;
@@ -2929,8 +2930,9 @@
   }
 
   // Returns compare(lhs1 + lhs2, rhs).
-  friend FMT_CONSTEXPR20 int add_compare(const bigint& lhs1, const bigint& lhs2,
-                                         const bigint& rhs) {
+  friend FMT_CONSTEXPR20 auto add_compare(const bigint& lhs1,
+                                          const bigint& lhs2, const bigint& rhs)
+      -> int {
     auto minimum = [](int a, int b) { return a < b ? a : b; };
     auto maximum = [](int a, int b) { return a > b ? a : b; };
     int max_lhs_bigits = maximum(lhs1.num_bigits(), lhs2.num_bigits());
@@ -3017,7 +3019,7 @@
 
   // Divides this bignum by divisor, assigning the remainder to this and
   // returning the quotient.
-  FMT_CONSTEXPR20 int divmod_assign(const bigint& divisor) {
+  FMT_CONSTEXPR20 auto divmod_assign(const bigint& divisor) -> int {
     FMT_ASSERT(this != &divisor, "");
     if (compare(*this, divisor) < 0) return 0;
     FMT_ASSERT(divisor.bigits_[divisor.bigits_.size() - 1u] != 0, "");
@@ -3260,7 +3262,7 @@
   format_hexfloat(static_cast<double>(value), precision, specs, buf);
 }
 
-constexpr uint32_t fractional_part_rounding_thresholds(int index) {
+constexpr auto fractional_part_rounding_thresholds(int index) -> uint32_t {
   // For checking rounding thresholds.
   // The kth entry is chosen to be the smallest integer such that the
   // upper 32-bits of 10^(k+1) times it is strictly bigger than 5 * 10^k.
@@ -3799,62 +3801,39 @@
   }
 };
 
-template <typename Char> struct custom_formatter {
-  basic_format_parse_context<Char>& parse_ctx;
-  buffer_context<Char>& ctx;
-
-  void operator()(
-      typename basic_format_arg<buffer_context<Char>>::handle h) const {
-    h.format(parse_ctx, ctx);
-  }
-  template <typename T> void operator()(T) const {}
-};
-
-template <typename ErrorHandler> class width_checker {
- public:
-  explicit FMT_CONSTEXPR width_checker(ErrorHandler& eh) : handler_(eh) {}
-
+struct width_checker {
   template <typename T, FMT_ENABLE_IF(is_integer<T>::value)>
   FMT_CONSTEXPR auto operator()(T value) -> unsigned long long {
-    if (is_negative(value)) handler_.on_error("negative width");
+    if (is_negative(value)) throw_format_error("negative width");
     return static_cast<unsigned long long>(value);
   }
 
   template <typename T, FMT_ENABLE_IF(!is_integer<T>::value)>
   FMT_CONSTEXPR auto operator()(T) -> unsigned long long {
-    handler_.on_error("width is not integer");
+    throw_format_error("width is not integer");
     return 0;
   }
-
- private:
-  ErrorHandler& handler_;
 };
 
-template <typename ErrorHandler> class precision_checker {
- public:
-  explicit FMT_CONSTEXPR precision_checker(ErrorHandler& eh) : handler_(eh) {}
-
+struct precision_checker {
   template <typename T, FMT_ENABLE_IF(is_integer<T>::value)>
   FMT_CONSTEXPR auto operator()(T value) -> unsigned long long {
-    if (is_negative(value)) handler_.on_error("negative precision");
+    if (is_negative(value)) throw_format_error("negative precision");
     return static_cast<unsigned long long>(value);
   }
 
   template <typename T, FMT_ENABLE_IF(!is_integer<T>::value)>
   FMT_CONSTEXPR auto operator()(T) -> unsigned long long {
-    handler_.on_error("precision is not integer");
+    throw_format_error("precision is not integer");
     return 0;
   }
-
- private:
-  ErrorHandler& handler_;
 };
 
-template <template <typename> class Handler, typename FormatArg,
-          typename ErrorHandler>
-FMT_CONSTEXPR auto get_dynamic_spec(FormatArg arg, ErrorHandler eh) -> int {
-  unsigned long long value = visit_format_arg(Handler<ErrorHandler>(eh), arg);
-  if (value > to_unsigned(max_value<int>())) eh.on_error("number is too big");
+template <typename Handler, typename FormatArg>
+FMT_CONSTEXPR auto get_dynamic_spec(FormatArg arg) -> int {
+  unsigned long long value = visit_format_arg(Handler(), arg);
+  if (value > to_unsigned(max_value<int>()))
+    throw_format_error("number is too big");
   return static_cast<int>(value);
 }
 
@@ -3865,7 +3844,7 @@
   return arg;
 }
 
-template <template <typename> class Handler, typename Context>
+template <typename Handler, typename Context>
 FMT_CONSTEXPR void handle_dynamic_spec(int& value,
                                        arg_ref<typename Context::char_type> ref,
                                        Context& ctx) {
@@ -3873,12 +3852,10 @@
   case arg_id_kind::none:
     break;
   case arg_id_kind::index:
-    value = detail::get_dynamic_spec<Handler>(get_arg(ctx, ref.val.index),
-                                              ctx.error_handler());
+    value = detail::get_dynamic_spec<Handler>(get_arg(ctx, ref.val.index));
     break;
   case arg_id_kind::name:
-    value = detail::get_dynamic_spec<Handler>(get_arg(ctx, ref.val.name),
-                                              ctx.error_handler());
+    value = detail::get_dynamic_spec<Handler>(get_arg(ctx, ref.val.name));
     break;
   }
 }
@@ -4050,12 +4027,10 @@
 
 template <typename T, typename Char>
 struct formatter<T, Char, enable_if_t<detail::has_format_as<T>::value>>
-    : private formatter<detail::format_as_t<T>, Char> {
-  using base = formatter<detail::format_as_t<T>, Char>;
-  using base::parse;
-
+    : formatter<detail::format_as_t<T>, Char> {
   template <typename FormatContext>
   auto format(const T& value, FormatContext& ctx) const -> decltype(ctx.out()) {
+    using base = formatter<detail::format_as_t<T>, Char>;
     return base::format(format_as(value), ctx);
   }
 };
@@ -4380,7 +4355,7 @@
   auto out = buffer_appender<Char>(buf);
   if (fmt.size() == 2 && equal2(fmt.data(), "{}")) {
     auto arg = args.get(0);
-    if (!arg) error_handler().on_error("argument not found");
+    if (!arg) throw_format_error("argument not found");
     visit_format_arg(default_arg_formatter<Char>{out, args, loc}, arg);
     return;
   }
@@ -4407,7 +4382,7 @@
     }
     FMT_CONSTEXPR auto on_arg_id(basic_string_view<Char> id) -> int {
       int arg_id = context.arg_id(id);
-      if (arg_id < 0) on_error("argument not found");
+      if (arg_id < 0) throw_format_error("argument not found");
       return arg_id;
     }
 
@@ -4422,11 +4397,9 @@
     auto on_format_specs(int id, const Char* begin, const Char* end)
         -> const Char* {
       auto arg = get_arg(context, id);
-      if (arg.type() == type::custom_type) {
-        parse_context.advance_to(begin);
-        visit_format_arg(custom_formatter<Char>{parse_context, context}, arg);
+      // Not using a visitor for custom types gives better codegen.
+      if (arg.format_custom(begin, parse_context, context))
         return parse_context.begin();
-      }
       auto specs = detail::dynamic_format_specs<Char>();
       begin = parse_format_specs(begin, end, specs, parse_context, arg.type());
       detail::handle_dynamic_spec<detail::width_checker>(
@@ -4434,7 +4407,7 @@
       detail::handle_dynamic_spec<detail::precision_checker>(
           specs.precision, specs.precision_ref, context);
       if (begin == end || *begin != '}')
-        on_error("missing '}' in format string");
+        throw_format_error("missing '}' in format string");
       auto f = arg_formatter<Char>{context.out(), specs, context.locale()};
       context.advance_to(visit_format_arg(f, arg));
       return begin;
@@ -4537,16 +4510,16 @@
                       detail::type::custom_type>>::format(const T& val,
                                                           FormatContext& ctx)
     const -> decltype(ctx.out()) {
-  if (specs_.width_ref.kind != detail::arg_id_kind::none ||
-      specs_.precision_ref.kind != detail::arg_id_kind::none) {
-    auto specs = specs_;
-    detail::handle_dynamic_spec<detail::width_checker>(specs.width,
-                                                       specs.width_ref, ctx);
-    detail::handle_dynamic_spec<detail::precision_checker>(
-        specs.precision, specs.precision_ref, ctx);
-    return detail::write<Char>(ctx.out(), val, specs, ctx.locale());
+  if (specs_.width_ref.kind == detail::arg_id_kind::none &&
+      specs_.precision_ref.kind == detail::arg_id_kind::none) {
+    return detail::write<Char>(ctx.out(), val, specs_, ctx.locale());
   }
-  return detail::write<Char>(ctx.out(), val, specs_, ctx.locale());
+  auto specs = specs_;
+  detail::handle_dynamic_spec<detail::width_checker>(specs.width,
+                                                     specs.width_ref, ctx);
+  detail::handle_dynamic_spec<detail::precision_checker>(
+      specs.precision, specs.precision_ref, ctx);
+  return detail::write<Char>(ctx.out(), val, specs, ctx.locale());
 }
 
 FMT_END_NAMESPACE
diff --git a/include/fmt/os.h b/include/fmt/os.h
index 2b517cd..3c7b3cc 100644
--- a/include/fmt/os.h
+++ b/include/fmt/os.h
@@ -48,6 +48,7 @@
 
 // Calls to system functions are wrapped in FMT_SYSTEM for testability.
 #ifdef FMT_SYSTEM
+#  define FMT_HAS_SYSTEM
 #  define FMT_POSIX_CALL(call) FMT_SYSTEM(call)
 #else
 #  define FMT_SYSTEM(call) ::call
@@ -116,7 +117,7 @@
   basic_cstring_view(const std::basic_string<Char>& s) : data_(s.c_str()) {}
 
   /** Returns the pointer to a C string. */
-  const Char* c_str() const { return data_; }
+  auto c_str() const -> const Char* { return data_; }
 };
 
 using cstring_view = basic_cstring_view<char>;
@@ -171,7 +172,7 @@
 // Can be used to report errors from destructors.
 FMT_API void report_windows_error(int error_code, const char* message) noexcept;
 #else
-inline const std::error_category& system_category() noexcept {
+inline auto system_category() noexcept -> const std::error_category& {
   return std::system_category();
 }
 #endif  // _WIN32
@@ -208,7 +209,7 @@
     other.file_ = nullptr;
   }
 
-  buffered_file& operator=(buffered_file&& other) {
+  auto operator=(buffered_file&& other) -> buffered_file& {
     close();
     file_ = other.file_;
     other.file_ = nullptr;
@@ -222,9 +223,9 @@
   FMT_API void close();
 
   // Returns the pointer to a FILE object representing this file.
-  FILE* get() const noexcept { return file_; }
+  auto get() const noexcept -> FILE* { return file_; }
 
-  FMT_API int descriptor() const;
+  FMT_API auto descriptor() const -> int;
 
   void vprint(string_view format_str, format_args args) {
     fmt::vprint(file_, format_str, args);
@@ -274,7 +275,7 @@
   file(file&& other) noexcept : fd_(other.fd_) { other.fd_ = -1; }
 
   // Move assignment is not noexcept because close may throw.
-  file& operator=(file&& other) {
+  auto operator=(file&& other) -> file& {
     close();
     fd_ = other.fd_;
     other.fd_ = -1;
@@ -285,24 +286,24 @@
   ~file() noexcept;
 
   // Returns the file descriptor.
-  int descriptor() const noexcept { return fd_; }
+  auto descriptor() const noexcept -> int { return fd_; }
 
   // Closes the file.
   void close();
 
   // Returns the file size. The size has signed type for consistency with
   // stat::st_size.
-  long long size() const;
+  auto size() const -> long long;
 
   // Attempts to read count bytes from the file into the specified buffer.
-  size_t read(void* buffer, size_t count);
+  auto read(void* buffer, size_t count) -> size_t;
 
   // Attempts to write count bytes from the specified buffer to the file.
-  size_t write(const void* buffer, size_t count);
+  auto write(const void* buffer, size_t count) -> size_t;
 
   // Duplicates a file descriptor with the dup function and returns
   // the duplicate as a file object.
-  static file dup(int fd);
+  static auto dup(int fd) -> file;
 
   // Makes fd be the copy of this file descriptor, closing fd first if
   // necessary.
@@ -314,11 +315,12 @@
 
   // Creates a pipe setting up read_end and write_end file objects for reading
   // and writing respectively.
+  // DEPRECATED! Taking files as out parameters is deprecated.
   static void pipe(file& read_end, file& write_end);
 
   // Creates a buffered_file object associated with this file and detaches
   // this file object from the file.
-  buffered_file fdopen(const char* mode);
+  auto fdopen(const char* mode) -> buffered_file;
 
 #  if defined(_WIN32) && !defined(__MINGW32__)
   // Opens a file and constructs a file object representing this file by
@@ -328,14 +330,14 @@
 };
 
 // Returns the memory page size.
-long getpagesize();
+auto getpagesize() -> long;
 
 namespace detail {
 
 struct buffer_size {
   buffer_size() = default;
   size_t value = 0;
-  buffer_size operator=(size_t val) const {
+  auto operator=(size_t val) const -> buffer_size {
     auto bs = buffer_size();
     bs.value = val;
     return bs;
@@ -412,7 +414,7 @@
   void flush() { buffer_.flush(); }
 
   template <typename... T>
-  friend ostream output_file(cstring_view path, T... params);
+  friend auto output_file(cstring_view path, T... params) -> ostream;
 
   void close() { buffer_.close(); }
 
@@ -442,7 +444,7 @@
   \endrst
  */
 template <typename... T>
-inline ostream output_file(cstring_view path, T... params) {
+inline auto output_file(cstring_view path, T... params) -> ostream {
   return {path, detail::ostream_params(params...)};
 }
 #endif  // FMT_USE_FCNTL
diff --git a/include/fmt/ostream.h b/include/fmt/ostream.h
index 782ace5..26fb3b5 100644
--- a/include/fmt/ostream.h
+++ b/include/fmt/ostream.h
@@ -21,9 +21,39 @@
 #include "format.h"
 
 FMT_BEGIN_NAMESPACE
-
 namespace detail {
 
+template <typename Streambuf> class formatbuf : public Streambuf {
+ private:
+  using char_type = typename Streambuf::char_type;
+  using streamsize = decltype(std::declval<Streambuf>().sputn(nullptr, 0));
+  using int_type = typename Streambuf::int_type;
+  using traits_type = typename Streambuf::traits_type;
+
+  buffer<char_type>& buffer_;
+
+ public:
+  explicit formatbuf(buffer<char_type>& buf) : buffer_(buf) {}
+
+ protected:
+  // The put area is always empty. This makes the implementation simpler and has
+  // the advantage that the streambuf and the buffer are always in sync and
+  // sputc never writes into uninitialized memory. A disadvantage is that each
+  // call to sputc always results in a (virtual) call to overflow. There is no
+  // disadvantage here for sputn since this always results in a call to xsputn.
+
+  auto overflow(int_type ch) -> int_type override {
+    if (!traits_type::eq_int_type(ch, traits_type::eof()))
+      buffer_.push_back(static_cast<char_type>(ch));
+    return ch;
+  }
+
+  auto xsputn(const char_type* s, streamsize count) -> streamsize override {
+    buffer_.append(s, s + count);
+    return count;
+  }
+};
+
 // Generate a unique explicit instantion in every translation unit using a tag
 // type in an anonymous namespace.
 namespace {
@@ -40,7 +70,8 @@
 auto get_file(std::filebuf&) -> FILE*;
 #endif
 
-inline bool write_ostream_unicode(std::ostream& os, fmt::string_view data) {
+inline auto write_ostream_unicode(std::ostream& os, fmt::string_view data)
+    -> bool {
   FILE* f = nullptr;
 #if FMT_MSC_VERSION
   if (auto* buf = dynamic_cast<std::filebuf*>(os.rdbuf()))
@@ -69,8 +100,8 @@
 #endif
   return false;
 }
-inline bool write_ostream_unicode(std::wostream&,
-                                  fmt::basic_string_view<wchar_t>) {
+inline auto write_ostream_unicode(std::wostream&,
+                                  fmt::basic_string_view<wchar_t>) -> bool {
   return false;
 }
 
@@ -91,12 +122,11 @@
 }
 
 template <typename Char, typename T>
-void format_value(buffer<Char>& buf, const T& value,
-                  locale_ref loc = locale_ref()) {
+void format_value(buffer<Char>& buf, const T& value) {
   auto&& format_buf = formatbuf<std::basic_streambuf<Char>>(buf);
   auto&& output = std::basic_ostream<Char>(&format_buf);
 #if !defined(FMT_STATIC_THOUSANDS_SEPARATOR)
-  if (loc) output.imbue(loc.get<std::locale>());
+  output.imbue(std::locale::classic());  // The default is always unlocalized.
 #endif
   output << value;
   output.exceptions(std::ios_base::failbit | std::ios_base::badbit);
@@ -117,7 +147,7 @@
   auto format(const T& value, basic_format_context<OutputIt, Char>& ctx) const
       -> OutputIt {
     auto buffer = basic_memory_buffer<Char>();
-    detail::format_value(buffer, value, ctx.locale());
+    detail::format_value(buffer, value);
     return formatter<basic_string_view<Char>, Char>::format(
         {buffer.data(), buffer.size()}, ctx);
   }
diff --git a/include/fmt/ranges.h b/include/fmt/ranges.h
index c0b51ae..3638fff 100644
--- a/include/fmt/ranges.h
+++ b/include/fmt/ranges.h
@@ -183,7 +183,7 @@
 template <typename T, T... N> struct integer_sequence {
   using value_type = T;
 
-  static FMT_CONSTEXPR size_t size() { return sizeof...(N); }
+  static FMT_CONSTEXPR auto size() -> size_t { return sizeof...(N); }
 };
 
 template <size_t... N> using index_sequence = integer_sequence<size_t, N...>;
@@ -207,15 +207,15 @@
 };
 template <typename T, typename C> class is_tuple_formattable_<T, C, true> {
   template <std::size_t... Is>
-  static std::true_type check2(index_sequence<Is...>,
-                               integer_sequence<bool, (Is == Is)...>);
-  static std::false_type check2(...);
+  static auto check2(index_sequence<Is...>,
+                     integer_sequence<bool, (Is == Is)...>) -> std::true_type;
+  static auto check2(...) -> std::false_type;
   template <std::size_t... Is>
-  static decltype(check2(
+  static auto check(index_sequence<Is...>) -> decltype(check2(
       index_sequence<Is...>{},
-      integer_sequence<
-          bool, (is_formattable<typename std::tuple_element<Is, T>::type,
-                                C>::value)...>{})) check(index_sequence<Is...>);
+      integer_sequence<bool,
+                       (is_formattable<typename std::tuple_element<Is, T>::type,
+                                       C>::value)...>{}));
 
  public:
   static constexpr const bool value =
@@ -417,6 +417,12 @@
 #endif
 }  // namespace detail
 
+template <typename...> struct conjunction : std::true_type {};
+template <typename P> struct conjunction<P> : P {};
+template <typename P1, typename... Pn>
+struct conjunction<P1, Pn...>
+    : conditional_t<bool(P1::value), conjunction<Pn...>, P1> {};
+
 template <typename T, typename Char, typename Enable = void>
 struct range_formatter;
 
@@ -482,7 +488,8 @@
     for (; it != end; ++it) {
       if (i > 0) out = detail::copy_str<Char>(separator_, out);
       ctx.advance_to(out);
-      out = underlying_.format(mapper.map(*it), ctx);
+      auto&& item = *it;
+      out = underlying_.format(mapper.map(item), ctx);
       ++i;
     }
     out = detail::copy_str<Char>(closing_bracket_, out);
diff --git a/include/fmt/std.h b/include/fmt/std.h
index 4d1f97d..7cff115 100644
--- a/include/fmt/std.h
+++ b/include/fmt/std.h
@@ -38,6 +38,10 @@
 #  endif
 #endif
 
+#if FMT_CPLUSPLUS > 201703L && FMT_HAS_INCLUDE(<source_location>)
+#  include <source_location>
+#endif
+
 // GCC 4 does not support FMT_HAS_INCLUDE.
 #if FMT_HAS_INCLUDE(<cxxabi.h>) || defined(__GLIBCXX__)
 #  include <cxxabi.h>
@@ -59,13 +63,31 @@
 #  endif
 #endif
 
-#ifdef __cpp_lib_filesystem
+// For older Xcode versions, __cpp_lib_xxx flags are inaccurately defined.
+#ifndef FMT_CPP_LIB_FILESYSTEM
+#  ifdef __cpp_lib_filesystem
+#    define FMT_CPP_LIB_FILESYSTEM __cpp_lib_filesystem
+#  else
+#    define FMT_CPP_LIB_FILESYSTEM 0
+#  endif
+#endif
+
+#ifndef FMT_CPP_LIB_VARIANT
+#  ifdef __cpp_lib_variant
+#    define FMT_CPP_LIB_VARIANT __cpp_lib_variant
+#  else
+#    define FMT_CPP_LIB_VARIANT 0
+#  endif
+#endif
+
+#if FMT_CPP_LIB_FILESYSTEM
 FMT_BEGIN_NAMESPACE
 
 namespace detail {
 
-template <typename Char, typename PathChar> auto get_path_string(
-  const std::filesystem::path& p, const std::basic_string<PathChar>& native) {
+template <typename Char, typename PathChar>
+auto get_path_string(const std::filesystem::path& p,
+                     const std::basic_string<PathChar>& native) {
   if constexpr (std::is_same_v<Char, char> && std::is_same_v<PathChar, wchar_t>)
     return to_utf8<wchar_t>(native, to_utf8_error_policy::replace);
   else
@@ -76,7 +98,8 @@
 void write_escaped_path(basic_memory_buffer<Char>& quoted,
                         const std::filesystem::path& p,
                         const std::basic_string<PathChar>& native) {
-  if constexpr (std::is_same_v<Char, char> && std::is_same_v<PathChar, wchar_t>) {
+  if constexpr (std::is_same_v<Char, char> &&
+                std::is_same_v<PathChar, wchar_t>) {
     auto buf = basic_memory_buffer<wchar_t>();
     write_escaped_string<wchar_t>(std::back_inserter(buf), native);
     bool valid = to_utf8<wchar_t>::convert(quoted, {buf.data(), buf.size()});
@@ -97,6 +120,7 @@
   format_specs<Char> specs_;
   detail::arg_ref<Char> width_ref_;
   bool debug_ = false;
+  char path_type_ = 0;
 
  public:
   FMT_CONSTEXPR void set_debug_format(bool set = true) { debug_ = set; }
@@ -113,27 +137,34 @@
       debug_ = true;
       ++it;
     }
+    if (it != end && (*it == 'g')) path_type_ = *it++;
     return it;
   }
 
   template <typename FormatContext>
   auto format(const std::filesystem::path& p, FormatContext& ctx) const {
     auto specs = specs_;
+#  ifdef _WIN32
+    auto path_string = !path_type_ ? p.native() : p.generic_wstring();
+#  else
+    auto path_string = !path_type_ ? p.native() : p.generic_string();
+#  endif
+
     detail::handle_dynamic_spec<detail::width_checker>(specs.width, width_ref_,
                                                        ctx);
     if (!debug_) {
-      auto s = detail::get_path_string<Char>(p, p.native());
+      auto s = detail::get_path_string<Char>(p, path_string);
       return detail::write(ctx.out(), basic_string_view<Char>(s), specs);
     }
     auto quoted = basic_memory_buffer<Char>();
-    detail::write_escaped_path(quoted, p, p.native());
+    detail::write_escaped_path(quoted, p, path_string);
     return detail::write(ctx.out(),
                          basic_string_view<Char>(quoted.data(), quoted.size()),
                          specs);
   }
 };
 FMT_END_NAMESPACE
-#endif
+#endif  // FMT_CPP_LIB_FILESYSTEM
 
 FMT_BEGIN_NAMESPACE
 FMT_EXPORT
@@ -145,7 +176,7 @@
     const std::bitset<N>& bs;
 
     template <typename OutputIt>
-    FMT_CONSTEXPR OutputIt operator()(OutputIt out) {
+    FMT_CONSTEXPR auto operator()(OutputIt out) -> OutputIt {
       for (auto pos = N; pos > 0; --pos) {
         out = detail::write<Char>(out, bs[pos - 1] ? Char('1') : Char('0'));
       }
@@ -197,7 +228,7 @@
   }
 
   template <typename FormatContext>
-  auto format(std::optional<T> const& opt, FormatContext& ctx) const
+  auto format(const std::optional<T>& opt, FormatContext& ctx) const
       -> decltype(ctx.out()) {
     if (!opt) return detail::write<Char>(ctx.out(), none);
 
@@ -211,7 +242,32 @@
 FMT_END_NAMESPACE
 #endif  // __cpp_lib_optional
 
-#ifdef __cpp_lib_variant
+#ifdef __cpp_lib_source_location
+FMT_BEGIN_NAMESPACE
+FMT_EXPORT
+template <> struct formatter<std::source_location> {
+  template <typename ParseContext> FMT_CONSTEXPR auto parse(ParseContext& ctx) {
+    return ctx.begin();
+  }
+
+  template <typename FormatContext>
+  auto format(const std::source_location& loc, FormatContext& ctx) const
+      -> decltype(ctx.out()) {
+    auto out = ctx.out();
+    out = detail::write(out, loc.file_name());
+    out = detail::write(out, ':');
+    out = detail::write<char>(out, loc.line());
+    out = detail::write(out, ':');
+    out = detail::write<char>(out, loc.column());
+    out = detail::write(out, ": ");
+    out = detail::write(out, loc.function_name());
+    return out;
+  }
+};
+FMT_END_NAMESPACE
+#endif
+
+#if FMT_CPP_LIB_VARIANT
 FMT_BEGIN_NAMESPACE
 namespace detail {
 
@@ -302,7 +358,7 @@
   }
 };
 FMT_END_NAMESPACE
-#endif  // __cpp_lib_variant
+#endif  // FMT_CPP_LIB_VARIANT
 
 FMT_BEGIN_NAMESPACE
 FMT_EXPORT
@@ -326,7 +382,7 @@
 FMT_EXPORT
 template <typename T, typename Char>
 struct formatter<
-    T, Char,
+    T, Char,  // DEPRECATED! Mixing code unit types.
     typename std::enable_if<std::is_base_of<std::exception, T>::value>::type> {
  private:
   bool with_typename_ = false;
@@ -357,7 +413,7 @@
 #  ifdef FMT_HAS_ABI_CXA_DEMANGLE
     int status = 0;
     std::size_t size = 0;
-    std::unique_ptr<char, decltype(&std::free)> demangled_name_ptr(
+    std::unique_ptr<char, void (*)(void*)> demangled_name_ptr(
         abi::__cxa_demangle(ti.name(), nullptr, &size, &status), &std::free);
 
     string_view demangled_name_view;
diff --git a/include/fmt/xchar.h b/include/fmt/xchar.h
index 625ec36..f609c5c 100644
--- a/include/fmt/xchar.h
+++ b/include/fmt/xchar.h
@@ -63,14 +63,15 @@
 template <> struct is_char<char32_t> : std::true_type {};
 
 template <typename... T>
-constexpr format_arg_store<wformat_context, T...> make_wformat_args(
-    const T&... args) {
+constexpr auto make_wformat_args(const T&... args)
+    -> format_arg_store<wformat_context, T...> {
   return {args...};
 }
 
 inline namespace literals {
 #if FMT_USE_USER_DEFINED_LITERALS && !FMT_USE_NONTYPE_TEMPLATE_ARGS
-constexpr detail::udl_arg<wchar_t> operator"" _a(const wchar_t* s, size_t) {
+constexpr auto operator""_a(const wchar_t* s, size_t)
+    -> detail::udl_arg<wchar_t> {
   return {s};
 }
 #endif
@@ -172,11 +173,11 @@
   return detail::get_iterator(buf, out);
 }
 
-template <
-    typename OutputIt, typename Locale, typename S, typename... T,
-    typename Char = char_t<S>,
-    bool enable = detail::is_output_iterator<OutputIt, Char>::value&&
-        detail::is_locale<Locale>::value&& detail::is_exotic_char<Char>::value>
+template <typename OutputIt, typename Locale, typename S, typename... T,
+          typename Char = char_t<S>,
+          bool enable = detail::is_output_iterator<OutputIt, Char>::value &&
+                        detail::is_locale<Locale>::value &&
+                        detail::is_exotic_char<Char>::value>
 inline auto format_to(OutputIt out, const Locale& loc, const S& format_str,
                       T&&... args) ->
     typename std::enable_if<enable, OutputIt>::type {
diff --git a/src/fmt.cc b/src/fmt.cc
index 6638bb4..5330463 100644
--- a/src/fmt.cc
+++ b/src/fmt.cc
@@ -101,7 +101,7 @@
 
 // gcc doesn't yet implement private module fragments
 #if !FMT_GCC_VERSION
-module : private;
+module :private;
 #endif
 
 #include "format.cc"
diff --git a/src/os.cc b/src/os.cc
index bca410e..a639e78 100644
--- a/src/os.cc
+++ b/src/os.cc
@@ -18,8 +18,8 @@
 #  include <sys/stat.h>
 #  include <sys/types.h>
 
-#  ifdef _WRS_KERNEL   // VxWorks7 kernel
-#    include <ioLib.h> // getpagesize
+#  ifdef _WRS_KERNEL    // VxWorks7 kernel
+#    include <ioLib.h>  // getpagesize
 #  endif
 
 #  ifndef _WIN32
@@ -182,10 +182,14 @@
 }
 
 int buffered_file::descriptor() const {
-#ifdef fileno  // fileno is a macro on OpenBSD so we cannot use FMT_POSIX_CALL.
-  int fd = fileno(file_);
-#else
+#if !defined(fileno)
   int fd = FMT_POSIX_CALL(fileno(file_));
+#elif defined(FMT_HAS_SYSTEM)
+  // fileno is a macro on OpenBSD so we cannot use FMT_POSIX_CALL.
+#  define FMT_DISABLE_MACRO
+  int fd = FMT_SYSTEM(fileno FMT_DISABLE_MACRO(file_));
+#else
+  int fd = fileno(file_);
 #endif
   if (fd == -1)
     FMT_THROW(system_error(errno, FMT_STRING("cannot get file descriptor")));
diff --git a/support/AndroidManifest.xml b/support/AndroidManifest.xml
index b5281fe..c282ef5 100644
--- a/support/AndroidManifest.xml
+++ b/support/AndroidManifest.xml
@@ -1 +1 @@
-<manifest package="net.fmtlib" />
+<manifest package="dev.fmt" />
diff --git a/support/manage.py b/support/manage.py
index 36f61d9..cfb4979 100755
--- a/support/manage.py
+++ b/support/manage.py
@@ -12,7 +12,7 @@
 
 from __future__ import print_function
 import datetime, docopt, errno, fileinput, json, os
-import re, requests, shutil, sys, tempfile
+import re, requests, shutil, sys
 from contextlib import contextmanager
 from distutils.version import LooseVersion
 from subprocess import check_call
@@ -229,12 +229,50 @@
     if not fmt_repo.update('-b', branch, fmt_repo_url):
         clean_checkout(fmt_repo, branch)
 
-    # Convert changelog from RST to GitHub-flavored Markdown and get the
-    # version.
+    # Update the date in the changelog and extract the version and the first
+    # section content.
     changelog = 'ChangeLog.md'
     changelog_path = os.path.join(fmt_repo.dir, changelog)
-    import rst2md
-    changes, version = rst2md.convert(changelog_path)
+    is_first_section = True
+    first_section = []
+    for i, line in enumerate(fileinput.input(changelog_path, inplace=True)):
+        if i == 0:
+            version = re.match(r'# (.*) - TBD', line).group(1)
+            line = '# {} - {}\n'.format(
+                version, datetime.date.today().isoformat())
+        elif not is_first_section:
+            pass
+        elif line.startswith('#'):
+            is_first_section = False
+        else:
+            first_section.append(line)
+        sys.stdout.write(line)
+    if first_section[0] == '\n':
+        first_section.pop(0)
+
+    changes = ''
+    code_block = False
+    stripped = False
+    for line in first_section:
+        if re.match(r'^\s*```', line):
+            code_block = not code_block
+            changes += line
+            stripped = False
+            continue
+        if code_block:
+            changes += line
+            continue
+        if line == '\n':
+            changes += line
+            if stripped:
+                changes += line
+                stripped = False
+            continue
+        if stripped:
+            line = ' ' + line.lstrip()
+        changes += line.rstrip()
+        stripped = True
+
     cmakelists = 'CMakeLists.txt'
     for line in fileinput.input(os.path.join(fmt_repo.dir, cmakelists),
                                 inplace=True):
@@ -243,23 +281,11 @@
             line = prefix + version + ')\n'
         sys.stdout.write(line)
 
-    # Update the version in the changelog.
-    title_len = 0
-    for line in fileinput.input(changelog_path, inplace=True):
-        if line.startswith(version + ' - TBD'):
-            line = version + ' - ' + datetime.date.today().isoformat()
-            title_len = len(line)
-            line += '\n'
-        elif title_len:
-            line = '-' * title_len + '\n'
-            title_len = 0
-        sys.stdout.write(line)
-
     # Add the version to the build script.
     script = os.path.join('doc', 'build.py')
     script_path = os.path.join(fmt_repo.dir, script)
     for line in fileinput.input(script_path, inplace=True):
-      m = re.match(r'( *versions = )\[(.+)\]', line)
+      m = re.match(r'( *versions \+= )\[(.+)\]', line)
       if m:
         line = '{}[{}, \'{}\']\n'.format(m.group(1), m.group(2), version)
       sys.stdout.write(line)
diff --git a/test/chrono-test.cc b/test/chrono-test.cc
index 0776068..b2d03f9 100644
--- a/test/chrono-test.cc
+++ b/test/chrono-test.cc
@@ -17,6 +17,9 @@
 using fmt::runtime;
 using testing::Contains;
 
+template <typename Duration>
+using sys_time = std::chrono::time_point<std::chrono::system_clock, Duration>;
+
 #if defined(__MINGW32__) && !defined(_UCRT)
 // Only C89 conversion specifiers when using MSVCRT instead of UCRT
 #  define FMT_HAS_C99_STRFTIME 0
@@ -24,6 +27,12 @@
 #  define FMT_HAS_C99_STRFTIME 1
 #endif
 
+#if defined(__cpp_lib_chrono) && __cpp_lib_chrono >= 201907L
+using days = std::chrono::days;
+#else
+using days = std::chrono::duration<std::chrono::hours::rep, std::ratio<86400>>;
+#endif
+
 auto make_tm() -> std::tm {
   auto time = std::tm();
   time.tm_mday = 1;
@@ -260,9 +269,8 @@
   EXPECT_EQ(strftime_full_utc(t1), fmt::format("{:%Y-%m-%d %H:%M:%S}", t1));
   EXPECT_EQ(strftime_full_utc(t1), fmt::format("{}", t1));
   EXPECT_EQ(strftime_full_utc(t1), fmt::format("{:}", t1));
-  using time_point =
-      std::chrono::time_point<std::chrono::system_clock, std::chrono::seconds>;
-  auto t2 = time_point(std::chrono::seconds(42));
+
+  auto t2 = sys_time<std::chrono::seconds>(std::chrono::seconds(42));
   EXPECT_EQ(strftime_full_utc(t2), fmt::format("{:%Y-%m-%d %H:%M:%S}", t2));
 
   std::vector<std::string> spec_list = {
@@ -331,14 +339,14 @@
     auto t = std::chrono::system_clock::to_time_t(t1);
     auto tm = *std::gmtime(&t);
 
-    EXPECT_EQ("+0000", fmt::format("{:%z}", t1));
-    EXPECT_EQ("+0000", fmt::format("{:%z}", tm));
+    EXPECT_EQ(fmt::format("{:%z}", t1), "+0000");
+    EXPECT_EQ(fmt::format("{:%z}", tm), "+0000");
 
-    EXPECT_EQ("+00:00", fmt::format("{:%Ez}", t1));
-    EXPECT_EQ("+00:00", fmt::format("{:%Ez}", tm));
+    EXPECT_EQ(fmt::format("{:%Ez}", t1), "+00:00");
+    EXPECT_EQ(fmt::format("{:%Ez}", tm), "+00:00");
 
-    EXPECT_EQ("+00:00", fmt::format("{:%Oz}", t1));
-    EXPECT_EQ("+00:00", fmt::format("{:%Oz}", tm));
+    EXPECT_EQ(fmt::format("{:%Oz}", t1), "+00:00");
+    EXPECT_EQ(fmt::format("{:%Oz}", tm), "+00:00");
   }
 }
 
@@ -423,122 +431,122 @@
 #ifndef FMT_STATIC_THOUSANDS_SEPARATOR
 
 TEST(chrono_test, format_default) {
-  EXPECT_EQ("42s", fmt::format("{}", std::chrono::seconds(42)));
-  EXPECT_EQ("42as",
-            fmt::format("{}", std::chrono::duration<int, std::atto>(42)));
-  EXPECT_EQ("42fs",
-            fmt::format("{}", std::chrono::duration<int, std::femto>(42)));
-  EXPECT_EQ("42ps",
-            fmt::format("{}", std::chrono::duration<int, std::pico>(42)));
-  EXPECT_EQ("42ns", fmt::format("{}", std::chrono::nanoseconds(42)));
-  EXPECT_EQ("42µs", fmt::format("{}", std::chrono::microseconds(42)));
-  EXPECT_EQ("42ms", fmt::format("{}", std::chrono::milliseconds(42)));
-  EXPECT_EQ("42cs",
-            fmt::format("{}", std::chrono::duration<int, std::centi>(42)));
-  EXPECT_EQ("42ds",
-            fmt::format("{}", std::chrono::duration<int, std::deci>(42)));
-  EXPECT_EQ("42s", fmt::format("{}", std::chrono::seconds(42)));
-  EXPECT_EQ("42das",
-            fmt::format("{}", std::chrono::duration<int, std::deca>(42)));
-  EXPECT_EQ("42hs",
-            fmt::format("{}", std::chrono::duration<int, std::hecto>(42)));
-  EXPECT_EQ("42ks",
-            fmt::format("{}", std::chrono::duration<int, std::kilo>(42)));
-  EXPECT_EQ("42Ms",
-            fmt::format("{}", std::chrono::duration<int, std::mega>(42)));
-  EXPECT_EQ("42Gs",
-            fmt::format("{}", std::chrono::duration<int, std::giga>(42)));
-  EXPECT_EQ("42Ts",
-            fmt::format("{}", std::chrono::duration<int, std::tera>(42)));
-  EXPECT_EQ("42Ps",
-            fmt::format("{}", std::chrono::duration<int, std::peta>(42)));
-  EXPECT_EQ("42Es",
-            fmt::format("{}", std::chrono::duration<int, std::exa>(42)));
-  EXPECT_EQ("42min", fmt::format("{}", std::chrono::minutes(42)));
-  EXPECT_EQ("42h", fmt::format("{}", std::chrono::hours(42)));
-#  if defined(__cpp_lib_chrono) && __cpp_lib_chrono >= 201907L
-  EXPECT_EQ("42d", fmt::format("{}", std::chrono::days(42)));
-#  endif
+  EXPECT_EQ(fmt::format("{}", std::chrono::seconds(42)), "42s");
+  EXPECT_EQ(fmt::format("{}", std::chrono::duration<int, std::atto>(42)),
+            "42as");
+  EXPECT_EQ(fmt::format("{}", std::chrono::duration<int, std::femto>(42)),
+            "42fs");
+  EXPECT_EQ(fmt::format("{}", std::chrono::duration<int, std::pico>(42)),
+            "42ps");
+  EXPECT_EQ(fmt::format("{}", std::chrono::nanoseconds(42)), "42ns");
+  EXPECT_EQ(fmt::format("{}", std::chrono::microseconds(42)), "42µs");
+  EXPECT_EQ(fmt::format("{}", std::chrono::milliseconds(42)), "42ms");
+  EXPECT_EQ(fmt::format("{}", std::chrono::duration<int, std::centi>(42)),
+            "42cs");
+  EXPECT_EQ(fmt::format("{}", std::chrono::duration<int, std::deci>(42)),
+            "42ds");
+  EXPECT_EQ(fmt::format("{}", std::chrono::seconds(42)), "42s");
+  EXPECT_EQ(fmt::format("{}", std::chrono::duration<int, std::deca>(42)),
+            "42das");
+  EXPECT_EQ(fmt::format("{}", std::chrono::duration<int, std::hecto>(42)),
+            "42hs");
+  EXPECT_EQ(fmt::format("{}", std::chrono::duration<int, std::kilo>(42)),
+            "42ks");
+  EXPECT_EQ(fmt::format("{}", std::chrono::duration<int, std::mega>(42)),
+            "42Ms");
+  EXPECT_EQ(fmt::format("{}", std::chrono::duration<int, std::giga>(42)),
+            "42Gs");
+  EXPECT_EQ(fmt::format("{}", std::chrono::duration<int, std::tera>(42)),
+            "42Ts");
+  EXPECT_EQ(fmt::format("{}", std::chrono::duration<int, std::peta>(42)),
+            "42Ps");
+  EXPECT_EQ(fmt::format("{}", std::chrono::duration<int, std::exa>(42)),
+            "42Es");
+  EXPECT_EQ(fmt::format("{}", std::chrono::minutes(42)), "42min");
+  EXPECT_EQ(fmt::format("{}", std::chrono::hours(42)), "42h");
+  EXPECT_EQ(fmt::format("{}", days(42)), "42d");
   EXPECT_EQ(
-      "42[15]s",
-      fmt::format("{}", std::chrono::duration<int, std::ratio<15, 1>>(42)));
+      fmt::format("{}", std::chrono::duration<int, std::ratio<15, 1>>(42)),
+      "42[15]s");
   EXPECT_EQ(
-      "42[15/4]s",
-      fmt::format("{}", std::chrono::duration<int, std::ratio<15, 4>>(42)));
+      fmt::format("{}", std::chrono::duration<int, std::ratio<15, 4>>(42)),
+      "42[15/4]s");
 }
 
 TEST(chrono_test, duration_align) {
   auto s = std::chrono::seconds(42);
-  EXPECT_EQ("42s  ", fmt::format("{:5}", s));
-  EXPECT_EQ("42s  ", fmt::format("{:{}}", s, 5));
-  EXPECT_EQ("  42s", fmt::format("{:>5}", s));
-  EXPECT_EQ("**42s**", fmt::format("{:*^7}", s));
-  EXPECT_EQ("03:25:45    ",
-            fmt::format("{:12%H:%M:%S}", std::chrono::seconds(12345)));
-  EXPECT_EQ("    03:25:45",
-            fmt::format("{:>12%H:%M:%S}", std::chrono::seconds(12345)));
-  EXPECT_EQ("~~03:25:45~~",
-            fmt::format("{:~^12%H:%M:%S}", std::chrono::seconds(12345)));
-  EXPECT_EQ("03:25:45    ",
-            fmt::format("{:{}%H:%M:%S}", std::chrono::seconds(12345), 12));
+  EXPECT_EQ(fmt::format("{:5}", s), "42s  ");
+  EXPECT_EQ(fmt::format("{:{}}", s, 5), "42s  ");
+  EXPECT_EQ(fmt::format("{:>5}", s), "  42s");
+  EXPECT_EQ(fmt::format("{:*^7}", s), "**42s**");
+  EXPECT_EQ(fmt::format("{:12%H:%M:%S}", std::chrono::seconds(12345)),
+            "03:25:45    ");
+  EXPECT_EQ(fmt::format("{:>12%H:%M:%S}", std::chrono::seconds(12345)),
+            "    03:25:45");
+  EXPECT_EQ(fmt::format("{:~^12%H:%M:%S}", std::chrono::seconds(12345)),
+            "~~03:25:45~~");
+  EXPECT_EQ(fmt::format("{:{}%H:%M:%S}", std::chrono::seconds(12345), 12),
+            "03:25:45    ");
 }
 
 TEST(chrono_test, tm_align) {
   auto t = make_tm(1975, 12, 29, 12, 14, 16);
-  EXPECT_EQ("1975-12-29 12:14:16", fmt::format("{:%F %T}", t));
-  EXPECT_EQ("1975-12-29 12:14:16           ", fmt::format("{:30%F %T}", t));
-  EXPECT_EQ("1975-12-29 12:14:16           ", fmt::format("{:{}%F %T}", t, 30));
-  EXPECT_EQ("1975-12-29 12:14:16           ", fmt::format("{:<30%F %T}", t));
-  EXPECT_EQ("     1975-12-29 12:14:16      ", fmt::format("{:^30%F %T}", t));
-  EXPECT_EQ("           1975-12-29 12:14:16", fmt::format("{:>30%F %T}", t));
+  EXPECT_EQ(fmt::format("{:%F %T}", t), "1975-12-29 12:14:16");
+  EXPECT_EQ(fmt::format("{:30%F %T}", t), "1975-12-29 12:14:16           ");
+  EXPECT_EQ(fmt::format("{:{}%F %T}", t, 30), "1975-12-29 12:14:16           ");
+  EXPECT_EQ(fmt::format("{:<30%F %T}", t), "1975-12-29 12:14:16           ");
+  EXPECT_EQ(fmt::format("{:^30%F %T}", t), "     1975-12-29 12:14:16      ");
+  EXPECT_EQ(fmt::format("{:>30%F %T}", t), "           1975-12-29 12:14:16");
 
-  EXPECT_EQ("1975-12-29 12:14:16***********", fmt::format("{:*<30%F %T}", t));
-  EXPECT_EQ("*****1975-12-29 12:14:16******", fmt::format("{:*^30%F %T}", t));
-  EXPECT_EQ("***********1975-12-29 12:14:16", fmt::format("{:*>30%F %T}", t));
+  EXPECT_EQ(fmt::format("{:*<30%F %T}", t), "1975-12-29 12:14:16***********");
+  EXPECT_EQ(fmt::format("{:*^30%F %T}", t), "*****1975-12-29 12:14:16******");
+  EXPECT_EQ(fmt::format("{:*>30%F %T}", t), "***********1975-12-29 12:14:16");
 }
 
 TEST(chrono_test, tp_align) {
   auto tp = std::chrono::time_point_cast<std::chrono::microseconds>(
       std::chrono::system_clock::from_time_t(0));
-  EXPECT_EQ("00:00.000000", fmt::format("{:%M:%S}", tp));
-  EXPECT_EQ("00:00.000000   ", fmt::format("{:15%M:%S}", tp));
-  EXPECT_EQ("00:00.000000   ", fmt::format("{:{}%M:%S}", tp, 15));
-  EXPECT_EQ("00:00.000000   ", fmt::format("{:<15%M:%S}", tp));
-  EXPECT_EQ(" 00:00.000000  ", fmt::format("{:^15%M:%S}", tp));
-  EXPECT_EQ("   00:00.000000", fmt::format("{:>15%M:%S}", tp));
+  EXPECT_EQ(fmt::format("{:%M:%S}", tp), "00:00.000000");
+  EXPECT_EQ(fmt::format("{:15%M:%S}", tp), "00:00.000000   ");
+  EXPECT_EQ(fmt::format("{:{}%M:%S}", tp, 15), "00:00.000000   ");
+  EXPECT_EQ(fmt::format("{:<15%M:%S}", tp), "00:00.000000   ");
+  EXPECT_EQ(fmt::format("{:^15%M:%S}", tp), " 00:00.000000  ");
+  EXPECT_EQ(fmt::format("{:>15%M:%S}", tp), "   00:00.000000");
 
-  EXPECT_EQ("00:00.000000***", fmt::format("{:*<15%M:%S}", tp));
-  EXPECT_EQ("*00:00.000000**", fmt::format("{:*^15%M:%S}", tp));
-  EXPECT_EQ("***00:00.000000", fmt::format("{:*>15%M:%S}", tp));
+  EXPECT_EQ(fmt::format("{:*<15%M:%S}", tp), "00:00.000000***");
+  EXPECT_EQ(fmt::format("{:*^15%M:%S}", tp), "*00:00.000000**");
+  EXPECT_EQ(fmt::format("{:*>15%M:%S}", tp), "***00:00.000000");
 }
 
 TEST(chrono_test, format_specs) {
-  EXPECT_EQ("%", fmt::format("{:%%}", std::chrono::seconds(0)));
-  EXPECT_EQ("\n", fmt::format("{:%n}", std::chrono::seconds(0)));
-  EXPECT_EQ("\t", fmt::format("{:%t}", std::chrono::seconds(0)));
-  EXPECT_EQ("00", fmt::format("{:%S}", std::chrono::seconds(0)));
-  EXPECT_EQ("00", fmt::format("{:%S}", std::chrono::seconds(60)));
-  EXPECT_EQ("42", fmt::format("{:%S}", std::chrono::seconds(42)));
-  EXPECT_EQ("01.234", fmt::format("{:%S}", std::chrono::milliseconds(1234)));
-  EXPECT_EQ("00", fmt::format("{:%M}", std::chrono::minutes(0)));
-  EXPECT_EQ("00", fmt::format("{:%M}", std::chrono::minutes(60)));
-  EXPECT_EQ("42", fmt::format("{:%M}", std::chrono::minutes(42)));
-  EXPECT_EQ("01", fmt::format("{:%M}", std::chrono::seconds(61)));
-  EXPECT_EQ("00", fmt::format("{:%H}", std::chrono::hours(0)));
-  EXPECT_EQ("00", fmt::format("{:%H}", std::chrono::hours(24)));
-  EXPECT_EQ("14", fmt::format("{:%H}", std::chrono::hours(14)));
-  EXPECT_EQ("01", fmt::format("{:%H}", std::chrono::minutes(61)));
-  EXPECT_EQ("12", fmt::format("{:%I}", std::chrono::hours(0)));
-  EXPECT_EQ("12", fmt::format("{:%I}", std::chrono::hours(12)));
-  EXPECT_EQ("12", fmt::format("{:%I}", std::chrono::hours(24)));
-  EXPECT_EQ("04", fmt::format("{:%I}", std::chrono::hours(4)));
-  EXPECT_EQ("02", fmt::format("{:%I}", std::chrono::hours(14)));
-  EXPECT_EQ("03:25:45",
-            fmt::format("{:%H:%M:%S}", std::chrono::seconds(12345)));
-  EXPECT_EQ("03:25", fmt::format("{:%R}", std::chrono::seconds(12345)));
-  EXPECT_EQ("03:25:45", fmt::format("{:%T}", std::chrono::seconds(12345)));
-  EXPECT_EQ("12345", fmt::format("{:%Q}", std::chrono::seconds(12345)));
-  EXPECT_EQ("s", fmt::format("{:%q}", std::chrono::seconds(12345)));
+  EXPECT_EQ(fmt::format("{:%%}", std::chrono::seconds(0)), "%");
+  EXPECT_EQ(fmt::format("{:%n}", std::chrono::seconds(0)), "\n");
+  EXPECT_EQ(fmt::format("{:%t}", std::chrono::seconds(0)), "\t");
+  EXPECT_EQ(fmt::format("{:%S}", std::chrono::seconds(0)), "00");
+  EXPECT_EQ(fmt::format("{:%S}", std::chrono::seconds(60)), "00");
+  EXPECT_EQ(fmt::format("{:%S}", std::chrono::seconds(42)), "42");
+  EXPECT_EQ(fmt::format("{:%S}", std::chrono::milliseconds(1234)), "01.234");
+  EXPECT_EQ(fmt::format("{:%M}", std::chrono::minutes(0)), "00");
+  EXPECT_EQ(fmt::format("{:%M}", std::chrono::minutes(60)), "00");
+  EXPECT_EQ(fmt::format("{:%M}", std::chrono::minutes(42)), "42");
+  EXPECT_EQ(fmt::format("{:%M}", std::chrono::seconds(61)), "01");
+  EXPECT_EQ(fmt::format("{:%H}", std::chrono::hours(0)), "00");
+  EXPECT_EQ(fmt::format("{:%H}", std::chrono::hours(24)), "00");
+  EXPECT_EQ(fmt::format("{:%H}", std::chrono::hours(14)), "14");
+  EXPECT_EQ(fmt::format("{:%H}", std::chrono::minutes(61)), "01");
+  EXPECT_EQ(fmt::format("{:%I}", std::chrono::hours(0)), "12");
+  EXPECT_EQ(fmt::format("{:%I}", std::chrono::hours(12)), "12");
+  EXPECT_EQ(fmt::format("{:%I}", std::chrono::hours(24)), "12");
+  EXPECT_EQ(fmt::format("{:%I}", std::chrono::hours(4)), "04");
+  EXPECT_EQ(fmt::format("{:%I}", std::chrono::hours(14)), "02");
+  EXPECT_EQ(fmt::format("{:%j}", days(12345)), "12345");
+  EXPECT_EQ(fmt::format("{:%j}", std::chrono::hours(12345 * 24 + 12)), "12345");
+  EXPECT_EQ(fmt::format("{:%H:%M:%S}", std::chrono::seconds(12345)),
+            "03:25:45");
+  EXPECT_EQ(fmt::format("{:%R}", std::chrono::seconds(12345)), "03:25");
+  EXPECT_EQ(fmt::format("{:%T}", std::chrono::seconds(12345)), "03:25:45");
+  EXPECT_EQ(fmt::format("{:%Q}", std::chrono::seconds(12345)), "12345");
+  EXPECT_EQ(fmt::format("{:%q}", std::chrono::seconds(12345)), "s");
 }
 
 TEST(chrono_test, invalid_specs) {
@@ -619,78 +627,77 @@
 using dms = std::chrono::duration<double, std::milli>;
 
 TEST(chrono_test, format_default_fp) {
-  typedef std::chrono::duration<float> fs;
-  EXPECT_EQ("1.234s", fmt::format("{}", fs(1.234)));
-  typedef std::chrono::duration<float, std::milli> fms;
-  EXPECT_EQ("1.234ms", fmt::format("{}", fms(1.234)));
-  typedef std::chrono::duration<double> ds;
-  EXPECT_EQ("1.234s", fmt::format("{}", ds(1.234)));
-  EXPECT_EQ("1.234ms", fmt::format("{}", dms(1.234)));
+  EXPECT_EQ(fmt::format("{}", std::chrono::duration<float>(1.234)), "1.234s");
+  EXPECT_EQ(fmt::format("{}", std::chrono::duration<float, std::milli>(1.234)),
+            "1.234ms");
+  EXPECT_EQ(fmt::format("{}", std::chrono::duration<double>(1.234)), "1.234s");
+  EXPECT_EQ(fmt::format("{}", dms(1.234)), "1.234ms");
 }
 
 TEST(chrono_test, format_precision) {
   EXPECT_THROW_MSG(
       (void)fmt::format(runtime("{:.2%Q}"), std::chrono::seconds(42)),
       fmt::format_error, "precision not allowed for this argument type");
-  EXPECT_EQ("1ms", fmt::format("{:.0}", dms(1.234)));
-  EXPECT_EQ("1.2ms", fmt::format("{:.1}", dms(1.234)));
-  EXPECT_EQ("1.23ms", fmt::format("{:.{}}", dms(1.234), 2));
+  EXPECT_EQ(fmt::format("{:.0}", dms(1.234)), "1ms");
+  EXPECT_EQ(fmt::format("{:.1}", dms(1.234)), "1.2ms");
+  EXPECT_EQ(fmt::format("{:.{}}", dms(1.234), 2), "1.23ms");
 
-  EXPECT_EQ("13ms", fmt::format("{:.0}", dms(12.56)));
-  EXPECT_EQ("12.6ms", fmt::format("{:.1}", dms(12.56)));
-  EXPECT_EQ("12.56ms", fmt::format("{:.2}", dms(12.56)));
+  EXPECT_EQ(fmt::format("{:.0}", dms(12.56)), "13ms");
+  EXPECT_EQ(fmt::format("{:.1}", dms(12.56)), "12.6ms");
+  EXPECT_EQ(fmt::format("{:.2}", dms(12.56)), "12.56ms");
 }
 
 TEST(chrono_test, format_full_specs) {
-  EXPECT_EQ("1ms   ", fmt::format("{:6.0}", dms(1.234)));
-  EXPECT_EQ("1.2ms ", fmt::format("{:6.1}", dms(1.234)));
-  EXPECT_EQ("  1.23ms", fmt::format("{:>8.{}}", dms(1.234), 2));
-  EXPECT_EQ(" 1.2ms ", fmt::format("{:^{}.{}}", dms(1.234), 7, 1));
-  EXPECT_EQ(" 1.23ms ", fmt::format("{0:^{2}.{1}}", dms(1.234), 2, 8));
-  EXPECT_EQ("=1.234ms=", fmt::format("{:=^{}.{}}", dms(1.234), 9, 3));
-  EXPECT_EQ("*1.2340ms*", fmt::format("{:*^10.4}", dms(1.234)));
+  EXPECT_EQ(fmt::format("{:6.0}", dms(1.234)), "1ms   ");
+  EXPECT_EQ(fmt::format("{:6.1}", dms(1.234)), "1.2ms ");
+  EXPECT_EQ(fmt::format("{:>8.{}}", dms(1.234), 2), "  1.23ms");
+  EXPECT_EQ(fmt::format("{:^{}.{}}", dms(1.234), 7, 1), " 1.2ms ");
+  EXPECT_EQ(fmt::format("{0:^{2}.{1}}", dms(1.234), 2, 8), " 1.23ms ");
+  EXPECT_EQ(fmt::format("{:=^{}.{}}", dms(1.234), 9, 3), "=1.234ms=");
+  EXPECT_EQ(fmt::format("{:*^10.4}", dms(1.234)), "*1.2340ms*");
 
-  EXPECT_EQ("13ms  ", fmt::format("{:6.0}", dms(12.56)));
-  EXPECT_EQ("    13ms", fmt::format("{:>8.{}}", dms(12.56), 0));
-  EXPECT_EQ(" 13ms ", fmt::format("{:^{}.{}}", dms(12.56), 6, 0));
-  EXPECT_EQ("  13ms  ", fmt::format("{0:^{2}.{1}}", dms(12.56), 0, 8));
-  EXPECT_EQ("==13ms===", fmt::format("{:=^{}.{}}", dms(12.56), 9, 0));
-  EXPECT_EQ("***13ms***", fmt::format("{:*^10.0}", dms(12.56)));
+  EXPECT_EQ(fmt::format("{:6.0}", dms(12.56)), "13ms  ");
+  EXPECT_EQ(fmt::format("{:>8.{}}", dms(12.56), 0), "    13ms");
+  EXPECT_EQ(fmt::format("{:^{}.{}}", dms(12.56), 6, 0), " 13ms ");
+  EXPECT_EQ(fmt::format("{0:^{2}.{1}}", dms(12.56), 0, 8), "  13ms  ");
+  EXPECT_EQ(fmt::format("{:=^{}.{}}", dms(12.56), 9, 0), "==13ms===");
+  EXPECT_EQ(fmt::format("{:*^10.0}", dms(12.56)), "***13ms***");
 }
 
 TEST(chrono_test, format_simple_q) {
-  typedef std::chrono::duration<float> fs;
-  EXPECT_EQ("1.234 s", fmt::format("{:%Q %q}", fs(1.234)));
-  typedef std::chrono::duration<float, std::milli> fms;
-  EXPECT_EQ("1.234 ms", fmt::format("{:%Q %q}", fms(1.234)));
-  typedef std::chrono::duration<double> ds;
-  EXPECT_EQ("1.234 s", fmt::format("{:%Q %q}", ds(1.234)));
-  EXPECT_EQ("1.234 ms", fmt::format("{:%Q %q}", dms(1.234)));
+  EXPECT_EQ(fmt::format("{:%Q %q}", std::chrono::duration<float>(1.234)),
+            "1.234 s");
+  EXPECT_EQ(
+      fmt::format("{:%Q %q}", std::chrono::duration<float, std::milli>(1.234)),
+      "1.234 ms");
+  EXPECT_EQ(fmt::format("{:%Q %q}", std::chrono::duration<double>(1.234)),
+            "1.234 s");
+  EXPECT_EQ(fmt::format("{:%Q %q}", dms(1.234)), "1.234 ms");
 }
 
 TEST(chrono_test, format_precision_q) {
   EXPECT_THROW_MSG(
       (void)fmt::format(runtime("{:.2%Q %q}"), std::chrono::seconds(42)),
       fmt::format_error, "precision not allowed for this argument type");
-  EXPECT_EQ("1.2 ms", fmt::format("{:.1%Q %q}", dms(1.234)));
-  EXPECT_EQ("1.23 ms", fmt::format("{:.{}%Q %q}", dms(1.234), 2));
+  EXPECT_EQ(fmt::format("{:.1%Q %q}", dms(1.234)), "1.2 ms");
+  EXPECT_EQ(fmt::format("{:.{}%Q %q}", dms(1.234), 2), "1.23 ms");
 }
 
 TEST(chrono_test, format_full_specs_q) {
-  EXPECT_EQ("1 ms   ", fmt::format("{:7.0%Q %q}", dms(1.234)));
-  EXPECT_EQ("1.2 ms ", fmt::format("{:7.1%Q %q}", dms(1.234)));
-  EXPECT_EQ(" 1.23 ms", fmt::format("{:>8.{}%Q %q}", dms(1.234), 2));
-  EXPECT_EQ(" 1.2 ms ", fmt::format("{:^{}.{}%Q %q}", dms(1.234), 8, 1));
-  EXPECT_EQ(" 1.23 ms ", fmt::format("{0:^{2}.{1}%Q %q}", dms(1.234), 2, 9));
-  EXPECT_EQ("=1.234 ms=", fmt::format("{:=^{}.{}%Q %q}", dms(1.234), 10, 3));
-  EXPECT_EQ("*1.2340 ms*", fmt::format("{:*^11.4%Q %q}", dms(1.234)));
+  EXPECT_EQ(fmt::format("{:7.0%Q %q}", dms(1.234)), "1 ms   ");
+  EXPECT_EQ(fmt::format("{:7.1%Q %q}", dms(1.234)), "1.2 ms ");
+  EXPECT_EQ(fmt::format("{:>8.{}%Q %q}", dms(1.234), 2), " 1.23 ms");
+  EXPECT_EQ(fmt::format("{:^{}.{}%Q %q}", dms(1.234), 8, 1), " 1.2 ms ");
+  EXPECT_EQ(fmt::format("{0:^{2}.{1}%Q %q}", dms(1.234), 2, 9), " 1.23 ms ");
+  EXPECT_EQ(fmt::format("{:=^{}.{}%Q %q}", dms(1.234), 10, 3), "=1.234 ms=");
+  EXPECT_EQ(fmt::format("{:*^11.4%Q %q}", dms(1.234)), "*1.2340 ms*");
 
-  EXPECT_EQ("13 ms  ", fmt::format("{:7.0%Q %q}", dms(12.56)));
-  EXPECT_EQ("   13 ms", fmt::format("{:>8.{}%Q %q}", dms(12.56), 0));
-  EXPECT_EQ(" 13 ms  ", fmt::format("{:^{}.{}%Q %q}", dms(12.56), 8, 0));
-  EXPECT_EQ("  13 ms  ", fmt::format("{0:^{2}.{1}%Q %q}", dms(12.56), 0, 9));
-  EXPECT_EQ("==13 ms==", fmt::format("{:=^{}.{}%Q %q}", dms(12.56), 9, 0));
-  EXPECT_EQ("***13 ms***", fmt::format("{:*^11.0%Q %q}", dms(12.56)));
+  EXPECT_EQ(fmt::format("{:7.0%Q %q}", dms(12.56)), "13 ms  ");
+  EXPECT_EQ(fmt::format("{:>8.{}%Q %q}", dms(12.56), 0), "   13 ms");
+  EXPECT_EQ(fmt::format("{:^{}.{}%Q %q}", dms(12.56), 8, 0), " 13 ms  ");
+  EXPECT_EQ(fmt::format("{0:^{2}.{1}%Q %q}", dms(12.56), 0, 9), "  13 ms  ");
+  EXPECT_EQ(fmt::format("{:=^{}.{}%Q %q}", dms(12.56), 9, 0), "==13 ms==");
+  EXPECT_EQ(fmt::format("{:*^11.0%Q %q}", dms(12.56)), "***13 ms***");
 }
 
 TEST(chrono_test, invalid_width_id) {
@@ -704,27 +711,26 @@
 }
 
 TEST(chrono_test, negative_durations) {
-  EXPECT_EQ("-12345", fmt::format("{:%Q}", std::chrono::seconds(-12345)));
-  EXPECT_EQ("-03:25:45",
-            fmt::format("{:%H:%M:%S}", std::chrono::seconds(-12345)));
-  EXPECT_EQ("-00:01",
-            fmt::format("{:%M:%S}", std::chrono::duration<double>(-1)));
-  EXPECT_EQ("s", fmt::format("{:%q}", std::chrono::seconds(-12345)));
-  EXPECT_EQ("-00.127",
-            fmt::format("{:%S}",
-                        std::chrono::duration<signed char, std::milli>{-127}));
+  EXPECT_EQ(fmt::format("{:%Q}", std::chrono::seconds(-12345)), "-12345");
+  EXPECT_EQ(fmt::format("{:%H:%M:%S}", std::chrono::seconds(-12345)),
+            "-03:25:45");
+  EXPECT_EQ(fmt::format("{:%M:%S}", std::chrono::duration<double>(-1)),
+            "-00:01");
+  EXPECT_EQ(fmt::format("{:%q}", std::chrono::seconds(-12345)), "s");
+  EXPECT_EQ(fmt::format("{:%S}",
+                        std::chrono::duration<signed char, std::milli>(-127)),
+            "-00.127");
   auto min = std::numeric_limits<int>::min();
   EXPECT_EQ(fmt::format("{}", min),
             fmt::format("{:%Q}", std::chrono::duration<int>(min)));
 }
 
 TEST(chrono_test, special_durations) {
-  auto value = fmt::format("{:%S}", std::chrono::duration<double>(1e20));
-  EXPECT_EQ(value, "40");
+  EXPECT_EQ(fmt::format("{:%S}", std::chrono::duration<double>(1e20)), "40");
   auto nan = std::numeric_limits<double>::quiet_NaN();
   EXPECT_EQ(
-      "nan nan nan nan nan:nan nan",
-      fmt::format("{:%I %H %M %S %R %r}", std::chrono::duration<double>(nan)));
+      fmt::format("{:%I %H %M %S %R %r}", std::chrono::duration<double>(nan)),
+      "nan nan nan nan nan:nan nan");
   EXPECT_EQ(fmt::format("{}", std::chrono::duration<float, std::exa>(1)),
             "1Es");
   EXPECT_EQ(fmt::format("{}", std::chrono::duration<float, std::atto>(1)),
@@ -733,13 +739,13 @@
             "03:33");
   EXPECT_EQ(fmt::format("{:%T}", std::chrono::duration<char, std::mega>{2}),
             "03:33:20");
-  EXPECT_EQ("01.234",
-            fmt::format("{:.3%S}", std::chrono::duration<float, std::pico>(
-                                     1.234e12)));
+  EXPECT_EQ(
+      fmt::format("{:.3%S}", std::chrono::duration<float, std::pico>(1.234e12)),
+      "01.234");
 }
 
 TEST(chrono_test, unsigned_duration) {
-  EXPECT_EQ("42s", fmt::format("{}", std::chrono::duration<unsigned>(42)));
+  EXPECT_EQ(fmt::format("{}", std::chrono::duration<unsigned>(42)), "42s");
 }
 
 TEST(chrono_test, weekday) {
@@ -852,103 +858,86 @@
 }
 #endif
 
-TEST(chrono_test, timestamps_ratios) {
-  std::chrono::time_point<std::chrono::system_clock, std::chrono::milliseconds>
-      t1(std::chrono::milliseconds(67890));
-
+TEST(chrono_test, timestamp_ratios) {
+  auto t1 =
+      sys_time<std::chrono::milliseconds>(std::chrono::milliseconds(67890));
   EXPECT_EQ(fmt::format("{:%M:%S}", t1), "01:07.890");
 
-  std::chrono::time_point<std::chrono::system_clock, std::chrono::minutes>
-      t2(std::chrono::minutes(7));
-
+  auto t2 = sys_time<std::chrono::minutes>(std::chrono::minutes(7));
   EXPECT_EQ(fmt::format("{:%M:%S}", t2), "07:00");
 
-  std::chrono::time_point<std::chrono::system_clock, 
-                          std::chrono::duration<int, std::ratio<9>>>
-      t3(std::chrono::duration<int, std::ratio<9>>(7));
-
+  auto t3 = sys_time<std::chrono::duration<int, std::ratio<9>>>(
+      std::chrono::duration<int, std::ratio<9>>(7));
   EXPECT_EQ(fmt::format("{:%M:%S}", t3), "01:03");
 
-  std::chrono::time_point<std::chrono::system_clock, 
-                          std::chrono::duration<int, std::ratio<63>>>
-      t4(std::chrono::duration<int, std::ratio<63>>(1));
-
+  auto t4 = sys_time<std::chrono::duration<int, std::ratio<63>>>(
+      std::chrono::duration<int, std::ratio<63>>(1));
   EXPECT_EQ(fmt::format("{:%M:%S}", t4), "01:03");
+
+  if (sizeof(time_t) > 4) {
+    auto tp =
+        sys_time<std::chrono::milliseconds>(std::chrono::seconds(32503680000));
+    EXPECT_EQ(fmt::format("{:%Y-%m-%d}", tp), "3000-01-01");
+  }
+
+  if (FMT_SAFE_DURATION_CAST) {
+    using years = std::chrono::duration<std::int64_t, std::ratio<31556952>>;
+    auto tp = sys_time<years>(years(std::numeric_limits<std::int64_t>::max()));
+    EXPECT_THROW_MSG((void)fmt::format("{:%Y-%m-%d}", tp), fmt::format_error,
+                     "cannot format duration");
+  }
 }
 
-TEST(chrono_test, timestamps_sub_seconds) {
-  std::chrono::time_point<std::chrono::system_clock,
-                          std::chrono::duration<long long, std::ratio<1, 3>>>
-      t1(std::chrono::duration<long long, std::ratio<1, 3>>(4));
-
+TEST(chrono_test, timestamp_sub_seconds) {
+  auto t1 = sys_time<std::chrono::duration<long long, std::ratio<1, 3>>>(
+      std::chrono::duration<long long, std::ratio<1, 3>>(4));
   EXPECT_EQ(fmt::format("{:%S}", t1), "01.333333");
 
-  std::chrono::time_point<std::chrono::system_clock,
-                          std::chrono::duration<double, std::ratio<1, 3>>>
-      t2(std::chrono::duration<double, std::ratio<1, 3>>(4));
-
+  auto t2 = sys_time<std::chrono::duration<double, std::ratio<1, 3>>>(
+      std::chrono::duration<double, std::ratio<1, 3>>(4));
   EXPECT_EQ(fmt::format("{:%S}", t2), "01.333333");
 
-  const std::chrono::time_point<std::chrono::system_clock, std::chrono::seconds>
-      t3(std::chrono::seconds(2));
-
+  auto t3 = sys_time<std::chrono::seconds>(std::chrono::seconds(2));
   EXPECT_EQ(fmt::format("{:%S}", t3), "02");
 
-  const std::chrono::time_point<std::chrono::system_clock,
-                                std::chrono::duration<double>>
-      t4(std::chrono::duration<double, std::ratio<1, 1>>(9.5));
-
+  auto t4 = sys_time<std::chrono::duration<double>>(
+      std::chrono::duration<double, std::ratio<1, 1>>(9.5));
   EXPECT_EQ(fmt::format("{:%S}", t4), "09.500000");
 
-  const std::chrono::time_point<std::chrono::system_clock,
-                                std::chrono::duration<double>>
-      t5(std::chrono::duration<double, std::ratio<1, 1>>(9));
-
+  auto t5 = sys_time<std::chrono::duration<double>>(
+      std::chrono::duration<double, std::ratio<1, 1>>(9));
   EXPECT_EQ(fmt::format("{:%S}", t5), "09");
 
-  const std::chrono::time_point<std::chrono::system_clock,
-                                std::chrono::milliseconds>
-      t6(std::chrono::seconds(1) + std::chrono::milliseconds(120));
-
+  auto t6 = sys_time<std::chrono::milliseconds>(std::chrono::seconds(1) +
+                                                std::chrono::milliseconds(120));
   EXPECT_EQ(fmt::format("{:%S}", t6), "01.120");
 
-  const std::chrono::time_point<std::chrono::system_clock,
-                                std::chrono::microseconds>
-      t7(std::chrono::microseconds(1234567));
-
+  auto t7 =
+      sys_time<std::chrono::microseconds>(std::chrono::microseconds(1234567));
   EXPECT_EQ(fmt::format("{:%S}", t7), "01.234567");
 
-  const std::chrono::time_point<std::chrono::system_clock,
-                                std::chrono::nanoseconds>
-      t8(std::chrono::nanoseconds(123456789));
-
+  auto t8 =
+      sys_time<std::chrono::nanoseconds>(std::chrono::nanoseconds(123456789));
   EXPECT_EQ(fmt::format("{:%S}", t8), "00.123456789");
 
-  const auto t9 = std::chrono::time_point_cast<std::chrono::nanoseconds>(
+  auto t9 = std::chrono::time_point_cast<std::chrono::nanoseconds>(
       std::chrono::system_clock::now());
-  const auto t9_sec = std::chrono::time_point_cast<std::chrono::seconds>(t9);
+  auto t9_sec = std::chrono::time_point_cast<std::chrono::seconds>(t9);
   auto t9_sub_sec_part = fmt::format("{0:09}", (t9 - t9_sec).count());
-
   EXPECT_EQ(fmt::format("{}.{}", strftime_full_utc(t9_sec), t9_sub_sec_part),
             fmt::format("{:%Y-%m-%d %H:%M:%S}", t9));
   EXPECT_EQ(fmt::format("{}.{}", strftime_full_utc(t9_sec), t9_sub_sec_part),
             fmt::format("{:%Y-%m-%d %T}", t9));
 
-  const std::chrono::time_point<std::chrono::system_clock,
-                                std::chrono::milliseconds>
-      t10(std::chrono::milliseconds(2000));
-
+  auto t10 =
+      sys_time<std::chrono::milliseconds>(std::chrono::milliseconds(2000));
   EXPECT_EQ(fmt::format("{:%S}", t10), "02.000");
 
-  {
-    const auto epoch = std::chrono::time_point<std::chrono::system_clock,
-                                               std::chrono::milliseconds>();
-    const auto d = std::chrono::milliseconds(250);
-
-    EXPECT_EQ("59.750", fmt::format("{:%S}", epoch - d));
-    EXPECT_EQ("00.000", fmt::format("{:%S}", epoch));
-    EXPECT_EQ("00.250", fmt::format("{:%S}", epoch + d));
-  }
+  auto epoch = sys_time<std::chrono::milliseconds>();
+  auto d = std::chrono::milliseconds(250);
+  EXPECT_EQ(fmt::format("{:%S}", epoch - d), "59.750");
+  EXPECT_EQ(fmt::format("{:%S}", epoch), "00.000");
+  EXPECT_EQ(fmt::format("{:%S}", epoch + d), "00.250");
 }
 
 TEST(chrono_test, glibc_extensions) {
@@ -1003,3 +992,8 @@
     EXPECT_EQ(fmt::format("{:%-S}", d), "3.140000");
   }
 }
+
+TEST(chrono_test, out_of_range) {
+  auto d = std::chrono::duration<unsigned long, std::giga>(538976288);
+  EXPECT_THROW((void)fmt::format("{:%j}", d), fmt::format_error);
+}
\ No newline at end of file
diff --git a/test/compile-test.cc b/test/compile-test.cc
index d6c7c64..8551303 100644
--- a/test/compile-test.cc
+++ b/test/compile-test.cc
@@ -280,15 +280,18 @@
 #endif
 
 // MSVS 2019 19.29.30145.0 - Support C++20 and OK.
-// MSVS 2022 19.32.31332.0 - compile-test.cc(362,3): fatal error C1001: Internal
-// compiler error.
+// MSVS 2022 19.32.31332.0, 19.37.32826.1 - compile-test.cc(362,3): fatal error
+// C1001: Internal compiler error.
 //  (compiler file
 //  'D:\a\_work\1\s\src\vctools\Compiler\CxxFE\sl\p1\c\constexpr\constexpr.cpp',
 //  line 8635)
-#if ((FMT_CPLUSPLUS >= 202002L) &&                           \
-     (!defined(_GLIBCXX_RELEASE) || _GLIBCXX_RELEASE > 9) && \
-     (!FMT_MSC_VERSION || FMT_MSC_VERSION < 1930)) ||        \
-    (FMT_CPLUSPLUS >= 201709L && FMT_GCC_VERSION >= 1002)
+#if (FMT_CPLUSPLUS >= 202002L ||                                \
+     (FMT_CPLUSPLUS >= 201709L && FMT_GCC_VERSION >= 1002)) &&  \
+    ((!defined(_GLIBCXX_RELEASE) || _GLIBCXX_RELEASE >= 10) &&  \
+     (!defined(_LIBCPP_VERSION) || _LIBCPP_VERSION >= 10000) && \
+     (!FMT_MSC_VERSION ||                                       \
+      (FMT_MSC_VERSION >= 1928 && FMT_MSC_VERSION < 1930))) &&  \
+    defined(__cpp_lib_is_constant_evaluated)
 template <size_t max_string_length, typename Char = char> struct test_string {
   template <typename T> constexpr bool operator==(const T& rhs) const noexcept {
     return fmt::basic_string_view<Char>(rhs).compare(buffer) == 0;
diff --git a/test/format-impl-test.cc b/test/format-impl-test.cc
index 4d6198b..eda1f23 100644
--- a/test/format-impl-test.cc
+++ b/test/format-impl-test.cc
@@ -246,13 +246,6 @@
   }
 }
 
-TEST(format_impl_test, compute_width) {
-  EXPECT_EQ(4,
-            fmt::detail::compute_width(
-                fmt::basic_string_view<fmt::detail::char8_type>(
-                    reinterpret_cast<const fmt::detail::char8_type*>("ёжик"))));
-}
-
 // Tests fmt::detail::count_digits for integer type Int.
 template <typename Int> void test_count_digits() {
   for (Int i = 0; i < 10; ++i) EXPECT_EQ(1u, fmt::detail::count_digits(i));
diff --git a/test/format-test.cc b/test/format-test.cc
index 9788c24..325bef9 100644
--- a/test/format-test.cc
+++ b/test/format-test.cc
@@ -173,6 +173,10 @@
   EXPECT_EQ(fmt::detail::parse_nonnegative_int(begin, end, -1), -1);
 }
 
+TEST(format_impl_test, compute_width) {
+  EXPECT_EQ(fmt::detail::compute_width("вожык"), 5);
+}
+
 TEST(util_test, utf8_to_utf16) {
   auto u = fmt::detail::utf8_to_utf16("лошадка");
   EXPECT_EQ(L"\x043B\x043E\x0448\x0430\x0434\x043A\x0430", u.str());
@@ -349,9 +353,9 @@
 }
 
 TEST(memory_buffer_test, grow) {
-  typedef allocator_ref<mock_allocator<int>> Allocator;
+  using allocator = allocator_ref<mock_allocator<int>>;
   mock_allocator<int> alloc;
-  basic_memory_buffer<int, 10, Allocator> buffer((Allocator(&alloc)));
+  basic_memory_buffer<int, 10, allocator> buffer((allocator(&alloc)));
   buffer.resize(7);
   using fmt::detail::to_unsigned;
   for (int i = 0; i < 7; ++i) buffer[to_unsigned(i)] = i * i;
@@ -448,18 +452,18 @@
 }
 
 TEST(format_test, escape) {
-  EXPECT_EQ("{", fmt::format("{{"));
-  EXPECT_EQ("before {", fmt::format("before {{"));
-  EXPECT_EQ("{ after", fmt::format("{{ after"));
-  EXPECT_EQ("before { after", fmt::format("before {{ after"));
+  EXPECT_EQ(fmt::format("{{"), "{");
+  EXPECT_EQ(fmt::format("before {{"), "before {");
+  EXPECT_EQ(fmt::format("{{ after"), "{ after");
+  EXPECT_EQ(fmt::format("before {{ after"), "before { after");
 
-  EXPECT_EQ("}", fmt::format("}}"));
-  EXPECT_EQ("before }", fmt::format("before }}"));
-  EXPECT_EQ("} after", fmt::format("}} after"));
-  EXPECT_EQ("before } after", fmt::format("before }} after"));
+  EXPECT_EQ(fmt::format("}}"), "}");
+  EXPECT_EQ(fmt::format("before }}"), "before }");
+  EXPECT_EQ(fmt::format("}} after"), "} after");
+  EXPECT_EQ(fmt::format("before }} after"), "before } after");
 
-  EXPECT_EQ("{}", fmt::format("{{}}"));
-  EXPECT_EQ("{42}", fmt::format("{{{0}}}", 42));
+  EXPECT_EQ(fmt::format("{{}}"), "{}");
+  EXPECT_EQ(fmt::format("{{{0}}}", 42), "{42}");
 }
 
 TEST(format_test, unmatched_braces) {
@@ -471,16 +475,16 @@
                    "invalid format string");
 }
 
-TEST(format_test, no_args) { EXPECT_EQ("test", fmt::format("test")); }
+TEST(format_test, no_args) { EXPECT_EQ(fmt::format("test"), "test"); }
 
 TEST(format_test, args_in_different_positions) {
-  EXPECT_EQ("42", fmt::format("{0}", 42));
-  EXPECT_EQ("before 42", fmt::format("before {0}", 42));
-  EXPECT_EQ("42 after", fmt::format("{0} after", 42));
-  EXPECT_EQ("before 42 after", fmt::format("before {0} after", 42));
-  EXPECT_EQ("answer = 42", fmt::format("{0} = {1}", "answer", 42));
-  EXPECT_EQ("42 is the answer", fmt::format("{1} is the {0}", "answer", 42));
-  EXPECT_EQ("abracadabra", fmt::format("{0}{1}{0}", "abra", "cad"));
+  EXPECT_EQ(fmt::format("{0}", 42), "42");
+  EXPECT_EQ(fmt::format("before {0}", 42), "before 42");
+  EXPECT_EQ(fmt::format("{0} after", 42), "42 after");
+  EXPECT_EQ(fmt::format("before {0} after", 42), "before 42 after");
+  EXPECT_EQ(fmt::format("{0} = {1}", "answer", 42), "answer = 42");
+  EXPECT_EQ(fmt::format("{1} is the {0}", "answer", 42), "42 is the answer");
+  EXPECT_EQ(fmt::format("{0}{1}{0}", "abra", "cad"), "abracadabra");
 }
 
 TEST(format_test, arg_errors) {
@@ -495,32 +499,29 @@
   EXPECT_THROW_MSG((void)fmt::format(runtime("{00}"), 42), format_error,
                    "invalid format string");
 
-  char format_str[buffer_size];
-  safe_sprintf(format_str, "{%u", INT_MAX);
-  EXPECT_THROW_MSG((void)fmt::format(runtime(format_str)), format_error,
+  auto int_max = std::to_string(INT_MAX);
+  EXPECT_THROW_MSG((void)fmt::format(runtime("{" + int_max)), format_error,
                    "invalid format string");
-  safe_sprintf(format_str, "{%u}", INT_MAX);
-  EXPECT_THROW_MSG((void)fmt::format(runtime(format_str)), format_error,
-                   "argument not found");
+  EXPECT_THROW_MSG((void)fmt::format(runtime("{" + int_max + "}")),
+                   format_error, "argument not found");
 
-  safe_sprintf(format_str, "{%u", INT_MAX + 1u);
-  EXPECT_THROW_MSG((void)fmt::format(runtime(format_str)), format_error,
+  auto int_maxer = std::to_string(INT_MAX + 1u);
+  EXPECT_THROW_MSG((void)fmt::format(runtime("{" + int_maxer)), format_error,
                    "invalid format string");
-  safe_sprintf(format_str, "{%u}", INT_MAX + 1u);
-  EXPECT_THROW_MSG((void)fmt::format(runtime(format_str)), format_error,
-                   "argument not found");
+  EXPECT_THROW_MSG((void)fmt::format(runtime("{" + int_maxer + "}")),
+                   format_error, "argument not found");
 }
 
 template <int N> struct test_format {
   template <typename... T>
-  static std::string format(fmt::string_view fmt, const T&... args) {
+  static auto format(fmt::string_view fmt, const T&... args) -> std::string {
     return test_format<N - 1>::format(fmt, N - 1, args...);
   }
 };
 
 template <> struct test_format<0> {
   template <typename... T>
-  static std::string format(fmt::string_view fmt, const T&... args) {
+  static auto format(fmt::string_view fmt, const T&... args) -> std::string {
     return fmt::format(runtime(fmt), args...);
   }
 };
@@ -540,10 +541,10 @@
 TEST(format_test, named_arg) {
   EXPECT_EQ("1/a/A", fmt::format("{_1}/{a_}/{A_}", fmt::arg("a_", 'a'),
                                  fmt::arg("A_", "A"), fmt::arg("_1", 1)));
-  EXPECT_EQ(" -42", fmt::format("{0:{width}}", -42, fmt::arg("width", 4)));
+  EXPECT_EQ(fmt::format("{0:{width}}", -42, fmt::arg("width", 4)), " -42");
   EXPECT_EQ("st",
             fmt::format("{0:.{precision}}", "str", fmt::arg("precision", 2)));
-  EXPECT_EQ("1 2", fmt::format("{} {two}", 1, fmt::arg("two", 2)));
+  EXPECT_EQ(fmt::format("{} {two}", 1, fmt::arg("two", 2)), "1 2");
   EXPECT_EQ("42",
             fmt::format("{c}", fmt::arg("a", 0), fmt::arg("b", 0),
                         fmt::arg("c", 42), fmt::arg("d", 0), fmt::arg("e", 0),
@@ -558,12 +559,12 @@
 }
 
 TEST(format_test, auto_arg_index) {
-  EXPECT_EQ("abc", fmt::format("{}{}{}", 'a', 'b', 'c'));
+  EXPECT_EQ(fmt::format("{}{}{}", 'a', 'b', 'c'), "abc");
   EXPECT_THROW_MSG((void)fmt::format(runtime("{0}{}"), 'a', 'b'), format_error,
                    "cannot switch from manual to automatic argument indexing");
   EXPECT_THROW_MSG((void)fmt::format(runtime("{}{0}"), 'a', 'b'), format_error,
                    "cannot switch from automatic to manual argument indexing");
-  EXPECT_EQ("1.2", fmt::format("{:.{}}", 1.2345, 2));
+  EXPECT_EQ(fmt::format("{:.{}}", 1.2345, 2), "1.2");
   EXPECT_THROW_MSG((void)fmt::format(runtime("{0}:.{}"), 1.2345, 2),
                    format_error,
                    "cannot switch from manual to automatic argument indexing");
@@ -574,57 +575,57 @@
                    "argument not found");
 }
 
-TEST(format_test, empty_specs) { EXPECT_EQ("42", fmt::format("{0:}", 42)); }
+TEST(format_test, empty_specs) { EXPECT_EQ(fmt::format("{0:}", 42), "42"); }
 
 TEST(format_test, left_align) {
-  EXPECT_EQ("42  ", fmt::format("{0:<4}", 42));
-  EXPECT_EQ("42  ", fmt::format("{0:<4o}", 042));
-  EXPECT_EQ("42  ", fmt::format("{0:<4x}", 0x42));
-  EXPECT_EQ("-42  ", fmt::format("{0:<5}", -42));
-  EXPECT_EQ("42   ", fmt::format("{0:<5}", 42u));
-  EXPECT_EQ("-42  ", fmt::format("{0:<5}", -42l));
-  EXPECT_EQ("42   ", fmt::format("{0:<5}", 42ul));
-  EXPECT_EQ("-42  ", fmt::format("{0:<5}", -42ll));
-  EXPECT_EQ("42   ", fmt::format("{0:<5}", 42ull));
-  EXPECT_EQ("-42  ", fmt::format("{0:<5}", -42.0));
-  EXPECT_EQ("-42  ", fmt::format("{0:<5}", -42.0l));
-  EXPECT_EQ("c    ", fmt::format("{0:<5}", 'c'));
-  EXPECT_EQ("abc  ", fmt::format("{0:<5}", "abc"));
-  EXPECT_EQ("0xface  ", fmt::format("{0:<8}", reinterpret_cast<void*>(0xface)));
+  EXPECT_EQ(fmt::format("{0:<4}", 42), "42  ");
+  EXPECT_EQ(fmt::format("{0:<4o}", 042), "42  ");
+  EXPECT_EQ(fmt::format("{0:<4x}", 0x42), "42  ");
+  EXPECT_EQ(fmt::format("{0:<5}", -42), "-42  ");
+  EXPECT_EQ(fmt::format("{0:<5}", 42u), "42   ");
+  EXPECT_EQ(fmt::format("{0:<5}", -42l), "-42  ");
+  EXPECT_EQ(fmt::format("{0:<5}", 42ul), "42   ");
+  EXPECT_EQ(fmt::format("{0:<5}", -42ll), "-42  ");
+  EXPECT_EQ(fmt::format("{0:<5}", 42ull), "42   ");
+  EXPECT_EQ(fmt::format("{0:<5}", -42.0), "-42  ");
+  EXPECT_EQ(fmt::format("{0:<5}", -42.0l), "-42  ");
+  EXPECT_EQ(fmt::format("{0:<5}", 'c'), "c    ");
+  EXPECT_EQ(fmt::format("{0:<5}", "abc"), "abc  ");
+  EXPECT_EQ(fmt::format("{0:<8}", reinterpret_cast<void*>(0xface)), "0xface  ");
 }
 
 TEST(format_test, right_align) {
-  EXPECT_EQ("  42", fmt::format("{0:>4}", 42));
-  EXPECT_EQ("  42", fmt::format("{0:>4o}", 042));
-  EXPECT_EQ("  42", fmt::format("{0:>4x}", 0x42));
-  EXPECT_EQ("  -42", fmt::format("{0:>5}", -42));
-  EXPECT_EQ("   42", fmt::format("{0:>5}", 42u));
-  EXPECT_EQ("  -42", fmt::format("{0:>5}", -42l));
-  EXPECT_EQ("   42", fmt::format("{0:>5}", 42ul));
-  EXPECT_EQ("  -42", fmt::format("{0:>5}", -42ll));
-  EXPECT_EQ("   42", fmt::format("{0:>5}", 42ull));
-  EXPECT_EQ("  -42", fmt::format("{0:>5}", -42.0));
-  EXPECT_EQ("  -42", fmt::format("{0:>5}", -42.0l));
-  EXPECT_EQ("    c", fmt::format("{0:>5}", 'c'));
-  EXPECT_EQ("  abc", fmt::format("{0:>5}", "abc"));
-  EXPECT_EQ("  0xface", fmt::format("{0:>8}", reinterpret_cast<void*>(0xface)));
+  EXPECT_EQ(fmt::format("{0:>4}", 42), "  42");
+  EXPECT_EQ(fmt::format("{0:>4o}", 042), "  42");
+  EXPECT_EQ(fmt::format("{0:>4x}", 0x42), "  42");
+  EXPECT_EQ(fmt::format("{0:>5}", -42), "  -42");
+  EXPECT_EQ(fmt::format("{0:>5}", 42u), "   42");
+  EXPECT_EQ(fmt::format("{0:>5}", -42l), "  -42");
+  EXPECT_EQ(fmt::format("{0:>5}", 42ul), "   42");
+  EXPECT_EQ(fmt::format("{0:>5}", -42ll), "  -42");
+  EXPECT_EQ(fmt::format("{0:>5}", 42ull), "   42");
+  EXPECT_EQ(fmt::format("{0:>5}", -42.0), "  -42");
+  EXPECT_EQ(fmt::format("{0:>5}", -42.0l), "  -42");
+  EXPECT_EQ(fmt::format("{0:>5}", 'c'), "    c");
+  EXPECT_EQ(fmt::format("{0:>5}", "abc"), "  abc");
+  EXPECT_EQ(fmt::format("{0:>8}", reinterpret_cast<void*>(0xface)), "  0xface");
 }
 
 TEST(format_test, center_align) {
-  EXPECT_EQ(" 42  ", fmt::format("{0:^5}", 42));
-  EXPECT_EQ(" 42  ", fmt::format("{0:^5o}", 042));
-  EXPECT_EQ(" 42  ", fmt::format("{0:^5x}", 0x42));
-  EXPECT_EQ(" -42 ", fmt::format("{0:^5}", -42));
-  EXPECT_EQ(" 42  ", fmt::format("{0:^5}", 42u));
-  EXPECT_EQ(" -42 ", fmt::format("{0:^5}", -42l));
-  EXPECT_EQ(" 42  ", fmt::format("{0:^5}", 42ul));
-  EXPECT_EQ(" -42 ", fmt::format("{0:^5}", -42ll));
-  EXPECT_EQ(" 42  ", fmt::format("{0:^5}", 42ull));
-  EXPECT_EQ(" -42 ", fmt::format("{0:^5}", -42.0));
-  EXPECT_EQ(" -42 ", fmt::format("{0:^5}", -42.0l));
-  EXPECT_EQ("  c  ", fmt::format("{0:^5}", 'c'));
-  EXPECT_EQ(" abc  ", fmt::format("{0:^6}", "abc"));
-  EXPECT_EQ(" 0xface ", fmt::format("{0:^8}", reinterpret_cast<void*>(0xface)));
+  EXPECT_EQ(fmt::format("{0:^5}", 42), " 42  ");
+  EXPECT_EQ(fmt::format("{0:^5o}", 042), " 42  ");
+  EXPECT_EQ(fmt::format("{0:^5x}", 0x42), " 42  ");
+  EXPECT_EQ(fmt::format("{0:^5}", -42), " -42 ");
+  EXPECT_EQ(fmt::format("{0:^5}", 42u), " 42  ");
+  EXPECT_EQ(fmt::format("{0:^5}", -42l), " -42 ");
+  EXPECT_EQ(fmt::format("{0:^5}", 42ul), " 42  ");
+  EXPECT_EQ(fmt::format("{0:^5}", -42ll), " -42 ");
+  EXPECT_EQ(fmt::format("{0:^5}", 42ull), " 42  ");
+  EXPECT_EQ(fmt::format("{0:^5}", -42.0), " -42 ");
+  EXPECT_EQ(fmt::format("{0:^5}", -42.0l), " -42 ");
+  EXPECT_EQ(fmt::format("{0:^5}", 'c'), "  c  ");
+  EXPECT_EQ(fmt::format("{0:^6}", "abc"), " abc  ");
+  EXPECT_EQ(fmt::format("{0:^8}", reinterpret_cast<void*>(0xface)), " 0xface ");
 }
 
 TEST(format_test, fill) {
@@ -632,44 +633,44 @@
                    "invalid fill character '{'");
   EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{<5}}"), 'c'), format_error,
                    "invalid fill character '{'");
-  EXPECT_EQ("**42", fmt::format("{0:*>4}", 42));
-  EXPECT_EQ("**-42", fmt::format("{0:*>5}", -42));
-  EXPECT_EQ("***42", fmt::format("{0:*>5}", 42u));
-  EXPECT_EQ("**-42", fmt::format("{0:*>5}", -42l));
-  EXPECT_EQ("***42", fmt::format("{0:*>5}", 42ul));
-  EXPECT_EQ("**-42", fmt::format("{0:*>5}", -42ll));
-  EXPECT_EQ("***42", fmt::format("{0:*>5}", 42ull));
-  EXPECT_EQ("**-42", fmt::format("{0:*>5}", -42.0));
-  EXPECT_EQ("**-42", fmt::format("{0:*>5}", -42.0l));
-  EXPECT_EQ("c****", fmt::format("{0:*<5}", 'c'));
-  EXPECT_EQ("abc**", fmt::format("{0:*<5}", "abc"));
+  EXPECT_EQ(fmt::format("{0:*>4}", 42), "**42");
+  EXPECT_EQ(fmt::format("{0:*>5}", -42), "**-42");
+  EXPECT_EQ(fmt::format("{0:*>5}", 42u), "***42");
+  EXPECT_EQ(fmt::format("{0:*>5}", -42l), "**-42");
+  EXPECT_EQ(fmt::format("{0:*>5}", 42ul), "***42");
+  EXPECT_EQ(fmt::format("{0:*>5}", -42ll), "**-42");
+  EXPECT_EQ(fmt::format("{0:*>5}", 42ull), "***42");
+  EXPECT_EQ(fmt::format("{0:*>5}", -42.0), "**-42");
+  EXPECT_EQ(fmt::format("{0:*>5}", -42.0l), "**-42");
+  EXPECT_EQ(fmt::format("{0:*<5}", 'c'), "c****");
+  EXPECT_EQ(fmt::format("{0:*<5}", "abc"), "abc**");
   EXPECT_EQ("**0xface",
             fmt::format("{0:*>8}", reinterpret_cast<void*>(0xface)));
-  EXPECT_EQ("foo=", fmt::format("{:}=", "foo"));
+  EXPECT_EQ(fmt::format("{:}=", "foo"), "foo=");
   EXPECT_EQ(std::string("\0\0\0*", 4),
             fmt::format(string_view("{:\0>4}", 6), '*'));
-  EXPECT_EQ("жж42", fmt::format("{0:ж>4}", 42));
+  EXPECT_EQ(fmt::format("{0:ж>4}", 42), "жж42");
   EXPECT_THROW_MSG((void)fmt::format(runtime("{:\x80\x80\x80\x80\x80>}"), 0),
                    format_error, "invalid format specifier");
 }
 
 TEST(format_test, plus_sign) {
-  EXPECT_EQ("+42", fmt::format("{0:+}", 42));
-  EXPECT_EQ("-42", fmt::format("{0:+}", -42));
-  EXPECT_EQ("+42", fmt::format("{0:+}", 42));
+  EXPECT_EQ(fmt::format("{0:+}", 42), "+42");
+  EXPECT_EQ(fmt::format("{0:+}", -42), "-42");
+  EXPECT_EQ(fmt::format("{0:+}", 42), "+42");
   EXPECT_THROW_MSG((void)fmt::format(runtime("{0:+}"), 42u), format_error,
                    "invalid format specifier");
-  EXPECT_EQ("+42", fmt::format("{0:+}", 42l));
+  EXPECT_EQ(fmt::format("{0:+}", 42l), "+42");
   EXPECT_THROW_MSG((void)fmt::format(runtime("{0:+}"), 42ul), format_error,
                    "invalid format specifier");
-  EXPECT_EQ("+42", fmt::format("{0:+}", 42ll));
+  EXPECT_EQ(fmt::format("{0:+}", 42ll), "+42");
 #if FMT_USE_INT128
-  EXPECT_EQ("+42", fmt::format("{0:+}", __int128_t(42)));
+  EXPECT_EQ(fmt::format("{0:+}", __int128_t(42)), "+42");
 #endif
   EXPECT_THROW_MSG((void)fmt::format(runtime("{0:+}"), 42ull), format_error,
                    "invalid format specifier");
-  EXPECT_EQ("+42", fmt::format("{0:+}", 42.0));
-  EXPECT_EQ("+42", fmt::format("{0:+}", 42.0l));
+  EXPECT_EQ(fmt::format("{0:+}", 42.0), "+42");
+  EXPECT_EQ(fmt::format("{0:+}", 42.0l), "+42");
   EXPECT_THROW_MSG((void)fmt::format(runtime("{0:+}"), 'c'), format_error,
                    "invalid format specifier");
   EXPECT_THROW_MSG((void)fmt::format(runtime("{0:+}"), "abc"), format_error,
@@ -680,19 +681,19 @@
 }
 
 TEST(format_test, minus_sign) {
-  EXPECT_EQ("42", fmt::format("{0:-}", 42));
-  EXPECT_EQ("-42", fmt::format("{0:-}", -42));
-  EXPECT_EQ("42", fmt::format("{0:-}", 42));
+  EXPECT_EQ(fmt::format("{0:-}", 42), "42");
+  EXPECT_EQ(fmt::format("{0:-}", -42), "-42");
+  EXPECT_EQ(fmt::format("{0:-}", 42), "42");
   EXPECT_THROW_MSG((void)fmt::format(runtime("{0:-}"), 42u), format_error,
                    "invalid format specifier");
-  EXPECT_EQ("42", fmt::format("{0:-}", 42l));
+  EXPECT_EQ(fmt::format("{0:-}", 42l), "42");
   EXPECT_THROW_MSG((void)fmt::format(runtime("{0:-}"), 42ul), format_error,
                    "invalid format specifier");
-  EXPECT_EQ("42", fmt::format("{0:-}", 42ll));
+  EXPECT_EQ(fmt::format("{0:-}", 42ll), "42");
   EXPECT_THROW_MSG((void)fmt::format(runtime("{0:-}"), 42ull), format_error,
                    "invalid format specifier");
-  EXPECT_EQ("42", fmt::format("{0:-}", 42.0));
-  EXPECT_EQ("42", fmt::format("{0:-}", 42.0l));
+  EXPECT_EQ(fmt::format("{0:-}", 42.0), "42");
+  EXPECT_EQ(fmt::format("{0:-}", 42.0l), "42");
   EXPECT_THROW_MSG((void)fmt::format(runtime("{0:-}"), 'c'), format_error,
                    "invalid format specifier");
   EXPECT_THROW_MSG((void)fmt::format(runtime("{0:-}"), "abc"), format_error,
@@ -703,19 +704,19 @@
 }
 
 TEST(format_test, space_sign) {
-  EXPECT_EQ(" 42", fmt::format("{0: }", 42));
-  EXPECT_EQ("-42", fmt::format("{0: }", -42));
-  EXPECT_EQ(" 42", fmt::format("{0: }", 42));
+  EXPECT_EQ(fmt::format("{0: }", 42), " 42");
+  EXPECT_EQ(fmt::format("{0: }", -42), "-42");
+  EXPECT_EQ(fmt::format("{0: }", 42), " 42");
   EXPECT_THROW_MSG((void)fmt::format(runtime("{0: }"), 42u), format_error,
                    "invalid format specifier");
-  EXPECT_EQ(" 42", fmt::format("{0: }", 42l));
+  EXPECT_EQ(fmt::format("{0: }", 42l), " 42");
   EXPECT_THROW_MSG((void)fmt::format(runtime("{0: }"), 42ul), format_error,
                    "invalid format specifier");
-  EXPECT_EQ(" 42", fmt::format("{0: }", 42ll));
+  EXPECT_EQ(fmt::format("{0: }", 42ll), " 42");
   EXPECT_THROW_MSG((void)fmt::format(runtime("{0: }"), 42ull), format_error,
                    "invalid format specifier");
-  EXPECT_EQ(" 42", fmt::format("{0: }", 42.0));
-  EXPECT_EQ(" 42", fmt::format("{0: }", 42.0l));
+  EXPECT_EQ(fmt::format("{0: }", 42.0), " 42");
+  EXPECT_EQ(fmt::format("{0: }", 42.0l), " 42");
   EXPECT_THROW_MSG((void)fmt::format(runtime("{0: }"), 'c'), format_error,
                    "invalid format specifier");
   EXPECT_THROW_MSG((void)fmt::format(runtime("{0: }"), "abc"), format_error,
@@ -726,45 +727,45 @@
 }
 
 TEST(format_test, hash_flag) {
-  EXPECT_EQ("42", fmt::format("{0:#}", 42));
-  EXPECT_EQ("-42", fmt::format("{0:#}", -42));
-  EXPECT_EQ("0b101010", fmt::format("{0:#b}", 42));
-  EXPECT_EQ("0B101010", fmt::format("{0:#B}", 42));
-  EXPECT_EQ("-0b101010", fmt::format("{0:#b}", -42));
-  EXPECT_EQ("0x42", fmt::format("{0:#x}", 0x42));
-  EXPECT_EQ("0X42", fmt::format("{0:#X}", 0x42));
-  EXPECT_EQ("-0x42", fmt::format("{0:#x}", -0x42));
-  EXPECT_EQ("0", fmt::format("{0:#o}", 0));
-  EXPECT_EQ("042", fmt::format("{0:#o}", 042));
-  EXPECT_EQ("-042", fmt::format("{0:#o}", -042));
-  EXPECT_EQ("42", fmt::format("{0:#}", 42u));
-  EXPECT_EQ("0x42", fmt::format("{0:#x}", 0x42u));
-  EXPECT_EQ("042", fmt::format("{0:#o}", 042u));
+  EXPECT_EQ(fmt::format("{0:#}", 42), "42");
+  EXPECT_EQ(fmt::format("{0:#}", -42), "-42");
+  EXPECT_EQ(fmt::format("{0:#b}", 42), "0b101010");
+  EXPECT_EQ(fmt::format("{0:#B}", 42), "0B101010");
+  EXPECT_EQ(fmt::format("{0:#b}", -42), "-0b101010");
+  EXPECT_EQ(fmt::format("{0:#x}", 0x42), "0x42");
+  EXPECT_EQ(fmt::format("{0:#X}", 0x42), "0X42");
+  EXPECT_EQ(fmt::format("{0:#x}", -0x42), "-0x42");
+  EXPECT_EQ(fmt::format("{0:#o}", 0), "0");
+  EXPECT_EQ(fmt::format("{0:#o}", 042), "042");
+  EXPECT_EQ(fmt::format("{0:#o}", -042), "-042");
+  EXPECT_EQ(fmt::format("{0:#}", 42u), "42");
+  EXPECT_EQ(fmt::format("{0:#x}", 0x42u), "0x42");
+  EXPECT_EQ(fmt::format("{0:#o}", 042u), "042");
 
-  EXPECT_EQ("-42", fmt::format("{0:#}", -42l));
-  EXPECT_EQ("0x42", fmt::format("{0:#x}", 0x42l));
-  EXPECT_EQ("-0x42", fmt::format("{0:#x}", -0x42l));
-  EXPECT_EQ("042", fmt::format("{0:#o}", 042l));
-  EXPECT_EQ("-042", fmt::format("{0:#o}", -042l));
-  EXPECT_EQ("42", fmt::format("{0:#}", 42ul));
-  EXPECT_EQ("0x42", fmt::format("{0:#x}", 0x42ul));
-  EXPECT_EQ("042", fmt::format("{0:#o}", 042ul));
+  EXPECT_EQ(fmt::format("{0:#}", -42l), "-42");
+  EXPECT_EQ(fmt::format("{0:#x}", 0x42l), "0x42");
+  EXPECT_EQ(fmt::format("{0:#x}", -0x42l), "-0x42");
+  EXPECT_EQ(fmt::format("{0:#o}", 042l), "042");
+  EXPECT_EQ(fmt::format("{0:#o}", -042l), "-042");
+  EXPECT_EQ(fmt::format("{0:#}", 42ul), "42");
+  EXPECT_EQ(fmt::format("{0:#x}", 0x42ul), "0x42");
+  EXPECT_EQ(fmt::format("{0:#o}", 042ul), "042");
 
-  EXPECT_EQ("-42", fmt::format("{0:#}", -42ll));
-  EXPECT_EQ("0x42", fmt::format("{0:#x}", 0x42ll));
-  EXPECT_EQ("-0x42", fmt::format("{0:#x}", -0x42ll));
-  EXPECT_EQ("042", fmt::format("{0:#o}", 042ll));
-  EXPECT_EQ("-042", fmt::format("{0:#o}", -042ll));
-  EXPECT_EQ("42", fmt::format("{0:#}", 42ull));
-  EXPECT_EQ("0x42", fmt::format("{0:#x}", 0x42ull));
-  EXPECT_EQ("042", fmt::format("{0:#o}", 042ull));
+  EXPECT_EQ(fmt::format("{0:#}", -42ll), "-42");
+  EXPECT_EQ(fmt::format("{0:#x}", 0x42ll), "0x42");
+  EXPECT_EQ(fmt::format("{0:#x}", -0x42ll), "-0x42");
+  EXPECT_EQ(fmt::format("{0:#o}", 042ll), "042");
+  EXPECT_EQ(fmt::format("{0:#o}", -042ll), "-042");
+  EXPECT_EQ(fmt::format("{0:#}", 42ull), "42");
+  EXPECT_EQ(fmt::format("{0:#x}", 0x42ull), "0x42");
+  EXPECT_EQ(fmt::format("{0:#o}", 042ull), "042");
 
-  EXPECT_EQ("-42.", fmt::format("{0:#}", -42.0));
-  EXPECT_EQ("-42.", fmt::format("{0:#}", -42.0l));
-  EXPECT_EQ("4.e+01", fmt::format("{:#.0e}", 42.0));
-  EXPECT_EQ("0.", fmt::format("{:#.0f}", 0.01));
-  EXPECT_EQ("0.50", fmt::format("{:#.2g}", 0.5));
-  EXPECT_EQ("0.", fmt::format("{:#.0f}", 0.5));
+  EXPECT_EQ(fmt::format("{0:#}", -42.0), "-42.");
+  EXPECT_EQ(fmt::format("{0:#}", -42.0l), "-42.");
+  EXPECT_EQ(fmt::format("{:#.0e}", 42.0), "4.e+01");
+  EXPECT_EQ(fmt::format("{:#.0f}", 0.01), "0.");
+  EXPECT_EQ(fmt::format("{:#.2g}", 0.5), "0.50");
+  EXPECT_EQ(fmt::format("{:#.0f}", 0.5), "0.");
   EXPECT_THROW_MSG((void)fmt::format(runtime("{0:#"), 'c'), format_error,
                    "missing '}' in format string");
   EXPECT_THROW_MSG((void)fmt::format(runtime("{0:#}"), 'c'), format_error,
@@ -777,15 +778,15 @@
 }
 
 TEST(format_test, zero_flag) {
-  EXPECT_EQ("42", fmt::format("{0:0}", 42));
-  EXPECT_EQ("-0042", fmt::format("{0:05}", -42));
-  EXPECT_EQ("00042", fmt::format("{0:05}", 42u));
-  EXPECT_EQ("-0042", fmt::format("{0:05}", -42l));
-  EXPECT_EQ("00042", fmt::format("{0:05}", 42ul));
-  EXPECT_EQ("-0042", fmt::format("{0:05}", -42ll));
-  EXPECT_EQ("00042", fmt::format("{0:05}", 42ull));
-  EXPECT_EQ("-000042", fmt::format("{0:07}", -42.0));
-  EXPECT_EQ("-000042", fmt::format("{0:07}", -42.0l));
+  EXPECT_EQ(fmt::format("{0:0}", 42), "42");
+  EXPECT_EQ(fmt::format("{0:05}", -42), "-0042");
+  EXPECT_EQ(fmt::format("{0:05}", 42u), "00042");
+  EXPECT_EQ(fmt::format("{0:05}", -42l), "-0042");
+  EXPECT_EQ(fmt::format("{0:05}", 42ul), "00042");
+  EXPECT_EQ(fmt::format("{0:05}", -42ll), "-0042");
+  EXPECT_EQ(fmt::format("{0:05}", 42ull), "00042");
+  EXPECT_EQ(fmt::format("{0:07}", -42.0), "-000042");
+  EXPECT_EQ(fmt::format("{0:07}", -42.0l), "-000042");
   EXPECT_THROW_MSG((void)fmt::format(runtime("{0:0"), 'c'), format_error,
                    "missing '}' in format string");
   EXPECT_THROW_MSG((void)fmt::format(runtime("{0:05}"), 'c'), format_error,
@@ -800,44 +801,33 @@
 TEST(format_test, zero_flag_and_align) {
   // If the 0 character and an align option both appear, the 0 character is
   // ignored.
-  EXPECT_EQ("42   ", fmt::format("{0:<05}", 42));
-  EXPECT_EQ("-42  ", fmt::format("{0:<05}", -42));
-  EXPECT_EQ(" 42  ", fmt::format("{0:^05}", 42));
-  EXPECT_EQ(" -42 ", fmt::format("{0:^05}", -42));
-  EXPECT_EQ("   42", fmt::format("{0:>05}", 42));
-  EXPECT_EQ("  -42", fmt::format("{0:>05}", -42));
+  EXPECT_EQ(fmt::format("{:<05}", 42), "42   ");
+  EXPECT_EQ(fmt::format("{:<05}", -42), "-42  ");
+  EXPECT_EQ(fmt::format("{:^05}", 42), " 42  ");
+  EXPECT_EQ(fmt::format("{:^05}", -42), " -42 ");
+  EXPECT_EQ(fmt::format("{:>05}", 42), "   42");
+  EXPECT_EQ(fmt::format("{:>05}", -42), "  -42");
 }
 
 TEST(format_test, width) {
-  char format_str[buffer_size];
-  safe_sprintf(format_str, "{0:%u", UINT_MAX);
-  increment(format_str + 3);
-  EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), 0), format_error,
-                   "number is too big");
-  size_t size = std::strlen(format_str);
-  format_str[size] = '}';
-  format_str[size + 1] = 0;
-  EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), 0), format_error,
-                   "number is too big");
+  auto int_maxer = std::to_string(INT_MAX + 1u);
+  EXPECT_THROW_MSG((void)fmt::format(runtime("{:" + int_maxer), 0),
+                   format_error, "number is too big");
+  EXPECT_THROW_MSG((void)fmt::format(runtime("{:" + int_maxer + "}"), 0),
+                   format_error, "number is too big");
 
-  safe_sprintf(format_str, "{0:%u", INT_MAX + 1u);
-  EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), 0), format_error,
-                   "number is too big");
-  safe_sprintf(format_str, "{0:%u}", INT_MAX + 1u);
-  EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), 0), format_error,
-                   "number is too big");
-  EXPECT_EQ(" -42", fmt::format("{0:4}", -42));
-  EXPECT_EQ("   42", fmt::format("{0:5}", 42u));
-  EXPECT_EQ("   -42", fmt::format("{0:6}", -42l));
-  EXPECT_EQ("     42", fmt::format("{0:7}", 42ul));
-  EXPECT_EQ("   -42", fmt::format("{0:6}", -42ll));
-  EXPECT_EQ("     42", fmt::format("{0:7}", 42ull));
-  EXPECT_EQ("   -1.23", fmt::format("{0:8}", -1.23));
-  EXPECT_EQ("    -1.23", fmt::format("{0:9}", -1.23l));
-  EXPECT_EQ("    0xcafe",
-            fmt::format("{0:10}", reinterpret_cast<void*>(0xcafe)));
-  EXPECT_EQ("x          ", fmt::format("{0:11}", 'x'));
-  EXPECT_EQ("str         ", fmt::format("{0:12}", "str"));
+  EXPECT_EQ(fmt::format("{:4}", -42), " -42");
+  EXPECT_EQ(fmt::format("{:5}", 42u), "   42");
+  EXPECT_EQ(fmt::format("{:6}", -42l), "   -42");
+  EXPECT_EQ(fmt::format("{:7}", 42ul), "     42");
+  EXPECT_EQ(fmt::format("{:6}", -42ll), "   -42");
+  EXPECT_EQ(fmt::format("{:7}", 42ull), "     42");
+  EXPECT_EQ(fmt::format("{:8}", -1.23), "   -1.23");
+  EXPECT_EQ(fmt::format("{:9}", -1.23l), "    -1.23");
+  EXPECT_EQ(fmt::format("{:10}", reinterpret_cast<void*>(0xcafe)),
+            "    0xcafe");
+  EXPECT_EQ(fmt::format("{:11}", 'x'), "x          ");
+  EXPECT_EQ(fmt::format("{:12}", "str"), "str         ");
   EXPECT_EQ(fmt::format("{:*^6}", "🤡"), "**🤡**");
   EXPECT_EQ(fmt::format("{:*^8}", "你好"), "**你好**");
   EXPECT_EQ(fmt::format("{:#6}", 42.0), "   42.");
@@ -846,20 +836,13 @@
 }
 
 TEST(format_test, runtime_width) {
-  char format_str[buffer_size];
-  safe_sprintf(format_str, "{0:{%u", UINT_MAX);
-  increment(format_str + 4);
-  EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), 0), format_error,
-                   "invalid format string");
-  size_t size = std::strlen(format_str);
-  format_str[size] = '}';
-  format_str[size + 1] = 0;
-  EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), 0), format_error,
-                   "argument not found");
-  format_str[size + 1] = '}';
-  format_str[size + 2] = 0;
-  EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), 0), format_error,
-                   "argument not found");
+  auto int_maxer = std::to_string(INT_MAX + 1u);
+  EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{" + int_maxer), 0),
+                   format_error, "invalid format string");
+  EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{" + int_maxer + "}"), 0),
+                   format_error, "argument not found");
+  EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{" + int_maxer + "}}"), 0),
+                   format_error, "argument not found");
 
   EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{"), 0), format_error,
                    "invalid format string");
@@ -892,18 +875,18 @@
   EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{1}}"), 0, 0.0), format_error,
                    "width is not integer");
 
-  EXPECT_EQ(" -42", fmt::format("{0:{1}}", -42, 4));
-  EXPECT_EQ("   42", fmt::format("{0:{1}}", 42u, 5));
-  EXPECT_EQ("   -42", fmt::format("{0:{1}}", -42l, 6));
-  EXPECT_EQ("     42", fmt::format("{0:{1}}", 42ul, 7));
-  EXPECT_EQ("   -42", fmt::format("{0:{1}}", -42ll, 6));
-  EXPECT_EQ("     42", fmt::format("{0:{1}}", 42ull, 7));
-  EXPECT_EQ("   -1.23", fmt::format("{0:{1}}", -1.23, 8));
-  EXPECT_EQ("    -1.23", fmt::format("{0:{1}}", -1.23l, 9));
+  EXPECT_EQ(fmt::format("{0:{1}}", -42, 4), " -42");
+  EXPECT_EQ(fmt::format("{0:{1}}", 42u, 5), "   42");
+  EXPECT_EQ(fmt::format("{0:{1}}", -42l, 6), "   -42");
+  EXPECT_EQ(fmt::format("{0:{1}}", 42ul, 7), "     42");
+  EXPECT_EQ(fmt::format("{0:{1}}", -42ll, 6), "   -42");
+  EXPECT_EQ(fmt::format("{0:{1}}", 42ull, 7), "     42");
+  EXPECT_EQ(fmt::format("{0:{1}}", -1.23, 8), "   -1.23");
+  EXPECT_EQ(fmt::format("{0:{1}}", -1.23l, 9), "    -1.23");
   EXPECT_EQ("    0xcafe",
             fmt::format("{0:{1}}", reinterpret_cast<void*>(0xcafe), 10));
-  EXPECT_EQ("x          ", fmt::format("{0:{1}}", 'x', 11));
-  EXPECT_EQ("str         ", fmt::format("{0:{1}}", "str", 12));
+  EXPECT_EQ(fmt::format("{0:{1}}", 'x', 11), "x          ");
+  EXPECT_EQ(fmt::format("{0:{1}}", "str", 12), "str         ");
   EXPECT_EQ(fmt::format("{:{}}", 42, short(4)), "  42");
 }
 
@@ -959,12 +942,12 @@
                    "invalid format specifier");
   EXPECT_THROW_MSG((void)fmt::format(runtime("{0:3.0}"), 'x'), format_error,
                    "invalid format specifier");
-  EXPECT_EQ("1.2", fmt::format("{0:.2}", 1.2345));
-  EXPECT_EQ("1.2", fmt::format("{0:.2}", 1.2345l));
-  EXPECT_EQ("1.2e+56", fmt::format("{:.2}", 1.234e56));
-  EXPECT_EQ("1.1", fmt::format("{0:.3}", 1.1));
-  EXPECT_EQ("1e+00", fmt::format("{:.0e}", 1.0L));
-  EXPECT_EQ("  0.0e+00", fmt::format("{:9.1e}", 0.0));
+  EXPECT_EQ(fmt::format("{0:.2}", 1.2345), "1.2");
+  EXPECT_EQ(fmt::format("{0:.2}", 1.2345l), "1.2");
+  EXPECT_EQ(fmt::format("{:.2}", 1.234e56), "1.2e+56");
+  EXPECT_EQ(fmt::format("{0:.3}", 1.1), "1.1");
+  EXPECT_EQ(fmt::format("{:.0e}", 1.0L), "1e+00");
+  EXPECT_EQ(fmt::format("{:9.1e}", 0.0), "  0.0e+00");
   EXPECT_EQ(
       fmt::format("{:.494}", 4.9406564584124654E-324),
       "4.9406564584124654417656879286822137236505980261432476442558568250067550"
@@ -1028,12 +1011,12 @@
         "3.6452e-4951");
   }
 
-  EXPECT_EQ("123.", fmt::format("{:#.0f}", 123.0));
-  EXPECT_EQ("1.23", fmt::format("{:.02f}", 1.234));
-  EXPECT_EQ("0.001", fmt::format("{:.1g}", 0.001));
-  EXPECT_EQ("1019666400", fmt::format("{}", 1019666432.0f));
-  EXPECT_EQ("1e+01", fmt::format("{:.0e}", 9.5));
-  EXPECT_EQ("1.0e-34", fmt::format("{:.1e}", 1e-34));
+  EXPECT_EQ(fmt::format("{:#.0f}", 123.0), "123.");
+  EXPECT_EQ(fmt::format("{:.02f}", 1.234), "1.23");
+  EXPECT_EQ(fmt::format("{:.1g}", 0.001), "0.001");
+  EXPECT_EQ(fmt::format("{}", 1019666432.0f), "1019666400");
+  EXPECT_EQ(fmt::format("{:.0e}", 9.5), "1e+01");
+  EXPECT_EQ(fmt::format("{:.1e}", 1e-34), "1.0e-34");
 
   EXPECT_THROW_MSG(
       (void)fmt::format(runtime("{0:.2}"), reinterpret_cast<void*>(0xcafe)),
@@ -1048,8 +1031,15 @@
       (void)fmt::format("{:.2147483646f}", -2.2121295195081227E+304),
       format_error, "number is too big");
 
-  EXPECT_EQ("st", fmt::format("{0:.2}", "str"));
-  EXPECT_EQ("вожык", fmt::format("{0:.5}", "вожыкі"));
+  EXPECT_EQ(fmt::format("{0:.2}", "str"), "st");
+  EXPECT_EQ(fmt::format("{0:.5}", "вожыкі"), "вожык");
+  EXPECT_EQ(fmt::format("{0:.6}", "123456\xad"), "123456");
+}
+
+TEST(xchar_test, utf8_precision) {
+  auto result = fmt::format("{:.4}", "caf\u00e9s");  // cafés
+  EXPECT_EQ(fmt::detail::compute_width(result), 4);
+  EXPECT_EQ(result, "caf\u00e9");
 }
 
 TEST(format_test, runtime_precision) {
@@ -1127,8 +1117,8 @@
                    format_error, "invalid format specifier");
   EXPECT_THROW_MSG((void)fmt::format(runtime("{0:3.{1}}"), 'x', 0),
                    format_error, "invalid format specifier");
-  EXPECT_EQ("1.2", fmt::format("{0:.{1}}", 1.2345, 2));
-  EXPECT_EQ("1.2", fmt::format("{1:.{0}}", 2, 1.2345l));
+  EXPECT_EQ(fmt::format("{0:.{1}}", 1.2345, 2), "1.2");
+  EXPECT_EQ(fmt::format("{1:.{0}}", 2, 1.2345l), "1.2");
 
   EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"),
                                      reinterpret_cast<void*>(0xcafe), 2),
@@ -1137,24 +1127,26 @@
                                      reinterpret_cast<void*>(0xcafe), 2),
                    format_error, "invalid format specifier");
 
-  EXPECT_EQ("st", fmt::format("{0:.{1}}", "str", 2));
+  EXPECT_EQ(fmt::format("{0:.{1}}", "str", 2), "st");
 }
 
 TEST(format_test, format_bool) {
-  EXPECT_EQ("true", fmt::format("{}", true));
-  EXPECT_EQ("false", fmt::format("{}", false));
-  EXPECT_EQ("1", fmt::format("{:d}", true));
-  EXPECT_EQ("true ", fmt::format("{:5}", true));
-  EXPECT_EQ("true", fmt::format("{:s}", true));
-  EXPECT_EQ("false", fmt::format("{:s}", false));
-  EXPECT_EQ("false ", fmt::format("{:6s}", false));
+  EXPECT_EQ(fmt::format("{}", true), "true");
+  EXPECT_EQ(fmt::format("{}", false), "false");
+  EXPECT_EQ(fmt::format("{:d}", true), "1");
+  EXPECT_EQ(fmt::format("{:5}", true), "true ");
+  EXPECT_EQ(fmt::format("{:s}", true), "true");
+  EXPECT_EQ(fmt::format("{:s}", false), "false");
+  EXPECT_EQ(fmt::format("{:6s}", false), "false ");
+  EXPECT_THROW_MSG((void)fmt::format(runtime("{:c}"), false), format_error,
+                   "invalid format specifier");
 }
 
 TEST(format_test, format_short) {
   short s = 42;
-  EXPECT_EQ("42", fmt::format("{0:d}", s));
+  EXPECT_EQ(fmt::format("{0:d}", s), "42");
   unsigned short us = 42;
-  EXPECT_EQ("42", fmt::format("{0:d}", us));
+  EXPECT_EQ(fmt::format("{0:d}", us), "42");
 }
 
 template <typename T>
@@ -1176,16 +1168,16 @@
   EXPECT_THROW_MSG((void)fmt::format(runtime("{0:v"), 42), format_error,
                    "invalid format specifier");
   check_unknown_types(42, "bBdoxXnLc", "integer");
-  EXPECT_EQ("x", fmt::format("{:c}", static_cast<int>('x')));
+  EXPECT_EQ(fmt::format("{:c}", static_cast<int>('x')), "x");
 }
 
 TEST(format_test, format_bin) {
-  EXPECT_EQ("0", fmt::format("{0:b}", 0));
-  EXPECT_EQ("101010", fmt::format("{0:b}", 42));
-  EXPECT_EQ("101010", fmt::format("{0:b}", 42u));
-  EXPECT_EQ("-101010", fmt::format("{0:b}", -42));
-  EXPECT_EQ("11000000111001", fmt::format("{0:b}", 12345));
-  EXPECT_EQ("10010001101000101011001111000", fmt::format("{0:b}", 0x12345678));
+  EXPECT_EQ(fmt::format("{0:b}", 0), "0");
+  EXPECT_EQ(fmt::format("{0:b}", 42), "101010");
+  EXPECT_EQ(fmt::format("{0:b}", 42u), "101010");
+  EXPECT_EQ(fmt::format("{0:b}", -42), "-101010");
+  EXPECT_EQ(fmt::format("{0:b}", 12345), "11000000111001");
+  EXPECT_EQ(fmt::format("{0:b}", 0x12345678), "10010001101000101011001111000");
   EXPECT_EQ("10010000101010111100110111101111",
             fmt::format("{0:b}", 0x90ABCDEF));
   EXPECT_EQ("11111111111111111111111111111111",
@@ -1201,17 +1193,17 @@
 #endif
 
 TEST(format_test, format_dec) {
-  EXPECT_EQ("0", fmt::format("{0}", 0));
-  EXPECT_EQ("42", fmt::format("{0}", 42));
-  EXPECT_EQ("42>", fmt::format("{:}>", 42));
-  EXPECT_EQ("42", fmt::format("{0:d}", 42));
-  EXPECT_EQ("42", fmt::format("{0}", 42u));
-  EXPECT_EQ("-42", fmt::format("{0}", -42));
-  EXPECT_EQ("12345", fmt::format("{0}", 12345));
-  EXPECT_EQ("67890", fmt::format("{0}", 67890));
+  EXPECT_EQ(fmt::format("{0}", 0), "0");
+  EXPECT_EQ(fmt::format("{0}", 42), "42");
+  EXPECT_EQ(fmt::format("{:}>", 42), "42>");
+  EXPECT_EQ(fmt::format("{0:d}", 42), "42");
+  EXPECT_EQ(fmt::format("{0}", 42u), "42");
+  EXPECT_EQ(fmt::format("{0}", -42), "-42");
+  EXPECT_EQ(fmt::format("{0}", 12345), "12345");
+  EXPECT_EQ(fmt::format("{0}", 67890), "67890");
 #if FMT_USE_INT128
-  EXPECT_EQ("0", fmt::format("{0}", static_cast<__int128_t>(0)));
-  EXPECT_EQ("0", fmt::format("{0}", static_cast<__uint128_t>(0)));
+  EXPECT_EQ(fmt::format("{0}", static_cast<__int128_t>(0)), "0");
+  EXPECT_EQ(fmt::format("{0}", static_cast<__uint128_t>(0)), "0");
   EXPECT_EQ("9223372036854775808",
             fmt::format("{0}", static_cast<__int128_t>(INT64_MAX) + 1));
   EXPECT_EQ("-9223372036854775809",
@@ -1242,17 +1234,17 @@
 }
 
 TEST(format_test, format_hex) {
-  EXPECT_EQ("0", fmt::format("{0:x}", 0));
-  EXPECT_EQ("42", fmt::format("{0:x}", 0x42));
-  EXPECT_EQ("42", fmt::format("{0:x}", 0x42u));
-  EXPECT_EQ("-42", fmt::format("{0:x}", -0x42));
-  EXPECT_EQ("12345678", fmt::format("{0:x}", 0x12345678));
-  EXPECT_EQ("90abcdef", fmt::format("{0:x}", 0x90abcdef));
-  EXPECT_EQ("12345678", fmt::format("{0:X}", 0x12345678));
-  EXPECT_EQ("90ABCDEF", fmt::format("{0:X}", 0x90ABCDEF));
+  EXPECT_EQ(fmt::format("{0:x}", 0), "0");
+  EXPECT_EQ(fmt::format("{0:x}", 0x42), "42");
+  EXPECT_EQ(fmt::format("{0:x}", 0x42u), "42");
+  EXPECT_EQ(fmt::format("{0:x}", -0x42), "-42");
+  EXPECT_EQ(fmt::format("{0:x}", 0x12345678), "12345678");
+  EXPECT_EQ(fmt::format("{0:x}", 0x90abcdef), "90abcdef");
+  EXPECT_EQ(fmt::format("{0:X}", 0x12345678), "12345678");
+  EXPECT_EQ(fmt::format("{0:X}", 0x90ABCDEF), "90ABCDEF");
 #if FMT_USE_INT128
-  EXPECT_EQ("0", fmt::format("{0:x}", static_cast<__int128_t>(0)));
-  EXPECT_EQ("0", fmt::format("{0:x}", static_cast<__uint128_t>(0)));
+  EXPECT_EQ(fmt::format("{0:x}", static_cast<__int128_t>(0)), "0");
+  EXPECT_EQ(fmt::format("{0:x}", static_cast<__uint128_t>(0)), "0");
   EXPECT_EQ("8000000000000000",
             fmt::format("{0:x}", static_cast<__int128_t>(INT64_MAX) + 1));
   EXPECT_EQ("-8000000000000001",
@@ -1283,14 +1275,14 @@
 }
 
 TEST(format_test, format_oct) {
-  EXPECT_EQ("0", fmt::format("{0:o}", 0));
-  EXPECT_EQ("42", fmt::format("{0:o}", 042));
-  EXPECT_EQ("42", fmt::format("{0:o}", 042u));
-  EXPECT_EQ("-42", fmt::format("{0:o}", -042));
-  EXPECT_EQ("12345670", fmt::format("{0:o}", 012345670));
+  EXPECT_EQ(fmt::format("{0:o}", 0), "0");
+  EXPECT_EQ(fmt::format("{0:o}", 042), "42");
+  EXPECT_EQ(fmt::format("{0:o}", 042u), "42");
+  EXPECT_EQ(fmt::format("{0:o}", -042), "-42");
+  EXPECT_EQ(fmt::format("{0:o}", 012345670), "12345670");
 #if FMT_USE_INT128
-  EXPECT_EQ("0", fmt::format("{0:o}", static_cast<__int128_t>(0)));
-  EXPECT_EQ("0", fmt::format("{0:o}", static_cast<__uint128_t>(0)));
+  EXPECT_EQ(fmt::format("{0:o}", static_cast<__int128_t>(0)), "0");
+  EXPECT_EQ(fmt::format("{0:o}", static_cast<__uint128_t>(0)), "0");
   EXPECT_EQ("1000000000000000000000",
             fmt::format("{0:o}", static_cast<__int128_t>(INT64_MAX) + 1));
   EXPECT_EQ("-1000000000000000000001",
@@ -1321,12 +1313,12 @@
 }
 
 TEST(format_test, format_int_locale) {
-  EXPECT_EQ("1234", fmt::format("{:L}", 1234));
+  EXPECT_EQ(fmt::format("{:L}", 1234), "1234");
 }
 
 TEST(format_test, format_float) {
-  EXPECT_EQ("0", fmt::format("{}", 0.0f));
-  EXPECT_EQ("392.500000", fmt::format("{0:f}", 392.5f));
+  EXPECT_EQ(fmt::format("{}", 0.0f), "0");
+  EXPECT_EQ(fmt::format("{0:f}", 392.5f), "392.500000");
 }
 
 TEST(format_test, format_double) {
@@ -1348,14 +1340,11 @@
   EXPECT_EQ(fmt::format("{0:e}", 392.65), "3.926500e+02");
   EXPECT_EQ(fmt::format("{0:E}", 392.65), "3.926500E+02");
   EXPECT_EQ(fmt::format("{0:+010.4g}", 392.65), "+0000392.6");
-  char buffer[buffer_size];
 
 #if FMT_CPLUSPLUS >= 201703L
   double xd = 0x1.ffffffffffp+2;
-  safe_sprintf(buffer, "%.*a", 10, xd);
-  EXPECT_EQ(fmt::format("{:.10a}", xd), buffer);
-  safe_sprintf(buffer, "%.*a", 9, xd);
-  EXPECT_EQ(fmt::format("{:.9a}", xd), buffer);
+  EXPECT_EQ(fmt::format("{:.10a}", xd), "0x1.ffffffffffp+2");
+  EXPECT_EQ(fmt::format("{:.9a}", xd), "0x2.000000000p+2");
 
   if (std::numeric_limits<long double>::digits == 64) {
     auto ld = 0xf.ffffffffffp-3l;
@@ -1371,8 +1360,7 @@
     EXPECT_EQ(fmt::format("{:#a}", d), "0x1.p-1022");
 
     d = (std::numeric_limits<double>::max)();
-    safe_sprintf(buffer, "%a", d);
-    EXPECT_EQ(fmt::format("{:a}", d), buffer);
+    EXPECT_EQ(fmt::format("{:a}", d), "0x1.fffffffffffffp+1023");
 
     d = std::numeric_limits<double>::denorm_min();
     EXPECT_EQ(fmt::format("{:a}", d), "0x0.0000000000001p-1022");
@@ -1389,8 +1377,7 @@
     EXPECT_EQ(fmt::format("{:a}", ld), "0x0.000000000000001p-16382");
   }
 
-  safe_sprintf(buffer, "%.*a", 10, 4.2);
-  EXPECT_EQ(fmt::format("{:.10a}", 4.2), buffer);
+  EXPECT_EQ(fmt::format("{:.10a}", 4.2), "0x1.0ccccccccdp+2");
 
   EXPECT_EQ(fmt::format("{:a}", -42.0), "-0x1.5p+5");
   EXPECT_EQ(fmt::format("{:A}", -42.0), "-0X1.5P+5");
@@ -1400,92 +1387,92 @@
 }
 
 TEST(format_test, precision_rounding) {
-  EXPECT_EQ("0", fmt::format("{:.0f}", 0.0));
-  EXPECT_EQ("0", fmt::format("{:.0f}", 0.01));
-  EXPECT_EQ("0", fmt::format("{:.0f}", 0.1));
-  EXPECT_EQ("0.000", fmt::format("{:.3f}", 0.00049));
-  EXPECT_EQ("0.001", fmt::format("{:.3f}", 0.0005));
-  EXPECT_EQ("0.001", fmt::format("{:.3f}", 0.00149));
-  EXPECT_EQ("0.002", fmt::format("{:.3f}", 0.0015));
-  EXPECT_EQ("1.000", fmt::format("{:.3f}", 0.9999));
-  EXPECT_EQ("0.00123", fmt::format("{:.3}", 0.00123));
-  EXPECT_EQ("0.1", fmt::format("{:.16g}", 0.1));
-  EXPECT_EQ("1", fmt::format("{:.0}", 1.0));
+  EXPECT_EQ(fmt::format("{:.0f}", 0.0), "0");
+  EXPECT_EQ(fmt::format("{:.0f}", 0.01), "0");
+  EXPECT_EQ(fmt::format("{:.0f}", 0.1), "0");
+  EXPECT_EQ(fmt::format("{:.3f}", 0.00049), "0.000");
+  EXPECT_EQ(fmt::format("{:.3f}", 0.0005), "0.001");
+  EXPECT_EQ(fmt::format("{:.3f}", 0.00149), "0.001");
+  EXPECT_EQ(fmt::format("{:.3f}", 0.0015), "0.002");
+  EXPECT_EQ(fmt::format("{:.3f}", 0.9999), "1.000");
+  EXPECT_EQ(fmt::format("{:.3}", 0.00123), "0.00123");
+  EXPECT_EQ(fmt::format("{:.16g}", 0.1), "0.1");
+  EXPECT_EQ(fmt::format("{:.0}", 1.0), "1");
   EXPECT_EQ("225.51575035152063720",
             fmt::format("{:.17f}", 225.51575035152064));
-  EXPECT_EQ("-761519619559038.2", fmt::format("{:.1f}", -761519619559038.2));
+  EXPECT_EQ(fmt::format("{:.1f}", -761519619559038.2), "-761519619559038.2");
   EXPECT_EQ("1.9156918820264798e-56",
             fmt::format("{}", 1.9156918820264798e-56));
-  EXPECT_EQ("0.0000", fmt::format("{:.4f}", 7.2809479766055470e-15));
+  EXPECT_EQ(fmt::format("{:.4f}", 7.2809479766055470e-15), "0.0000");
 }
 
 TEST(format_test, prettify_float) {
-  EXPECT_EQ("0.0001", fmt::format("{}", 1e-4));
-  EXPECT_EQ("1e-05", fmt::format("{}", 1e-5));
-  EXPECT_EQ("1000000000000000", fmt::format("{}", 1e15));
-  EXPECT_EQ("1e+16", fmt::format("{}", 1e16));
-  EXPECT_EQ("9.999e-05", fmt::format("{}", 9.999e-5));
-  EXPECT_EQ("10000000000", fmt::format("{}", 1e10));
-  EXPECT_EQ("100000000000", fmt::format("{}", 1e11));
-  EXPECT_EQ("12340000000", fmt::format("{}", 1234e7));
-  EXPECT_EQ("12.34", fmt::format("{}", 1234e-2));
-  EXPECT_EQ("0.001234", fmt::format("{}", 1234e-6));
-  EXPECT_EQ("0.1", fmt::format("{}", 0.1f));
-  EXPECT_EQ("1.3563156e-19", fmt::format("{}", 1.35631564e-19f));
+  EXPECT_EQ(fmt::format("{}", 1e-4), "0.0001");
+  EXPECT_EQ(fmt::format("{}", 1e-5), "1e-05");
+  EXPECT_EQ(fmt::format("{}", 1e15), "1000000000000000");
+  EXPECT_EQ(fmt::format("{}", 1e16), "1e+16");
+  EXPECT_EQ(fmt::format("{}", 9.999e-5), "9.999e-05");
+  EXPECT_EQ(fmt::format("{}", 1e10), "10000000000");
+  EXPECT_EQ(fmt::format("{}", 1e11), "100000000000");
+  EXPECT_EQ(fmt::format("{}", 1234e7), "12340000000");
+  EXPECT_EQ(fmt::format("{}", 1234e-2), "12.34");
+  EXPECT_EQ(fmt::format("{}", 1234e-6), "0.001234");
+  EXPECT_EQ(fmt::format("{}", 0.1f), "0.1");
+  EXPECT_EQ(fmt::format("{}", 1.35631564e-19f), "1.3563156e-19");
 }
 
 TEST(format_test, format_nan) {
   double nan = std::numeric_limits<double>::quiet_NaN();
-  EXPECT_EQ("nan", fmt::format("{}", nan));
-  EXPECT_EQ("+nan", fmt::format("{:+}", nan));
-  EXPECT_EQ("  +nan", fmt::format("{:+06}", nan));
-  EXPECT_EQ("+nan  ", fmt::format("{:<+06}", nan));
-  EXPECT_EQ(" +nan ", fmt::format("{:^+06}", nan));
-  EXPECT_EQ("  +nan", fmt::format("{:>+06}", nan));
+  EXPECT_EQ(fmt::format("{}", nan), "nan");
+  EXPECT_EQ(fmt::format("{:+}", nan), "+nan");
+  EXPECT_EQ(fmt::format("{:+06}", nan), "  +nan");
+  EXPECT_EQ(fmt::format("{:<+06}", nan), "+nan  ");
+  EXPECT_EQ(fmt::format("{:^+06}", nan), " +nan ");
+  EXPECT_EQ(fmt::format("{:>+06}", nan), "  +nan");
   if (std::signbit(-nan)) {
-    EXPECT_EQ("-nan", fmt::format("{}", -nan));
-    EXPECT_EQ("  -nan", fmt::format("{:+06}", -nan));
+    EXPECT_EQ(fmt::format("{}", -nan), "-nan");
+    EXPECT_EQ(fmt::format("{:+06}", -nan), "  -nan");
   } else {
     fmt::print("Warning: compiler doesn't handle negative NaN correctly");
   }
-  EXPECT_EQ(" nan", fmt::format("{: }", nan));
-  EXPECT_EQ("NAN", fmt::format("{:F}", nan));
-  EXPECT_EQ("nan    ", fmt::format("{:<7}", nan));
-  EXPECT_EQ("  nan  ", fmt::format("{:^7}", nan));
-  EXPECT_EQ("    nan", fmt::format("{:>7}", nan));
+  EXPECT_EQ(fmt::format("{: }", nan), " nan");
+  EXPECT_EQ(fmt::format("{:F}", nan), "NAN");
+  EXPECT_EQ(fmt::format("{:<7}", nan), "nan    ");
+  EXPECT_EQ(fmt::format("{:^7}", nan), "  nan  ");
+  EXPECT_EQ(fmt::format("{:>7}", nan), "    nan");
 }
 
 TEST(format_test, format_infinity) {
   double inf = std::numeric_limits<double>::infinity();
-  EXPECT_EQ("inf", fmt::format("{}", inf));
-  EXPECT_EQ("+inf", fmt::format("{:+}", inf));
-  EXPECT_EQ("-inf", fmt::format("{}", -inf));
-  EXPECT_EQ("  +inf", fmt::format("{:+06}", inf));
-  EXPECT_EQ("  -inf", fmt::format("{:+06}", -inf));
-  EXPECT_EQ("+inf  ", fmt::format("{:<+06}", inf));
-  EXPECT_EQ(" +inf ", fmt::format("{:^+06}", inf));
-  EXPECT_EQ("  +inf", fmt::format("{:>+06}", inf));
-  EXPECT_EQ(" inf", fmt::format("{: }", inf));
-  EXPECT_EQ("INF", fmt::format("{:F}", inf));
-  EXPECT_EQ("inf    ", fmt::format("{:<7}", inf));
-  EXPECT_EQ("  inf  ", fmt::format("{:^7}", inf));
-  EXPECT_EQ("    inf", fmt::format("{:>7}", inf));
+  EXPECT_EQ(fmt::format("{}", inf), "inf");
+  EXPECT_EQ(fmt::format("{:+}", inf), "+inf");
+  EXPECT_EQ(fmt::format("{}", -inf), "-inf");
+  EXPECT_EQ(fmt::format("{:+06}", inf), "  +inf");
+  EXPECT_EQ(fmt::format("{:+06}", -inf), "  -inf");
+  EXPECT_EQ(fmt::format("{:<+06}", inf), "+inf  ");
+  EXPECT_EQ(fmt::format("{:^+06}", inf), " +inf ");
+  EXPECT_EQ(fmt::format("{:>+06}", inf), "  +inf");
+  EXPECT_EQ(fmt::format("{: }", inf), " inf");
+  EXPECT_EQ(fmt::format("{:F}", inf), "INF");
+  EXPECT_EQ(fmt::format("{:<7}", inf), "inf    ");
+  EXPECT_EQ(fmt::format("{:^7}", inf), "  inf  ");
+  EXPECT_EQ(fmt::format("{:>7}", inf), "    inf");
 }
 
 TEST(format_test, format_long_double) {
-  EXPECT_EQ("0", fmt::format("{0:}", 0.0l));
-  EXPECT_EQ("0.000000", fmt::format("{0:f}", 0.0l));
-  EXPECT_EQ("0.0", fmt::format("{:.1f}", 0.000000001l));
-  EXPECT_EQ("0.10", fmt::format("{:.2f}", 0.099l));
-  EXPECT_EQ("392.65", fmt::format("{0:}", 392.65l));
-  EXPECT_EQ("392.65", fmt::format("{0:g}", 392.65l));
-  EXPECT_EQ("392.65", fmt::format("{0:G}", 392.65l));
-  EXPECT_EQ("392.650000", fmt::format("{0:f}", 392.65l));
-  EXPECT_EQ("392.650000", fmt::format("{0:F}", 392.65l));
+  EXPECT_EQ(fmt::format("{0:}", 0.0l), "0");
+  EXPECT_EQ(fmt::format("{0:f}", 0.0l), "0.000000");
+  EXPECT_EQ(fmt::format("{:.1f}", 0.000000001l), "0.0");
+  EXPECT_EQ(fmt::format("{:.2f}", 0.099l), "0.10");
+  EXPECT_EQ(fmt::format("{0:}", 392.65l), "392.65");
+  EXPECT_EQ(fmt::format("{0:g}", 392.65l), "392.65");
+  EXPECT_EQ(fmt::format("{0:G}", 392.65l), "392.65");
+  EXPECT_EQ(fmt::format("{0:f}", 392.65l), "392.650000");
+  EXPECT_EQ(fmt::format("{0:F}", 392.65l), "392.650000");
   char buffer[buffer_size];
   safe_sprintf(buffer, "%Le", 392.65l);
   EXPECT_EQ(buffer, fmt::format("{0:e}", 392.65l));
-  EXPECT_EQ("+0000392.6", fmt::format("{0:+010.4g}", 392.64l));
+  EXPECT_EQ(fmt::format("{0:+010.4g}", 392.64l), "+0000392.6");
 
   auto ld = 3.31l;
   if (fmt::detail::is_double_double<decltype(ld)>::value) {
@@ -1499,8 +1486,8 @@
 TEST(format_test, format_char) {
   const char types[] = "cbBdoxX";
   check_unknown_types('a', types, "char");
-  EXPECT_EQ("a", fmt::format("{0}", 'a'));
-  EXPECT_EQ("z", fmt::format("{0:c}", 'z'));
+  EXPECT_EQ(fmt::format("{0}", 'a'), "a");
+  EXPECT_EQ(fmt::format("{0:c}", 'z'), "z");
   int n = 'x';
   for (const char* type = types + 1; *type; ++type) {
     std::string format_str = fmt::format("{{:{}}}", *type);
@@ -1510,39 +1497,41 @@
   }
   EXPECT_EQ(fmt::format("{:02X}", n), fmt::format("{:02X}", 'x'));
 
-  EXPECT_EQ("\n", fmt::format("{}", '\n'));
-  EXPECT_EQ("'\\n'", fmt::format("{:?}", '\n'));
-  EXPECT_EQ("ff", fmt::format("{:x}", '\xff'));
+  EXPECT_EQ(fmt::format("{}", '\n'), "\n");
+  EXPECT_EQ(fmt::format("{:?}", '\n'), "'\\n'");
+  EXPECT_EQ(fmt::format("{:x}", '\xff'), "ff");
 }
 
 TEST(format_test, format_volatile_char) {
   volatile char c = 'x';
-  EXPECT_EQ("x", fmt::format("{}", c));
+  EXPECT_EQ(fmt::format("{}", c), "x");
 }
 
 TEST(format_test, format_unsigned_char) {
-  EXPECT_EQ("42", fmt::format("{}", static_cast<unsigned char>(42)));
-  EXPECT_EQ("42", fmt::format("{}", static_cast<uint8_t>(42)));
+  EXPECT_EQ(fmt::format("{}", static_cast<unsigned char>(42)), "42");
+  EXPECT_EQ(fmt::format("{}", static_cast<uint8_t>(42)), "42");
 }
 
 TEST(format_test, format_cstring) {
   check_unknown_types("test", "sp", "string");
-  EXPECT_EQ("test", fmt::format("{0}", "test"));
-  EXPECT_EQ("test", fmt::format("{0:s}", "test"));
+  EXPECT_EQ(fmt::format("{0}", "test"), "test");
+  EXPECT_EQ(fmt::format("{0:s}", "test"), "test");
   char nonconst[] = "nonconst";
-  EXPECT_EQ("nonconst", fmt::format("{0}", nonconst));
-  EXPECT_THROW_MSG(
-      (void)fmt::format(runtime("{0}"), static_cast<const char*>(nullptr)),
-      format_error, "string pointer is null");
+  EXPECT_EQ(fmt::format("{0}", nonconst), "nonconst");
+  auto nullstr = static_cast<const char*>(nullptr);
+  EXPECT_THROW_MSG((void)fmt::format("{}", nullstr), format_error,
+                   "string pointer is null");
+  EXPECT_THROW_MSG((void)fmt::format("{:s}", nullstr), format_error,
+                   "string pointer is null");
 }
 
 void function_pointer_test(int, double, std::string) {}
 
 TEST(format_test, format_pointer) {
   check_unknown_types(reinterpret_cast<void*>(0x1234), "p", "pointer");
-  EXPECT_EQ("0x0", fmt::format("{0}", static_cast<void*>(nullptr)));
-  EXPECT_EQ("0x1234", fmt::format("{0}", reinterpret_cast<void*>(0x1234)));
-  EXPECT_EQ("0x1234", fmt::format("{0:p}", reinterpret_cast<void*>(0x1234)));
+  EXPECT_EQ(fmt::format("{0}", static_cast<void*>(nullptr)), "0x0");
+  EXPECT_EQ(fmt::format("{0}", reinterpret_cast<void*>(0x1234)), "0x1234");
+  EXPECT_EQ(fmt::format("{0:p}", reinterpret_cast<void*>(0x1234)), "0x1234");
   // On CHERI (or other fat-pointer) systems, the size of a pointer is greater
   // than the size an integer that can hold a virtual address.  There is no
   // portable address-as-an-integer type (yet) in C++, so we use `size_t` as
@@ -1566,7 +1555,7 @@
   EXPECT_EQ(fmt::format("{}", fmt::detail::bit_cast<const void*>(
                                   &function_pointer_test)),
             fmt::format("{}", fmt::ptr(function_pointer_test)));
-  EXPECT_EQ("0x0", fmt::format("{}", nullptr));
+  EXPECT_EQ(fmt::format("{}", nullptr), "0x0");
 }
 
 TEST(format_test, write_uintptr_fallback) {
@@ -1604,9 +1593,9 @@
 }
 
 TEST(format_test, format_string_view) {
-  EXPECT_EQ("test", fmt::format("{}", string_view("test")));
-  EXPECT_EQ("\"t\\nst\"", fmt::format("{:?}", string_view("t\nst")));
-  EXPECT_EQ("", fmt::format("{}", string_view()));
+  EXPECT_EQ(fmt::format("{}", string_view("test")), "test");
+  EXPECT_EQ(fmt::format("{:?}", string_view("t\nst")), "\"t\\nst\"");
+  EXPECT_EQ(fmt::format("{}", string_view()), "");
 }
 
 #ifdef FMT_USE_STRING_VIEW
@@ -1621,8 +1610,8 @@
 FMT_END_NAMESPACE
 
 TEST(format_test, format_std_string_view) {
-  EXPECT_EQ("test", fmt::format("{}", std::string_view("test")));
-  EXPECT_EQ("foo", fmt::format("{}", string_viewable()));
+  EXPECT_EQ(fmt::format("{}", std::string_view("test")), "test");
+  EXPECT_EQ(fmt::format("{}", string_viewable()), "foo");
 }
 
 struct explicitly_convertible_to_std_string_view {
@@ -1673,8 +1662,8 @@
 TEST(format_test, format_custom) {
   EXPECT_THROW_MSG((void)fmt::format(runtime("{:s}"), date(2012, 12, 9)),
                    format_error, "unknown format specifier");
-  EXPECT_EQ("42", fmt::format("{0}", Answer()));
-  EXPECT_EQ("0042", fmt::format("{:04}", Answer()));
+  EXPECT_EQ(fmt::format("{0}", Answer()), "42");
+  EXPECT_EQ(fmt::format("{:04}", Answer()), "0042");
 }
 
 TEST(format_test, format_to_custom) {
@@ -1694,7 +1683,7 @@
   std::string message = fmt::format("The answer is {}", 42);
   EXPECT_EQ("The answer is 42", message);
 
-  EXPECT_EQ("42", fmt::format("{}", 42));
+  EXPECT_EQ(fmt::format("{}", 42), "42");
 
   memory_buffer out;
   fmt::format_to(std::back_inserter(out), "The answer is {}.", 42);
@@ -1716,17 +1705,17 @@
 
   EXPECT_EQ("First, thou shalt count to three",
             fmt::format("First, thou shalt count to {0}", "three"));
-  EXPECT_EQ("Bring me a shrubbery", fmt::format("Bring me a {}", "shrubbery"));
-  EXPECT_EQ("From 1 to 3", fmt::format("From {} to {}", 1, 3));
+  EXPECT_EQ(fmt::format("Bring me a {}", "shrubbery"), "Bring me a shrubbery");
+  EXPECT_EQ(fmt::format("From {} to {}", 1, 3), "From 1 to 3");
 
   char buffer[buffer_size];
   safe_sprintf(buffer, "%03.2f", -1.2);
   EXPECT_EQ(buffer, fmt::format("{:03.2f}", -1.2));
 
-  EXPECT_EQ("a, b, c", fmt::format("{0}, {1}, {2}", 'a', 'b', 'c'));
-  EXPECT_EQ("a, b, c", fmt::format("{}, {}, {}", 'a', 'b', 'c'));
-  EXPECT_EQ("c, b, a", fmt::format("{2}, {1}, {0}", 'a', 'b', 'c'));
-  EXPECT_EQ("abracadabra", fmt::format("{0}{1}{0}", "abra", "cad"));
+  EXPECT_EQ(fmt::format("{0}, {1}, {2}", 'a', 'b', 'c'), "a, b, c");
+  EXPECT_EQ(fmt::format("{}, {}, {}", 'a', 'b', 'c'), "a, b, c");
+  EXPECT_EQ(fmt::format("{2}, {1}, {0}", 'a', 'b', 'c'), "c, b, a");
+  EXPECT_EQ(fmt::format("{0}{1}{0}", "abra", "cad"), "abracadabra");
 
   EXPECT_EQ("left aligned                  ",
             fmt::format("{:<30}", "left aligned"));
@@ -1737,16 +1726,16 @@
   EXPECT_EQ("***********centered***********",
             fmt::format("{:*^30}", "centered"));
 
-  EXPECT_EQ("+3.140000; -3.140000", fmt::format("{:+f}; {:+f}", 3.14, -3.14));
-  EXPECT_EQ(" 3.140000; -3.140000", fmt::format("{: f}; {: f}", 3.14, -3.14));
-  EXPECT_EQ("3.140000; -3.140000", fmt::format("{:-f}; {:-f}", 3.14, -3.14));
+  EXPECT_EQ(fmt::format("{:+f}; {:+f}", 3.14, -3.14), "+3.140000; -3.140000");
+  EXPECT_EQ(fmt::format("{: f}; {: f}", 3.14, -3.14), " 3.140000; -3.140000");
+  EXPECT_EQ(fmt::format("{:-f}; {:-f}", 3.14, -3.14), "3.140000; -3.140000");
 
   EXPECT_EQ("int: 42;  hex: 2a;  oct: 52",
             fmt::format("int: {0:d};  hex: {0:x};  oct: {0:o}", 42));
   EXPECT_EQ("int: 42;  hex: 0x2a;  oct: 052",
             fmt::format("int: {0:d};  hex: {0:#x};  oct: {0:#o}", 42));
 
-  EXPECT_EQ("The answer is 42", fmt::format("The answer is {}", 42));
+  EXPECT_EQ(fmt::format("The answer is {}", 42), "The answer is 42");
   EXPECT_THROW_MSG(
       (void)fmt::format(runtime("The answer is {:d}"), "forty-two"),
       format_error, "invalid format specifier");
@@ -1765,7 +1754,7 @@
 }
 
 TEST(format_test, variadic) {
-  EXPECT_EQ("abc1", fmt::format("{}c{}", "ab", 1));
+  EXPECT_EQ(fmt::format("{}c{}", "ab", 1), "abc1");
 }
 
 TEST(format_test, bytes) {
@@ -1810,23 +1799,23 @@
   v2.push_back(3.4f);
   void* v3[2] = {&v1[0], &v1[1]};
 
-  EXPECT_EQ("(1, 2, 3)", fmt::format("({})", join(v1, v1 + 3, ", ")));
-  EXPECT_EQ("(1)", fmt::format("({})", join(v1, v1 + 1, ", ")));
-  EXPECT_EQ("()", fmt::format("({})", join(v1, v1, ", ")));
-  EXPECT_EQ("(001, 002, 003)", fmt::format("({:03})", join(v1, v1 + 3, ", ")));
+  EXPECT_EQ(fmt::format("({})", join(v1, v1 + 3, ", ")), "(1, 2, 3)");
+  EXPECT_EQ(fmt::format("({})", join(v1, v1 + 1, ", ")), "(1)");
+  EXPECT_EQ(fmt::format("({})", join(v1, v1, ", ")), "()");
+  EXPECT_EQ(fmt::format("({:03})", join(v1, v1 + 3, ", ")), "(001, 002, 003)");
   EXPECT_EQ("(+01.20, +03.40)",
             fmt::format("({:+06.2f})", join(v2.begin(), v2.end(), ", ")));
 
-  EXPECT_EQ("1, 2, 3", fmt::format("{0:{1}}", join(v1, v1 + 3, ", "), 1));
+  EXPECT_EQ(fmt::format("{0:{1}}", join(v1, v1 + 3, ", "), 1), "1, 2, 3");
 
   EXPECT_EQ(fmt::format("{}, {}", v3[0], v3[1]),
             fmt::format("{}", join(v3, v3 + 2, ", ")));
 
-  EXPECT_EQ("(1, 2, 3)", fmt::format("({})", join(v1, ", ")));
-  EXPECT_EQ("(+01.20, +03.40)", fmt::format("({:+06.2f})", join(v2, ", ")));
+  EXPECT_EQ(fmt::format("({})", join(v1, ", ")), "(1, 2, 3)");
+  EXPECT_EQ(fmt::format("({:+06.2f})", join(v2, ", ")), "(+01.20, +03.40)");
 
   auto v4 = std::vector<test_enum>{foo, bar, foo};
-  EXPECT_EQ("0 1 0", fmt::format("{}", join(v4, " ")));
+  EXPECT_EQ(fmt::format("{}", join(v4, " ")), "0 1 0");
 }
 
 #ifdef __cpp_lib_byte
@@ -1873,45 +1862,45 @@
 static constexpr const char static_no_null[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(fmt::format(FMT_STRING("foo")), "foo");
+  EXPECT_EQ(fmt::format(FMT_STRING("{}"), 42), "42");
 
 #if FMT_USE_NONTYPE_TEMPLATE_ARGS
   using namespace fmt::literals;
   EXPECT_EQ("foobar", fmt::format(FMT_STRING("{foo}{bar}"), "bar"_a = "bar",
                                   "foo"_a = "foo"));
-  EXPECT_EQ("", fmt::format(FMT_STRING("")));
-  EXPECT_EQ("", fmt::format(FMT_STRING(""), "arg"_a = 42));
-  EXPECT_EQ("42", fmt::format(FMT_STRING("{answer}"), "answer"_a = Answer()));
-  EXPECT_EQ("1 2", fmt::format(FMT_STRING("{} {two}"), 1, "two"_a = 2));
+  EXPECT_EQ(fmt::format(FMT_STRING("")), "");
+  EXPECT_EQ(fmt::format(FMT_STRING(""), "arg"_a = 42), "");
+  EXPECT_EQ(fmt::format(FMT_STRING("{answer}"), "answer"_a = Answer()), "42");
+  EXPECT_EQ(fmt::format(FMT_STRING("{} {two}"), 1, "two"_a = 2), "1 2");
 #endif
 
   (void)static_with_null;
   (void)static_no_null;
 #ifndef _MSC_VER
-  EXPECT_EQ("42", fmt::format(FMT_STRING(static_with_null), 42));
-  EXPECT_EQ("42", fmt::format(FMT_STRING(static_no_null), 42));
+  EXPECT_EQ(fmt::format(FMT_STRING(static_with_null), 42), "42");
+  EXPECT_EQ(fmt::format(FMT_STRING(static_no_null), 42), "42");
 #endif
 
   (void)with_null;
   (void)no_null;
 #if FMT_CPLUSPLUS >= 201703L
-  EXPECT_EQ("42", fmt::format(FMT_STRING(with_null), 42));
-  EXPECT_EQ("42", fmt::format(FMT_STRING(no_null), 42));
+  EXPECT_EQ(fmt::format(FMT_STRING(with_null), 42), "42");
+  EXPECT_EQ(fmt::format(FMT_STRING(no_null), 42), "42");
 #endif
 #if defined(FMT_USE_STRING_VIEW) && FMT_CPLUSPLUS >= 201703L
-  EXPECT_EQ("42", fmt::format(FMT_STRING(std::string_view("{}")), 42));
+  EXPECT_EQ(fmt::format(FMT_STRING(std::string_view("{}")), 42), "42");
 #endif
 }
 
 TEST(format_test, custom_format_compile_time_string) {
-  EXPECT_EQ("42", fmt::format(FMT_STRING("{}"), Answer()));
+  EXPECT_EQ(fmt::format(FMT_STRING("{}"), Answer()), "42");
   auto answer = Answer();
-  EXPECT_EQ("42", fmt::format(FMT_STRING("{}"), answer));
+  EXPECT_EQ(fmt::format(FMT_STRING("{}"), answer), "42");
   char buf[10] = {};
   fmt::format_to(buf, FMT_STRING("{}"), answer);
   const Answer const_answer = Answer();
-  EXPECT_EQ("42", fmt::format(FMT_STRING("{}"), const_answer));
+  EXPECT_EQ(fmt::format(FMT_STRING("{}"), const_answer), "42");
 }
 
 #if FMT_USE_USER_DEFINED_LITERALS
@@ -1924,11 +1913,11 @@
                   fmt::arg("second", "cad"), fmt::arg("third", 99)),
       udl_a);
 
-  EXPECT_EQ("42", fmt::format("{answer}", "answer"_a = Answer()));
+  EXPECT_EQ(fmt::format("{answer}", "answer"_a = Answer()), "42");
 }
 #endif  // FMT_USE_USER_DEFINED_LITERALS
 
-TEST(format_test, enum) { EXPECT_EQ("0", fmt::format("{}", foo)); }
+TEST(format_test, enum) { EXPECT_EQ(fmt::format("{}", foo), "0"); }
 
 TEST(format_test, formatter_not_specialized) {
   static_assert(!fmt::has_formatter<fmt::formatter<test_enum>,
@@ -1941,12 +1930,12 @@
 auto format_as(big_enum e) -> unsigned long long { return e; }
 
 TEST(format_test, strong_enum) {
-  EXPECT_EQ("5000000000", fmt::format("{}", big_enum_value));
+  EXPECT_EQ(fmt::format("{}", big_enum_value), "5000000000");
 }
 #endif
 
 TEST(format_test, non_null_terminated_format_string) {
-  EXPECT_EQ("42", fmt::format(string_view("{}foo", 2), 42));
+  EXPECT_EQ(fmt::format(string_view("{}foo", 2), 42), "42");
 }
 
 namespace adl_test {
@@ -2079,16 +2068,16 @@
   using pointer = void;
   using reference = void;
 
-  test_output_iterator& operator++() {
+  auto operator++() -> test_output_iterator& {
     ++data;
     return *this;
   }
-  test_output_iterator operator++(int) {
+  auto operator++(int) -> test_output_iterator {
     auto tmp = *this;
     ++data;
     return tmp;
   }
-  char& operator*() { return *data; }
+  auto operator*() -> char& { return *data; }
 };
 
 TEST(format_test, format_to_n_output_iterator) {
@@ -2103,13 +2092,13 @@
   auto args = fmt::make_format_args<context>(n);
   auto s = std::string();
   fmt::vformat_to(std::back_inserter(s), "{}", args);
-  EXPECT_EQ("42", s);
+  EXPECT_EQ(s, "42");
   s.clear();
   fmt::vformat_to(std::back_inserter(s), FMT_STRING("{}"), args);
-  EXPECT_EQ("42", s);
+  EXPECT_EQ(s, "42");
 }
 
-TEST(format_test, char_traits_is_not_ambiguous) {
+TEST(format_test, char_traits_not_ambiguous) {
   // Test that we don't inject detail names into the std namespace.
   using namespace std;
   auto c = char_traits<char>::char_type();
@@ -2156,6 +2145,13 @@
 
 struct struct_as_int {};
 auto format_as(struct_as_int) -> int { return 42; }
+
+struct struct_as_const_reference {
+  const std::string name = "foo";
+};
+auto format_as(const struct_as_const_reference& s) -> const std::string& {
+  return s.name;
+}
 }  // namespace test
 
 TEST(format_test, format_as) {
@@ -2163,6 +2159,7 @@
   EXPECT_EQ(fmt::format("{}", test::scoped_enum_as_string_view()), "foo");
   EXPECT_EQ(fmt::format("{}", test::scoped_enum_as_string()), "foo");
   EXPECT_EQ(fmt::format("{}", test::struct_as_int()), "42");
+  EXPECT_EQ(fmt::format("{}", test::struct_as_const_reference()), "foo");
 }
 
 TEST(format_test, format_as_to_string) {
@@ -2172,7 +2169,7 @@
   EXPECT_EQ(fmt::to_string(test::struct_as_int()), "42");
 }
 
-template <typename Char, typename T> bool check_enabled_formatter() {
+template <typename Char, typename T> auto check_enabled_formatter() -> bool {
   static_assert(std::is_default_constructible<fmt::formatter<T, Char>>::value,
                 "");
   return true;
@@ -2198,21 +2195,20 @@
 
 TEST(format_int_test, data) {
   fmt::format_int format_int(42);
-  EXPECT_EQ("42", std::string(format_int.data(), format_int.size()));
+  EXPECT_EQ(std::string(format_int.data(), format_int.size()), "42");
 }
 
 TEST(format_int_test, format_int) {
-  EXPECT_EQ("42", fmt::format_int(42).str());
-  EXPECT_EQ(2u, fmt::format_int(42).size());
-  EXPECT_EQ("-42", fmt::format_int(-42).str());
-  EXPECT_EQ(3u, fmt::format_int(-42).size());
-  EXPECT_EQ("42", fmt::format_int(42ul).str());
-  EXPECT_EQ("-42", fmt::format_int(-42l).str());
-  EXPECT_EQ("42", fmt::format_int(42ull).str());
-  EXPECT_EQ("-42", fmt::format_int(-42ll).str());
-  std::ostringstream os;
-  os << max_value<int64_t>();
-  EXPECT_EQ(os.str(), fmt::format_int(max_value<int64_t>()).str());
+  EXPECT_EQ(fmt::format_int(42).str(), "42");
+  EXPECT_EQ(fmt::format_int(42).size(), 2u);
+  EXPECT_EQ(fmt::format_int(-42).str(), "-42");
+  EXPECT_EQ(fmt::format_int(-42).size(), 3u);
+  EXPECT_EQ(fmt::format_int(42ul).str(), "42");
+  EXPECT_EQ(fmt::format_int(-42l).str(), "-42");
+  EXPECT_EQ(fmt::format_int(42ull).str(), "42");
+  EXPECT_EQ(fmt::format_int(-42ll).str(), "-42");\
+  EXPECT_EQ(fmt::format_int(max_value<int64_t>()).str(),
+            std::to_string(max_value<int64_t>()));
 }
 
 #ifndef FMT_STATIC_THOUSANDS_SEPARATOR
@@ -2272,6 +2268,14 @@
             "42");
 }
 
+TEST(format_test, format_locale) {
+  auto loc = std::locale({}, new fmt::format_facet<std::locale>(","));
+  EXPECT_EQ(fmt::format(loc, "{:Lx}", 123456789), "7,5bc,d15");
+  EXPECT_EQ(fmt::format(loc, "{:#Lb}", -123456789),
+            "-0b111,010,110,111,100,110,100,010,101");
+  EXPECT_EQ(fmt::format(loc, "{:10Lo}", 12345), "    30,071");
+}
+
 #endif  // FMT_STATIC_THOUSANDS_SEPARATOR
 
 struct convertible_to_nonconst_cstring {
diff --git a/test/gtest-extra-test.cc b/test/gtest-extra-test.cc
index 42340a2..34054b6 100644
--- a/test/gtest-extra-test.cc
+++ b/test/gtest-extra-test.cc
@@ -354,7 +354,7 @@
   FMT_POSIX(close(fd));
   std::unique_ptr<output_redirect> redir{nullptr};
   EXPECT_SYSTEM_ERROR_NOASSERT(
-      redir.reset(new output_redirect(f.get())), EBADF,
+      redir.reset(new output_redirect(f.get(), false)), EBADF,
       fmt::format("cannot duplicate file descriptor {}", fd));
   copy.dup2(fd);  // "undo" close or dtor will fail
 }
diff --git a/test/gtest-extra.cc b/test/gtest-extra.cc
index 542e4b5..3d27cf9 100644
--- a/test/gtest-extra.cc
+++ b/test/gtest-extra.cc
@@ -11,8 +11,8 @@
 
 using fmt::file;
 
-output_redirect::output_redirect(FILE* f) : file_(f) {
-  flush();
+output_redirect::output_redirect(FILE* f, bool flush) : file_(f) {
+  if (flush) this->flush();
   int fd = FMT_POSIX(fileno(f));
   // Create a file object referring to the original file.
   original_ = file::dup(fd);
diff --git a/test/gtest-extra.h b/test/gtest-extra.h
index 03a07a2..e08c94c 100644
--- a/test/gtest-extra.h
+++ b/test/gtest-extra.h
@@ -77,7 +77,7 @@
   void restore();
 
  public:
-  explicit output_redirect(FILE* file);
+  explicit output_redirect(FILE* file, bool flush = true);
   ~output_redirect() noexcept;
 
   output_redirect(const output_redirect&) = delete;
diff --git a/test/gtest/gmock-gtest-all.cc b/test/gtest/gmock-gtest-all.cc
index 7b33134..9d3b9bc 100644
--- a/test/gtest/gmock-gtest-all.cc
+++ b/test/gtest/gmock-gtest-all.cc
@@ -1912,7 +1912,7 @@
 namespace {
 
 // When TEST_P is found without a matching INSTANTIATE_TEST_SUITE_P
-// to creates test cases for it, a syntetic test case is
+// to creates test cases for it, a synthetic test case is
 // inserted to report ether an error or a log message.
 //
 // This configuration bit will likely be removed at some point.
diff --git a/test/ostream-test.cc b/test/ostream-test.cc
index b2d1546..98ee075 100644
--- a/test/ostream-test.cc
+++ b/test/ostream-test.cc
@@ -17,7 +17,7 @@
 // included after fmt/format.h.
 namespace fmt {
 template <> struct formatter<test> : formatter<int> {
-  auto format(const test&, format_context& ctx) -> decltype(ctx.out()) {
+  auto format(const test&, format_context& ctx) const -> decltype(ctx.out()) {
     return formatter<int>::format(42, ctx);
   }
 };
@@ -289,3 +289,20 @@
   std::ofstream ofs;
   fmt::print(ofs, "discard");
 }
+
+struct unlocalized {};
+
+auto operator<<(std::ostream& os, unlocalized)
+    -> std::ostream& {
+  return os << 12345;
+}
+
+namespace fmt {
+template <> struct formatter<unlocalized> : ostream_formatter {};
+}  // namespace fmt
+
+TEST(ostream_test, unlocalized) {
+  auto loc = get_locale("en_US.UTF-8");
+  std::locale::global(loc);
+  EXPECT_EQ(fmt::format(loc, "{}", unlocalized()), "12345");
+}
diff --git a/test/posix-mock.h b/test/posix-mock.h
index 4f2a42c..5458087 100644
--- a/test/posix-mock.h
+++ b/test/posix-mock.h
@@ -30,13 +30,13 @@
 
 #ifndef _MSC_VER
 // Size type for read and write.
-typedef size_t size_t;
-typedef ssize_t ssize_t;
+using size_t = size_t;
+using ssize_t = ssize_t;
 int open(const char* path, int oflag, int mode);
 int fstat(int fd, struct stat* buf);
 #else
-typedef unsigned size_t;
-typedef int ssize_t;
+using size_t = unsigned;
+using ssize_t = int;
 #endif
 
 #ifndef _WIN32
diff --git a/test/printf-test.cc b/test/printf-test.cc
index 81db9b2..7e09ecc 100644
--- a/test/printf-test.cc
+++ b/test/printf-test.cc
@@ -310,10 +310,10 @@
   }
 }
 
-template <typename T> struct make_signed { typedef T type; };
+template <typename T> struct make_signed { using type = T; };
 
 #define SPECIALIZE_MAKE_SIGNED(T, S) \
-  template <> struct make_signed<T> { typedef S type; }
+  template <> struct make_signed<T> { using type = S; }
 
 SPECIALIZE_MAKE_SIGNED(char, signed char);
 SPECIALIZE_MAKE_SIGNED(unsigned char, signed char);
diff --git a/test/ranges-test.cc b/test/ranges-test.cc
index ba5c464..8ab66b3 100644
--- a/test/ranges-test.cc
+++ b/test/ranges-test.cc
@@ -7,13 +7,19 @@
 
 #include "fmt/ranges.h"
 
+#include <list>
 #include <map>
+#include <numeric>
 #include <queue>
 #include <stack>
 #include <string>
 #include <utility>
 #include <vector>
 
+#if FMT_HAS_INCLUDE(<ranges>)
+#  include <ranges>
+#endif
+
 #include "gtest/gtest.h"
 
 #if !FMT_GCC_VERSION || FMT_GCC_VERSION >= 601
@@ -242,7 +248,7 @@
   explicit non_const_only_range(Args&&... args)
       : vec(std::forward<Args>(args)...) {}
 
-  auto begin() -> const_iterator{ return vec.begin(); }
+  auto begin() -> const_iterator { return vec.begin(); }
   auto end() -> const_iterator { return vec.end(); }
 };
 
@@ -360,7 +366,7 @@
     iterator() = default;
     iterator(int i) : val(i) {}
     auto operator*() const -> int { return val; }
-    auto operator++() -> iterator&{
+    auto operator++() -> iterator& {
       ++val;
       return *this;
     }
@@ -416,6 +422,17 @@
 }
 #endif  // FMT_RANGES_TEST_ENABLE_JOIN
 
+#if defined(__cpp_lib_ranges) && __cpp_lib_ranges >= 202302L
+TEST(ranges_test, nested_ranges) {
+  auto l = std::list{1, 2, 3};
+  auto r = std::views::iota(0, 3) | std::views::transform([&l](auto i) {
+             return std::views::take(std::ranges::subrange(l), i);
+           }) |
+           std::views::transform(std::views::reverse);
+  EXPECT_EQ(fmt::format("{}", r), "[[], [1], [2, 1]]");
+}
+#endif
+
 TEST(ranges_test, is_printable) {
   using fmt::detail::is_printable;
   EXPECT_TRUE(is_printable(0x0323));
@@ -528,3 +545,16 @@
     EXPECT_EQ(fmt::format("{}", m), "[1, 2]");
   }
 }
+
+struct tieable {
+  int a = 3;
+  double b = 0.42;
+};
+
+auto format_as(const tieable& t) -> std::tuple<int, double> {
+  return std::tie(t.a, t.b);
+}
+
+TEST(ranges_test, format_as_tie) {
+  EXPECT_EQ(fmt::format("{}", tieable()), "(3, 0.42)");
+}
diff --git a/test/scan-test.cc b/test/scan-test.cc
index bec5413..f0ede19 100644
--- a/test/scan-test.cc
+++ b/test/scan-test.cc
@@ -10,23 +10,29 @@
 #include <time.h>
 
 #include <climits>
+#include <thread>
 
+#include "fmt/os.h"
 #include "gmock/gmock.h"
 #include "gtest-extra.h"
 
 TEST(scan_test, read_text) {
-  auto s = fmt::string_view("foo");
+  fmt::string_view s = "foo";
   auto end = fmt::scan(s, "foo");
   EXPECT_EQ(end, s.end());
   EXPECT_THROW_MSG(fmt::scan("fob", "foo"), fmt::format_error, "invalid input");
 }
 
 TEST(scan_test, read_int) {
-  auto n = int();
+  int n = 0;
   fmt::scan("42", "{}", n);
   EXPECT_EQ(n, 42);
   fmt::scan("-42", "{}", n);
   EXPECT_EQ(n, -42);
+  fmt::scan("42", "{:}", n);
+  EXPECT_EQ(n, 42);
+  EXPECT_THROW_MSG(fmt::scan(std::to_string(INT_MAX + 1u), "{}", n),
+                   fmt::format_error, "number is too big");
 }
 
 TEST(scan_test, read_longlong) {
@@ -38,7 +44,7 @@
 }
 
 TEST(scan_test, read_uint) {
-  auto n = unsigned();
+  unsigned n = 0;
   fmt::scan("42", "{}", n);
   EXPECT_EQ(n, 42);
   EXPECT_THROW_MSG(fmt::scan("-42", "{}", n), fmt::format_error,
@@ -53,52 +59,63 @@
                    "invalid input");
 }
 
+TEST(scan_test, read_hex) {
+  unsigned n = 0;
+  fmt::scan("2a", "{:x}", n);
+  EXPECT_EQ(n, 42);
+  auto num_digits = std::numeric_limits<unsigned>::digits / 4;
+  EXPECT_THROW_MSG(fmt::scan(fmt::format("1{:0{}}", 0, num_digits), "{:x}", n),
+                   fmt::format_error, "number is too big");
+}
+
 TEST(scan_test, read_string) {
-  auto s = std::string();
+  std::string s;
   fmt::scan("foo", "{}", s);
   EXPECT_EQ(s, "foo");
 }
 
 TEST(scan_test, read_string_view) {
-  auto s = fmt::string_view();
+  fmt::string_view s;
   fmt::scan("foo", "{}", s);
   EXPECT_EQ(s, "foo");
 }
 
-#ifdef FMT_HAVE_STRPTIME
-namespace fmt {
-template <> struct scanner<tm> {
-  std::string format;
+TEST(scan_test, separator) {
+  int n1 = 0, n2 = 0;
+  fmt::scan("10 20", "{} {}", n1, n2);
+  EXPECT_EQ(n1, 10);
+  EXPECT_EQ(n2, 20);
+}
 
-  scan_parse_context::iterator parse(scan_parse_context& ctx) {
-    auto it = ctx.begin();
-    if (it != ctx.end() && *it == ':') ++it;
-    auto end = it;
-    while (end != ctx.end() && *end != '}') ++end;
-    format.reserve(detail::to_unsigned(end - it + 1));
-    format.append(it, end);
-    format.push_back('\0');
-    return end;
+struct num {
+  int value;
+};
+
+namespace fmt {
+template <> struct scanner<num> {
+  bool hex = false;
+
+  auto parse(scan_parse_context& ctx) -> scan_parse_context::iterator {
+    auto it = ctx.begin(), end = ctx.end();
+    if (it != end && *it == 'x') hex = true;
+    if (it != end && *it != '}') throw_format_error("invalid format");
+    return it;
   }
 
   template <class ScanContext>
-  typename ScanContext::iterator scan(tm& t, ScanContext& ctx) {
-    auto result = strptime(ctx.begin(), format.c_str(), &t);
-    if (!result) throw format_error("failed to parse time");
-    return result;
+  auto scan(num& n, ScanContext& ctx) const -> typename ScanContext::iterator {
+    // TODO: handle specifier
+    return fmt::scan(ctx, "{}", n.value);
   }
 };
 }  // namespace fmt
 
 TEST(scan_test, read_custom) {
-  auto input = "Date: 1985-10-25";
-  auto t = tm();
-  fmt::scan(input, "Date: {0:%Y-%m-%d}", t);
-  EXPECT_EQ(t.tm_year, 85);
-  EXPECT_EQ(t.tm_mon, 9);
-  EXPECT_EQ(t.tm_mday, 25);
+  auto input = "42";
+  auto n = num();
+  fmt::scan(input, "{:}", n);
+  EXPECT_EQ(n.value, 42);
 }
-#endif
 
 TEST(scan_test, invalid_format) {
   EXPECT_THROW_MSG(fmt::scan("", "{}"), fmt::format_error,
@@ -108,9 +125,64 @@
 }
 
 TEST(scan_test, example) {
-  auto key = std::string();
-  auto value = int();
+  std::string key;
+  int value = 0;
   fmt::scan("answer = 42", "{} = {}", key, value);
   EXPECT_EQ(key, "answer");
   EXPECT_EQ(value, 42);
 }
+
+TEST(scan_test, end_of_input) {
+  int value = 0;
+  fmt::scan("", "{}", value);
+}
+
+#if FMT_USE_FCNTL
+TEST(scan_test, file) {
+  fmt::file read_end, write_end;
+  fmt::file::pipe(read_end, write_end);
+
+  fmt::string_view input = "10 20";
+  write_end.write(input.data(), input.size());
+  write_end.close();
+
+  int n1 = 0, n2 = 0;
+  fmt::buffered_file f = read_end.fdopen("r");
+  fmt::scan(f.get(), "{} {}", n1, n2);
+  EXPECT_EQ(n1, 10);
+  EXPECT_EQ(n2, 20);
+}
+
+TEST(scan_test, lock) {
+  fmt::file read_end, write_end;
+  fmt::file::pipe(read_end, write_end);
+
+  std::thread producer([&]() {
+    fmt::string_view input = "42 ";
+    for (int i = 0; i < 1000; ++i) write_end.write(input.data(), input.size());
+    write_end.close();
+  });
+
+  std::atomic<int> count(0);
+  fmt::buffered_file f = read_end.fdopen("r");
+  auto fun = [&]() {
+    int value = 0;
+    while (fmt::scan(f.get(), "{}", value)) {
+      if (value != 42) {
+        read_end.close();
+        EXPECT_EQ(value, 42);
+        break;
+      }
+      ++count;
+    }
+  };
+  std::thread consumer1(fun);
+  std::thread consumer2(fun);
+  
+  producer.join();
+  consumer1.join();
+  consumer2.join();
+  EXPECT_EQ(count, 1000);
+
+}
+#endif  // FMT_USE_FCNTL
diff --git a/test/scan.h b/test/scan.h
index a2cb2aa..a68c77c 100644
--- a/test/scan.h
+++ b/test/scan.h
@@ -12,6 +12,264 @@
 #include "fmt/format.h"
 
 FMT_BEGIN_NAMESPACE
+namespace detail {
+
+inline auto is_whitespace(char c) -> bool { return c == ' ' || c == '\n'; }
+
+// If c is a hex digit returns its numeric value, othewise -1.
+inline auto to_hex_digit(char c) -> int {
+  if (c >= '0' && c <= '9') return c - '0';
+  if (c >= 'a' && c <= 'f') return c - 'a' + 10;
+  if (c >= 'A' && c <= 'F') return c - 'A' + 10;
+  return -1;
+}
+
+struct maybe_contiguous_range {
+  const char* begin;
+  const char* end;
+
+  explicit operator bool() const { return begin != nullptr; }
+};
+
+class scan_buffer {
+ private:
+  const char* ptr_;
+  const char* end_;
+  bool contiguous_;
+
+ protected:
+  scan_buffer(const char* ptr, const char* end, bool contiguous)
+      : ptr_(ptr), end_(end), contiguous_(contiguous) {}
+  ~scan_buffer() = default;
+
+  void set(string_view buf) {
+    ptr_ = buf.begin();
+    end_ = buf.end();
+  }
+
+  auto ptr() const -> const char* { return ptr_; }
+
+ public:
+  scan_buffer(const scan_buffer&) = delete;
+  void operator=(const scan_buffer&) = delete;
+
+  // Fills the buffer with more input if available.
+  virtual void consume() = 0;
+
+  class sentinel {};
+
+  class iterator {
+   private:
+    const char** ptr_;
+    scan_buffer* buf_;  // This could be merged with ptr_.
+    char value_;
+
+    static auto get_sentinel() -> const char** {
+      static const char* ptr = nullptr;
+      return &ptr;
+    }
+
+    friend class scan_buffer;
+
+    friend auto operator==(iterator lhs, sentinel) -> bool {
+      return *lhs.ptr_ == nullptr;
+    }
+    friend auto operator!=(iterator lhs, sentinel) -> bool {
+      return *lhs.ptr_ != nullptr;
+    }
+
+    iterator(scan_buffer* buf) : buf_(buf) {
+      if (buf->ptr_ == buf->end_) {
+        ptr_ = get_sentinel();
+        return;
+      }
+      ptr_ = &buf->ptr_;
+      value_ = *buf->ptr_;
+    }
+
+    friend scan_buffer& get_buffer(iterator it) { return *it.buf_; }
+
+   public:
+    iterator() : ptr_(get_sentinel()), buf_(nullptr) {}
+
+    auto operator++() -> iterator& {
+      if (!buf_->try_consume()) ptr_ = get_sentinel();
+      value_ = *buf_->ptr_;
+      return *this;
+    }
+    auto operator++(int) -> iterator {
+      iterator copy = *this;
+      ++*this;
+      return copy;
+    }
+    auto operator*() const -> char { return value_; }
+
+    auto base() const -> const char* { return buf_->ptr_; }
+
+    friend auto to_contiguous(iterator it) -> maybe_contiguous_range;
+    friend auto advance(iterator it, size_t n) -> iterator;
+  };
+
+  friend auto to_contiguous(iterator it) -> maybe_contiguous_range {
+    if (it.buf_->is_contiguous()) return {it.buf_->ptr_, it.buf_->end_};
+    return {nullptr, nullptr};
+  }
+  friend auto advance(iterator it, size_t n) -> iterator {
+    FMT_ASSERT(it.buf_->is_contiguous(), "");
+    const char*& ptr = it.buf_->ptr_;
+    ptr += n;
+    it.value_ = *ptr;
+    if (ptr == it.buf_->end_) it.ptr_ = iterator::get_sentinel();
+    return it;
+  }
+
+  auto begin() -> iterator { return this; }
+  auto end() -> sentinel { return {}; }
+
+  auto is_contiguous() const -> bool { return contiguous_; }
+
+  // Tries consuming a single code unit. Returns true iff there is more input.
+  auto try_consume() -> bool {
+    FMT_ASSERT(ptr_ != end_, "");
+    ++ptr_;
+    if (ptr_ != end_) return true;
+    consume();
+    return ptr_ != end_;
+  }
+};
+
+using scan_iterator = scan_buffer::iterator;
+using scan_sentinel = scan_buffer::sentinel;
+
+class string_scan_buffer : public scan_buffer {
+ private:
+  void consume() override {}
+
+ public:
+  explicit string_scan_buffer(string_view s)
+      : scan_buffer(s.begin(), s.end(), true) {}
+};
+
+#ifdef _WIN32
+void flockfile(FILE* f) { _lock_file(f); }
+void funlockfile(FILE* f) { _unlock_file(f); }
+int getc_unlocked(FILE* f) { return _fgetc_nolock(f); }
+#endif
+
+// A FILE wrapper. F is FILE defined as a template parameter to make
+// system-specific API detection work.
+template <typename F> class file_base {
+ protected:
+  F* file_;
+
+ public:
+  file_base(F* file) : file_(file) {}
+  operator F*() const { return file_; }
+
+  // Reads a code unit from the stream.
+  auto get() -> int {
+    int result = getc_unlocked(file_);
+    if (result == EOF && ferror(file_) != 0)
+      FMT_THROW(system_error(errno, FMT_STRING("getc failed")));
+    return result;
+  }
+
+  // Puts the code unit back into the stream buffer.
+  void unget(char c) {
+    if (ungetc(c, file_) == EOF)
+      FMT_THROW(system_error(errno, FMT_STRING("ungetc failed")));
+  }
+};
+
+// A FILE wrapper for glibc.
+template <typename F> class glibc_file : public file_base<F> {
+ public:
+  using file_base<F>::file_base;
+
+  // Returns the file's read buffer as a string_view.
+  auto buffer() const -> string_view {
+    return {this->file_->_IO_read_ptr,
+            to_unsigned(this->file_->_IO_read_end - this->file_->_IO_read_ptr)};
+  }
+};
+
+// A FILE wrapper for Apple's libc.
+template <typename F> class apple_file : public file_base<F> {
+ public:
+  using file_base<F>::file_base;
+
+  auto buffer() const -> string_view {
+    return {reinterpret_cast<char*>(this->file_->_p),
+            to_unsigned(this->file_->_r)};
+  }
+};
+
+// A fallback FILE wrapper.
+template <typename F> class fallback_file : public file_base<F> {
+ private:
+  char next_;  // The next unconsumed character in the buffer.
+  bool has_next_ = false;
+
+ public:
+  using file_base<F>::file_base;
+
+  auto buffer() const -> string_view { return {&next_, has_next_ ? 1u : 0u}; }
+
+  auto get() -> int {
+    has_next_ = false;
+    return file_base<F>::get();
+  }
+
+  void unget(char c) {
+    file_base<F>::unget(c);
+    next_ = c;
+    has_next_ = true;
+  }
+};
+
+class file_scan_buffer : public scan_buffer {
+ private:
+  template <typename F, FMT_ENABLE_IF(sizeof(F::_IO_read_ptr) != 0)>
+  static auto get_file(F* f, int) -> glibc_file<F> {
+    return f;
+  }
+  template <typename F, FMT_ENABLE_IF(sizeof(F::_p) != 0)>
+  static auto get_file(F* f, int) -> apple_file<F> {
+    return f;
+  }
+  static auto get_file(FILE* f, ...) -> fallback_file<FILE> { return f; }
+
+  decltype(get_file(static_cast<FILE*>(nullptr), 0)) file_;
+
+  // Fills the buffer if it is empty.
+  void fill() {
+    string_view buf = file_.buffer();
+    if (buf.size() == 0) {
+      int c = file_.get();
+      // Put the character back since we are only filling the buffer.
+      if (c != EOF) file_.unget(static_cast<char>(c));
+      buf = file_.buffer();
+    }
+    set(buf);
+  }
+
+  void consume() override {
+    // Consume the current buffer content.
+    size_t n = to_unsigned(ptr() - file_.buffer().begin());
+    for (size_t i = 0; i != n; ++i) file_.get();
+    fill();
+  }
+
+ public:
+  explicit file_scan_buffer(FILE* f)
+      : scan_buffer(nullptr, nullptr, false), file_(f) {
+    flockfile(f);
+    fill();
+  }
+  ~file_scan_buffer() { funlockfile(file_); }
+};
+}  // namespace detail
+
 template <typename T, typename Char = char> struct scanner {
   // A deleted default constructor indicates a disabled scanner.
   scanner() = delete;
@@ -27,31 +285,14 @@
   explicit FMT_CONSTEXPR scan_parse_context(string_view format)
       : format_(format) {}
 
-  FMT_CONSTEXPR iterator begin() const { return format_.begin(); }
-  FMT_CONSTEXPR iterator end() const { return format_.end(); }
+  FMT_CONSTEXPR auto begin() const -> iterator { return format_.begin(); }
+  FMT_CONSTEXPR auto end() const -> iterator { return format_.end(); }
 
   void advance_to(iterator it) {
     format_.remove_prefix(detail::to_unsigned(it - begin()));
   }
 };
 
-struct scan_context {
- private:
-  string_view input_;
-
- public:
-  using iterator = const char*;
-
-  explicit FMT_CONSTEXPR scan_context(string_view input) : input_(input) {}
-
-  iterator begin() const { return input_.data(); }
-  iterator end() const { return begin() + input_.size(); }
-
-  void advance_to(iterator it) {
-    input_.remove_prefix(detail::to_unsigned(it - begin()));
-  }
-};
-
 namespace detail {
 enum class scan_type {
   none_type,
@@ -64,181 +305,362 @@
   custom_type
 };
 
-struct custom_scan_arg {
+template <typename Context> struct custom_scan_arg {
   void* value;
-  void (*scan)(void* arg, scan_parse_context& parse_ctx, scan_context& ctx);
-};
-
-class scan_arg {
- public:
-  scan_type type;
-  union {
-    int* int_value;
-    unsigned* uint_value;
-    long long* long_long_value;
-    unsigned long long* ulong_long_value;
-    std::string* string;
-    fmt::string_view* string_view;
-    custom_scan_arg custom;
-    // TODO: more types
-  };
-
-  FMT_CONSTEXPR scan_arg() : type(scan_type::none_type), int_value(nullptr) {}
-  FMT_CONSTEXPR scan_arg(int& value)
-      : type(scan_type::int_type), int_value(&value) {}
-  FMT_CONSTEXPR scan_arg(unsigned& value)
-      : type(scan_type::uint_type), uint_value(&value) {}
-  FMT_CONSTEXPR scan_arg(long long& value)
-      : type(scan_type::long_long_type), long_long_value(&value) {}
-  FMT_CONSTEXPR scan_arg(unsigned long long& value)
-      : type(scan_type::ulong_long_type), ulong_long_value(&value) {}
-  FMT_CONSTEXPR scan_arg(std::string& value)
-      : type(scan_type::string_type), string(&value) {}
-  FMT_CONSTEXPR scan_arg(fmt::string_view& value)
-      : type(scan_type::string_view_type), string_view(&value) {}
-  template <typename T>
-  FMT_CONSTEXPR scan_arg(T& value) : type(scan_type::custom_type) {
-    custom.value = &value;
-    custom.scan = scan_custom_arg<T>;
-  }
-
- private:
-  template <typename T>
-  static void scan_custom_arg(void* arg, scan_parse_context& parse_ctx,
-                              scan_context& ctx) {
-    scanner<T> s;
-    parse_ctx.advance_to(s.parse(parse_ctx));
-    ctx.advance_to(s.scan(*static_cast<T*>(arg), ctx));
-  }
+  void (*scan)(void* arg, scan_parse_context& parse_ctx, Context& ctx);
 };
 }  // namespace detail
 
+// A scan argument. Context is a template parameter for the compiled API where
+// output can be unbuffered.
+template <typename Context> class basic_scan_arg {
+ private:
+  using scan_type = detail::scan_type;
+  scan_type type_;
+  union {
+    int* int_value_;
+    unsigned* uint_value_;
+    long long* long_long_value_;
+    unsigned long long* ulong_long_value_;
+    std::string* string_;
+    string_view* string_view_;
+    detail::custom_scan_arg<Context> custom_;
+    // TODO: more types
+  };
+
+  template <typename T>
+  static void scan_custom_arg(void* arg, scan_parse_context& parse_ctx,
+                              Context& ctx) {
+    auto s = scanner<T>();
+    parse_ctx.advance_to(s.parse(parse_ctx));
+    ctx.advance_to(s.scan(*static_cast<T*>(arg), ctx));
+  }
+
+ public:
+  FMT_CONSTEXPR basic_scan_arg()
+      : type_(scan_type::none_type), int_value_(nullptr) {}
+  FMT_CONSTEXPR basic_scan_arg(int& value)
+      : type_(scan_type::int_type), int_value_(&value) {}
+  FMT_CONSTEXPR basic_scan_arg(unsigned& value)
+      : type_(scan_type::uint_type), uint_value_(&value) {}
+  FMT_CONSTEXPR basic_scan_arg(long long& value)
+      : type_(scan_type::long_long_type), long_long_value_(&value) {}
+  FMT_CONSTEXPR basic_scan_arg(unsigned long long& value)
+      : type_(scan_type::ulong_long_type), ulong_long_value_(&value) {}
+  FMT_CONSTEXPR basic_scan_arg(std::string& value)
+      : type_(scan_type::string_type), string_(&value) {}
+  FMT_CONSTEXPR basic_scan_arg(string_view& value)
+      : type_(scan_type::string_view_type), string_view_(&value) {}
+  template <typename T>
+  FMT_CONSTEXPR basic_scan_arg(T& value) : type_(scan_type::custom_type) {
+    custom_.value = &value;
+    custom_.scan = scan_custom_arg<T>;
+  }
+
+  constexpr explicit operator bool() const noexcept {
+    return type_ != scan_type::none_type;
+  }
+
+  auto type() const -> detail::scan_type { return type_; }
+
+  template <typename Visitor>
+  auto visit(Visitor&& vis) -> decltype(vis(monostate())) {
+    switch (type_) {
+    case scan_type::none_type:
+      break;
+    case scan_type::int_type:
+      return vis(*int_value_);
+    case scan_type::uint_type:
+      return vis(*uint_value_);
+    case scan_type::long_long_type:
+      return vis(*long_long_value_);
+    case scan_type::ulong_long_type:
+      return vis(*ulong_long_value_);
+    case scan_type::string_type:
+      return vis(*string_);
+    case scan_type::string_view_type:
+      return vis(*string_view_);
+    case scan_type::custom_type:
+      break;
+    }
+    return vis(monostate());
+  }
+
+  auto scan_custom(const char* parse_begin, scan_parse_context& parse_ctx,
+                   Context& ctx) const -> bool {
+    if (type_ != scan_type::custom_type) return false;
+    parse_ctx.advance_to(parse_begin);
+    custom_.scan(custom_.value, parse_ctx, ctx);
+    return true;
+  }
+};
+
+class scan_context;
+using scan_arg = basic_scan_arg<scan_context>;
+
 struct scan_args {
   int size;
-  const detail::scan_arg* data;
+  const scan_arg* data;
 
   template <size_t N>
-  FMT_CONSTEXPR scan_args(const std::array<detail::scan_arg, N>& store)
+  FMT_CONSTEXPR scan_args(const std::array<scan_arg, N>& store)
       : size(N), data(store.data()) {
     static_assert(N < INT_MAX, "too many arguments");
   }
 };
 
+class scan_context {
+ private:
+  detail::scan_buffer& buf_;
+  scan_args args_;
+
+ public:
+  using iterator = detail::scan_iterator;
+  using sentinel = detail::scan_sentinel;
+
+  explicit FMT_CONSTEXPR scan_context(detail::scan_buffer& buf, scan_args args)
+      : buf_(buf), args_(args) {}
+
+  FMT_CONSTEXPR auto arg(int id) const -> scan_arg {
+    return id < args_.size ? args_.data[id] : scan_arg();
+  }
+
+  auto begin() const -> iterator { return buf_.begin(); }
+  auto end() const -> sentinel { return {}; }
+
+  void advance_to(iterator) { buf_.consume(); }
+};
+
 namespace detail {
 
+const char* parse_scan_specs(const char* begin, const char* end,
+                             format_specs<>& specs, scan_type) {
+  while (begin != end) {
+    switch (to_ascii(*begin)) {
+    // TODO: parse more scan format specifiers
+    case 'x':
+      specs.type = presentation_type::hex_lower;
+      ++begin;
+      break;
+    case '}':
+      return begin;
+    }
+  }
+  return begin;
+}
+
+template <typename T, FMT_ENABLE_IF(std::is_unsigned<T>::value)>
+auto read(scan_iterator it, T& value)
+    -> scan_iterator {
+  if (it == scan_sentinel()) return it;
+  char c = *it;
+  if (c < '0' || c > '9') throw_format_error("invalid input");
+
+  int num_digits = 0;
+  T n = 0, prev = 0;
+  char prev_digit = c;
+  do {
+    prev = n;
+    n = n * 10 + static_cast<unsigned>(c - '0');
+    prev_digit = c;
+    c = *++it;
+    ++num_digits;
+    if (c < '0' || c > '9') break;
+  } while (it != scan_sentinel());
+
+  // Check overflow.
+  if (num_digits <= std::numeric_limits<int>::digits10) {
+    value = n;
+    return it;
+  }
+  unsigned max = to_unsigned((std::numeric_limits<int>::max)());
+  if (num_digits == std::numeric_limits<int>::digits10 + 1 &&
+      prev * 10ull + unsigned(prev_digit - '0') <= max) {
+    value = n;
+  } else {
+    throw_format_error("number is too big");
+  }
+  return it;
+}
+
+template <typename T, FMT_ENABLE_IF(std::is_unsigned<T>::value)>
+auto read_hex(scan_iterator it, T& value)
+    -> scan_iterator {
+  if (it == scan_sentinel()) return it;
+  int digit = to_hex_digit(*it);
+  if (digit < 0) throw_format_error("invalid input");
+
+  int num_digits = 0;
+  T n = 0;
+  do {
+    n = (n << 4) + static_cast<unsigned>(digit);
+    ++num_digits;
+    digit = to_hex_digit(*++it);
+    if (digit < 0) break;
+  } while (it != scan_sentinel());
+
+  // Check overflow.
+  if (num_digits <= (std::numeric_limits<T>::digits >> 2))
+    value = n;
+  else
+    throw_format_error("number is too big");
+  return it;
+}
+
+template <typename T, FMT_ENABLE_IF(std::is_unsigned<T>::value)>
+auto read(scan_iterator it, T& value, const format_specs<>& specs)
+    -> scan_iterator {
+  if (specs.type == presentation_type::hex_lower)
+    return read_hex(it, value);
+  return read(it, value);
+}
+
+template <typename T, FMT_ENABLE_IF(std::is_signed<T>::value)>
+auto read(scan_iterator it, T& value, const format_specs<>& = {})
+    -> scan_iterator {
+  bool negative = it != scan_sentinel() && *it == '-';
+  if (negative) {
+    ++it;
+    if (it == scan_sentinel()) throw_format_error("invalid input");
+  }
+  using unsigned_type = typename std::make_unsigned<T>::type;
+  unsigned_type abs_value = 0;
+  it = read(it, abs_value);
+  auto n = static_cast<T>(abs_value);
+  value = negative ? -n : n;
+  return it;
+}
+
+auto read(scan_iterator it, std::string& value, const format_specs<>& = {})
+    -> scan_iterator {
+  while (it != scan_sentinel() && *it != ' ') value.push_back(*it++);
+  return it;
+}
+
+auto read(scan_iterator it, string_view& value, const format_specs<>& = {})
+    -> scan_iterator {
+  auto range = to_contiguous(it);
+  // This could also be checked at compile time in scan.
+  if (!range) throw_format_error("string_view requires contiguous input");
+  auto p = range.begin;
+  while (p != range.end && *p != ' ') ++p;
+  size_t size = to_unsigned(p - range.begin);
+  value = {range.begin, size};
+  return advance(it, size);
+}
+
+auto read(scan_iterator it, monostate, const format_specs<>& = {})
+    -> scan_iterator {
+  return it;
+}
+
+// An argument scanner that uses the default format, e.g. decimal for integers.
+struct default_arg_scanner {
+  scan_iterator it;
+
+  template <typename T> FMT_INLINE auto operator()(T&& value) -> scan_iterator {
+    return read(it, value);
+  }
+};
+
+// An argument scanner with format specifiers.
+struct arg_scanner {
+  scan_iterator it;
+  const format_specs<>& specs;
+
+  template <typename T> auto operator()(T&& value) -> scan_iterator {
+    return read(it, value, specs);
+  }
+};
+
 struct scan_handler : error_handler {
  private:
   scan_parse_context parse_ctx_;
   scan_context scan_ctx_;
-  scan_args args_;
   int next_arg_id_;
-  scan_arg arg_;
 
-  template <typename T = unsigned> T read_uint() {
-    T value = 0;
-    auto it = scan_ctx_.begin(), end = scan_ctx_.end();
-    while (it != end) {
-      char c = *it++;
-      if (c < '0' || c > '9') on_error("invalid input");
-      // TODO: check overflow
-      value = value * 10 + static_cast<unsigned>(c - '0');
-    }
-    scan_ctx_.advance_to(it);
-    return value;
-  }
-
-  template <typename T = int> T read_int() {
-    auto it = scan_ctx_.begin(), end = scan_ctx_.end();
-    bool negative = it != end && *it == '-';
-    if (negative) ++it;
-    scan_ctx_.advance_to(it);
-    const auto value = read_uint<typename std::make_unsigned<T>::type>();
-    if (negative) return -static_cast<T>(value);
-    return static_cast<T>(value);
-  }
+  using sentinel = scan_buffer::sentinel;
 
  public:
-  FMT_CONSTEXPR scan_handler(string_view format, string_view input,
+  FMT_CONSTEXPR scan_handler(string_view format, scan_buffer& buf,
                              scan_args args)
-      : parse_ctx_(format), scan_ctx_(input), args_(args), next_arg_id_(0) {}
+      : parse_ctx_(format), scan_ctx_(buf, args), next_arg_id_(0) {}
 
-  const char* pos() const { return scan_ctx_.begin(); }
+  auto pos() const -> scan_buffer::iterator { return scan_ctx_.begin(); }
 
   void on_text(const char* begin, const char* end) {
-    auto size = to_unsigned(end - begin);
+    if (begin == end) return;
     auto it = scan_ctx_.begin();
-    if (it + size > scan_ctx_.end() || !std::equal(begin, end, it))
-      on_error("invalid input");
-    scan_ctx_.advance_to(it + size);
+    for (; begin != end; ++begin, ++it) {
+      if (it == sentinel() || *begin != *it) on_error("invalid input");
+    }
+    scan_ctx_.advance_to(it);
   }
 
-  FMT_CONSTEXPR int on_arg_id() { return on_arg_id(next_arg_id_++); }
-  FMT_CONSTEXPR int on_arg_id(int id) {
-    if (id >= args_.size) on_error("argument index out of range");
-    arg_ = args_.data[id];
+  FMT_CONSTEXPR auto on_arg_id() -> int { return on_arg_id(next_arg_id_++); }
+  FMT_CONSTEXPR auto on_arg_id(int id) -> int {
+    if (!scan_ctx_.arg(id)) on_error("argument index out of range");
     return id;
   }
-  FMT_CONSTEXPR int on_arg_id(string_view id) {
+  FMT_CONSTEXPR auto on_arg_id(string_view id) -> int {
     if (id.data()) on_error("invalid format");
     return 0;
   }
 
-  void on_replacement_field(int, const char*) {
-    auto it = scan_ctx_.begin(), end = scan_ctx_.end();
-    switch (arg_.type) {
-    case scan_type::int_type:
-      *arg_.int_value = read_int();
-      break;
-    case scan_type::uint_type:
-      *arg_.uint_value = read_uint();
-      break;
-    case scan_type::long_long_type:
-      *arg_.long_long_value = read_int<long long>();
-      break;
-    case scan_type::ulong_long_type:
-      *arg_.ulong_long_value = read_uint<unsigned long long>();
-      break;
-    case scan_type::string_type:
-      while (it != end && *it != ' ') arg_.string->push_back(*it++);
-      scan_ctx_.advance_to(it);
-      break;
-    case scan_type::string_view_type: {
-      auto s = it;
-      while (it != end && *it != ' ') ++it;
-      *arg_.string_view = fmt::string_view(s, to_unsigned(it - s));
-      scan_ctx_.advance_to(it);
-      break;
-    }
-    case scan_type::none_type:
-    case scan_type::custom_type:
-      assert(false);
-    }
+  void on_replacement_field(int arg_id, const char*) {
+    scan_arg arg = scan_ctx_.arg(arg_id);
+    auto it = scan_ctx_.begin();
+    while (it != sentinel() && is_whitespace(*it)) ++it;
+    scan_ctx_.advance_to(arg.visit(default_arg_scanner{it}));
   }
 
-  const char* on_format_specs(int, const char* begin, const char*) {
-    if (arg_.type != scan_type::custom_type) return begin;
-    parse_ctx_.advance_to(begin);
-    arg_.custom.scan(arg_.custom.value, parse_ctx_, scan_ctx_);
-    return parse_ctx_.begin();
+  auto on_format_specs(int arg_id, const char* begin, const char* end) -> const
+      char* {
+    scan_arg arg = scan_ctx_.arg(arg_id);
+    if (arg.scan_custom(begin, parse_ctx_, scan_ctx_))
+      return parse_ctx_.begin();
+    auto specs = format_specs<>();
+    begin = parse_scan_specs(begin, end, specs, arg.type());
+    if (begin == end || *begin != '}') on_error("missing '}' in format string");
+    scan_ctx_.advance_to(arg.visit(arg_scanner{scan_ctx_.begin(), specs}));
+    return begin;
   }
+
+  void on_error(const char* message) { error_handler::on_error(message); }
 };
 }  // namespace detail
 
-template <typename... Args>
-std::array<detail::scan_arg, sizeof...(Args)> make_scan_args(Args&... args) {
+template <typename... T>
+auto make_scan_args(T&... args) -> std::array<scan_arg, sizeof...(T)> {
   return {{args...}};
 }
 
-string_view::iterator vscan(string_view input, string_view format_str,
-                            scan_args args) {
-  detail::scan_handler h(format_str, input, args);
-  detail::parse_format_string<false>(format_str, h);
-  return input.begin() + (h.pos() - &*input.begin());
+void vscan(detail::scan_buffer& buf, string_view fmt, scan_args args) {
+  auto h = detail::scan_handler(fmt, buf, args);
+  detail::parse_format_string<false>(fmt, h);
 }
 
-template <typename... Args>
-string_view::iterator scan(string_view input, string_view format_str,
-                           Args&... args) {
-  return vscan(input, format_str, make_scan_args(args...));
+template <typename... T>
+auto scan(string_view input, string_view fmt, T&... args)
+    -> string_view::iterator {
+  auto&& buf = detail::string_scan_buffer(input);
+  vscan(buf, fmt, make_scan_args(args...));
+  return input.begin() + (buf.begin().base() - input.data());
 }
+
+template <typename InputRange, typename... T,
+          FMT_ENABLE_IF(!std::is_convertible<InputRange, string_view>::value)>
+auto scan(InputRange&& input, string_view fmt, T&... args)
+    -> decltype(std::begin(input)) {
+  auto it = std::begin(input);
+  vscan(get_buffer(it), fmt, make_scan_args(args...));
+  return it;
+}
+
+template <typename... T> bool scan(std::FILE* f, string_view fmt, T&... args) {
+  auto&& buf = detail::file_scan_buffer(f);
+  vscan(buf, fmt, make_scan_args(args...));
+  return buf.begin() != buf.end();
+}
+
 FMT_END_NAMESPACE
diff --git a/test/std-test.cc b/test/std-test.cc
index 41183db..de3feaa 100644
--- a/test/std-test.cc
+++ b/test/std-test.cc
@@ -26,10 +26,13 @@
   EXPECT_EQ(fmt::format("{}", path("foo\"bar")), "foo\"bar");
   EXPECT_EQ(fmt::format("{:?}", path("foo\"bar")), "\"foo\\\"bar\"");
 
+  EXPECT_EQ(fmt::format("{:g}", path("/usr/bin")), "/usr/bin");
 #  ifdef _WIN32
-  EXPECT_EQ(fmt::format("{}", path(
-                                  L"\x0428\x0447\x0443\x0447\x044B\x043D\x0448"
-                                  L"\x0447\x044B\x043D\x0430")),
+  EXPECT_EQ(fmt::format("{}", path("C:\\foo")), "C:\\foo");
+  EXPECT_EQ(fmt::format("{:g}", path("C:\\foo")), "C:/foo");
+
+  EXPECT_EQ(fmt::format("{}", path(L"\x0428\x0447\x0443\x0447\x044B\x043D\x0448"
+                                   L"\x0447\x044B\x043D\x0430")),
             "Шчучыншчына");
   EXPECT_EQ(fmt::format("{}", path(L"\xd800")), "�");
   EXPECT_EQ(fmt::format("{:?}", path(L"\xd800")), "\"\\ud800\"");
@@ -62,6 +65,15 @@
   EXPECT_FALSE(fmt::format("{}", std::this_thread::get_id()).empty());
 }
 
+#ifdef __cpp_lib_source_location
+TEST(std_test, source_location) {
+  std::source_location loc = std::source_location::current();
+  EXPECT_EQ(fmt::format("{}", loc),
+            fmt::format("{}:{}:{}: {}", loc.file_name(), loc.line(),
+                        loc.column(), loc.function_name()));
+}
+#endif
+
 TEST(std_test, optional) {
 #ifdef __cpp_lib_optional
   EXPECT_EQ(fmt::format("{}", std::optional<int>{}), "none");
@@ -90,6 +102,36 @@
 #endif
 }
 
+namespace my_nso {
+enum class my_number {
+  one,
+  two,
+};
+auto format_as(my_number number) -> fmt::string_view {
+  return number == my_number::one ? "first" : "second";
+}
+
+class my_class {
+ public:
+  int av;
+
+ private:
+  friend auto format_as(const my_class& elm) -> std::string {
+    return fmt::to_string(elm.av);
+  }
+};
+}  // namespace my_nso
+TEST(std_test, optional_format_as) {
+#ifdef __cpp_lib_optional
+  EXPECT_EQ(fmt::format("{}", std::optional<my_nso::my_number>{}), "none");
+  EXPECT_EQ(fmt::format("{}", std::optional{my_nso::my_number::one}),
+            "optional(\"first\")");
+  EXPECT_EQ(fmt::format("{}", std::optional<my_nso::my_class>{}), "none");
+  EXPECT_EQ(fmt::format("{}", std::optional{my_nso::my_class{7}}),
+            "optional(\"7\")");
+#endif
+}
+
 struct throws_on_move {
   throws_on_move() = default;
 
@@ -255,10 +297,10 @@
 #ifdef __cpp_lib_atomic_flag_test
 TEST(std_test, format_atomic_flag) {
   std::atomic_flag f = ATOMIC_FLAG_INIT;
-  (void) f.test_and_set();
+  (void)f.test_and_set();
   EXPECT_EQ(fmt::format("{}", f), "true");
 
   const std::atomic_flag cf = ATOMIC_FLAG_INIT;
   EXPECT_EQ(fmt::format("{}", cf), "false");
 }
-#endif // __cpp_lib_atomic_flag_test
+#endif  // __cpp_lib_atomic_flag_test
diff --git a/test/util.cc b/test/util.cc
index 4ff34a9..d3f2dc7 100644
--- a/test/util.cc
+++ b/test/util.cc
@@ -39,6 +39,11 @@
   auto loc = do_get_locale(name);
   if (loc == std::locale::classic() && alt_name)
     loc = do_get_locale(alt_name);
+#ifdef __OpenBSD__
+    // Locales are not working in OpenBSD:
+    // https://github.com/fmtlib/fmt/issues/3670.
+    loc = std::locale::classic();
+#endif
   if (loc == std::locale::classic())
     fmt::print(stderr, "{} locale is missing.\n", name);
   return loc;
diff --git a/test/util.h b/test/util.h
index 9120e22..803cdee 100644
--- a/test/util.h
+++ b/test/util.h
@@ -29,9 +29,9 @@
 extern const char* const file_content;
 
 // Opens a buffered file for reading.
-fmt::buffered_file open_buffered_file(FILE** fp = nullptr);
+auto open_buffered_file(FILE** fp = nullptr) -> fmt::buffered_file;
 
-inline FILE* safe_fopen(const char* filename, const char* mode) {
+inline auto safe_fopen(const char* filename, const char* mode) -> FILE* {
 #if defined(_WIN32) && !defined(__MINGW32__)
   // Fix MSVC warning about "unsafe" fopen.
   FILE* f = nullptr;
@@ -51,17 +51,17 @@
  public:
   explicit basic_test_string(const Char* value = empty) : value_(value) {}
 
-  const std::basic_string<Char>& value() const { return value_; }
+  auto value() const -> const std::basic_string<Char>& { return value_; }
 };
 
 template <typename Char> const Char basic_test_string<Char>::empty[] = {0};
 
-typedef basic_test_string<char> test_string;
-typedef basic_test_string<wchar_t> test_wstring;
+using test_string = basic_test_string<char>;
+using test_wstring = basic_test_string<wchar_t>;
 
 template <typename Char>
-std::basic_ostream<Char>& operator<<(std::basic_ostream<Char>& os,
-                                     const basic_test_string<Char>& s) {
+auto operator<<(std::basic_ostream<Char>& os, const basic_test_string<Char>& s)
+    -> std::basic_ostream<Char>& {
   os << s.value();
   return os;
 }
@@ -72,10 +72,12 @@
  public:
   date(int year, int month, int day) : year_(year), month_(month), day_(day) {}
 
-  int year() const { return year_; }
-  int month() const { return month_; }
-  int day() const { return day_; }
+  auto year() const -> int { return year_; }
+  auto month() const -> int { return month_; }
+  auto day() const -> int { return day_; }
 };
 
-// Returns a locale with the given name if available or classic locale otherwise.
-std::locale get_locale(const char* name, const char* alt_name = nullptr);
+// Returns a locale with the given name if available or classic locale
+// otherwise.
+auto get_locale(const char* name, const char* alt_name = nullptr)
+    -> std::locale;
diff --git a/test/xchar-test.cc b/test/xchar-test.cc
index f72e94d..7f33fb2 100644
--- a/test/xchar-test.cc
+++ b/test/xchar-test.cc
@@ -187,18 +187,6 @@
   return std::string(str.begin(), str.end());
 }
 
-TEST(xchar_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(xchar_test, format_to) {
   auto buf = std::vector<wchar_t>();
   fmt::format_to(std::back_inserter(buf), L"{}{}", 42, L'\0');
@@ -581,7 +569,7 @@
       basic_format_parse_context<charT>& ctx) {
     auto end = parse_format_specs(ctx.begin(), ctx.end(), specs_, ctx,
                                   detail::type::float_type);
-    detail::parse_float_type_spec(specs_, detail::error_handler());
+    detail::parse_float_type_spec(specs_);
     return end;
   }
 
@@ -599,9 +587,9 @@
                             fmt::runtime("{:" + specs + "}"), c.imag());
     auto fill_align_width = std::string();
     if (specs_.width > 0) fill_align_width = fmt::format(">{}", specs_.width);
-    return format_to(ctx.out(), runtime("{:" + fill_align_width + "}"),
-                     c.real() != 0 ? fmt::format("({}+{}i)", real, imag)
-                                   : fmt::format("{}i", imag));
+    return fmt::format_to(ctx.out(), runtime("{:" + fill_align_width + "}"),
+                          c.real() != 0 ? fmt::format("({}+{}i)", real, imag)
+                                        : fmt::format("{}i", imag));
   }
 };
 FMT_END_NAMESPACE