about summary refs log tree commit diff
path: root/absl
diff options
context:
space:
mode:
Diffstat (limited to 'absl')
-rw-r--r--absl/container/inlined_vector.h4
-rw-r--r--absl/container/inlined_vector_test.cc26
-rw-r--r--absl/strings/BUILD.bazel139
-rw-r--r--absl/strings/CMakeLists.txt117
-rw-r--r--absl/strings/internal/str_format/arg.cc399
-rw-r--r--absl/strings/internal/str_format/arg.h434
-rw-r--r--absl/strings/internal/str_format/arg_test.cc111
-rw-r--r--absl/strings/internal/str_format/bind.cc232
-rw-r--r--absl/strings/internal/str_format/bind.h189
-rw-r--r--absl/strings/internal/str_format/bind_test.cc131
-rw-r--r--absl/strings/internal/str_format/checker.h325
-rw-r--r--absl/strings/internal/str_format/checker_test.cc150
-rw-r--r--absl/strings/internal/str_format/convert_test.cc575
-rw-r--r--absl/strings/internal/str_format/extension.cc84
-rw-r--r--absl/strings/internal/str_format/extension.h406
-rw-r--r--absl/strings/internal/str_format/extension_test.cc65
-rw-r--r--absl/strings/internal/str_format/float_conversion.cc476
-rw-r--r--absl/strings/internal/str_format/float_conversion.h21
-rw-r--r--absl/strings/internal/str_format/output.cc47
-rw-r--r--absl/strings/internal/str_format/output.h101
-rw-r--r--absl/strings/internal/str_format/output_test.cc78
-rw-r--r--absl/strings/internal/str_format/parser.cc294
-rw-r--r--absl/strings/internal/str_format/parser.h291
-rw-r--r--absl/strings/internal/str_format/parser_test.cc379
-rw-r--r--absl/strings/str_format.h512
-rw-r--r--absl/strings/str_format_test.cc603
-rw-r--r--absl/time/format.cc14
-rw-r--r--absl/time/internal/cctz/include/cctz/time_zone.h39
-rw-r--r--absl/time/internal/cctz/src/time_zone_fixed.cc12
-rw-r--r--absl/time/internal/cctz/src/time_zone_fixed.h6
-rw-r--r--absl/time/internal/cctz/src/time_zone_format.cc12
-rw-r--r--absl/time/internal/cctz/src/time_zone_format_test.cc277
-rw-r--r--absl/time/internal/cctz/src/time_zone_if.h24
-rw-r--r--absl/time/internal/cctz/src/time_zone_impl.cc4
-rw-r--r--absl/time/internal/cctz/src/time_zone_impl.h7
-rw-r--r--absl/time/internal/cctz/src/time_zone_info.cc38
-rw-r--r--absl/time/internal/cctz/src/time_zone_info.h8
-rw-r--r--absl/time/internal/cctz/src/time_zone_libc.cc6
-rw-r--r--absl/time/internal/cctz/src/time_zone_libc.h6
-rw-r--r--absl/time/internal/cctz/src/time_zone_lookup.cc4
-rw-r--r--absl/time/internal/cctz/src/time_zone_lookup_test.cc199
-rw-r--r--absl/time/internal/cctz/src/zone_info_source.cc10
-rw-r--r--absl/time/time.cc13
-rw-r--r--absl/time/time_zone_test.cc2
-rw-r--r--absl/types/internal/variant.h15
-rw-r--r--absl/types/variant.h28
46 files changed, 6559 insertions, 354 deletions
diff --git a/absl/container/inlined_vector.h b/absl/container/inlined_vector.h
index 78f78ea70221..101ded8557eb 100644
--- a/absl/container/inlined_vector.h
+++ b/absl/container/inlined_vector.h
@@ -89,7 +89,9 @@ class InlinedVector {
       : allocator_and_tag_(alloc) {}
 
   // Create a vector with n copies of value_type().
-  explicit InlinedVector(size_type n) : allocator_and_tag_(allocator_type()) {
+  explicit InlinedVector(size_type n,
+                         const allocator_type& alloc = allocator_type())
+      : allocator_and_tag_(alloc) {
     InitAssign(n);
   }
 
diff --git a/absl/container/inlined_vector_test.cc b/absl/container/inlined_vector_test.cc
index 26a7d5bca86f..f81fad569dcd 100644
--- a/absl/container/inlined_vector_test.cc
+++ b/absl/container/inlined_vector_test.cc
@@ -1763,4 +1763,30 @@ TEST(AllocatorSupportTest, ScopedAllocatorWorks) {
   EXPECT_EQ(allocated, 0);
 }
 
+TEST(AllocatorSupportTest, SizeAllocConstructor) {
+  constexpr int inlined_size = 4;
+  using Alloc = CountingAllocator<int>;
+  using AllocVec = absl::InlinedVector<int, inlined_size, Alloc>;
+
+  {
+    auto len = inlined_size / 2;
+    int64_t allocated = 0;
+    auto v = AllocVec(len, Alloc(&allocated));
+
+    // Inline storage used; allocator should not be invoked
+    EXPECT_THAT(allocated, 0);
+    EXPECT_THAT(v, AllOf(SizeIs(len), Each(0)));
+  }
+
+  {
+    auto len = inlined_size * 2;
+    int64_t allocated = 0;
+    auto v = AllocVec(len, Alloc(&allocated));
+
+    // Out of line storage used; allocation of 8 elements expected
+    EXPECT_THAT(allocated, len * sizeof(int));
+    EXPECT_THAT(v, AllOf(SizeIs(len), Each(0)));
+  }
+}
+
 }  // anonymous namespace
diff --git a/absl/strings/BUILD.bazel b/absl/strings/BUILD.bazel
index 328f52f33d26..3e50d24a322a 100644
--- a/absl/strings/BUILD.bazel
+++ b/absl/strings/BUILD.bazel
@@ -492,3 +492,142 @@ cc_test(
         "@com_github_google_benchmark//:benchmark_main",
     ],
 )
+
+cc_library(
+    name = "str_format",
+    hdrs = [
+        "str_format.h",
+    ],
+    copts = ABSL_DEFAULT_COPTS,
+    deps = [
+        ":str_format_internal",
+    ],
+)
+
+cc_library(
+    name = "str_format_internal",
+    srcs = [
+        "internal/str_format/arg.cc",
+        "internal/str_format/bind.cc",
+        "internal/str_format/extension.cc",
+        "internal/str_format/float_conversion.cc",
+        "internal/str_format/output.cc",
+        "internal/str_format/parser.cc",
+    ],
+    hdrs = [
+        "internal/str_format/arg.h",
+        "internal/str_format/bind.h",
+        "internal/str_format/checker.h",
+        "internal/str_format/extension.h",
+        "internal/str_format/float_conversion.h",
+        "internal/str_format/output.h",
+        "internal/str_format/parser.h",
+    ],
+    copts = ABSL_DEFAULT_COPTS,
+    visibility = ["//visibility:private"],
+    deps = [
+        ":strings",
+        "//absl/base:core_headers",
+        "//absl/container:inlined_vector",
+        "//absl/meta:type_traits",
+        "//absl/numeric:int128",
+        "//absl/types:span",
+    ],
+)
+
+cc_test(
+    name = "str_format_test",
+    srcs = ["str_format_test.cc"],
+    copts = ABSL_TEST_COPTS,
+    visibility = ["//visibility:private"],
+    deps = [
+        ":str_format",
+        ":strings",
+        "//absl/base:core_headers",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+cc_test(
+    name = "str_format_extension_test",
+    srcs = [
+        "internal/str_format/extension_test.cc",
+    ],
+    copts = ABSL_TEST_COPTS,
+    visibility = ["//visibility:private"],
+    deps = [
+        ":str_format",
+        ":str_format_internal",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+cc_test(
+    name = "str_format_arg_test",
+    srcs = ["internal/str_format/arg_test.cc"],
+    copts = ABSL_TEST_COPTS,
+    visibility = ["//visibility:private"],
+    deps = [
+        ":str_format",
+        ":str_format_internal",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+cc_test(
+    name = "str_format_bind_test",
+    srcs = ["internal/str_format/bind_test.cc"],
+    copts = ABSL_TEST_COPTS,
+    visibility = ["//visibility:private"],
+    deps = [
+        ":str_format_internal",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+cc_test(
+    name = "str_format_checker_test",
+    srcs = ["internal/str_format/checker_test.cc"],
+    copts = ABSL_TEST_COPTS,
+    visibility = ["//visibility:private"],
+    deps = [
+        ":str_format",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+cc_test(
+    name = "str_format_convert_test",
+    size = "small",
+    srcs = ["internal/str_format/convert_test.cc"],
+    copts = ABSL_TEST_COPTS,
+    visibility = ["//visibility:private"],
+    deps = [
+        ":str_format_internal",
+        "//absl/numeric:int128",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+cc_test(
+    name = "str_format_output_test",
+    srcs = ["internal/str_format/output_test.cc"],
+    copts = ABSL_TEST_COPTS,
+    visibility = ["//visibility:private"],
+    deps = [
+        ":str_format_internal",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+cc_test(
+    name = "str_format_parser_test",
+    srcs = ["internal/str_format/parser_test.cc"],
+    copts = ABSL_TEST_COPTS,
+    visibility = ["//visibility:private"],
+    deps = [
+        ":str_format_internal",
+        "//absl/base:core_headers",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
diff --git a/absl/strings/CMakeLists.txt b/absl/strings/CMakeLists.txt
index cab2c4561e48..3d30c72a0cda 100644
--- a/absl/strings/CMakeLists.txt
+++ b/absl/strings/CMakeLists.txt
@@ -81,6 +81,58 @@ absl_library(
     strings
 )
 
+# add str_format library
+absl_library(
+  TARGET
+    absl_str_format
+  SOURCES
+    "str_format.h"
+  PUBLIC_LIBRARIES
+    str_format_internal
+  EXPORT_NAME
+    str_format
+)
+
+# str_format_internal
+ absl_library(
+  TARGET
+    str_format_internal
+  SOURCES
+    "internal/str_format/arg.cc"
+    "internal/str_format/bind.cc"
+    "internal/str_format/extension.cc"
+    "internal/str_format/float_conversion.cc"
+    "internal/str_format/output.cc"
+    "internal/str_format/parser.cc"
+    "internal/str_format/arg.h"
+    "internal/str_format/bind.h"
+    "internal/str_format/checker.h"
+    "internal/str_format/extension.h"
+    "internal/str_format/float_conversion.h"
+    "internal/str_format/output.h"
+    "internal/str_format/parser.h"
+  PUBLIC_LIBRARIES
+    str_format_extension_internal
+    absl::strings
+    absl::base
+    absl::numeric
+    absl::container
+    absl::span
+)
+
+# str_format_extension_internal
+absl_library(
+  TARGET
+  str_format_extension_internal
+  SOURCES
+    "internal/str_format/extension.cc"
+    "internal/str_format/extension.h"
+    "internal/str_format/output.cc"
+    "internal/str_format/output.h"
+  PUBLIC_LIBRARIES
+    absl::base
+    absl::strings
+)
 
 #
 ## TESTS
@@ -347,3 +399,68 @@ absl_test(
   PUBLIC_LIBRARIES
     ${CHARCONV_BIGINT_TEST_PUBLIC_LIBRARIES}
 )
+# test str_format_test
+absl_test(
+  TARGET
+    str_format_test
+  SOURCES
+    "str_format_test.cc"
+  PUBLIC_LIBRARIES
+    absl::base
+    absl::str_format
+    absl::strings
+)
+
+# test str_format_bind_test
+absl_test(
+  TARGET
+    str_format_bind_test
+  SOURCES
+    "internal/str_format/bind_test.cc"
+  PUBLIC_LIBRARIES
+    str_format_internal
+)
+
+# test str_format_checker_test
+absl_test(
+  TARGET
+    str_format_checker_test
+  SOURCES
+    "internal/str_format/checker_test.cc"
+  PUBLIC_LIBRARIES
+    absl::str_format
+)
+
+# test str_format_convert_test
+absl_test(
+  TARGET
+    str_format_convert_test
+  SOURCES
+    "internal/str_format/convert_test.cc"
+  PUBLIC_LIBRARIES
+    str_format_internal
+    absl::numeric
+)
+
+# test str_format_output_test
+absl_test(
+  TARGET
+    str_format_output_test
+  SOURCES
+    "internal/str_format/output_test.cc"
+  PUBLIC_LIBRARIES
+    str_format_extension_internal
+)
+
+# test str_format_parser_test
+absl_test(
+  TARGET
+    str_format_parser_test
+  SOURCES
+    "internal/str_format/parser_test.cc"
+  PUBLIC_LIBRARIES
+    str_format_internal
+    absl::base
+)
+
+
diff --git a/absl/strings/internal/str_format/arg.cc b/absl/strings/internal/str_format/arg.cc
new file mode 100644
index 000000000000..eafb068fe286
--- /dev/null
+++ b/absl/strings/internal/str_format/arg.cc
@@ -0,0 +1,399 @@
+//
+// POSIX spec:
+//   http://pubs.opengroup.org/onlinepubs/009695399/functions/fprintf.html
+//
+#include "absl/strings/internal/str_format/arg.h"
+
+#include <cassert>
+#include <cerrno>
+#include <cstdlib>
+#include <string>
+#include <type_traits>
+
+#include "absl/base/port.h"
+#include "absl/strings/internal/str_format/float_conversion.h"
+
+namespace absl {
+namespace str_format_internal {
+namespace {
+
+const char kDigit[2][32] = { "0123456789abcdef", "0123456789ABCDEF" };
+
+// Reduce *capacity by s.size(), clipped to a 0 minimum.
+void ReducePadding(string_view s, size_t *capacity) {
+  *capacity = Excess(s.size(), *capacity);
+}
+
+// Reduce *capacity by n, clipped to a 0 minimum.
+void ReducePadding(size_t n, size_t *capacity) {
+  *capacity = Excess(n, *capacity);
+}
+
+template <typename T>
+struct MakeUnsigned : std::make_unsigned<T> {};
+template <>
+struct MakeUnsigned<absl::uint128> {
+  using type = absl::uint128;
+};
+
+template <typename T>
+struct IsSigned : std::is_signed<T> {};
+template <>
+struct IsSigned<absl::uint128> : std::false_type {};
+
+class ConvertedIntInfo {
+ public:
+  template <typename T>
+  ConvertedIntInfo(T v, ConversionChar conv) {
+    using Unsigned = typename MakeUnsigned<T>::type;
+    auto u = static_cast<Unsigned>(v);
+    if (IsNeg(v)) {
+      is_neg_ = true;
+      u = Unsigned{} - u;
+    } else {
+      is_neg_ = false;
+    }
+    UnsignedToStringRight(u, conv);
+  }
+
+  string_view digits() const {
+    return {end() - size_, static_cast<size_t>(size_)};
+  }
+  bool is_neg() const { return is_neg_; }
+
+ private:
+  template <typename T, bool IsSigned>
+  struct IsNegImpl {
+    static bool Eval(T v) { return v < 0; }
+  };
+  template <typename T>
+  struct IsNegImpl<T, false> {
+    static bool Eval(T) {
+      return false;
+    }
+  };
+
+  template <typename T>
+  bool IsNeg(T v) {
+    return IsNegImpl<T, IsSigned<T>::value>::Eval(v);
+  }
+
+  template <typename T>
+  void UnsignedToStringRight(T u, ConversionChar conv) {
+    char *p = end();
+    switch (conv.radix()) {
+      default:
+      case 10:
+        for (; u; u /= 10)
+          *--p = static_cast<char>('0' + static_cast<size_t>(u % 10));
+        break;
+      case 8:
+        for (; u; u /= 8)
+          *--p = static_cast<char>('0' + static_cast<size_t>(u % 8));
+        break;
+      case 16: {
+        const char *digits = kDigit[conv.upper() ? 1 : 0];
+        for (; u; u /= 16) *--p = digits[static_cast<size_t>(u % 16)];
+        break;
+      }
+    }
+    size_ = static_cast<int>(end() - p);
+  }
+
+  const char *end() const { return storage_ + sizeof(storage_); }
+  char *end() { return storage_ + sizeof(storage_); }
+
+  bool is_neg_;
+  int size_;
+  // Max size: 128 bit value as octal -> 43 digits
+  char storage_[128 / 3 + 1];
+};
+
+// Note: 'o' conversions do not have a base indicator, it's just that
+// the '#' flag is specified to modify the precision for 'o' conversions.
+string_view BaseIndicator(const ConvertedIntInfo &info,
+                          const ConversionSpec &conv) {
+  bool alt = conv.flags().alt;
+  int radix = conv.conv().radix();
+  if (conv.conv().id() == ConversionChar::p)
+    alt = true;  // always show 0x for %p.
+  // From the POSIX description of '#' flag:
+  //   "For x or X conversion specifiers, a non-zero result shall have
+  //   0x (or 0X) prefixed to it."
+  if (alt && radix == 16 && !info.digits().empty()) {
+    if (conv.conv().upper()) return "0X";
+    return "0x";
+  }
+  return {};
+}
+
+string_view SignColumn(bool neg, const ConversionSpec &conv) {
+  if (conv.conv().is_signed()) {
+    if (neg) return "-";
+    if (conv.flags().show_pos) return "+";
+    if (conv.flags().sign_col) return " ";
+  }
+  return {};
+}
+
+bool ConvertCharImpl(unsigned char v, const ConversionSpec &conv,
+                     FormatSinkImpl *sink) {
+  size_t fill = 0;
+  if (conv.width() >= 0) fill = conv.width();
+  ReducePadding(1, &fill);
+  if (!conv.flags().left) sink->Append(fill, ' ');
+  sink->Append(1, v);
+  if (conv.flags().left) sink->Append(fill, ' ');
+  return true;
+}
+
+bool ConvertIntImplInner(const ConvertedIntInfo &info,
+                         const ConversionSpec &conv, FormatSinkImpl *sink) {
+  // Print as a sequence of Substrings:
+  //   [left_spaces][sign][base_indicator][zeroes][formatted][right_spaces]
+  size_t fill = 0;
+  if (conv.width() >= 0) fill = conv.width();
+
+  string_view formatted = info.digits();
+  ReducePadding(formatted, &fill);
+
+  string_view sign = SignColumn(info.is_neg(), conv);
+  ReducePadding(sign, &fill);
+
+  string_view base_indicator = BaseIndicator(info, conv);
+  ReducePadding(base_indicator, &fill);
+
+  int precision = conv.precision();
+  bool precision_specified = precision >= 0;
+  if (!precision_specified)
+    precision = 1;
+
+  if (conv.flags().alt && conv.conv().id() == ConversionChar::o) {
+    // From POSIX description of the '#' (alt) flag:
+    //   "For o conversion, it increases the precision (if necessary) to
+    //   force the first digit of the result to be zero."
+    if (formatted.empty() || *formatted.begin() != '0') {
+      int needed = static_cast<int>(formatted.size()) + 1;
+      precision = std::max(precision, needed);
+    }
+  }
+
+  size_t num_zeroes = Excess(formatted.size(), precision);
+  ReducePadding(num_zeroes, &fill);
+
+  size_t num_left_spaces = !conv.flags().left ? fill : 0;
+  size_t num_right_spaces = conv.flags().left ? fill : 0;
+
+  // From POSIX description of the '0' (zero) flag:
+  //   "For d, i, o, u, x, and X conversion specifiers, if a precision
+  //   is specified, the '0' flag is ignored."
+  if (!precision_specified && conv.flags().zero) {
+    num_zeroes += num_left_spaces;
+    num_left_spaces = 0;
+  }
+
+  sink->Append(num_left_spaces, ' ');
+  sink->Append(sign);
+  sink->Append(base_indicator);
+  sink->Append(num_zeroes, '0');
+  sink->Append(formatted);
+  sink->Append(num_right_spaces, ' ');
+  return true;
+}
+
+template <typename T>
+bool ConvertIntImplInner(T v, const ConversionSpec &conv,
+                         FormatSinkImpl *sink) {
+  ConvertedIntInfo info(v, conv.conv());
+  if (conv.flags().basic && conv.conv().id() != ConversionChar::p) {
+    if (info.is_neg()) sink->Append(1, '-');
+    if (info.digits().empty()) {
+      sink->Append(1, '0');
+    } else {
+      sink->Append(info.digits());
+    }
+    return true;
+  }
+  return ConvertIntImplInner(info, conv, sink);
+}
+
+template <typename T>
+bool ConvertIntArg(T v, const ConversionSpec &conv, FormatSinkImpl *sink) {
+  if (conv.conv().is_float()) {
+    return FormatConvertImpl(static_cast<double>(v), conv, sink).value;
+  }
+  if (conv.conv().id() == ConversionChar::c)
+    return ConvertCharImpl(static_cast<unsigned char>(v), conv, sink);
+  if (!conv.conv().is_integral())
+    return false;
+  if (!conv.conv().is_signed() && IsSigned<T>::value) {
+    using U = typename MakeUnsigned<T>::type;
+    return FormatConvertImpl(static_cast<U>(v), conv, sink).value;
+  }
+  return ConvertIntImplInner(v, conv, sink);
+}
+
+template <typename T>
+bool ConvertFloatArg(T v, const ConversionSpec &conv, FormatSinkImpl *sink) {
+  return conv.conv().is_float() && ConvertFloatImpl(v, conv, sink);
+}
+
+inline bool ConvertStringArg(string_view v, const ConversionSpec &conv,
+                             FormatSinkImpl *sink) {
+  if (conv.conv().id() != ConversionChar::s)
+    return false;
+  if (conv.flags().basic) {
+    sink->Append(v);
+    return true;
+  }
+  return sink->PutPaddedString(v, conv.width(), conv.precision(),
+                               conv.flags().left);
+}
+
+}  // namespace
+
+// ==================== Strings ====================
+ConvertResult<Conv::s> FormatConvertImpl(const std::string &v,
+                                         const ConversionSpec &conv,
+                                         FormatSinkImpl *sink) {
+  return {ConvertStringArg(v, conv, sink)};
+}
+
+ConvertResult<Conv::s> FormatConvertImpl(string_view v,
+                                         const ConversionSpec &conv,
+                                         FormatSinkImpl *sink) {
+  return {ConvertStringArg(v, conv, sink)};
+}
+
+ConvertResult<Conv::s | Conv::p> FormatConvertImpl(const char *v,
+                                                   const ConversionSpec &conv,
+                                                   FormatSinkImpl *sink) {
+  if (conv.conv().id() == ConversionChar::p)
+    return {FormatConvertImpl(VoidPtr(v), conv, sink).value};
+  size_t len;
+  if (v == nullptr) {
+    len = 0;
+  } else if (conv.precision() < 0) {
+    len = std::strlen(v);
+  } else {
+    // If precision is set, we look for the null terminator on the valid range.
+    len = std::find(v, v + conv.precision(), '\0') - v;
+  }
+  return {ConvertStringArg(string_view(v, len), conv, sink)};
+}
+
+// ==================== Raw pointers ====================
+ConvertResult<Conv::p> FormatConvertImpl(VoidPtr v, const ConversionSpec &conv,
+                                         FormatSinkImpl *sink) {
+  if (conv.conv().id() != ConversionChar::p)
+    return {false};
+  if (!v.value) {
+    sink->Append("(nil)");
+    return {true};
+  }
+  return {ConvertIntImplInner(v.value, conv, sink)};
+}
+
+// ==================== Floats ====================
+FloatingConvertResult FormatConvertImpl(float v, const ConversionSpec &conv,
+                                        FormatSinkImpl *sink) {
+  return {ConvertFloatArg(v, conv, sink)};
+}
+FloatingConvertResult FormatConvertImpl(double v, const ConversionSpec &conv,
+                                        FormatSinkImpl *sink) {
+  return {ConvertFloatArg(v, conv, sink)};
+}
+FloatingConvertResult FormatConvertImpl(long double v,
+                                        const ConversionSpec &conv,
+                                        FormatSinkImpl *sink) {
+  return {ConvertFloatArg(v, conv, sink)};
+}
+
+// ==================== Chars ====================
+IntegralConvertResult FormatConvertImpl(char v, const ConversionSpec &conv,
+                                        FormatSinkImpl *sink) {
+  return {ConvertIntArg(v, conv, sink)};
+}
+IntegralConvertResult FormatConvertImpl(signed char v,
+                                        const ConversionSpec &conv,
+                                        FormatSinkImpl *sink) {
+  return {ConvertIntArg(v, conv, sink)};
+}
+IntegralConvertResult FormatConvertImpl(unsigned char v,
+                                        const ConversionSpec &conv,
+                                        FormatSinkImpl *sink) {
+  return {ConvertIntArg(v, conv, sink)};
+}
+
+// ==================== Ints ====================
+IntegralConvertResult FormatConvertImpl(short v,  // NOLINT
+                                        const ConversionSpec &conv,
+                                        FormatSinkImpl *sink) {
+  return {ConvertIntArg(v, conv, sink)};
+}
+IntegralConvertResult FormatConvertImpl(unsigned short v,  // NOLINT
+                                        const ConversionSpec &conv,
+                                        FormatSinkImpl *sink) {
+  return {ConvertIntArg(v, conv, sink)};
+}
+IntegralConvertResult FormatConvertImpl(int v, const ConversionSpec &conv,
+                                        FormatSinkImpl *sink) {
+  return {ConvertIntArg(v, conv, sink)};
+}
+IntegralConvertResult FormatConvertImpl(unsigned v, const ConversionSpec &conv,
+                                        FormatSinkImpl *sink) {
+  return {ConvertIntArg(v, conv, sink)};
+}
+IntegralConvertResult FormatConvertImpl(long v,  // NOLINT
+                                        const ConversionSpec &conv,
+                                        FormatSinkImpl *sink) {
+  return {ConvertIntArg(v, conv, sink)};
+}
+IntegralConvertResult FormatConvertImpl(unsigned long v,  // NOLINT
+                                        const ConversionSpec &conv,
+                                        FormatSinkImpl *sink) {
+  return {ConvertIntArg(v, conv, sink)};
+}
+IntegralConvertResult FormatConvertImpl(long long v,  // NOLINT
+                                        const ConversionSpec &conv,
+                                        FormatSinkImpl *sink) {
+  return {ConvertIntArg(v, conv, sink)};
+}
+IntegralConvertResult FormatConvertImpl(unsigned long long v,  // NOLINT
+                                        const ConversionSpec &conv,
+                                        FormatSinkImpl *sink) {
+  return {ConvertIntArg(v, conv, sink)};
+}
+IntegralConvertResult FormatConvertImpl(absl::uint128 v,
+                                        const ConversionSpec &conv,
+                                        FormatSinkImpl *sink) {
+  return {ConvertIntArg(v, conv, sink)};
+}
+
+template struct FormatArgImpl::TypedVTable<str_format_internal::VoidPtr>;
+
+template struct FormatArgImpl::TypedVTable<bool>;
+template struct FormatArgImpl::TypedVTable<char>;
+template struct FormatArgImpl::TypedVTable<signed char>;
+template struct FormatArgImpl::TypedVTable<unsigned char>;
+template struct FormatArgImpl::TypedVTable<short>;           // NOLINT
+template struct FormatArgImpl::TypedVTable<unsigned short>;  // NOLINT
+template struct FormatArgImpl::TypedVTable<int>;
+template struct FormatArgImpl::TypedVTable<unsigned>;
+template struct FormatArgImpl::TypedVTable<long>;                // NOLINT
+template struct FormatArgImpl::TypedVTable<unsigned long>;       // NOLINT
+template struct FormatArgImpl::TypedVTable<long long>;           // NOLINT
+template struct FormatArgImpl::TypedVTable<unsigned long long>;  // NOLINT
+template struct FormatArgImpl::TypedVTable<absl::uint128>;
+
+template struct FormatArgImpl::TypedVTable<float>;
+template struct FormatArgImpl::TypedVTable<double>;
+template struct FormatArgImpl::TypedVTable<long double>;
+
+template struct FormatArgImpl::TypedVTable<const char *>;
+template struct FormatArgImpl::TypedVTable<std::string>;
+template struct FormatArgImpl::TypedVTable<string_view>;
+
+}  // namespace str_format_internal
+
+}  // namespace absl
diff --git a/absl/strings/internal/str_format/arg.h b/absl/strings/internal/str_format/arg.h
new file mode 100644
index 000000000000..a9562188ea91
--- /dev/null
+++ b/absl/strings/internal/str_format/arg.h
@@ -0,0 +1,434 @@
+#ifndef ABSL_STRINGS_INTERNAL_STR_FORMAT_ARG_H_
+#define ABSL_STRINGS_INTERNAL_STR_FORMAT_ARG_H_
+
+#include <string.h>
+#include <wchar.h>
+
+#include <cstdio>
+#include <iomanip>
+#include <limits>
+#include <sstream>
+#include <string>
+#include <type_traits>
+
+#include "absl/base/port.h"
+#include "absl/meta/type_traits.h"
+#include "absl/numeric/int128.h"
+#include "absl/strings/internal/str_format/extension.h"
+#include "absl/strings/string_view.h"
+
+class Cord;
+class CordReader;
+
+namespace absl {
+
+class FormatCountCapture;
+class FormatSink;
+
+namespace str_format_internal {
+
+template <typename T, typename = void>
+struct HasUserDefinedConvert : std::false_type {};
+
+template <typename T>
+struct HasUserDefinedConvert<
+    T, void_t<decltype(AbslFormatConvert(
+           std::declval<const T&>(), std::declval<const ConversionSpec&>(),
+           std::declval<FormatSink*>()))>> : std::true_type {};
+template <typename T>
+class StreamedWrapper;
+
+// If 'v' can be converted (in the printf sense) according to 'conv',
+// then convert it, appending to `sink` and return `true`.
+// Otherwise fail and return `false`.
+// Raw pointers.
+struct VoidPtr {
+  VoidPtr() = default;
+  template <typename T,
+            decltype(reinterpret_cast<uintptr_t>(std::declval<T*>())) = 0>
+  VoidPtr(T* ptr)  // NOLINT
+      : value(ptr ? reinterpret_cast<uintptr_t>(ptr) : 0) {}
+  uintptr_t value;
+};
+ConvertResult<Conv::p> FormatConvertImpl(VoidPtr v, const ConversionSpec& conv,
+                                         FormatSinkImpl* sink);
+
+// Strings.
+ConvertResult<Conv::s> FormatConvertImpl(const std::string& v,
+                                         const ConversionSpec& conv,
+                                         FormatSinkImpl* sink);
+ConvertResult<Conv::s> FormatConvertImpl(string_view v,
+                                         const ConversionSpec& conv,
+                                         FormatSinkImpl* sink);
+ConvertResult<Conv::s | Conv::p> FormatConvertImpl(const char* v,
+                                                   const ConversionSpec& conv,
+                                                   FormatSinkImpl* sink);
+template <class AbslCord,
+          typename std::enable_if<
+              std::is_same<AbslCord, ::Cord>::value>::type* = nullptr,
+          class AbslCordReader = ::CordReader>
+ConvertResult<Conv::s> FormatConvertImpl(const AbslCord& value,
+                                         const ConversionSpec& conv,
+                                         FormatSinkImpl* sink) {
+  if (conv.conv().id() != ConversionChar::s) return {false};
+
+  bool is_left = conv.flags().left;
+  size_t space_remaining = 0;
+
+  int width = conv.width();
+  if (width >= 0) space_remaining = width;
+
+  size_t to_write = value.size();
+
+  int precision = conv.precision();
+  if (precision >= 0)
+    to_write = std::min(to_write, static_cast<size_t>(precision));
+
+  space_remaining = Excess(to_write, space_remaining);
+
+  if (space_remaining > 0 && !is_left) sink->Append(space_remaining, ' ');
+
+  string_view piece;
+  for (AbslCordReader reader(value);
+       to_write > 0 && reader.ReadFragment(&piece); to_write -= piece.size()) {
+    if (piece.size() > to_write) piece.remove_suffix(piece.size() - to_write);
+    sink->Append(piece);
+  }
+
+  if (space_remaining > 0 && is_left) sink->Append(space_remaining, ' ');
+  return {true};
+}
+
+using IntegralConvertResult =
+    ConvertResult<Conv::c | Conv::numeric | Conv::star>;
+using FloatingConvertResult = ConvertResult<Conv::floating>;
+
+// Floats.
+FloatingConvertResult FormatConvertImpl(float v, const ConversionSpec& conv,
+                                        FormatSinkImpl* sink);
+FloatingConvertResult FormatConvertImpl(double v, const ConversionSpec& conv,
+                                        FormatSinkImpl* sink);
+FloatingConvertResult FormatConvertImpl(long double v,
+                                        const ConversionSpec& conv,
+                                        FormatSinkImpl* sink);
+
+// Chars.
+IntegralConvertResult FormatConvertImpl(char v, const ConversionSpec& conv,
+                                        FormatSinkImpl* sink);
+IntegralConvertResult FormatConvertImpl(signed char v,
+                                        const ConversionSpec& conv,
+                                        FormatSinkImpl* sink);
+IntegralConvertResult FormatConvertImpl(unsigned char v,
+                                        const ConversionSpec& conv,
+                                        FormatSinkImpl* sink);
+
+// Ints.
+IntegralConvertResult FormatConvertImpl(short v,  // NOLINT
+                                        const ConversionSpec& conv,
+                                        FormatSinkImpl* sink);
+IntegralConvertResult FormatConvertImpl(unsigned short v,  // NOLINT
+                                        const ConversionSpec& conv,
+                                        FormatSinkImpl* sink);
+IntegralConvertResult FormatConvertImpl(int v, const ConversionSpec& conv,
+                                        FormatSinkImpl* sink);
+IntegralConvertResult FormatConvertImpl(unsigned v, const ConversionSpec& conv,
+                                        FormatSinkImpl* sink);
+IntegralConvertResult FormatConvertImpl(long v,  // NOLINT
+                                        const ConversionSpec& conv,
+                                        FormatSinkImpl* sink);
+IntegralConvertResult FormatConvertImpl(unsigned long v,  // NOLINT
+                                        const ConversionSpec& conv,
+                                        FormatSinkImpl* sink);
+IntegralConvertResult FormatConvertImpl(long long v,  // NOLINT
+                                        const ConversionSpec& conv,
+                                        FormatSinkImpl* sink);
+IntegralConvertResult FormatConvertImpl(unsigned long long v,  // NOLINT
+                                        const ConversionSpec& conv,
+                                        FormatSinkImpl* sink);
+IntegralConvertResult FormatConvertImpl(uint128 v, const ConversionSpec& conv,
+                                        FormatSinkImpl* sink);
+template <typename T, enable_if_t<std::is_same<T, bool>::value, int> = 0>
+IntegralConvertResult FormatConvertImpl(T v, const ConversionSpec& conv,
+                                        FormatSinkImpl* sink) {
+  return FormatConvertImpl(static_cast<int>(v), conv, sink);
+}
+
+// We provide this function to help the checker, but it is never defined.
+// FormatArgImpl will use the underlying Convert functions instead.
+template <typename T>
+typename std::enable_if<std::is_enum<T>::value &&
+                            !HasUserDefinedConvert<T>::value,
+                        IntegralConvertResult>::type
+FormatConvertImpl(T v, const ConversionSpec& conv, FormatSinkImpl* sink);
+
+template <typename T>
+ConvertResult<Conv::s> FormatConvertImpl(const StreamedWrapper<T>& v,
+                                         const ConversionSpec& conv,
+                                         FormatSinkImpl* out) {
+  std::ostringstream oss;
+  oss << v.v_;
+  if (!oss) return {false};
+  return str_format_internal::FormatConvertImpl(oss.str(), conv, out);
+}
+
+// Use templates and dependent types to delay evaluation of the function
+// until after FormatCountCapture is fully defined.
+struct FormatCountCaptureHelper {
+  template <class T = int>
+  static ConvertResult<Conv::n> ConvertHelper(const FormatCountCapture& v,
+                                              const ConversionSpec& conv,
+                                              FormatSinkImpl* sink) {
+    const absl::enable_if_t<sizeof(T) != 0, FormatCountCapture>& v2 = v;
+
+    if (conv.conv().id() != str_format_internal::ConversionChar::n)
+      return {false};
+    *v2.p_ = static_cast<int>(sink->size());
+    return {true};
+  }
+};
+
+template <class T = int>
+ConvertResult<Conv::n> FormatConvertImpl(const FormatCountCapture& v,
+                                         const ConversionSpec& conv,
+                                         FormatSinkImpl* sink) {
+  return FormatCountCaptureHelper::ConvertHelper(v, conv, sink);
+}
+
+// Helper friend struct to hide implementation details from the public API of
+// FormatArgImpl.
+struct FormatArgImplFriend {
+  template <typename Arg>
+  static bool ToInt(Arg arg, int* out) {
+    if (!arg.vtbl_->to_int) return false;
+    *out = arg.vtbl_->to_int(arg.data_);
+    return true;
+  }
+
+  template <typename Arg>
+  static bool Convert(Arg arg, const str_format_internal::ConversionSpec& conv,
+                      FormatSinkImpl* out) {
+    return arg.vtbl_->convert(arg.data_, conv, out);
+  }
+
+  template <typename Arg>
+  static const void* GetVTablePtrForTest(Arg arg) {
+    return arg.vtbl_;
+  }
+};
+
+// A type-erased handle to a format argument.
+class FormatArgImpl {
+ private:
+  enum { kInlinedSpace = 8 };
+
+  using VoidPtr = str_format_internal::VoidPtr;
+
+  union Data {
+    const void* ptr;
+    const volatile void* volatile_ptr;
+    char buf[kInlinedSpace];
+  };
+
+  struct VTable {
+    bool (*convert)(Data, const str_format_internal::ConversionSpec& conv,
+                    FormatSinkImpl* out);
+    int (*to_int)(Data);
+  };
+
+  template <typename T>
+  struct store_by_value
+      : std::integral_constant<bool, (sizeof(T) <= kInlinedSpace) &&
+                                         (std::is_integral<T>::value ||
+                                          std::is_floating_point<T>::value ||
+                                          std::is_pointer<T>::value ||
+                                          std::is_same<VoidPtr, T>::value)> {};
+
+  enum StoragePolicy { ByPointer, ByVolatilePointer, ByValue };
+  template <typename T>
+  struct storage_policy
+      : std::integral_constant<StoragePolicy,
+                               (std::is_volatile<T>::value
+                                    ? ByVolatilePointer
+                                    : (store_by_value<T>::value ? ByValue
+                                                                : ByPointer))> {
+  };
+
+  // An instance of an FormatArgImpl::VTable suitable for 'T'.
+  template <typename T>
+  struct TypedVTable;
+
+  // To reduce the number of vtables we will decay values before hand.
+  // Anything with a user-defined Convert will get its own vtable.
+  // For everything else:
+  //   - Decay char* and char arrays into `const char*`
+  //   - Decay any other pointer to `const void*`
+  //   - Decay all enums to their underlying type.
+  //   - Decay function pointers to void*.
+  template <typename T, typename = void>
+  struct DecayType {
+    static constexpr bool kHasUserDefined =
+        str_format_internal::HasUserDefinedConvert<T>::value;
+    using type = typename std::conditional<
+        !kHasUserDefined && std::is_convertible<T, const char*>::value,
+        const char*,
+        typename std::conditional<!kHasUserDefined &&
+                                      std::is_convertible<T, VoidPtr>::value,
+                                  VoidPtr, const T&>::type>::type;
+  };
+  template <typename T>
+  struct DecayType<T,
+                   typename std::enable_if<
+                       !str_format_internal::HasUserDefinedConvert<T>::value &&
+                       std::is_enum<T>::value>::type> {
+    using type = typename std::underlying_type<T>::type;
+  };
+
+ public:
+  template <typename T>
+  explicit FormatArgImpl(const T& value) {
+    using D = typename DecayType<T>::type;
+    static_assert(
+        std::is_same<D, const T&>::value || storage_policy<D>::value == ByValue,
+        "Decayed types must be stored by value");
+    Init(static_cast<D>(value));
+  }
+
+ private:
+  friend struct str_format_internal::FormatArgImplFriend;
+  template <typename T, StoragePolicy = storage_policy<T>::value>
+  struct Manager;
+
+  template <typename T>
+  struct Manager<T, ByPointer> {
+    static Data SetValue(const T& value) {
+      Data data;
+      data.ptr = &value;
+      return data;
+    }
+
+    static const T& Value(Data arg) { return *static_cast<const T*>(arg.ptr); }
+  };
+
+  template <typename T>
+  struct Manager<T, ByVolatilePointer> {
+    static Data SetValue(const T& value) {
+      Data data;
+      data.volatile_ptr = &value;
+      return data;
+    }
+
+    static const T& Value(Data arg) {
+      return *static_cast<const T*>(arg.volatile_ptr);
+    }
+  };
+
+  template <typename T>
+  struct Manager<T, ByValue> {
+    static Data SetValue(const T& value) {
+      Data data;
+      memcpy(data.buf, &value, sizeof(value));
+      return data;
+    }
+
+    static T Value(Data arg) {
+      T value;
+      memcpy(&value, arg.buf, sizeof(T));
+      return value;
+    }
+  };
+
+  template <typename T>
+  void Init(const T& value);
+
+  template <typename T>
+  static int ToIntVal(const T& val) {
+    using CommonType = typename std::conditional<std::is_signed<T>::value,
+                                                 int64_t, uint64_t>::type;
+    if (static_cast<CommonType>(val) >
+        static_cast<CommonType>(std::numeric_limits<int>::max())) {
+      return std::numeric_limits<int>::max();
+    } else if (std::is_signed<T>::value &&
+               static_cast<CommonType>(val) <
+                   static_cast<CommonType>(std::numeric_limits<int>::min())) {
+      return std::numeric_limits<int>::min();
+    }
+    return static_cast<int>(val);
+  }
+
+  Data data_;
+  const VTable* vtbl_;
+};
+
+template <typename T>
+struct FormatArgImpl::TypedVTable {
+ private:
+  static bool ConvertImpl(Data arg,
+                          const str_format_internal::ConversionSpec& conv,
+                          FormatSinkImpl* out) {
+    return str_format_internal::FormatConvertImpl(Manager<T>::Value(arg), conv,
+                                                  out)
+        .value;
+  }
+
+  template <typename U = T, typename = void>
+  struct ToIntImpl {
+    static constexpr int (*value)(Data) = nullptr;
+  };
+
+  template <typename U>
+  struct ToIntImpl<U,
+                   typename std::enable_if<std::is_integral<U>::value>::type> {
+    static int Invoke(Data arg) { return ToIntVal(Manager<T>::Value(arg)); }
+    static constexpr int (*value)(Data) = &Invoke;
+  };
+
+  template <typename U>
+  struct ToIntImpl<U, typename std::enable_if<std::is_enum<U>::value>::type> {
+    static int Invoke(Data arg) {
+      return ToIntVal(static_cast<typename std::underlying_type<T>::type>(
+          Manager<T>::Value(arg)));
+    }
+    static constexpr int (*value)(Data) = &Invoke;
+  };
+
+ public:
+  static constexpr VTable value{&ConvertImpl, ToIntImpl<>::value};
+};
+
+template <typename T>
+constexpr FormatArgImpl::VTable FormatArgImpl::TypedVTable<T>::value;
+
+template <typename T>
+void FormatArgImpl::Init(const T& value) {
+  data_ = Manager<T>::SetValue(value);
+  vtbl_ = &TypedVTable<T>::value;
+}
+
+extern template struct FormatArgImpl::TypedVTable<str_format_internal::VoidPtr>;
+
+extern template struct FormatArgImpl::TypedVTable<bool>;
+extern template struct FormatArgImpl::TypedVTable<char>;
+extern template struct FormatArgImpl::TypedVTable<signed char>;
+extern template struct FormatArgImpl::TypedVTable<unsigned char>;
+extern template struct FormatArgImpl::TypedVTable<short>;           // NOLINT
+extern template struct FormatArgImpl::TypedVTable<unsigned short>;  // NOLINT
+extern template struct FormatArgImpl::TypedVTable<int>;
+extern template struct FormatArgImpl::TypedVTable<unsigned>;
+extern template struct FormatArgImpl::TypedVTable<long>;           // NOLINT
+extern template struct FormatArgImpl::TypedVTable<unsigned long>;  // NOLINT
+extern template struct FormatArgImpl::TypedVTable<long long>;      // NOLINT
+extern template struct FormatArgImpl::TypedVTable<
+    unsigned long long>;  // NOLINT
+extern template struct FormatArgImpl::TypedVTable<uint128>;
+
+extern template struct FormatArgImpl::TypedVTable<float>;
+extern template struct FormatArgImpl::TypedVTable<double>;
+extern template struct FormatArgImpl::TypedVTable<long double>;
+
+extern template struct FormatArgImpl::TypedVTable<const char*>;
+extern template struct FormatArgImpl::TypedVTable<std::string>;
+extern template struct FormatArgImpl::TypedVTable<string_view>;
+}  // namespace str_format_internal
+}  // namespace absl
+
+#endif  // ABSL_STRINGS_INTERNAL_STR_FORMAT_ARG_H_
diff --git a/absl/strings/internal/str_format/arg_test.cc b/absl/strings/internal/str_format/arg_test.cc
new file mode 100644
index 000000000000..83d59048ea27
--- /dev/null
+++ b/absl/strings/internal/str_format/arg_test.cc
@@ -0,0 +1,111 @@
+// Copyright 2017 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+#include "absl/strings/internal/str_format/arg.h"
+
+#include <ostream>
+#include <string>
+#include "gtest/gtest.h"
+#include "absl/strings/str_format.h"
+
+namespace absl {
+namespace str_format_internal {
+namespace {
+
+class FormatArgImplTest : public ::testing::Test {
+ public:
+  enum Color { kRed, kGreen, kBlue };
+
+  static const char *hi() { return "hi"; }
+};
+
+TEST_F(FormatArgImplTest, ToInt) {
+  int out = 0;
+  EXPECT_TRUE(FormatArgImplFriend::ToInt(FormatArgImpl(1), &out));
+  EXPECT_EQ(1, out);
+  EXPECT_TRUE(FormatArgImplFriend::ToInt(FormatArgImpl(-1), &out));
+  EXPECT_EQ(-1, out);
+  EXPECT_TRUE(
+      FormatArgImplFriend::ToInt(FormatArgImpl(static_cast<char>(64)), &out));
+  EXPECT_EQ(64, out);
+  EXPECT_TRUE(FormatArgImplFriend::ToInt(
+      FormatArgImpl(static_cast<unsigned long long>(123456)), &out));  // NOLINT
+  EXPECT_EQ(123456, out);
+  EXPECT_TRUE(FormatArgImplFriend::ToInt(
+      FormatArgImpl(static_cast<unsigned long long>(  // NOLINT
+                        std::numeric_limits<int>::max()) +
+                    1),
+      &out));
+  EXPECT_EQ(std::numeric_limits<int>::max(), out);
+  EXPECT_TRUE(FormatArgImplFriend::ToInt(
+      FormatArgImpl(static_cast<long long>(  // NOLINT
+                        std::numeric_limits<int>::min()) -
+                    10),
+      &out));
+  EXPECT_EQ(std::numeric_limits<int>::min(), out);
+  EXPECT_TRUE(FormatArgImplFriend::ToInt(FormatArgImpl(false), &out));
+  EXPECT_EQ(0, out);
+  EXPECT_TRUE(FormatArgImplFriend::ToInt(FormatArgImpl(true), &out));
+  EXPECT_EQ(1, out);
+  EXPECT_FALSE(FormatArgImplFriend::ToInt(FormatArgImpl(2.2), &out));
+  EXPECT_FALSE(FormatArgImplFriend::ToInt(FormatArgImpl(3.2f), &out));
+  EXPECT_FALSE(FormatArgImplFriend::ToInt(
+      FormatArgImpl(static_cast<int *>(nullptr)), &out));
+  EXPECT_FALSE(FormatArgImplFriend::ToInt(FormatArgImpl(hi()), &out));
+  EXPECT_FALSE(FormatArgImplFriend::ToInt(FormatArgImpl("hi"), &out));
+  EXPECT_TRUE(FormatArgImplFriend::ToInt(FormatArgImpl(kBlue), &out));
+  EXPECT_EQ(2, out);
+}
+
+extern const char kMyArray[];
+
+TEST_F(FormatArgImplTest, CharArraysDecayToCharPtr) {
+  const char* a = "";
+  EXPECT_EQ(FormatArgImplFriend::GetVTablePtrForTest(FormatArgImpl(a)),
+            FormatArgImplFriend::GetVTablePtrForTest(FormatArgImpl("")));
+  EXPECT_EQ(FormatArgImplFriend::GetVTablePtrForTest(FormatArgImpl(a)),
+            FormatArgImplFriend::GetVTablePtrForTest(FormatArgImpl("A")));
+  EXPECT_EQ(FormatArgImplFriend::GetVTablePtrForTest(FormatArgImpl(a)),
+            FormatArgImplFriend::GetVTablePtrForTest(FormatArgImpl("ABC")));
+  EXPECT_EQ(FormatArgImplFriend::GetVTablePtrForTest(FormatArgImpl(a)),
+            FormatArgImplFriend::GetVTablePtrForTest(FormatArgImpl(kMyArray)));
+}
+
+TEST_F(FormatArgImplTest, OtherPtrDecayToVoidPtr) {
+  auto expected = FormatArgImplFriend::GetVTablePtrForTest(
+      FormatArgImpl(static_cast<void *>(nullptr)));
+  EXPECT_EQ(FormatArgImplFriend::GetVTablePtrForTest(
+                FormatArgImpl(static_cast<int *>(nullptr))),
+            expected);
+  EXPECT_EQ(FormatArgImplFriend::GetVTablePtrForTest(
+                FormatArgImpl(static_cast<volatile int *>(nullptr))),
+            expected);
+
+  auto p = static_cast<void (*)()>([] {});
+  EXPECT_EQ(FormatArgImplFriend::GetVTablePtrForTest(FormatArgImpl(p)),
+            expected);
+}
+
+TEST_F(FormatArgImplTest, WorksWithCharArraysOfUnknownSize) {
+  std::string s;
+  FormatSinkImpl sink(&s);
+  ConversionSpec conv;
+  conv.set_conv(ConversionChar::FromChar('s'));
+  conv.set_flags(Flags());
+  conv.set_width(-1);
+  conv.set_precision(-1);
+  EXPECT_TRUE(
+      FormatArgImplFriend::Convert(FormatArgImpl(kMyArray), conv, &sink));
+  sink.Flush();
+  EXPECT_EQ("ABCDE", s);
+}
+const char kMyArray[] = "ABCDE";
+
+}  // namespace
+}  // namespace str_format_internal
+}  // namespace absl
diff --git a/absl/strings/internal/str_format/bind.cc b/absl/strings/internal/str_format/bind.cc
new file mode 100644
index 000000000000..33e8641558ab
--- /dev/null
+++ b/absl/strings/internal/str_format/bind.cc
@@ -0,0 +1,232 @@
+#include "absl/strings/internal/str_format/bind.h"
+
+#include <cerrno>
+#include <limits>
+#include <sstream>
+#include <string>
+
+namespace absl {
+namespace str_format_internal {
+
+namespace {
+
+inline bool BindFromPosition(int position, int* value,
+                             absl::Span<const FormatArgImpl> pack) {
+  assert(position > 0);
+  if (static_cast<size_t>(position) > pack.size()) {
+    return false;
+  }
+  // -1 because positions are 1-based
+  return FormatArgImplFriend::ToInt(pack[position - 1], value);
+}
+
+class ArgContext {
+ public:
+  explicit ArgContext(absl::Span<const FormatArgImpl> pack) : pack_(pack) {}
+
+  // Fill 'bound' with the results of applying the context's argument pack
+  // to the specified 'props'. We synthesize a BoundConversion by
+  // lining up a UnboundConversion with a user argument. We also
+  // resolve any '*' specifiers for width and precision, so after
+  // this call, 'bound' has all the information it needs to be formatted.
+  // Returns false on failure.
+  bool Bind(const UnboundConversion *props, BoundConversion *bound);
+
+ private:
+  absl::Span<const FormatArgImpl> pack_;
+};
+
+inline bool ArgContext::Bind(const UnboundConversion* unbound,
+                             BoundConversion* bound) {
+  const FormatArgImpl* arg = nullptr;
+  int arg_position = unbound->arg_position;
+  if (static_cast<size_t>(arg_position - 1) >= pack_.size()) return false;
+  arg = &pack_[arg_position - 1];  // 1-based
+
+  if (!unbound->flags.basic) {
+    int width = unbound->width.value();
+    bool force_left = false;
+    if (unbound->width.is_from_arg()) {
+      if (!BindFromPosition(unbound->width.get_from_arg(), &width, pack_))
+        return false;
+      if (width < 0) {
+        // "A negative field width is taken as a '-' flag followed by a
+        // positive field width."
+        force_left = true;
+        width = -width;
+      }
+    }
+
+    int precision = unbound->precision.value();
+    if (unbound->precision.is_from_arg()) {
+      if (!BindFromPosition(unbound->precision.get_from_arg(), &precision,
+                            pack_))
+        return false;
+    }
+
+    bound->set_width(width);
+    bound->set_precision(precision);
+    bound->set_flags(unbound->flags);
+    if (force_left)
+      bound->set_left(true);
+  } else {
+    bound->set_flags(unbound->flags);
+    bound->set_width(-1);
+    bound->set_precision(-1);
+  }
+
+  bound->set_length_mod(unbound->length_mod);
+  bound->set_conv(unbound->conv);
+  bound->set_arg(arg);
+  return true;
+}
+
+template <typename Converter>
+class ConverterConsumer {
+ public:
+  ConverterConsumer(Converter converter, absl::Span<const FormatArgImpl> pack)
+      : converter_(converter), arg_context_(pack) {}
+
+  bool Append(string_view s) {
+    converter_.Append(s);
+    return true;
+  }
+  bool ConvertOne(const UnboundConversion& conv, string_view conv_string) {
+    BoundConversion bound;
+    if (!arg_context_.Bind(&conv, &bound)) return false;
+    return converter_.ConvertOne(bound, conv_string);
+  }
+
+ private:
+  Converter converter_;
+  ArgContext arg_context_;
+};
+
+template <typename Converter>
+bool ConvertAll(const UntypedFormatSpecImpl& format,
+                absl::Span<const FormatArgImpl> args,
+                const Converter& converter) {
+  const ParsedFormatBase* pc = format.parsed_conversion();
+  if (pc)
+    return pc->ProcessFormat(ConverterConsumer<Converter>(converter, args));
+
+  return ParseFormatString(format.str(),
+                           ConverterConsumer<Converter>(converter, args));
+}
+
+class DefaultConverter {
+ public:
+  explicit DefaultConverter(FormatSinkImpl* sink) : sink_(sink) {}
+
+  void Append(string_view s) const { sink_->Append(s); }
+
+  bool ConvertOne(const BoundConversion& bound, string_view /*conv*/) const {
+    return FormatArgImplFriend::Convert(*bound.arg(), bound, sink_);
+  }
+
+ private:
+  FormatSinkImpl* sink_;
+};
+
+class SummarizingConverter {
+ public:
+  explicit SummarizingConverter(FormatSinkImpl* sink) : sink_(sink) {}
+
+  void Append(string_view s) const { sink_->Append(s); }
+
+  bool ConvertOne(const BoundConversion& bound, string_view /*conv*/) const {
+    UntypedFormatSpecImpl spec("%d");
+
+    std::ostringstream ss;
+    ss << "{" << Streamable(spec, {*bound.arg()}) << ":" << bound.flags();
+    if (bound.width() >= 0) ss << bound.width();
+    if (bound.precision() >= 0) ss << "." << bound.precision();
+    ss << bound.length_mod() << bound.conv() << "}";
+    Append(ss.str());
+    return true;
+  }
+
+ private:
+  FormatSinkImpl* sink_;
+};
+
+}  // namespace
+
+bool BindWithPack(const UnboundConversion* props,
+                  absl::Span<const FormatArgImpl> pack,
+                  BoundConversion* bound) {
+  return ArgContext(pack).Bind(props, bound);
+}
+
+std::string Summarize(const UntypedFormatSpecImpl& format,
+                 absl::Span<const FormatArgImpl> args) {
+  typedef SummarizingConverter Converter;
+  std::string out;
+  {
+    // inner block to destroy sink before returning out. It ensures a last
+    // flush.
+    FormatSinkImpl sink(&out);
+    if (!ConvertAll(format, args, Converter(&sink))) {
+      sink.Flush();
+      out.clear();
+    }
+  }
+  return out;
+}
+
+bool FormatUntyped(FormatRawSinkImpl raw_sink,
+                   const UntypedFormatSpecImpl& format,
+                   absl::Span<const FormatArgImpl> args) {
+  FormatSinkImpl sink(raw_sink);
+  using Converter = DefaultConverter;
+  if (!ConvertAll(format, args, Converter(&sink))) {
+    sink.Flush();
+    return false;
+  }
+  return true;
+}
+
+std::ostream& Streamable::Print(std::ostream& os) const {
+  if (!FormatUntyped(&os, format_, args_)) os.setstate(std::ios::failbit);
+  return os;
+}
+
+std::string& AppendPack(std::string* out, const UntypedFormatSpecImpl& format,
+                   absl::Span<const FormatArgImpl> args) {
+  size_t orig = out->size();
+  if (!FormatUntyped(out, format, args)) out->resize(orig);
+  return *out;
+}
+
+int FprintF(std::FILE* output, const UntypedFormatSpecImpl& format,
+            absl::Span<const FormatArgImpl> args) {
+  FILERawSink sink(output);
+  if (!FormatUntyped(&sink, format, args)) {
+    errno = EINVAL;
+    return -1;
+  }
+  if (sink.error()) {
+    errno = sink.error();
+    return -1;
+  }
+  if (sink.count() > std::numeric_limits<int>::max()) {
+    errno = EFBIG;
+    return -1;
+  }
+  return static_cast<int>(sink.count());
+}
+
+int SnprintF(char* output, size_t size, const UntypedFormatSpecImpl& format,
+             absl::Span<const FormatArgImpl> args) {
+  BufferRawSink sink(output, size ? size - 1 : 0);
+  if (!FormatUntyped(&sink, format, args)) {
+    errno = EINVAL;
+    return -1;
+  }
+  size_t total = sink.total_written();
+  if (size) output[std::min(total, size - 1)] = 0;
+  return static_cast<int>(total);
+}
+
+}  // namespace str_format_internal
+}  // namespace absl
diff --git a/absl/strings/internal/str_format/bind.h b/absl/strings/internal/str_format/bind.h
new file mode 100644
index 000000000000..4008611211cf
--- /dev/null
+++ b/absl/strings/internal/str_format/bind.h
@@ -0,0 +1,189 @@
+#ifndef ABSL_STRINGS_INTERNAL_STR_FORMAT_BIND_H_
+#define ABSL_STRINGS_INTERNAL_STR_FORMAT_BIND_H_
+
+#include <array>
+#include <cstdio>
+#include <sstream>
+#include <string>
+
+#include "absl/base/port.h"
+#include "absl/container/inlined_vector.h"
+#include "absl/strings/internal/str_format/arg.h"
+#include "absl/strings/internal/str_format/checker.h"
+#include "absl/strings/internal/str_format/parser.h"
+#include "absl/types/span.h"
+
+namespace absl {
+
+class UntypedFormatSpec;
+
+namespace str_format_internal {
+
+class BoundConversion : public ConversionSpec {
+ public:
+  const FormatArgImpl* arg() const { return arg_; }
+  void set_arg(const FormatArgImpl* a) { arg_ = a; }
+
+ private:
+  const FormatArgImpl* arg_;
+};
+
+// This is the type-erased class that the implementation uses.
+class UntypedFormatSpecImpl {
+ public:
+  UntypedFormatSpecImpl() = delete;
+
+  explicit UntypedFormatSpecImpl(string_view s) : str_(s), pc_() {}
+  explicit UntypedFormatSpecImpl(
+      const str_format_internal::ParsedFormatBase* pc)
+      : pc_(pc) {}
+  string_view str() const { return str_; }
+  const str_format_internal::ParsedFormatBase* parsed_conversion() const {
+    return pc_;
+  }
+
+  template <typename T>
+  static const UntypedFormatSpecImpl& Extract(const T& s) {
+    return s.spec_;
+  }
+
+ private:
+  string_view str_;
+  const str_format_internal::ParsedFormatBase* pc_;
+};
+
+template <typename T, typename...>
+struct MakeDependent {
+  using type = T;
+};
+
+// Implicitly convertible from `const char*`, `string_view`, and the
+// `ExtendedParsedFormat` type. This abstraction allows all format functions to
+// operate on any without providing too many overloads.
+template <typename... Args>
+class FormatSpecTemplate
+    : public MakeDependent<UntypedFormatSpec, Args...>::type {
+  using Base = typename MakeDependent<UntypedFormatSpec, Args...>::type;
+
+ public:
+#if ABSL_INTERNAL_ENABLE_FORMAT_CHECKER
+
+  // Honeypot overload for when the std::string is not constexpr.
+  // We use the 'unavailable' attribute to give a better compiler error than
+  // just 'method is deleted'.
+  FormatSpecTemplate(...)  // NOLINT
+      __attribute__((unavailable("Format std::string is not constexpr.")));
+
+  // Honeypot overload for when the format is constexpr and invalid.
+  // We use the 'unavailable' attribute to give a better compiler error than
+  // just 'method is deleted'.
+  // To avoid checking the format twice, we just check that the format is
+  // constexpr. If is it valid, then the overload below will kick in.
+  // We add the template here to make this overload have lower priority.
+  template <typename = void>
+  FormatSpecTemplate(const char* s)  // NOLINT
+      __attribute__((
+          enable_if(str_format_internal::EnsureConstexpr(s), "constexpr trap"),
+          unavailable(
+              "Format specified does not match the arguments passed.")));
+
+  template <typename T = void>
+  FormatSpecTemplate(string_view s)  // NOLINT
+      __attribute__((enable_if(str_format_internal::EnsureConstexpr(s),
+                               "constexpr trap"))) {
+    static_assert(sizeof(T*) == 0,
+                  "Format specified does not match the arguments passed.");
+  }
+
+  // Good format overload.
+  FormatSpecTemplate(const char* s)  // NOLINT
+      __attribute__((enable_if(ValidFormatImpl<ArgumentToConv<Args>()...>(s),
+                               "bad format trap")))
+      : Base(s) {}
+
+  FormatSpecTemplate(string_view s)  // NOLINT
+      __attribute__((enable_if(ValidFormatImpl<ArgumentToConv<Args>()...>(s),
+                               "bad format trap")))
+      : Base(s) {}
+
+#else  // ABSL_INTERNAL_ENABLE_FORMAT_CHECKER
+
+  FormatSpecTemplate(const char* s) : Base(s) {}  // NOLINT
+  FormatSpecTemplate(string_view s) : Base(s) {}  // NOLINT
+
+#endif  // ABSL_INTERNAL_ENABLE_FORMAT_CHECKER
+
+  template <Conv... C, typename = typename std::enable_if<
+                           sizeof...(C) == sizeof...(Args) &&
+                           AllOf(Contains(ArgumentToConv<Args>(),
+                                          C)...)>::type>
+  FormatSpecTemplate(const ExtendedParsedFormat<C...>& pc)  // NOLINT
+      : Base(&pc) {}
+};
+
+template <typename... Args>
+struct FormatSpecDeductionBarrier {
+  using type = FormatSpecTemplate<Args...>;
+};
+
+class Streamable {
+ public:
+  Streamable(const UntypedFormatSpecImpl& format,
+             absl::Span<const FormatArgImpl> args)
+      : format_(format), args_(args.begin(), args.end()) {}
+
+  std::ostream& Print(std::ostream& os) const;
+
+  friend std::ostream& operator<<(std::ostream& os, const Streamable& l) {
+    return l.Print(os);
+  }
+
+ private:
+  const UntypedFormatSpecImpl& format_;
+  absl::InlinedVector<FormatArgImpl, 4> args_;
+};
+
+// for testing
+std::string Summarize(const UntypedFormatSpecImpl& format,
+                 absl::Span<const FormatArgImpl> args);
+bool BindWithPack(const UnboundConversion* props,
+                  absl::Span<const FormatArgImpl> pack, BoundConversion* bound);
+
+bool FormatUntyped(FormatRawSinkImpl raw_sink,
+                   const UntypedFormatSpecImpl& format,
+                   absl::Span<const FormatArgImpl> args);
+
+std::string& AppendPack(std::string* out, const UntypedFormatSpecImpl& format,
+                   absl::Span<const FormatArgImpl> args);
+
+inline std::string FormatPack(const UntypedFormatSpecImpl& format,
+                         absl::Span<const FormatArgImpl> args) {
+  std::string out;
+  AppendPack(&out, format, args);
+  return out;
+}
+
+int FprintF(std::FILE* output, const UntypedFormatSpecImpl& format,
+            absl::Span<const FormatArgImpl> args);
+int SnprintF(char* output, size_t size, const UntypedFormatSpecImpl& format,
+             absl::Span<const FormatArgImpl> args);
+
+// Returned by Streamed(v). Converts via '%s' to the std::string created
+// by std::ostream << v.
+template <typename T>
+class StreamedWrapper {
+ public:
+  explicit StreamedWrapper(const T& v) : v_(v) { }
+
+ private:
+  template <typename S>
+  friend ConvertResult<Conv::s> FormatConvertImpl(const StreamedWrapper<S>& v,
+                                                  const ConversionSpec& conv,
+                                                  FormatSinkImpl* out);
+  const T& v_;
+};
+
+}  // namespace str_format_internal
+}  // namespace absl
+
+#endif  // ABSL_STRINGS_INTERNAL_STR_FORMAT_BIND_H_
diff --git a/absl/strings/internal/str_format/bind_test.cc b/absl/strings/internal/str_format/bind_test.cc
new file mode 100644
index 000000000000..47575739ba13
--- /dev/null
+++ b/absl/strings/internal/str_format/bind_test.cc
@@ -0,0 +1,131 @@
+#include "absl/strings/internal/str_format/bind.h"
+
+#include <string.h>
+
+#include "gtest/gtest.h"
+
+namespace absl {
+namespace str_format_internal {
+namespace {
+
+template <typename T, size_t N>
+size_t ArraySize(T (&)[N]) {
+  return N;
+}
+
+class FormatBindTest : public ::testing::Test {
+ public:
+  bool Extract(const char *s, UnboundConversion *props, int *next) const {
+    absl::string_view src = s;
+    return ConsumeUnboundConversion(&src, props, next) && src.empty();
+  }
+};
+
+TEST_F(FormatBindTest, BindSingle) {
+  struct Expectation {
+    int line;
+    const char *fmt;
+    int ok_phases;
+    const FormatArgImpl *arg;
+    int width;
+    int precision;
+    int next_arg;
+  };
+  const int no = -1;
+  const int ia[] = { 10, 20, 30, 40};
+  const FormatArgImpl args[] = {FormatArgImpl(ia[0]), FormatArgImpl(ia[1]),
+                                FormatArgImpl(ia[2]), FormatArgImpl(ia[3])};
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
+  const Expectation kExpect[] = {
+    {__LINE__, "d",          2, &args[0], no, no, 2},
+    {__LINE__, "4d",         2, &args[0],  4, no, 2},
+    {__LINE__, ".5d",        2, &args[0], no,  5, 2},
+    {__LINE__, "4.5d",       2, &args[0],  4,  5, 2},
+    {__LINE__, "*d",         2, &args[1], 10, no, 3},
+    {__LINE__, ".*d",        2, &args[1], no, 10, 3},
+    {__LINE__, "*.*d",       2, &args[2], 10, 20, 4},
+    {__LINE__, "1$d",        2, &args[0], no, no, 0},
+    {__LINE__, "2$d",        2, &args[1], no, no, 0},
+    {__LINE__, "3$d",        2, &args[2], no, no, 0},
+    {__LINE__, "4$d",        2, &args[3], no, no, 0},
+    {__LINE__, "2$*1$d",     2, &args[1], 10, no, 0},
+    {__LINE__, "2$*2$d",     2, &args[1], 20, no, 0},
+    {__LINE__, "2$*3$d",     2, &args[1], 30, no, 0},
+    {__LINE__, "2$.*1$d",    2, &args[1], no, 10, 0},
+    {__LINE__, "2$.*2$d",    2, &args[1], no, 20, 0},
+    {__LINE__, "2$.*3$d",    2, &args[1], no, 30, 0},
+    {__LINE__, "2$*3$.*1$d", 2, &args[1], 30, 10, 0},
+    {__LINE__, "2$*2$.*2$d", 2, &args[1], 20, 20, 0},
+    {__LINE__, "2$*1$.*3$d", 2, &args[1], 10, 30, 0},
+    {__LINE__, "2$*3$.*1$d", 2, &args[1], 30, 10, 0},
+    {__LINE__, "1$*d",       0},  // indexed, then positional
+    {__LINE__, "*2$d",       0},  // positional, then indexed
+    {__LINE__, "6$d",        1},  // arg position out of bounds
+    {__LINE__, "1$6$d",      0},  // width position incorrectly specified
+    {__LINE__, "1$.6$d",     0},  // precision position incorrectly specified
+    {__LINE__, "1$*6$d",     1},  // width position out of bounds
+    {__LINE__, "1$.*6$d",    1},  // precision position out of bounds
+  };
+#pragma GCC diagnostic pop
+  for (const Expectation &e : kExpect) {
+    SCOPED_TRACE(e.line);
+    SCOPED_TRACE(e.fmt);
+    UnboundConversion props;
+    BoundConversion bound;
+    int ok_phases = 0;
+    int next = 0;
+    if (Extract(e.fmt, &props, &next)) {
+      ++ok_phases;
+      if (BindWithPack(&props, args, &bound)) {
+        ++ok_phases;
+      }
+    }
+    EXPECT_EQ(e.ok_phases, ok_phases);
+    if (e.ok_phases < 2) continue;
+    if (e.arg != nullptr) {
+      EXPECT_EQ(e.arg, bound.arg());
+    }
+    EXPECT_EQ(e.width, bound.width());
+    EXPECT_EQ(e.precision, bound.precision());
+  }
+}
+
+TEST_F(FormatBindTest, FormatPack) {
+  struct Expectation {
+    int line;
+    const char *fmt;
+    const char *summary;
+  };
+  const int ia[] = { 10, 20, 30, 40, -10 };
+  const FormatArgImpl args[] = {FormatArgImpl(ia[0]), FormatArgImpl(ia[1]),
+                                FormatArgImpl(ia[2]), FormatArgImpl(ia[3]),
+                                FormatArgImpl(ia[4])};
+  const Expectation kExpect[] = {
+    {__LINE__, "a%4db%dc", "a{10:4d}b{20:d}c"},
+    {__LINE__, "a%.4db%dc", "a{10:.4d}b{20:d}c"},
+    {__LINE__, "a%4.5db%dc", "a{10:4.5d}b{20:d}c"},
+    {__LINE__, "a%db%4.5dc", "a{10:d}b{20:4.5d}c"},
+    {__LINE__, "a%db%*.*dc", "a{10:d}b{40:20.30d}c"},
+    {__LINE__, "a%.*fb", "a{20:.10f}b"},
+    {__LINE__, "a%1$db%2$*3$.*4$dc", "a{10:d}b{20:30.40d}c"},
+    {__LINE__, "a%4$db%3$*2$.*1$dc", "a{40:d}b{30:20.10d}c"},
+    {__LINE__, "a%04ldb", "a{10:04ld}b"},
+    {__LINE__, "a%-#04lldb", "a{10:-#04lld}b"},
+    {__LINE__, "a%1$*5$db", "a{10:-10d}b"},
+    {__LINE__, "a%1$.*5$db", "a{10:d}b"},
+  };
+  for (const Expectation &e : kExpect) {
+    absl::string_view fmt = e.fmt;
+    SCOPED_TRACE(e.line);
+    SCOPED_TRACE(e.fmt);
+    UntypedFormatSpecImpl format(fmt);
+    EXPECT_EQ(e.summary,
+              str_format_internal::Summarize(format, absl::MakeSpan(args)))
+        << "line:" << e.line;
+  }
+}
+
+}  // namespace
+}  // namespace str_format_internal
+}  // namespace absl
diff --git a/absl/strings/internal/str_format/checker.h b/absl/strings/internal/str_format/checker.h
new file mode 100644
index 000000000000..8b594f2d5cc6
--- /dev/null
+++ b/absl/strings/internal/str_format/checker.h
@@ -0,0 +1,325 @@
+#ifndef ABSL_STRINGS_INTERNAL_STR_FORMAT_CHECKER_H_
+#define ABSL_STRINGS_INTERNAL_STR_FORMAT_CHECKER_H_
+
+#include "absl/strings/internal/str_format/arg.h"
+#include "absl/strings/internal/str_format/extension.h"
+
+// Compile time check support for entry points.
+
+#ifndef ABSL_INTERNAL_ENABLE_FORMAT_CHECKER
+#if defined(__clang__) && !defined(__native_client__)
+#if __has_attribute(enable_if)
+#define ABSL_INTERNAL_ENABLE_FORMAT_CHECKER 1
+#endif  // __has_attribute(enable_if)
+#endif  // defined(__clang__) && !defined(__native_client__)
+#endif  // ABSL_INTERNAL_ENABLE_FORMAT_CHECKER
+
+namespace absl {
+namespace str_format_internal {
+
+constexpr bool AllOf() { return true; }
+
+template <typename... T>
+constexpr bool AllOf(bool b, T... t) {
+  return b && AllOf(t...);
+}
+
+template <typename Arg>
+constexpr Conv ArgumentToConv() {
+  return decltype(str_format_internal::FormatConvertImpl(
+      std::declval<const Arg&>(), std::declval<const ConversionSpec&>(),
+      std::declval<FormatSinkImpl*>()))::kConv;
+}
+
+#if ABSL_INTERNAL_ENABLE_FORMAT_CHECKER
+
+constexpr bool ContainsChar(const char* chars, char c) {
+  return *chars == c || (*chars && ContainsChar(chars + 1, c));
+}
+
+// A constexpr compatible list of Convs.
+struct ConvList {
+  const Conv* array;
+  int count;
+
+  // We do the bound check here to avoid having to do it on the callers.
+  // Returning an empty Conv has the same effect as short circuiting because it
+  // will never match any conversion.
+  constexpr Conv operator[](int i) const {
+    return i < count ? array[i] : Conv{};
+  }
+
+  constexpr ConvList without_front() const {
+    return count != 0 ? ConvList{array + 1, count - 1} : *this;
+  }
+};
+
+template <size_t count>
+struct ConvListT {
+  // Make sure the array has size > 0.
+  Conv list[count ? count : 1];
+};
+
+constexpr char GetChar(string_view str, size_t index) {
+  return index < str.size() ? str[index] : char{};
+}
+
+constexpr string_view ConsumeFront(string_view str, size_t len = 1) {
+  return len <= str.size() ? string_view(str.data() + len, str.size() - len)
+                           : string_view();
+}
+
+constexpr string_view ConsumeAnyOf(string_view format, const char* chars) {
+  return ContainsChar(chars, GetChar(format, 0))
+             ? ConsumeAnyOf(ConsumeFront(format), chars)
+             : format;
+}
+
+constexpr bool IsDigit(char c) { return c >= '0' && c <= '9'; }
+
+// Helper class for the ParseDigits function.
+// It encapsulates the two return values we need there.
+struct Integer {
+  string_view format;
+  int value;
+
+  // If the next character is a '$', consume it.
+  // Otherwise, make `this` an invalid positional argument.
+  constexpr Integer ConsumePositionalDollar() const {
+    return GetChar(format, 0) == '$' ? Integer{ConsumeFront(format), value}
+                                     : Integer{format, 0};
+  }
+};
+
+constexpr Integer ParseDigits(string_view format, int value = 0) {
+  return IsDigit(GetChar(format, 0))
+             ? ParseDigits(ConsumeFront(format),
+                           10 * value + GetChar(format, 0) - '0')
+             : Integer{format, value};
+}
+
+// Parse digits for a positional argument.
+// The parsing also consumes the '$'.
+constexpr Integer ParsePositional(string_view format) {
+  return ParseDigits(format).ConsumePositionalDollar();
+}
+
+// Parses a single conversion specifier.
+// See ConvParser::Run() for post conditions.
+class ConvParser {
+  constexpr ConvParser SetFormat(string_view format) const {
+    return ConvParser(format, args_, error_, arg_position_, is_positional_);
+  }
+
+  constexpr ConvParser SetArgs(ConvList args) const {
+    return ConvParser(format_, args, error_, arg_position_, is_positional_);
+  }
+
+  constexpr ConvParser SetError(bool error) const {
+    return ConvParser(format_, args_, error_ || error, arg_position_,
+                      is_positional_);
+  }
+
+  constexpr ConvParser SetArgPosition(int arg_position) const {
+    return ConvParser(format_, args_, error_, arg_position, is_positional_);
+  }
+
+  // Consumes the next arg and verifies that it matches `conv`.
+  // `error_` is set if there is no next arg or if it doesn't match `conv`.
+  constexpr ConvParser ConsumeNextArg(char conv) const {
+    return SetArgs(args_.without_front()).SetError(!Contains(args_[0], conv));
+  }
+
+  // Verify that positional argument `i.value` matches `conv`.
+  // `error_` is set if `i.value` is not a valid argument or if it doesn't
+  // match.
+  constexpr ConvParser VerifyPositional(Integer i, char conv) const {
+    return SetFormat(i.format).SetError(!Contains(args_[i.value - 1], conv));
+  }
+
+  // Parse the position of the arg and store it in `arg_position_`.
+  constexpr ConvParser ParseArgPosition(Integer arg) const {
+    return SetFormat(arg.format).SetArgPosition(arg.value);
+  }
+
+  // Consume the flags.
+  constexpr ConvParser ParseFlags() const {
+    return SetFormat(ConsumeAnyOf(format_, "-+ #0"));
+  }
+
+  // Consume the width.
+  // If it is '*', we verify that it matches `args_`. `error_` is set if it
+  // doesn't match.
+  constexpr ConvParser ParseWidth() const {
+    return IsDigit(GetChar(format_, 0))
+               ? SetFormat(ParseDigits(format_).format)
+               : GetChar(format_, 0) == '*'
+                     ? is_positional_
+                           ? VerifyPositional(
+                                 ParsePositional(ConsumeFront(format_)), '*')
+                           : SetFormat(ConsumeFront(format_))
+                                 .ConsumeNextArg('*')
+                     : *this;
+  }
+
+  // Consume the precision.
+  // If it is '*', we verify that it matches `args_`. `error_` is set if it
+  // doesn't match.
+  constexpr ConvParser ParsePrecision() const {
+    return GetChar(format_, 0) != '.'
+               ? *this
+               : GetChar(format_, 1) == '*'
+                     ? is_positional_
+                           ? VerifyPositional(
+                                 ParsePositional(ConsumeFront(format_, 2)), '*')
+                           : SetFormat(ConsumeFront(format_, 2))
+                                 .ConsumeNextArg('*')
+                     : SetFormat(ParseDigits(ConsumeFront(format_)).format);
+  }
+
+  // Consume the length characters.
+  constexpr ConvParser ParseLength() const {
+    return SetFormat(ConsumeAnyOf(format_, "lLhjztq"));
+  }
+
+  // Consume the conversion character and verify that it matches `args_`.
+  // `error_` is set if it doesn't match.
+  constexpr ConvParser ParseConversion() const {
+    return is_positional_
+               ? VerifyPositional({ConsumeFront(format_), arg_position_},
+                                  GetChar(format_, 0))
+               : ConsumeNextArg(GetChar(format_, 0))
+                     .SetFormat(ConsumeFront(format_));
+  }
+
+  constexpr ConvParser(string_view format, ConvList args, bool error,
+                       int arg_position, bool is_positional)
+      : format_(format),
+        args_(args),
+        error_(error),
+        arg_position_(arg_position),
+        is_positional_(is_positional) {}
+
+ public:
+  constexpr ConvParser(string_view format, ConvList args, bool is_positional)
+      : format_(format),
+        args_(args),
+        error_(false),
+        arg_position_(0),
+        is_positional_(is_positional) {}
+
+  // Consume the whole conversion specifier.
+  // `format()` will be set to the character after the conversion character.
+  // `error()` will be set if any of the arguments do not match.
+  constexpr ConvParser Run() const {
+    return (is_positional_ ? ParseArgPosition(ParsePositional(format_)) : *this)
+        .ParseFlags()
+        .ParseWidth()
+        .ParsePrecision()
+        .ParseLength()
+        .ParseConversion();
+  }
+
+  constexpr string_view format() const { return format_; }
+  constexpr ConvList args() const { return args_; }
+  constexpr bool error() const { return error_; }
+  constexpr bool is_positional() const { return is_positional_; }
+
+ private:
+  string_view format_;
+  // Current list of arguments. If we are not in positional mode we will consume
+  // from the front.
+  ConvList args_;
+  bool error_;
+  // Holds the argument position of the conversion character, if we are in
+  // positional mode. Otherwise, it is unspecified.
+  int arg_position_;
+  // Whether we are in positional mode.
+  // It changes the behavior of '*' and where to find the converted argument.
+  bool is_positional_;
+};
+
+// Parses a whole format expression.
+// See FormatParser::Run().
+class FormatParser {
+  static constexpr bool FoundPercent(string_view format) {
+    return format.empty() ||
+           (GetChar(format, 0) == '%' && GetChar(format, 1) != '%');
+  }
+
+  // We use an inner function to increase the recursion limit.
+  // The inner function consumes up to `limit` characters on every run.
+  // This increases the limit from 512 to ~512*limit.
+  static constexpr string_view ConsumeNonPercentInner(string_view format,
+                                                      int limit = 20) {
+    return FoundPercent(format) || !limit
+               ? format
+               : ConsumeNonPercentInner(
+                     ConsumeFront(format, GetChar(format, 0) == '%' &&
+                                                  GetChar(format, 1) == '%'
+                                              ? 2
+                                              : 1),
+                     limit - 1);
+  }
+
+  // Consume characters until the next conversion spec %.
+  // It skips %%.
+  static constexpr string_view ConsumeNonPercent(string_view format) {
+    return FoundPercent(format)
+               ? format
+               : ConsumeNonPercent(ConsumeNonPercentInner(format));
+  }
+
+  static constexpr bool IsPositional(string_view format) {
+    return IsDigit(GetChar(format, 0)) ? IsPositional(ConsumeFront(format))
+                                       : GetChar(format, 0) == '$';
+  }
+
+  constexpr bool RunImpl(bool is_positional) const {
+    // In non-positional mode we require all arguments to be consumed.
+    // In positional mode just reaching the end of the format without errors is
+    // enough.
+    return (format_.empty() && (is_positional || args_.count == 0)) ||
+           (!format_.empty() &&
+            ValidateArg(
+                ConvParser(ConsumeFront(format_), args_, is_positional).Run()));
+  }
+
+  constexpr bool ValidateArg(ConvParser conv) const {
+    return !conv.error() && FormatParser(conv.format(), conv.args())
+                                .RunImpl(conv.is_positional());
+  }
+
+ public:
+  constexpr FormatParser(string_view format, ConvList args)
+      : format_(ConsumeNonPercent(format)), args_(args) {}
+
+  // Runs the parser for `format` and `args`.
+  // It verifies that the format is valid and that all conversion specifiers
+  // match the arguments passed.
+  // In non-positional mode it also verfies that all arguments are consumed.
+  constexpr bool Run() const {
+    return RunImpl(!format_.empty() && IsPositional(ConsumeFront(format_)));
+  }
+
+ private:
+  string_view format_;
+  // Current list of arguments.
+  // If we are not in positional mode we will consume from the front and will
+  // have to be empty in the end.
+  ConvList args_;
+};
+
+template <Conv... C>
+constexpr bool ValidFormatImpl(string_view format) {
+  return FormatParser(format,
+                      {ConvListT<sizeof...(C)>{{C...}}.list, sizeof...(C)})
+      .Run();
+}
+
+#endif  // ABSL_INTERNAL_ENABLE_FORMAT_CHECKER
+
+}  // namespace str_format_internal
+}  // namespace absl
+
+#endif  // ABSL_STRINGS_INTERNAL_STR_FORMAT_CHECKER_H_
diff --git a/absl/strings/internal/str_format/checker_test.cc b/absl/strings/internal/str_format/checker_test.cc
new file mode 100644
index 000000000000..14d11ea8bd30
--- /dev/null
+++ b/absl/strings/internal/str_format/checker_test.cc
@@ -0,0 +1,150 @@
+#include <string>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "absl/strings/str_format.h"
+
+namespace absl {
+namespace str_format_internal {
+namespace {
+
+std::string ConvToString(Conv conv) {
+  std::string out;
+#define CONV_SET_CASE(c) \
+  if (Contains(conv, Conv::c)) out += #c;
+  ABSL_CONVERSION_CHARS_EXPAND_(CONV_SET_CASE, )
+#undef CONV_SET_CASE
+  if (Contains(conv, Conv::star)) out += "*";
+  return out;
+}
+
+TEST(StrFormatChecker, ArgumentToConv) {
+  Conv conv = ArgumentToConv<std::string>();
+  EXPECT_EQ(ConvToString(conv), "s");
+
+  conv = ArgumentToConv<const char*>();
+  EXPECT_EQ(ConvToString(conv), "sp");
+
+  conv = ArgumentToConv<double>();
+  EXPECT_EQ(ConvToString(conv), "fFeEgGaA");
+
+  conv = ArgumentToConv<int>();
+  EXPECT_EQ(ConvToString(conv), "cdiouxXfFeEgGaA*");
+
+  conv = ArgumentToConv<std::string*>();
+  EXPECT_EQ(ConvToString(conv), "p");
+}
+
+#if ABSL_INTERNAL_ENABLE_FORMAT_CHECKER
+
+struct Case {
+  bool result;
+  const char* format;
+};
+
+template <typename... Args>
+constexpr Case ValidFormat(const char* format) {
+  return {ValidFormatImpl<ArgumentToConv<Args>()...>(format), format};
+}
+
+TEST(StrFormatChecker, ValidFormat) {
+  // We want to make sure these expressions are constexpr and they have the
+  // expected value.
+  // If they are not constexpr the attribute will just ignore them and not give
+  // a compile time error.
+  enum e {};
+  enum class e2 {};
+  constexpr Case trues[] = {
+      ValidFormat<>("abc"),  //
+
+      ValidFormat<e>("%d"),                             //
+      ValidFormat<e2>("%d"),                            //
+      ValidFormat<int>("%% %d"),                        //
+      ValidFormat<int>("%ld"),                          //
+      ValidFormat<int>("%lld"),                         //
+      ValidFormat<std::string>("%s"),                        //
+      ValidFormat<std::string>("%10s"),                      //
+      ValidFormat<int>("%.10x"),                        //
+      ValidFormat<int, int>("%*.3x"),                   //
+      ValidFormat<int>("%1.d"),                         //
+      ValidFormat<int>("%.d"),                          //
+      ValidFormat<int, double>("%d %g"),                //
+      ValidFormat<int, std::string>("%*s"),                  //
+      ValidFormat<int, double>("%.*f"),                 //
+      ValidFormat<void (*)(), volatile int*>("%p %p"),  //
+      ValidFormat<string_view, const char*, double, void*>(
+          "string_view=%s const char*=%s double=%f void*=%p)"),
+
+      ValidFormat<int>("%% %1$d"),            //
+      ValidFormat<int>("%1$ld"),              //
+      ValidFormat<int>("%1$lld"),             //
+      ValidFormat<std::string>("%1$s"),            //
+      ValidFormat<std::string>("%1$10s"),          //
+      ValidFormat<int>("%1$.10x"),            //
+      ValidFormat<int>("%1$*1$.*1$d"),        //
+      ValidFormat<int, int>("%1$*2$.3x"),     //
+      ValidFormat<int>("%1$1.d"),             //
+      ValidFormat<int>("%1$.d"),              //
+      ValidFormat<double, int>("%2$d %1$g"),  //
+      ValidFormat<int, std::string>("%2$*1$s"),    //
+      ValidFormat<int, double>("%2$.*1$f"),   //
+      ValidFormat<void*, string_view, const char*, double>(
+          "string_view=%2$s const char*=%3$s double=%4$f void*=%1$p "
+          "repeat=%3$s)")};
+
+  for (Case c : trues) {
+    EXPECT_TRUE(c.result) << c.format;
+  }
+
+  constexpr Case falses[] = {
+      ValidFormat<int>(""),  //
+
+      ValidFormat<e>("%s"),             //
+      ValidFormat<e2>("%s"),            //
+      ValidFormat<>("%s"),              //
+      ValidFormat<>("%r"),              //
+      ValidFormat<int>("%s"),           //
+      ValidFormat<int>("%.1.d"),        //
+      ValidFormat<int>("%*1d"),         //
+      ValidFormat<int>("%1-d"),         //
+      ValidFormat<std::string, int>("%*s"),  //
+      ValidFormat<int>("%*d"),          //
+      ValidFormat<std::string>("%p"),        //
+      ValidFormat<int (*)(int)>("%d"),  //
+
+      ValidFormat<>("%3$d"),                //
+      ValidFormat<>("%1$r"),                //
+      ValidFormat<int>("%1$s"),             //
+      ValidFormat<int>("%1$.1.d"),          //
+      ValidFormat<int>("%1$*2$1d"),         //
+      ValidFormat<int>("%1$1-d"),           //
+      ValidFormat<std::string, int>("%2$*1$s"),  //
+      ValidFormat<std::string>("%1$p"),
+
+      ValidFormat<int, int>("%d %2$d"),  //
+  };
+
+  for (Case c : falses) {
+    EXPECT_FALSE(c.result) << c.format;
+  }
+}
+
+TEST(StrFormatChecker, LongFormat) {
+#define CHARS_X_40 "1234567890123456789012345678901234567890"
+#define CHARS_X_400                                                            \
+  CHARS_X_40 CHARS_X_40 CHARS_X_40 CHARS_X_40 CHARS_X_40 CHARS_X_40 CHARS_X_40 \
+      CHARS_X_40 CHARS_X_40 CHARS_X_40
+#define CHARS_X_4000                                                      \
+  CHARS_X_400 CHARS_X_400 CHARS_X_400 CHARS_X_400 CHARS_X_400 CHARS_X_400 \
+      CHARS_X_400 CHARS_X_400 CHARS_X_400 CHARS_X_400
+  constexpr char long_format[] =
+      CHARS_X_4000 "%d" CHARS_X_4000 "%s" CHARS_X_4000;
+  constexpr bool is_valid = ValidFormat<int, std::string>(long_format).result;
+  EXPECT_TRUE(is_valid);
+}
+
+#endif  // ABSL_INTERNAL_ENABLE_FORMAT_CHECKER
+
+}  // namespace
+}  // namespace str_format_internal
+}  // namespace absl
diff --git a/absl/strings/internal/str_format/convert_test.cc b/absl/strings/internal/str_format/convert_test.cc
new file mode 100644
index 000000000000..32f8a0f9ad1a
--- /dev/null
+++ b/absl/strings/internal/str_format/convert_test.cc
@@ -0,0 +1,575 @@
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <cmath>
+#include <string>
+
+#include "gtest/gtest.h"
+#include "absl/strings/internal/str_format/bind.h"
+
+namespace absl {
+namespace str_format_internal {
+namespace {
+
+template <typename T, size_t N>
+size_t ArraySize(T (&)[N]) {
+  return N;
+}
+
+std::string LengthModFor(float) { return ""; }
+std::string LengthModFor(double) { return ""; }
+std::string LengthModFor(long double) { return "L"; }
+std::string LengthModFor(char) { return "hh"; }
+std::string LengthModFor(signed char) { return "hh"; }
+std::string LengthModFor(unsigned char) { return "hh"; }
+std::string LengthModFor(short) { return "h"; }           // NOLINT
+std::string LengthModFor(unsigned short) { return "h"; }  // NOLINT
+std::string LengthModFor(int) { return ""; }
+std::string LengthModFor(unsigned) { return ""; }
+std::string LengthModFor(long) { return "l"; }                 // NOLINT
+std::string LengthModFor(unsigned long) { return "l"; }        // NOLINT
+std::string LengthModFor(long long) { return "ll"; }           // NOLINT
+std::string LengthModFor(unsigned long long) { return "ll"; }  // NOLINT
+
+std::string EscCharImpl(int v) {
+  if (isprint(v)) return std::string(1, static_cast<char>(v));
+  char buf[64];
+  int n = snprintf(buf, sizeof(buf), "\\%#.2x",
+                   static_cast<unsigned>(v & 0xff));
+  assert(n > 0 && n < sizeof(buf));
+  return std::string(buf, n);
+}
+
+std::string Esc(char v) { return EscCharImpl(v); }
+std::string Esc(signed char v) { return EscCharImpl(v); }
+std::string Esc(unsigned char v) { return EscCharImpl(v); }
+
+template <typename T>
+std::string Esc(const T &v) {
+  std::ostringstream oss;
+  oss << v;
+  return oss.str();
+}
+
+void StrAppend(std::string *dst, const char *format, va_list ap) {
+  // First try with a small fixed size buffer
+  static const int kSpaceLength = 1024;
+  char space[kSpaceLength];
+
+  // It's possible for methods that use a va_list to invalidate
+  // the data in it upon use.  The fix is to make a copy
+  // of the structure before using it and use that copy instead.
+  va_list backup_ap;
+  va_copy(backup_ap, ap);
+  int result = vsnprintf(space, kSpaceLength, format, backup_ap);
+  va_end(backup_ap);
+  if (result < kSpaceLength) {
+    if (result >= 0) {
+      // Normal case -- everything fit.
+      dst->append(space, result);
+      return;
+    }
+    if (result < 0) {
+      // Just an error.
+      return;
+    }
+  }
+
+  // Increase the buffer size to the size requested by vsnprintf,
+  // plus one for the closing \0.
+  int length = result + 1;
+  char *buf = new char[length];
+
+  // Restore the va_list before we use it again
+  va_copy(backup_ap, ap);
+  result = vsnprintf(buf, length, format, backup_ap);
+  va_end(backup_ap);
+
+  if (result >= 0 && result < length) {
+    // It fit
+    dst->append(buf, result);
+  }
+  delete[] buf;
+}
+
+std::string StrPrint(const char *format, ...) {
+  va_list ap;
+  va_start(ap, format);
+  std::string result;
+  StrAppend(&result, format, ap);
+  va_end(ap);
+  return result;
+}
+
+class FormatConvertTest : public ::testing::Test { };
+
+template <typename T>
+void TestStringConvert(const T& str) {
+  const FormatArgImpl args[] = {FormatArgImpl(str)};
+  struct Expectation {
+    const char *out;
+    const char *fmt;
+  };
+  const Expectation kExpect[] = {
+    {"hello",  "%1$s"      },
+    {"",       "%1$.s"     },
+    {"",       "%1$.0s"    },
+    {"h",      "%1$.1s"    },
+    {"he",     "%1$.2s"    },
+    {"hello",  "%1$.10s"   },
+    {" hello", "%1$6s"     },
+    {"   he",  "%1$5.2s"   },
+    {"he   ",  "%1$-5.2s"  },
+    {"hello ", "%1$-6.10s" },
+  };
+  for (const Expectation &e : kExpect) {
+    UntypedFormatSpecImpl format(e.fmt);
+    EXPECT_EQ(e.out, FormatPack(format, absl::MakeSpan(args)));
+  }
+}
+
+TEST_F(FormatConvertTest, BasicString) {
+  TestStringConvert("hello");  // As char array.
+  TestStringConvert(static_cast<const char*>("hello"));
+  TestStringConvert(std::string("hello"));
+  TestStringConvert(string_view("hello"));
+}
+
+TEST_F(FormatConvertTest, NullString) {
+  const char* p = nullptr;
+  UntypedFormatSpecImpl format("%s");
+  EXPECT_EQ("", FormatPack(format, {FormatArgImpl(p)}));
+}
+
+TEST_F(FormatConvertTest, StringPrecision) {
+  // We cap at the precision.
+  char c = 'a';
+  const char* p = &c;
+  UntypedFormatSpecImpl format("%.1s");
+  EXPECT_EQ("a", FormatPack(format, {FormatArgImpl(p)}));
+
+  // We cap at the nul terminator.
+  p = "ABC";
+  UntypedFormatSpecImpl format2("%.10s");
+  EXPECT_EQ("ABC", FormatPack(format2, {FormatArgImpl(p)}));
+}
+
+TEST_F(FormatConvertTest, Pointer) {
+#if _MSC_VER
+  // MSVC's printf implementation prints pointers differently. We can't easily
+  // compare our implementation to theirs.
+  return;
+#endif
+  static int x = 0;
+  const int *xp = &x;
+  char c = 'h';
+  char *mcp = &c;
+  const char *cp = "hi";
+  const char *cnil = nullptr;
+  const int *inil = nullptr;
+  using VoidF = void (*)();
+  VoidF fp = [] {}, fnil = nullptr;
+  volatile char vc;
+  volatile char* vcp = &vc;
+  volatile char* vcnil = nullptr;
+  const FormatArgImpl args[] = {
+      FormatArgImpl(xp),   FormatArgImpl(cp),  FormatArgImpl(inil),
+      FormatArgImpl(cnil), FormatArgImpl(mcp), FormatArgImpl(fp),
+      FormatArgImpl(fnil), FormatArgImpl(vcp), FormatArgImpl(vcnil),
+  };
+  struct Expectation {
+    std::string out;
+    const char *fmt;
+  };
+  const Expectation kExpect[] = {
+      {StrPrint("%p", &x), "%p"},
+      {StrPrint("%20p", &x), "%20p"},
+      {StrPrint("%.1p", &x), "%.1p"},
+      {StrPrint("%.20p", &x), "%.20p"},
+      {StrPrint("%30.20p", &x), "%30.20p"},
+
+      {StrPrint("%-p", &x), "%-p"},
+      {StrPrint("%-20p", &x), "%-20p"},
+      {StrPrint("%-.1p", &x), "%-.1p"},
+      {StrPrint("%.20p", &x), "%.20p"},
+      {StrPrint("%-30.20p", &x), "%-30.20p"},
+
+      {StrPrint("%p", cp), "%2$p"},   // const char*
+      {"(nil)", "%3$p"},              // null const char *
+      {"(nil)", "%4$p"},              // null const int *
+      {StrPrint("%p", mcp), "%5$p"},  // nonconst char*
+
+      {StrPrint("%p", fp), "%6$p"},   // function pointer
+      {StrPrint("%p", vcp), "%8$p"},  // function pointer
+
+#ifndef __APPLE__
+      // Apple's printf differs here (0x0 vs. nil)
+      {StrPrint("%p", fnil), "%7$p"},   // null function pointer
+      {StrPrint("%p", vcnil), "%9$p"},  // null function pointer
+#endif
+  };
+  for (const Expectation &e : kExpect) {
+    UntypedFormatSpecImpl format(e.fmt);
+    EXPECT_EQ(e.out, FormatPack(format, absl::MakeSpan(args))) << e.fmt;
+  }
+}
+
+struct Cardinal {
+  enum Pos { k1 = 1, k2 = 2, k3 = 3 };
+  enum Neg { kM1 = -1, kM2 = -2, kM3 = -3 };
+};
+
+TEST_F(FormatConvertTest, Enum) {
+  const Cardinal::Pos k3 = Cardinal::k3;
+  const Cardinal::Neg km3 = Cardinal::kM3;
+  const FormatArgImpl args[] = {FormatArgImpl(k3), FormatArgImpl(km3)};
+  UntypedFormatSpecImpl format("%1$d");
+  UntypedFormatSpecImpl format2("%2$d");
+  EXPECT_EQ("3", FormatPack(format, absl::MakeSpan(args)));
+  EXPECT_EQ("-3", FormatPack(format2, absl::MakeSpan(args)));
+}
+
+template <typename T>
+class TypedFormatConvertTest : public FormatConvertTest { };
+
+TYPED_TEST_CASE_P(TypedFormatConvertTest);
+
+std::vector<std::string> AllFlagCombinations() {
+  const char kFlags[] = {'-', '#', '0', '+', ' '};
+  std::vector<std::string> result;
+  for (size_t fsi = 0; fsi < (1ull << ArraySize(kFlags)); ++fsi) {
+    std::string flag_set;
+    for (size_t fi = 0; fi < ArraySize(kFlags); ++fi)
+      if (fsi & (1ull << fi))
+        flag_set += kFlags[fi];
+    result.push_back(flag_set);
+  }
+  return result;
+}
+
+TYPED_TEST_P(TypedFormatConvertTest, AllIntsWithFlags) {
+  typedef TypeParam T;
+  typedef typename std::make_unsigned<T>::type UnsignedT;
+  using remove_volatile_t = typename std::remove_volatile<T>::type;
+  const T kMin = std::numeric_limits<remove_volatile_t>::min();
+  const T kMax = std::numeric_limits<remove_volatile_t>::max();
+  const T kVals[] = {
+      remove_volatile_t(1),
+      remove_volatile_t(2),
+      remove_volatile_t(3),
+      remove_volatile_t(123),
+      remove_volatile_t(-1),
+      remove_volatile_t(-2),
+      remove_volatile_t(-3),
+      remove_volatile_t(-123),
+      remove_volatile_t(0),
+      kMax - remove_volatile_t(1),
+      kMax,
+      kMin + remove_volatile_t(1),
+      kMin,
+  };
+  const char kConvChars[] = {'d', 'i', 'u', 'o', 'x', 'X'};
+  const std::string kWid[] = {"", "4", "10"};
+  const std::string kPrec[] = {"", ".", ".0", ".4", ".10"};
+
+  const std::vector<std::string> flag_sets = AllFlagCombinations();
+
+  for (size_t vi = 0; vi < ArraySize(kVals); ++vi) {
+    const T val = kVals[vi];
+    SCOPED_TRACE(Esc(val));
+    const FormatArgImpl args[] = {FormatArgImpl(val)};
+    for (size_t ci = 0; ci < ArraySize(kConvChars); ++ci) {
+      const char conv_char = kConvChars[ci];
+      for (size_t fsi = 0; fsi < flag_sets.size(); ++fsi) {
+        const std::string &flag_set = flag_sets[fsi];
+        for (size_t wi = 0; wi < ArraySize(kWid); ++wi) {
+          const std::string &wid = kWid[wi];
+          for (size_t pi = 0; pi < ArraySize(kPrec); ++pi) {
+            const std::string &prec = kPrec[pi];
+
+            const bool is_signed_conv = (conv_char == 'd' || conv_char == 'i');
+            const bool is_unsigned_to_signed =
+                !std::is_signed<T>::value && is_signed_conv;
+            // Don't consider sign-related flags '+' and ' ' when doing
+            // unsigned to signed conversions.
+            if (is_unsigned_to_signed &&
+                flag_set.find_first_of("+ ") != std::string::npos) {
+              continue;
+            }
+
+            std::string new_fmt("%");
+            new_fmt += flag_set;
+            new_fmt += wid;
+            new_fmt += prec;
+            // old and new always agree up to here.
+            std::string old_fmt = new_fmt;
+            new_fmt += conv_char;
+            std::string old_result;
+            if (is_unsigned_to_signed) {
+              // don't expect agreement on unsigned formatted as signed,
+              // as printf can't do that conversion properly. For those
+              // cases, we do expect agreement with printf with a "%u"
+              // and the unsigned equivalent of 'val'.
+              UnsignedT uval = val;
+              old_fmt += LengthModFor(uval);
+              old_fmt += "u";
+              old_result = StrPrint(old_fmt.c_str(), uval);
+            } else {
+              old_fmt += LengthModFor(val);
+              old_fmt += conv_char;
+              old_result = StrPrint(old_fmt.c_str(), val);
+            }
+
+            SCOPED_TRACE(std::string() + " old_fmt: \"" + old_fmt +
+                         "\"'"
+                         " new_fmt: \"" +
+                         new_fmt + "\"");
+            UntypedFormatSpecImpl format(new_fmt);
+            EXPECT_EQ(old_result, FormatPack(format, absl::MakeSpan(args)));
+          }
+        }
+      }
+    }
+  }
+}
+
+TYPED_TEST_P(TypedFormatConvertTest, Char) {
+  typedef TypeParam T;
+  using remove_volatile_t = typename std::remove_volatile<T>::type;
+  static const T kMin = std::numeric_limits<remove_volatile_t>::min();
+  static const T kMax = std::numeric_limits<remove_volatile_t>::max();
+  T kVals[] = {
+    remove_volatile_t(1), remove_volatile_t(2), remove_volatile_t(10),
+    remove_volatile_t(-1), remove_volatile_t(-2), remove_volatile_t(-10),
+    remove_volatile_t(0),
+    kMin + remove_volatile_t(1), kMin,
+    kMax - remove_volatile_t(1), kMax
+  };
+  for (const T &c : kVals) {
+    const FormatArgImpl args[] = {FormatArgImpl(c)};
+    UntypedFormatSpecImpl format("%c");
+    EXPECT_EQ(StrPrint("%c", c), FormatPack(format, absl::MakeSpan(args)));
+  }
+}
+
+REGISTER_TYPED_TEST_CASE_P(TypedFormatConvertTest, AllIntsWithFlags, Char);
+
+typedef ::testing::Types<
+    int, unsigned, volatile int,
+    short, unsigned short,
+    long, unsigned long,
+    long long, unsigned long long,
+    signed char, unsigned char, char>
+    AllIntTypes;
+INSTANTIATE_TYPED_TEST_CASE_P(TypedFormatConvertTestWithAllIntTypes,
+                              TypedFormatConvertTest, AllIntTypes);
+TEST_F(FormatConvertTest, Uint128) {
+  absl::uint128 v = static_cast<absl::uint128>(0x1234567890abcdef) * 1979;
+  absl::uint128 max = absl::Uint128Max();
+  const FormatArgImpl args[] = {FormatArgImpl(v), FormatArgImpl(max)};
+
+  struct Case {
+    const char* format;
+    const char* expected;
+  } cases[] = {
+      {"%1$d", "2595989796776606496405"},
+      {"%1$30d", "        2595989796776606496405"},
+      {"%1$-30d", "2595989796776606496405        "},
+      {"%1$u", "2595989796776606496405"},
+      {"%1$x", "8cba9876066020f695"},
+      {"%2$d", "340282366920938463463374607431768211455"},
+      {"%2$u", "340282366920938463463374607431768211455"},
+      {"%2$x", "ffffffffffffffffffffffffffffffff"},
+  };
+
+  for (auto c : cases) {
+    UntypedFormatSpecImpl format(c.format);
+    EXPECT_EQ(c.expected, FormatPack(format, absl::MakeSpan(args)));
+  }
+}
+
+TEST_F(FormatConvertTest, Float) {
+#if _MSC_VER
+  // MSVC has a different rounding policy than us so we can't test our
+  // implementation against the native one there.
+  return;
+#endif  // _MSC_VER
+
+  const char *const kFormats[] = {
+      "%",  "%.3",  "%8.5",   "%9",   "%.60", "%.30",   "%03",    "%+",
+      "% ", "%-10", "%#15.3", "%#.0", "%.0",  "%1$*2$", "%1$.*2$"};
+
+  std::vector<double> doubles = {0.0,
+                                 -0.0,
+                                 .99999999999999,
+                                 99999999999999.,
+                                 std::numeric_limits<double>::max(),
+                                 -std::numeric_limits<double>::max(),
+                                 std::numeric_limits<double>::min(),
+                                 -std::numeric_limits<double>::min(),
+                                 std::numeric_limits<double>::lowest(),
+                                 -std::numeric_limits<double>::lowest(),
+                                 std::numeric_limits<double>::epsilon(),
+                                 std::numeric_limits<double>::epsilon() + 1,
+                                 std::numeric_limits<double>::infinity(),
+                                 -std::numeric_limits<double>::infinity()};
+
+#ifndef __APPLE__
+  // Apple formats NaN differently (+nan) vs. (nan)
+  doubles.push_back(std::nan(""));
+#endif
+
+  // Some regression tests.
+  doubles.push_back(0.99999999999999989);
+
+  if (std::numeric_limits<double>::has_denorm != std::denorm_absent) {
+    doubles.push_back(std::numeric_limits<double>::denorm_min());
+    doubles.push_back(-std::numeric_limits<double>::denorm_min());
+  }
+
+  for (double base :
+       {1., 12., 123., 1234., 12345., 123456., 1234567., 12345678., 123456789.,
+        1234567890., 12345678901., 123456789012., 1234567890123.}) {
+    for (int exp = -123; exp <= 123; ++exp) {
+      for (int sign : {1, -1}) {
+        doubles.push_back(sign * std::ldexp(base, exp));
+      }
+    }
+  }
+
+  for (const char *fmt : kFormats) {
+    for (char f : {'f', 'F',  //
+                   'g', 'G',  //
+                   'a', 'A',  //
+                   'e', 'E'}) {
+      std::string fmt_str = std::string(fmt) + f;
+      for (double d : doubles) {
+        int i = -10;
+        FormatArgImpl args[2] = {FormatArgImpl(d), FormatArgImpl(i)};
+        UntypedFormatSpecImpl format(fmt_str);
+        // We use ASSERT_EQ here because failures are usually correlated and a
+        // bug would print way too many failed expectations causing the test to
+        // time out.
+        ASSERT_EQ(StrPrint(fmt_str.c_str(), d, i),
+                  FormatPack(format, absl::MakeSpan(args)))
+            << fmt_str << " " << StrPrint("%.18g", d) << " "
+            << StrPrint("%.999f", d);
+      }
+    }
+  }
+}
+
+TEST_F(FormatConvertTest, LongDouble) {
+  const char *const kFormats[] = {"%",    "%.3", "%8.5", "%9",
+                                  "%.60", "%+",  "% ",   "%-10"};
+
+  // This value is not representable in double, but it is in long double that
+  // uses the extended format.
+  // This is to verify that we are not truncating the value mistakenly through a
+  // double.
+  long double very_precise = 10000000000000000.25L;
+
+  std::vector<long double> doubles = {
+      0.0,
+      -0.0,
+      very_precise,
+      1 / very_precise,
+      std::numeric_limits<long double>::max(),
+      -std::numeric_limits<long double>::max(),
+      std::numeric_limits<long double>::min(),
+      -std::numeric_limits<long double>::min(),
+      std::numeric_limits<long double>::infinity(),
+      -std::numeric_limits<long double>::infinity()};
+
+  for (const char *fmt : kFormats) {
+    for (char f : {'f', 'F',  //
+                   'g', 'G',  //
+                   'a', 'A',  //
+                   'e', 'E'}) {
+      std::string fmt_str = std::string(fmt) + 'L' + f;
+      for (auto d : doubles) {
+        FormatArgImpl arg(d);
+        UntypedFormatSpecImpl format(fmt_str);
+        // We use ASSERT_EQ here because failures are usually correlated and a
+        // bug would print way too many failed expectations causing the test to
+        // time out.
+        ASSERT_EQ(StrPrint(fmt_str.c_str(), d),
+                  FormatPack(format, {&arg, 1}))
+            << fmt_str << " " << StrPrint("%.18Lg", d) << " "
+            << StrPrint("%.999Lf", d);
+      }
+    }
+  }
+}
+
+TEST_F(FormatConvertTest, IntAsFloat) {
+  const int kMin = std::numeric_limits<int>::min();
+  const int kMax = std::numeric_limits<int>::max();
+  const int ia[] = {
+    1, 2, 3, 123,
+    -1, -2, -3, -123,
+    0, kMax - 1, kMax, kMin + 1, kMin };
+  for (const int fx : ia) {
+    SCOPED_TRACE(fx);
+    const FormatArgImpl args[] = {FormatArgImpl(fx)};
+    struct Expectation {
+      int line;
+      std::string out;
+      const char *fmt;
+    };
+    const double dx = static_cast<double>(fx);
+    const Expectation kExpect[] = {
+      { __LINE__, StrPrint("%f", dx), "%f" },
+      { __LINE__, StrPrint("%12f", dx), "%12f" },
+      { __LINE__, StrPrint("%.12f", dx), "%.12f" },
+      { __LINE__, StrPrint("%12a", dx), "%12a" },
+      { __LINE__, StrPrint("%.12a", dx), "%.12a" },
+    };
+    for (const Expectation &e : kExpect) {
+      SCOPED_TRACE(e.line);
+      SCOPED_TRACE(e.fmt);
+      UntypedFormatSpecImpl format(e.fmt);
+      EXPECT_EQ(e.out, FormatPack(format, absl::MakeSpan(args)));
+    }
+  }
+}
+
+template <typename T>
+bool FormatFails(const char* test_format, T value) {
+  std::string format_string = std::string("<<") + test_format + ">>";
+  UntypedFormatSpecImpl format(format_string);
+
+  int one = 1;
+  const FormatArgImpl args[] = {FormatArgImpl(value), FormatArgImpl(one)};
+  EXPECT_EQ(FormatPack(format, absl::MakeSpan(args)), "")
+      << "format=" << test_format << " value=" << value;
+  return FormatPack(format, absl::MakeSpan(args)).empty();
+}
+
+TEST_F(FormatConvertTest, ExpectedFailures) {
+  // Int input
+  EXPECT_TRUE(FormatFails("%p", 1));
+  EXPECT_TRUE(FormatFails("%s", 1));
+  EXPECT_TRUE(FormatFails("%n", 1));
+
+  // Double input
+  EXPECT_TRUE(FormatFails("%p", 1.));
+  EXPECT_TRUE(FormatFails("%s", 1.));
+  EXPECT_TRUE(FormatFails("%n", 1.));
+  EXPECT_TRUE(FormatFails("%c", 1.));
+  EXPECT_TRUE(FormatFails("%d", 1.));
+  EXPECT_TRUE(FormatFails("%x", 1.));
+  EXPECT_TRUE(FormatFails("%*d", 1.));
+
+  // String input
+  EXPECT_TRUE(FormatFails("%n", ""));
+  EXPECT_TRUE(FormatFails("%c", ""));
+  EXPECT_TRUE(FormatFails("%d", ""));
+  EXPECT_TRUE(FormatFails("%x", ""));
+  EXPECT_TRUE(FormatFails("%f", ""));
+  EXPECT_TRUE(FormatFails("%*d", ""));
+}
+
+}  // namespace
+}  // namespace str_format_internal
+}  // namespace absl
diff --git a/absl/strings/internal/str_format/extension.cc b/absl/strings/internal/str_format/extension.cc
new file mode 100644
index 000000000000..c2174703c3e8
--- /dev/null
+++ b/absl/strings/internal/str_format/extension.cc
@@ -0,0 +1,84 @@
+//
+// Copyright 2017 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/strings/internal/str_format/extension.h"
+
+#include <errno.h>
+#include <algorithm>
+#include <string>
+
+namespace absl {
+namespace str_format_internal {
+namespace {
+// clang-format off
+#define ABSL_LENGTH_MODS_EXPAND_ \
+  X_VAL(h) X_SEP \
+  X_VAL(hh) X_SEP \
+  X_VAL(l) X_SEP \
+  X_VAL(ll) X_SEP \
+  X_VAL(L) X_SEP \
+  X_VAL(j) X_SEP \
+  X_VAL(z) X_SEP \
+  X_VAL(t) X_SEP \
+  X_VAL(q)
+// clang-format on
+}  // namespace
+
+const LengthMod::Spec LengthMod::kSpecs[] = {
+#define X_VAL(id) { LengthMod::id, #id, strlen(#id) }
+#define X_SEP ,
+    ABSL_LENGTH_MODS_EXPAND_, {LengthMod::none, "", 0}
+#undef X_VAL
+#undef X_SEP
+};
+
+const ConversionChar::Spec ConversionChar::kSpecs[] = {
+#define X_VAL(id) { ConversionChar::id, #id[0] }
+#define X_SEP ,
+    ABSL_CONVERSION_CHARS_EXPAND_(X_VAL, X_SEP),
+    {ConversionChar::none, '\0'},
+#undef X_VAL
+#undef X_SEP
+};
+
+std::string Flags::ToString() const {
+  std::string s;
+  s.append(left     ? "-" : "");
+  s.append(show_pos ? "+" : "");
+  s.append(sign_col ? " " : "");
+  s.append(alt      ? "#" : "");
+  s.append(zero     ? "0" : "");
+  return s;
+}
+
+const size_t LengthMod::kNumValues;
+
+const size_t ConversionChar::kNumValues;
+
+bool FormatSinkImpl::PutPaddedString(string_view v, int w, int p, bool l) {
+  size_t space_remaining = 0;
+  if (w >= 0) space_remaining = w;
+  size_t n = v.size();
+  if (p >= 0) n = std::min(n, static_cast<size_t>(p));
+  string_view shown(v.data(), n);
+  space_remaining = Excess(shown.size(), space_remaining);
+  if (!l) Append(space_remaining, ' ');
+  Append(shown);
+  if (l) Append(space_remaining, ' ');
+  return true;
+}
+
+}  // namespace str_format_internal
+}  // namespace absl
diff --git a/absl/strings/internal/str_format/extension.h b/absl/strings/internal/str_format/extension.h
new file mode 100644
index 000000000000..810330b9d71b
--- /dev/null
+++ b/absl/strings/internal/str_format/extension.h
@@ -0,0 +1,406 @@
+//
+// Copyright 2017 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//
+#ifndef ABSL_STRINGS_INTERNAL_STR_FORMAT_EXTENSION_H_
+#define ABSL_STRINGS_INTERNAL_STR_FORMAT_EXTENSION_H_
+
+#include <limits.h>
+#include <cstring>
+#include <ostream>
+
+#include "absl/base/port.h"
+#include "absl/strings/internal/str_format/output.h"
+#include "absl/strings/string_view.h"
+
+class Cord;
+
+namespace absl {
+
+namespace str_format_internal {
+
+class FormatRawSinkImpl {
+ public:
+  // Implicitly convert from any type that provides the hook function as
+  // described above.
+  template <typename T, decltype(str_format_internal::InvokeFlush(
+                            std::declval<T*>(), string_view()))* = nullptr>
+  FormatRawSinkImpl(T* raw)  // NOLINT
+      : sink_(raw), write_(&FormatRawSinkImpl::Flush<T>) {}
+
+  void Write(string_view s) { write_(sink_, s); }
+
+  template <typename T>
+  static FormatRawSinkImpl Extract(T s) {
+    return s.sink_;
+  }
+
+ private:
+  template <typename T>
+  static void Flush(void* r, string_view s) {
+    str_format_internal::InvokeFlush(static_cast<T*>(r), s);
+  }
+
+  void* sink_;
+  void (*write_)(void*, string_view);
+};
+
+// An abstraction to which conversions write their std::string data.
+class FormatSinkImpl {
+ public:
+  explicit FormatSinkImpl(FormatRawSinkImpl raw) : raw_(raw) {}
+
+  ~FormatSinkImpl() { Flush(); }
+
+  void Flush() {
+    raw_.Write(string_view(buf_, pos_ - buf_));
+    pos_ = buf_;
+  }
+
+  void Append(size_t n, char c) {
+    if (n == 0) return;
+    size_ += n;
+    auto raw_append = [&](size_t count) {
+      memset(pos_, c, count);
+      pos_ += count;
+    };
+    while (n > Avail()) {
+      n -= Avail();
+      if (Avail() > 0) {
+        raw_append(Avail());
+      }
+      Flush();
+    }
+    raw_append(n);
+  }
+
+  void Append(string_view v) {
+    size_t n = v.size();
+    if (n == 0) return;
+    size_ += n;
+    if (n >= Avail()) {
+      Flush();
+      raw_.Write(v);
+      return;
+    }
+    memcpy(pos_, v.data(), n);
+    pos_ += n;
+  }
+
+  size_t size() const { return size_; }
+
+  // Put 'v' to 'sink' with specified width, precision, and left flag.
+  bool PutPaddedString(string_view v, int w, int p, bool l);
+
+  template <typename T>
+  T Wrap() {
+    return T(this);
+  }
+
+  template <typename T>
+  static FormatSinkImpl* Extract(T* s) {
+    return s->sink_;
+  }
+
+ private:
+  size_t Avail() const { return buf_ + sizeof(buf_) - pos_; }
+
+  FormatRawSinkImpl raw_;
+  size_t size_ = 0;
+  char* pos_ = buf_;
+  char buf_[1024];
+};
+
+struct Flags {
+  bool basic : 1;     // fastest conversion: no flags, width, or precision
+  bool left : 1;      // "-"
+  bool show_pos : 1;  // "+"
+  bool sign_col : 1;  // " "
+  bool alt : 1;       // "#"
+  bool zero : 1;      // "0"
+  std::string ToString() const;
+  friend std::ostream& operator<<(std::ostream& os, const Flags& v) {
+    return os << v.ToString();
+  }
+};
+
+struct LengthMod {
+ public:
+  enum Id : uint8_t {
+    h, hh, l, ll, L, j, z, t, q, none
+  };
+  static const size_t kNumValues = none + 1;
+
+  LengthMod() : id_(none) {}
+
+  // Index into the opaque array of LengthMod enums.
+  // Requires: i < kNumValues
+  static LengthMod FromIndex(size_t i) {
+    return LengthMod(kSpecs[i].value);
+  }
+
+  static LengthMod FromId(Id id) { return LengthMod(id); }
+
+  // The length modifier std::string associated with a specified LengthMod.
+  string_view name() const {
+    const Spec& spec = kSpecs[id_];
+    return {spec.name, spec.name_length};
+  }
+
+  Id id() const { return id_; }
+
+  friend bool operator==(const LengthMod& a, const LengthMod& b) {
+    return a.id() == b.id();
+  }
+  friend bool operator!=(const LengthMod& a, const LengthMod& b) {
+    return !(a == b);
+  }
+  friend std::ostream& operator<<(std::ostream& os, const LengthMod& v) {
+    return os << v.name();
+  }
+
+ private:
+  struct Spec {
+    Id value;
+    const char *name;
+    size_t name_length;
+  };
+  static const Spec kSpecs[];
+
+  explicit LengthMod(Id id) : id_(id) {}
+
+  Id id_;
+};
+
+// clang-format off
+#define ABSL_CONVERSION_CHARS_EXPAND_(X_VAL, X_SEP) \
+  /* text */ \
+  X_VAL(c) X_SEP X_VAL(C) X_SEP X_VAL(s) X_SEP X_VAL(S) X_SEP \
+  /* ints */ \
+  X_VAL(d) X_SEP X_VAL(i) X_SEP X_VAL(o) X_SEP \
+  X_VAL(u) X_SEP X_VAL(x) X_SEP X_VAL(X) X_SEP \
+  /* floats */ \
+  X_VAL(f) X_SEP X_VAL(F) X_SEP X_VAL(e) X_SEP X_VAL(E) X_SEP \
+  X_VAL(g) X_SEP X_VAL(G) X_SEP X_VAL(a) X_SEP X_VAL(A) X_SEP \
+  /* misc */ \
+  X_VAL(n) X_SEP X_VAL(p)
+// clang-format on
+
+struct ConversionChar {
+ public:
+  enum Id : uint8_t {
+    c, C, s, S,              // text
+    d, i, o, u, x, X,        // int
+    f, F, e, E, g, G, a, A,  // float
+    n, p,                    // misc
+    none
+  };
+  static const size_t kNumValues = none + 1;
+
+  ConversionChar() : id_(none) {}
+
+ public:
+  // Index into the opaque array of ConversionChar enums.
+  // Requires: i < kNumValues
+  static ConversionChar FromIndex(size_t i) {
+    return ConversionChar(kSpecs[i].value);
+  }
+
+  static ConversionChar FromChar(char c) {
+    ConversionChar::Id out_id = ConversionChar::none;
+    switch (c) {
+#define X_VAL(id)                \
+  case #id[0]:                   \
+    out_id = ConversionChar::id; \
+    break;
+      ABSL_CONVERSION_CHARS_EXPAND_(X_VAL, )
+#undef X_VAL
+      default:
+        break;
+    }
+    return ConversionChar(out_id);
+  }
+
+  static ConversionChar FromId(Id id) { return ConversionChar(id); }
+  Id id() const { return id_; }
+
+  int radix() const {
+    switch (id()) {
+      case x: case X: case a: case A: case p: return 16;
+      case o: return 8;
+      default: return 10;
+    }
+  }
+
+  bool upper() const {
+    switch (id()) {
+      case X: case F: case E: case G: case A: return true;
+      default: return false;
+    }
+  }
+
+  bool is_signed() const {
+    switch (id()) {
+      case d: case i: return true;
+      default: return false;
+    }
+  }
+
+  bool is_integral() const {
+    switch (id()) {
+      case d: case i: case u: case o: case x: case X:
+        return true;
+      default: return false;
+    }
+  }
+
+  bool is_float() const {
+    switch (id()) {
+      case a: case e: case f: case g: case A: case E: case F: case G:
+        return true;
+      default: return false;
+    }
+  }
+
+  bool IsValid() const { return id() != none; }
+
+  // The associated char.
+  char Char() const { return kSpecs[id_].name; }
+
+  friend bool operator==(const ConversionChar& a, const ConversionChar& b) {
+    return a.id() == b.id();
+  }
+  friend bool operator!=(const ConversionChar& a, const ConversionChar& b) {
+    return !(a == b);
+  }
+  friend std::ostream& operator<<(std::ostream& os, const ConversionChar& v) {
+    char c = v.Char();
+    if (!c) c = '?';
+    return os << c;
+  }
+
+ private:
+  struct Spec {
+    Id value;
+    char name;
+  };
+  static const Spec kSpecs[];
+
+  explicit ConversionChar(Id id) : id_(id) {}
+
+  Id id_;
+};
+
+class ConversionSpec {
+ public:
+  Flags flags() const { return flags_; }
+  LengthMod length_mod() const { return length_mod_; }
+  ConversionChar conv() const { return conv_; }
+
+  // Returns the specified width. If width is unspecfied, it returns a negative
+  // value.
+  int width() const { return width_; }
+  // Returns the specified precision. If precision is unspecfied, it returns a
+  // negative value.
+  int precision() const { return precision_; }
+
+  void set_flags(Flags f) { flags_ = f; }
+  void set_length_mod(LengthMod lm) { length_mod_ = lm; }
+  void set_conv(ConversionChar c) { conv_ = c; }
+  void set_width(int w) { width_ = w; }
+  void set_precision(int p) { precision_ = p; }
+  void set_left(bool b) { flags_.left = b; }
+
+ private:
+  Flags flags_;
+  LengthMod length_mod_;
+  ConversionChar conv_;
+  int width_;
+  int precision_;
+};
+
+constexpr uint64_t ConversionCharToConvValue(char conv) {
+  return
+#define CONV_SET_CASE(c) \
+  conv == #c[0] ? (uint64_t{1} << (1 + ConversionChar::Id::c)):
+      ABSL_CONVERSION_CHARS_EXPAND_(CONV_SET_CASE, )
+#undef CONV_SET_CASE
+                  conv == '*'
+          ? 1
+          : 0;
+}
+
+enum class Conv : uint64_t {
+#define CONV_SET_CASE(c) c = ConversionCharToConvValue(#c[0]),
+  ABSL_CONVERSION_CHARS_EXPAND_(CONV_SET_CASE, )
+#undef CONV_SET_CASE
+
+  // Used for width/precision '*' specification.
+  star = ConversionCharToConvValue('*'),
+
+  // Some predefined values:
+  integral = d | i | u | o | x | X,
+  floating = a | e | f | g | A | E | F | G,
+  numeric = integral | floating,
+  string = s,  // absl:ignore(std::string)
+  pointer = p
+};
+
+// Type safe OR operator.
+// We need this for two reasons:
+//  1. operator| on enums makes them decay to integers and the result is an
+//     integer. We need the result to stay as an enum.
+//  2. We use "enum class" which would not work even if we accepted the decay.
+constexpr Conv operator|(Conv a, Conv b) {
+  return Conv(static_cast<uint64_t>(a) | static_cast<uint64_t>(b));
+}
+
+// Get a conversion with a single character in it.
+constexpr Conv ConversionCharToConv(char c) {
+  return Conv(ConversionCharToConvValue(c));
+}
+
+// Checks whether `c` exists in `set`.
+constexpr bool Contains(Conv set, char c) {
+  return (static_cast<uint64_t>(set) & ConversionCharToConvValue(c)) != 0;
+}
+
+// Checks whether all the characters in `c` are contained in `set`
+constexpr bool Contains(Conv set, Conv c) {
+  return (static_cast<uint64_t>(set) & static_cast<uint64_t>(c)) ==
+         static_cast<uint64_t>(c);
+}
+
+// Return type of the AbslFormatConvert() functions.
+// The Conv template parameter is used to inform the framework of what
+// conversion characters are supported by that AbslFormatConvert routine.
+template <Conv C>
+struct ConvertResult {
+  static constexpr Conv kConv = C;
+  bool value;
+};
+template <Conv C>
+constexpr Conv ConvertResult<C>::kConv;
+
+// Return capacity - used, clipped to a minimum of 0.
+inline size_t Excess(size_t used, size_t capacity) {
+  return used < capacity ? capacity - used : 0;
+}
+
+}  // namespace str_format_internal
+
+}  // namespace absl
+
+#endif  // ABSL_STRINGS_STR_FORMAT_EXTENSION_H_
diff --git a/absl/strings/internal/str_format/extension_test.cc b/absl/strings/internal/str_format/extension_test.cc
new file mode 100644
index 000000000000..224fc923d3e3
--- /dev/null
+++ b/absl/strings/internal/str_format/extension_test.cc
@@ -0,0 +1,65 @@
+//
+// Copyright 2017 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "absl/strings/internal/str_format/extension.h"
+
+#include <random>
+#include <string>
+#include "absl/strings/str_format.h"
+
+#include "gtest/gtest.h"
+
+namespace {
+
+std::string MakeRandomString(size_t len) {
+  std::random_device rd;
+  std::mt19937 gen(rd());
+  std::uniform_int_distribution<> dis('a', 'z');
+  std::string s(len, '0');
+  for (char& c : s) {
+    c = dis(gen);
+  }
+  return s;
+}
+
+TEST(FormatExtensionTest, SinkAppendSubstring) {
+  for (size_t chunk_size : {1, 10, 100, 1000, 10000}) {
+    std::string expected, actual;
+    absl::str_format_internal::FormatSinkImpl sink(&actual);
+    for (size_t chunks = 0; chunks < 10; ++chunks) {
+      std::string rand = MakeRandomString(chunk_size);
+      expected += rand;
+      sink.Append(rand);
+    }
+    sink.Flush();
+    EXPECT_EQ(actual, expected);
+  }
+}
+
+TEST(FormatExtensionTest, SinkAppendChars) {
+  for (size_t chunk_size : {1, 10, 100, 1000, 10000}) {
+    std::string expected, actual;
+    absl::str_format_internal::FormatSinkImpl sink(&actual);
+    for (size_t chunks = 0; chunks < 10; ++chunks) {
+      std::string rand = MakeRandomString(1);
+      expected.append(chunk_size, rand[0]);
+      sink.Append(chunk_size, rand[0]);
+    }
+    sink.Flush();
+    EXPECT_EQ(actual, expected);
+  }
+}
+}  // namespace
diff --git a/absl/strings/internal/str_format/float_conversion.cc b/absl/strings/internal/str_format/float_conversion.cc
new file mode 100644
index 000000000000..37952b4699e9
--- /dev/null
+++ b/absl/strings/internal/str_format/float_conversion.cc
@@ -0,0 +1,476 @@
+#include "absl/strings/internal/str_format/float_conversion.h"
+
+#include <string.h>
+#include <algorithm>
+#include <cassert>
+#include <cmath>
+#include <string>
+
+namespace absl {
+namespace str_format_internal {
+
+namespace {
+
+char *CopyStringTo(string_view v, char *out) {
+  std::memcpy(out, v.data(), v.size());
+  return out + v.size();
+}
+
+template <typename Float>
+bool FallbackToSnprintf(const Float v, const ConversionSpec &conv,
+                        FormatSinkImpl *sink) {
+  int w = conv.width() >= 0 ? conv.width() : 0;
+  int p = conv.precision() >= 0 ? conv.precision() : -1;
+  char fmt[32];
+  {
+    char *fp = fmt;
+    *fp++ = '%';
+    fp = CopyStringTo(conv.flags().ToString(), fp);
+    fp = CopyStringTo("*.*", fp);
+    if (std::is_same<long double, Float>()) {
+      *fp++ = 'L';
+    }
+    *fp++ = conv.conv().Char();
+    *fp = 0;
+    assert(fp < fmt + sizeof(fmt));
+  }
+  std::string space(512, '\0');
+  string_view result;
+  while (true) {
+    int n = snprintf(&space[0], space.size(), fmt, w, p, v);
+    if (n < 0) return false;
+    if (static_cast<size_t>(n) < space.size()) {
+      result = string_view(space.data(), n);
+      break;
+    }
+    space.resize(n + 1);
+  }
+  sink->Append(result);
+  return true;
+}
+
+// 128-bits in decimal: ceil(128*log(2)/log(10))
+//   or std::numeric_limits<__uint128_t>::digits10
+constexpr int kMaxFixedPrecision = 39;
+
+constexpr int kBufferLength = /*sign*/ 1 +
+                              /*integer*/ kMaxFixedPrecision +
+                              /*point*/ 1 +
+                              /*fraction*/ kMaxFixedPrecision +
+                              /*exponent e+123*/ 5;
+
+struct Buffer {
+  void push_front(char c) {
+    assert(begin > data);
+    *--begin = c;
+  }
+  void push_back(char c) {
+    assert(end < data + sizeof(data));
+    *end++ = c;
+  }
+  void pop_back() {
+    assert(begin < end);
+    --end;
+  }
+
+  char &back() {
+    assert(begin < end);
+    return end[-1];
+  }
+
+  char last_digit() const { return end[-1] == '.' ? end[-2] : end[-1]; }
+
+  int size() const { return static_cast<int>(end - begin); }
+
+  char data[kBufferLength];
+  char *begin;
+  char *end;
+};
+
+enum class FormatStyle { Fixed, Precision };
+
+// If the value is Inf or Nan, print it and return true.
+// Otherwise, return false.
+template <typename Float>
+bool ConvertNonNumericFloats(char sign_char, Float v,
+                             const ConversionSpec &conv, FormatSinkImpl *sink) {
+  char text[4], *ptr = text;
+  if (sign_char) *ptr++ = sign_char;
+  if (std::isnan(v)) {
+    ptr = std::copy_n(conv.conv().upper() ? "NAN" : "nan", 3, ptr);
+  } else if (std::isinf(v)) {
+    ptr = std::copy_n(conv.conv().upper() ? "INF" : "inf", 3, ptr);
+  } else {
+    return false;
+  }
+
+  return sink->PutPaddedString(string_view(text, ptr - text), conv.width(), -1,
+                               conv.flags().left);
+}
+
+// Round up the last digit of the value.
+// It will carry over and potentially overflow. 'exp' will be adjusted in that
+// case.
+template <FormatStyle mode>
+void RoundUp(Buffer *buffer, int *exp) {
+  char *p = &buffer->back();
+  while (p >= buffer->begin && (*p == '9' || *p == '.')) {
+    if (*p == '9') *p = '0';
+    --p;
+  }
+
+  if (p < buffer->begin) {
+    *p = '1';
+    buffer->begin = p;
+    if (mode == FormatStyle::Precision) {
+      std::swap(p[1], p[2]);  // move the .
+      ++*exp;
+      buffer->pop_back();
+    }
+  } else {
+    ++*p;
+  }
+}
+
+void PrintExponent(int exp, char e, Buffer *out) {
+  out->push_back(e);
+  if (exp < 0) {
+    out->push_back('-');
+    exp = -exp;
+  } else {
+    out->push_back('+');
+  }
+  // Exponent digits.
+  if (exp > 99) {
+    out->push_back(exp / 100 + '0');
+    out->push_back(exp / 10 % 10 + '0');
+    out->push_back(exp % 10 + '0');
+  } else {
+    out->push_back(exp / 10 + '0');
+    out->push_back(exp % 10 + '0');
+  }
+}
+
+template <typename Float, typename Int>
+constexpr bool CanFitMantissa() {
+  return std::numeric_limits<Float>::digits <= std::numeric_limits<Int>::digits;
+}
+
+template <typename Float>
+struct Decomposed {
+  Float mantissa;
+  int exponent;
+};
+
+// Decompose the double into an integer mantissa and an exponent.
+template <typename Float>
+Decomposed<Float> Decompose(Float v) {
+  int exp;
+  Float m = std::frexp(v, &exp);
+  m = std::ldexp(m, std::numeric_limits<Float>::digits);
+  exp -= std::numeric_limits<Float>::digits;
+  return {m, exp};
+}
+
+// Print 'digits' as decimal.
+// In Fixed mode, we add a '.' at the end.
+// In Precision mode, we add a '.' after the first digit.
+template <FormatStyle mode, typename Int>
+int PrintIntegralDigits(Int digits, Buffer *out) {
+  int printed = 0;
+  if (digits) {
+    for (; digits; digits /= 10) out->push_front(digits % 10 + '0');
+    printed = out->size();
+    if (mode == FormatStyle::Precision) {
+      out->push_front(*out->begin);
+      out->begin[1] = '.';
+    } else {
+      out->push_back('.');
+    }
+  } else if (mode == FormatStyle::Fixed) {
+    out->push_front('0');
+    out->push_back('.');
+    printed = 1;
+  }
+  return printed;
+}
+
+// Back out 'extra_digits' digits and round up if necessary.
+bool RemoveExtraPrecision(int extra_digits, bool has_leftover_value,
+                          Buffer *out, int *exp_out) {
+  if (extra_digits <= 0) return false;
+
+  // Back out the extra digits
+  out->end -= extra_digits;
+
+  bool needs_to_round_up = [&] {
+    // We look at the digit just past the end.
+    // There must be 'extra_digits' extra valid digits after end.
+    if (*out->end > '5') return true;
+    if (*out->end < '5') return false;
+    if (has_leftover_value || std::any_of(out->end + 1, out->end + extra_digits,
+                                          [](char c) { return c != '0'; }))
+      return true;
+
+    // Ends in ...50*, round to even.
+    return out->last_digit() % 2 == 1;
+  }();
+
+  if (needs_to_round_up) {
+    RoundUp<FormatStyle::Precision>(out, exp_out);
+  }
+  return true;
+}
+
+// Print the value into the buffer.
+// This will not include the exponent, which will be returned in 'exp_out' for
+// Precision mode.
+template <typename Int, typename Float, FormatStyle mode>
+bool FloatToBufferImpl(Int int_mantissa, int exp, int precision, Buffer *out,
+                       int *exp_out) {
+  assert((CanFitMantissa<Float, Int>()));
+
+  const int int_bits = std::numeric_limits<Int>::digits;
+
+  // In precision mode, we start printing one char to the right because it will
+  // also include the '.'
+  // In fixed mode we put the dot afterwards on the right.
+  out->begin = out->end =
+      out->data + 1 + kMaxFixedPrecision + (mode == FormatStyle::Precision);
+
+  if (exp >= 0) {
+    if (std::numeric_limits<Float>::digits + exp > int_bits) {
+      // The value will overflow the Int
+      return false;
+    }
+    int digits_printed = PrintIntegralDigits<mode>(int_mantissa << exp, out);
+    int digits_to_zero_pad = precision;
+    if (mode == FormatStyle::Precision) {
+      *exp_out = digits_printed - 1;
+      digits_to_zero_pad -= digits_printed - 1;
+      if (RemoveExtraPrecision(-digits_to_zero_pad, false, out, exp_out)) {
+        return true;
+      }
+    }
+    for (; digits_to_zero_pad-- > 0;) out->push_back('0');
+    return true;
+  }
+
+  exp = -exp;
+  // We need at least 4 empty bits for the next decimal digit.
+  // We will multiply by 10.
+  if (exp > int_bits - 4) return false;
+
+  const Int mask = (Int{1} << exp) - 1;
+
+  // Print the integral part first.
+  int digits_printed = PrintIntegralDigits<mode>(int_mantissa >> exp, out);
+  int_mantissa &= mask;
+
+  int fractional_count = precision;
+  if (mode == FormatStyle::Precision) {
+    if (digits_printed == 0) {
+      // Find the first non-zero digit, when in Precision mode.
+      *exp_out = 0;
+      if (int_mantissa) {
+        while (int_mantissa <= mask) {
+          int_mantissa *= 10;
+          --*exp_out;
+        }
+      }
+      out->push_front(static_cast<char>(int_mantissa >> exp) + '0');
+      out->push_back('.');
+      int_mantissa &= mask;
+    } else {
+      // We already have a digit, and a '.'
+      *exp_out = digits_printed - 1;
+      fractional_count -= *exp_out;
+      if (RemoveExtraPrecision(-fractional_count, int_mantissa != 0, out,
+                               exp_out)) {
+        // If we had enough digits, return right away.
+        // The code below will try to round again otherwise.
+        return true;
+      }
+    }
+  }
+
+  auto get_next_digit = [&] {
+    int_mantissa *= 10;
+    int digit = static_cast<int>(int_mantissa >> exp);
+    int_mantissa &= mask;
+    return digit;
+  };
+
+  // Print fractional_count more digits, if available.
+  for (; fractional_count > 0; --fractional_count) {
+    out->push_back(get_next_digit() + '0');
+  }
+
+  int next_digit = get_next_digit();
+  if (next_digit > 5 ||
+      (next_digit == 5 && (int_mantissa || out->last_digit() % 2 == 1))) {
+    RoundUp<mode>(out, exp_out);
+  }
+
+  return true;
+}
+
+template <FormatStyle mode, typename Float>
+bool FloatToBuffer(Decomposed<Float> decomposed, int precision, Buffer *out,
+                   int *exp) {
+  if (precision > kMaxFixedPrecision) return false;
+
+  // Try with uint64_t.
+  if (CanFitMantissa<Float, std::uint64_t>() &&
+      FloatToBufferImpl<std::uint64_t, Float, mode>(
+          static_cast<std::uint64_t>(decomposed.mantissa),
+          static_cast<std::uint64_t>(decomposed.exponent), precision, out, exp))
+    return true;
+
+#if defined(__SIZEOF_INT128__)
+  // If that is not enough, try with __uint128_t.
+  return CanFitMantissa<Float, __uint128_t>() &&
+         FloatToBufferImpl<__uint128_t, Float, mode>(
+             static_cast<__uint128_t>(decomposed.mantissa),
+             static_cast<__uint128_t>(decomposed.exponent), precision, out,
+             exp);
+#endif
+  return false;
+}
+
+void WriteBufferToSink(char sign_char, string_view str,
+                       const ConversionSpec &conv, FormatSinkImpl *sink) {
+  int left_spaces = 0, zeros = 0, right_spaces = 0;
+  int missing_chars =
+      conv.width() >= 0 ? std::max(conv.width() - static_cast<int>(str.size()) -
+                                       static_cast<int>(sign_char != 0),
+                                   0)
+                        : 0;
+  if (conv.flags().left) {
+    right_spaces = missing_chars;
+  } else if (conv.flags().zero) {
+    zeros = missing_chars;
+  } else {
+    left_spaces = missing_chars;
+  }
+
+  sink->Append(left_spaces, ' ');
+  if (sign_char) sink->Append(1, sign_char);
+  sink->Append(zeros, '0');
+  sink->Append(str);
+  sink->Append(right_spaces, ' ');
+}
+
+template <typename Float>
+bool FloatToSink(const Float v, const ConversionSpec &conv,
+                 FormatSinkImpl *sink) {
+  // Print the sign or the sign column.
+  Float abs_v = v;
+  char sign_char = 0;
+  if (std::signbit(abs_v)) {
+    sign_char = '-';
+    abs_v = -abs_v;
+  } else if (conv.flags().show_pos) {
+    sign_char = '+';
+  } else if (conv.flags().sign_col) {
+    sign_char = ' ';
+  }
+
+  // Print nan/inf.
+  if (ConvertNonNumericFloats(sign_char, abs_v, conv, sink)) {
+    return true;
+  }
+
+  int precision = conv.precision() < 0 ? 6 : conv.precision();
+
+  int exp = 0;
+
+  auto decomposed = Decompose(abs_v);
+
+  Buffer buffer;
+
+  switch (conv.conv().id()) {
+    case ConversionChar::f:
+    case ConversionChar::F:
+      if (!FloatToBuffer<FormatStyle::Fixed>(decomposed, precision, &buffer,
+                                             nullptr)) {
+        return FallbackToSnprintf(v, conv, sink);
+      }
+      if (!conv.flags().alt && buffer.back() == '.') buffer.pop_back();
+      break;
+
+    case ConversionChar::e:
+    case ConversionChar::E:
+      if (!FloatToBuffer<FormatStyle::Precision>(decomposed, precision, &buffer,
+                                                 &exp)) {
+        return FallbackToSnprintf(v, conv, sink);
+      }
+      if (!conv.flags().alt && buffer.back() == '.') buffer.pop_back();
+      PrintExponent(exp, conv.conv().upper() ? 'E' : 'e', &buffer);
+      break;
+
+    case ConversionChar::g:
+    case ConversionChar::G:
+      precision = std::max(0, precision - 1);
+      if (!FloatToBuffer<FormatStyle::Precision>(decomposed, precision, &buffer,
+                                                 &exp)) {
+        return FallbackToSnprintf(v, conv, sink);
+      }
+      if (precision + 1 > exp && exp >= -4) {
+        if (exp < 0) {
+          // Have 1.23456, needs 0.00123456
+          // Move the first digit
+          buffer.begin[1] = *buffer.begin;
+          // Add some zeros
+          for (; exp < -1; ++exp) *buffer.begin-- = '0';
+          *buffer.begin-- = '.';
+          *buffer.begin = '0';
+        } else if (exp > 0) {
+          // Have 1.23456, needs 1234.56
+          // Move the '.' exp positions to the right.
+          std::rotate(buffer.begin + 1, buffer.begin + 2,
+                      buffer.begin + exp + 2);
+        }
+        exp = 0;
+      }
+      if (!conv.flags().alt) {
+        while (buffer.back() == '0') buffer.pop_back();
+        if (buffer.back() == '.') buffer.pop_back();
+      }
+      if (exp) PrintExponent(exp, conv.conv().upper() ? 'E' : 'e', &buffer);
+      break;
+
+    case ConversionChar::a:
+    case ConversionChar::A:
+      return FallbackToSnprintf(v, conv, sink);
+
+    default:
+      return false;
+  }
+
+  WriteBufferToSink(sign_char,
+                    string_view(buffer.begin, buffer.end - buffer.begin), conv,
+                    sink);
+
+  return true;
+}
+
+}  // namespace
+
+bool ConvertFloatImpl(long double v, const ConversionSpec &conv,
+                      FormatSinkImpl *sink) {
+  return FloatToSink(v, conv, sink);
+}
+
+bool ConvertFloatImpl(float v, const ConversionSpec &conv,
+                      FormatSinkImpl *sink) {
+  return FloatToSink(v, conv, sink);
+}
+
+bool ConvertFloatImpl(double v, const ConversionSpec &conv,
+                      FormatSinkImpl *sink) {
+  return FloatToSink(v, conv, sink);
+}
+
+}  // namespace str_format_internal
+}  // namespace absl
diff --git a/absl/strings/internal/str_format/float_conversion.h b/absl/strings/internal/str_format/float_conversion.h
new file mode 100644
index 000000000000..8ba5566d3eef
--- /dev/null
+++ b/absl/strings/internal/str_format/float_conversion.h
@@ -0,0 +1,21 @@
+#ifndef ABSL_STRINGS_INTERNAL_STR_FORMAT_FLOAT_CONVERSION_H_
+#define ABSL_STRINGS_INTERNAL_STR_FORMAT_FLOAT_CONVERSION_H_
+
+#include "absl/strings/internal/str_format/extension.h"
+
+namespace absl {
+namespace str_format_internal {
+
+bool ConvertFloatImpl(float v, const ConversionSpec &conv,
+                      FormatSinkImpl *sink);
+
+bool ConvertFloatImpl(double v, const ConversionSpec &conv,
+                      FormatSinkImpl *sink);
+
+bool ConvertFloatImpl(long double v, const ConversionSpec &conv,
+                      FormatSinkImpl *sink);
+
+}  // namespace str_format_internal
+}  // namespace absl
+
+#endif  // ABSL_STRINGS_INTERNAL_STR_FORMAT_FLOAT_CONVERSION_H_
diff --git a/absl/strings/internal/str_format/output.cc b/absl/strings/internal/str_format/output.cc
new file mode 100644
index 000000000000..5c3795b737ca
--- /dev/null
+++ b/absl/strings/internal/str_format/output.cc
@@ -0,0 +1,47 @@
+// Copyright 2017 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/strings/internal/str_format/output.h"
+
+#include <errno.h>
+#include <cstring>
+
+namespace absl {
+namespace str_format_internal {
+
+void BufferRawSink::Write(string_view v) {
+  size_t to_write = std::min(v.size(), size_);
+  std::memcpy(buffer_, v.data(), to_write);
+  buffer_ += to_write;
+  size_ -= to_write;
+  total_written_ += v.size();
+}
+
+void FILERawSink::Write(string_view v) {
+  while (!v.empty() && !error_) {
+    if (size_t result = std::fwrite(v.data(), 1, v.size(), output_)) {
+      // Some progress was made.
+      count_ += result;
+      v.remove_prefix(result);
+    } else {
+      // Some error occurred.
+      if (errno != EINTR) {
+        error_ = errno;
+      }
+    }
+  }
+}
+
+}  // namespace str_format_internal
+}  // namespace absl
diff --git a/absl/strings/internal/str_format/output.h b/absl/strings/internal/str_format/output.h
new file mode 100644
index 000000000000..3b0aa5e7157e
--- /dev/null
+++ b/absl/strings/internal/str_format/output.h
@@ -0,0 +1,101 @@
+// Copyright 2017 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// Output extension hooks for the Format library.
+// `internal::InvokeFlush` calls the appropriate flush function for the
+// specified output argument.
+// `BufferRawSink` is a simple output sink for a char buffer. Used by SnprintF.
+// `FILERawSink` is a std::FILE* based sink. Used by PrintF and FprintF.
+
+#ifndef ABSL_STRINGS_INTERNAL_STR_FORMAT_OUTPUT_H_
+#define ABSL_STRINGS_INTERNAL_STR_FORMAT_OUTPUT_H_
+
+#include <cstdio>
+#include <ostream>
+#include <string>
+
+#include "absl/base/port.h"
+#include "absl/strings/string_view.h"
+
+class Cord;
+
+namespace absl {
+namespace str_format_internal {
+
+// RawSink implementation that writes into a char* buffer.
+// It will not overflow the buffer, but will keep the total count of chars
+// that would have been written.
+class BufferRawSink {
+ public:
+  BufferRawSink(char* buffer, size_t size) : buffer_(buffer), size_(size) {}
+
+  size_t total_written() const { return total_written_; }
+  void Write(string_view v);
+
+ private:
+  char* buffer_;
+  size_t size_;
+  size_t total_written_ = 0;
+};
+
+// RawSink implementation that writes into a FILE*.
+// It keeps track of the total number of bytes written and any error encountered
+// during the writes.
+class FILERawSink {
+ public:
+  explicit FILERawSink(std::FILE* output) : output_(output) {}
+
+  void Write(string_view v);
+
+  size_t count() const { return count_; }
+  int error() const { return error_; }
+
+ private:
+  std::FILE* output_;
+  int error_ = 0;
+  size_t count_ = 0;
+};
+
+// Provide RawSink integration with common types from the STL.
+inline void AbslFormatFlush(std::string* out, string_view s) {
+  out->append(s.begin(), s.size());
+}
+inline void AbslFormatFlush(std::ostream* out, string_view s) {
+  out->write(s.begin(), s.size());
+}
+
+template <class AbslCord, typename = typename std::enable_if<
+                              std::is_same<AbslCord, ::Cord>::value>::type>
+inline void AbslFormatFlush(AbslCord* out, string_view s) {
+  out->Append(s);
+}
+
+inline void AbslFormatFlush(FILERawSink* sink, string_view v) {
+  sink->Write(v);
+}
+
+inline void AbslFormatFlush(BufferRawSink* sink, string_view v) {
+  sink->Write(v);
+}
+
+template <typename T>
+auto InvokeFlush(T* out, string_view s)
+    -> decltype(str_format_internal::AbslFormatFlush(out, s)) {
+  str_format_internal::AbslFormatFlush(out, s);
+}
+
+}  // namespace str_format_internal
+}  // namespace absl
+
+#endif  // ABSL_STRINGS_INTERNAL_STR_FORMAT_OUTPUT_H_
diff --git a/absl/strings/internal/str_format/output_test.cc b/absl/strings/internal/str_format/output_test.cc
new file mode 100644
index 000000000000..cc3c615557fe
--- /dev/null
+++ b/absl/strings/internal/str_format/output_test.cc
@@ -0,0 +1,78 @@
+// Copyright 2017 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/strings/internal/str_format/output.h"
+
+#include <sstream>
+#include <string>
+
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+namespace absl {
+namespace {
+
+TEST(InvokeFlush, String) {
+  std::string str = "ABC";
+  str_format_internal::InvokeFlush(&str, "DEF");
+  EXPECT_EQ(str, "ABCDEF");
+
+#if UTIL_FORMAT_HAS_GLOBAL_STRING
+  std::string str2 = "ABC";
+  str_format_internal::InvokeFlush(&str2, "DEF");
+  EXPECT_EQ(str2, "ABCDEF");
+#endif  // UTIL_FORMAT_HAS_GLOBAL_STRING
+}
+
+TEST(InvokeFlush, Stream) {
+  std::stringstream str;
+  str << "ABC";
+  str_format_internal::InvokeFlush(&str, "DEF");
+  EXPECT_EQ(str.str(), "ABCDEF");
+}
+
+TEST(BufferRawSink, Limits) {
+  char buf[16];
+  {
+    std::fill(std::begin(buf), std::end(buf), 'x');
+    str_format_internal::BufferRawSink bufsink(buf, sizeof(buf) - 1);
+    str_format_internal::InvokeFlush(&bufsink, "Hello World237");
+    EXPECT_EQ(std::string(buf, sizeof(buf)), "Hello World237xx");
+  }
+  {
+    std::fill(std::begin(buf), std::end(buf), 'x');
+    str_format_internal::BufferRawSink bufsink(buf, sizeof(buf) - 1);
+    str_format_internal::InvokeFlush(&bufsink, "Hello World237237");
+    EXPECT_EQ(std::string(buf, sizeof(buf)), "Hello World2372x");
+  }
+  {
+    std::fill(std::begin(buf), std::end(buf), 'x');
+    str_format_internal::BufferRawSink bufsink(buf, sizeof(buf) - 1);
+    str_format_internal::InvokeFlush(&bufsink, "Hello World");
+    str_format_internal::InvokeFlush(&bufsink, "237");
+    EXPECT_EQ(std::string(buf, sizeof(buf)), "Hello World237xx");
+  }
+  {
+    std::fill(std::begin(buf), std::end(buf), 'x');
+    str_format_internal::BufferRawSink bufsink(buf, sizeof(buf) - 1);
+    str_format_internal::InvokeFlush(&bufsink, "Hello World");
+    str_format_internal::InvokeFlush(&bufsink, "237237");
+    EXPECT_EQ(std::string(buf, sizeof(buf)), "Hello World2372x");
+  }
+}
+
+}  // namespace
+}  // namespace absl
+
diff --git a/absl/strings/internal/str_format/parser.cc b/absl/strings/internal/str_format/parser.cc
new file mode 100644
index 000000000000..10114f489c01
--- /dev/null
+++ b/absl/strings/internal/str_format/parser.cc
@@ -0,0 +1,294 @@
+#include "absl/strings/internal/str_format/parser.h"
+
+#include <assert.h>
+#include <string.h>
+#include <wchar.h>
+#include <cctype>
+#include <cstdint>
+
+#include <algorithm>
+#include <initializer_list>
+#include <limits>
+#include <ostream>
+#include <string>
+#include <unordered_set>
+
+namespace absl {
+namespace str_format_internal {
+namespace {
+
+bool CheckFastPathSetting(const UnboundConversion& conv) {
+  bool should_be_basic = !conv.flags.left &&      //
+                         !conv.flags.show_pos &&  //
+                         !conv.flags.sign_col &&  //
+                         !conv.flags.alt &&       //
+                         !conv.flags.zero &&      //
+                         (conv.width.value() == -1) &&
+                         (conv.precision.value() == -1);
+  if (should_be_basic != conv.flags.basic) {
+    fprintf(stderr,
+            "basic=%d left=%d show_pos=%d sign_col=%d alt=%d zero=%d "
+            "width=%d precision=%d\n",
+            conv.flags.basic, conv.flags.left, conv.flags.show_pos,
+            conv.flags.sign_col, conv.flags.alt, conv.flags.zero,
+            conv.width.value(), conv.precision.value());
+  }
+  return should_be_basic == conv.flags.basic;
+}
+
+// Keep a single table for all the conversion chars and length modifiers.
+// We invert the length modifiers to make them negative so that we can easily
+// test for them.
+// Everything else is `none`, which is a negative constant.
+using CC = ConversionChar::Id;
+using LM = LengthMod::Id;
+static constexpr std::int8_t none = -128;
+static constexpr std::int8_t kIds[] = {
+    none,   none,   none,   none,  none,   none,  none,  none,   // 00-07
+    none,   none,   none,   none,  none,   none,  none,  none,   // 08-0f
+    none,   none,   none,   none,  none,   none,  none,  none,   // 10-17
+    none,   none,   none,   none,  none,   none,  none,  none,   // 18-1f
+    none,   none,   none,   none,  none,   none,  none,  none,   // 20-27
+    none,   none,   none,   none,  none,   none,  none,  none,   // 28-2f
+    none,   none,   none,   none,  none,   none,  none,  none,   // 30-37
+    none,   none,   none,   none,  none,   none,  none,  none,   // 38-3f
+    none,   CC::A,  none,   CC::C, none,   CC::E, CC::F, CC::G,  // @ABCDEFG
+    none,   none,   none,   none,  ~LM::L, none,  none,  none,   // HIJKLMNO
+    none,   none,   none,   CC::S, none,   none,  none,  none,   // PQRSTUVW
+    CC::X,  none,   none,   none,  none,   none,  none,  none,   // XYZ[\]^_
+    none,   CC::a,  none,   CC::c, CC::d,  CC::e, CC::f, CC::g,  // `abcdefg
+    ~LM::h, CC::i,  ~LM::j, none,  ~LM::l, none,  CC::n, CC::o,  // hijklmno
+    CC::p,  ~LM::q, none,   CC::s, ~LM::t, CC::u, none,  none,   // pqrstuvw
+    CC::x,  none,   ~LM::z, none,  none,   none,  none,  none,   // xyz{|}~!
+    none,   none,   none,   none,  none,   none,  none,  none,   // 80-87
+    none,   none,   none,   none,  none,   none,  none,  none,   // 88-8f
+    none,   none,   none,   none,  none,   none,  none,  none,   // 90-97
+    none,   none,   none,   none,  none,   none,  none,  none,   // 98-9f
+    none,   none,   none,   none,  none,   none,  none,  none,   // a0-a7
+    none,   none,   none,   none,  none,   none,  none,  none,   // a8-af
+    none,   none,   none,   none,  none,   none,  none,  none,   // b0-b7
+    none,   none,   none,   none,  none,   none,  none,  none,   // b8-bf
+    none,   none,   none,   none,  none,   none,  none,  none,   // c0-c7
+    none,   none,   none,   none,  none,   none,  none,  none,   // c8-cf
+    none,   none,   none,   none,  none,   none,  none,  none,   // d0-d7
+    none,   none,   none,   none,  none,   none,  none,  none,   // d8-df
+    none,   none,   none,   none,  none,   none,  none,  none,   // e0-e7
+    none,   none,   none,   none,  none,   none,  none,  none,   // e8-ef
+    none,   none,   none,   none,  none,   none,  none,  none,   // f0-f7
+    none,   none,   none,   none,  none,   none,  none,  none,   // f8-ff
+};
+
+template <bool is_positional>
+bool ConsumeConversion(string_view *src, UnboundConversion *conv,
+                       int *next_arg) {
+  const char *pos = src->begin();
+  const char *const end = src->end();
+  char c;
+  // Read the next char into `c` and update `pos`. Reads '\0' if at end.
+  const auto get_char = [&] { c = pos == end ? '\0' : *pos++; };
+
+  const auto parse_digits = [&] {
+    int digits = c - '0';
+    // We do not want to overflow `digits` so we consume at most digits10-1
+    // digits. If there are more digits the parsing will fail later on when the
+    // digit doesn't match the expected characters.
+    int num_digits = std::numeric_limits<int>::digits10 - 2;
+    for (get_char(); num_digits && std::isdigit(c); get_char()) {
+      --num_digits;
+      digits = 10 * digits + c - '0';
+    }
+    return digits;
+  };
+
+  if (is_positional) {
+    get_char();
+    if (c < '1' || c > '9') return false;
+    conv->arg_position = parse_digits();
+    assert(conv->arg_position > 0);
+    if (c != '$') return false;
+  }
+
+  get_char();
+
+  // We should start with the basic flag on.
+  assert(conv->flags.basic);
+
+  // Any non alpha character makes this conversion not basic.
+  // This includes flags (-+ #0), width (1-9, *) or precision (.).
+  // All conversion characters and length modifiers are alpha characters.
+  if (c < 'A') {
+    conv->flags.basic = false;
+
+    for (; c <= '0'; get_char()) {
+      switch (c) {
+        case '-':
+          conv->flags.left = true;
+          continue;
+        case '+':
+          conv->flags.show_pos = true;
+          continue;
+        case ' ':
+          conv->flags.sign_col = true;
+          continue;
+        case '#':
+          conv->flags.alt = true;
+          continue;
+        case '0':
+          conv->flags.zero = true;
+          continue;
+      }
+      break;
+    }
+
+    if (c <= '9') {
+      if (c >= '0') {
+        int maybe_width = parse_digits();
+        if (!is_positional && c == '$') {
+          if (*next_arg != 0) return false;
+          // Positional conversion.
+          *next_arg = -1;
+          conv->flags = Flags();
+          conv->flags.basic = true;
+          return ConsumeConversion<true>(src, conv, next_arg);
+        }
+        conv->width.set_value(maybe_width);
+      } else if (c == '*') {
+        get_char();
+        if (is_positional) {
+          if (c < '1' || c > '9') return false;
+          conv->width.set_from_arg(parse_digits());
+          if (c != '$') return false;
+          get_char();
+        } else {
+          conv->width.set_from_arg(++*next_arg);
+        }
+      }
+    }
+
+    if (c == '.') {
+      get_char();
+      if (std::isdigit(c)) {
+        conv->precision.set_value(parse_digits());
+      } else if (c == '*') {
+        get_char();
+        if (is_positional) {
+          if (c < '1' || c > '9') return false;
+          conv->precision.set_from_arg(parse_digits());
+          if (c != '$') return false;
+          get_char();
+        } else {
+          conv->precision.set_from_arg(++*next_arg);
+        }
+      } else {
+        conv->precision.set_value(0);
+      }
+    }
+  }
+
+  std::int8_t id = kIds[static_cast<unsigned char>(c)];
+
+  if (id < 0) {
+    if (id == none) return false;
+
+    // It is a length modifier.
+    using str_format_internal::LengthMod;
+    LengthMod length_mod = LengthMod::FromId(static_cast<LM>(~id));
+    get_char();
+    if (c == 'h' && length_mod.id() == LengthMod::h) {
+      conv->length_mod = LengthMod::FromId(LengthMod::hh);
+      get_char();
+    } else if (c == 'l' && length_mod.id() == LengthMod::l) {
+      conv->length_mod = LengthMod::FromId(LengthMod::ll);
+      get_char();
+    } else {
+      conv->length_mod = length_mod;
+    }
+    id = kIds[static_cast<unsigned char>(c)];
+    if (id < 0) return false;
+  }
+
+  assert(CheckFastPathSetting(*conv));
+  (void)(&CheckFastPathSetting);
+
+  conv->conv = ConversionChar::FromId(static_cast<CC>(id));
+  if (!is_positional) conv->arg_position = ++*next_arg;
+  *src = string_view(pos, end - pos);
+  return true;
+}
+
+}  // namespace
+
+bool ConsumeUnboundConversion(string_view *src, UnboundConversion *conv,
+                              int *next_arg) {
+  if (*next_arg < 0) return ConsumeConversion<true>(src, conv, next_arg);
+  return ConsumeConversion<false>(src, conv, next_arg);
+}
+
+struct ParsedFormatBase::ParsedFormatConsumer {
+  explicit ParsedFormatConsumer(ParsedFormatBase *parsedformat)
+      : parsed(parsedformat), data_pos(parsedformat->data_.get()) {}
+
+  bool Append(string_view s) {
+    if (s.empty()) return true;
+
+    size_t text_end = AppendText(s);
+
+    if (!parsed->items_.empty() && !parsed->items_.back().is_conversion) {
+      // Let's extend the existing text run.
+      parsed->items_.back().text_end = text_end;
+    } else {
+      // Let's make a new text run.
+      parsed->items_.push_back({false, text_end, {}});
+    }
+    return true;
+  }
+
+  bool ConvertOne(const UnboundConversion &conv, string_view s) {
+    size_t text_end = AppendText(s);
+    parsed->items_.push_back({true, text_end, conv});
+    return true;
+  }
+
+  size_t AppendText(string_view s) {
+    memcpy(data_pos, s.data(), s.size());
+    data_pos += s.size();
+    return static_cast<size_t>(data_pos - parsed->data_.get());
+  }
+
+  ParsedFormatBase *parsed;
+  char* data_pos;
+};
+
+ParsedFormatBase::ParsedFormatBase(string_view format, bool allow_ignored,
+                                   std::initializer_list<Conv> convs)
+    : data_(format.empty() ? nullptr : new char[format.size()]) {
+  has_error_ = !ParseFormatString(format, ParsedFormatConsumer(this)) ||
+               !MatchesConversions(allow_ignored, convs);
+}
+
+bool ParsedFormatBase::MatchesConversions(
+    bool allow_ignored, std::initializer_list<Conv> convs) const {
+  std::unordered_set<int> used;
+  auto add_if_valid_conv = [&](int pos, char c) {
+      if (static_cast<size_t>(pos) > convs.size() ||
+          !Contains(convs.begin()[pos - 1], c))
+        return false;
+      used.insert(pos);
+      return true;
+  };
+  for (const ConversionItem &item : items_) {
+    if (!item.is_conversion) continue;
+    auto &conv = item.conv;
+    if (conv.precision.is_from_arg() &&
+        !add_if_valid_conv(conv.precision.get_from_arg(), '*'))
+      return false;
+    if (conv.width.is_from_arg() &&
+        !add_if_valid_conv(conv.width.get_from_arg(), '*'))
+      return false;
+    if (!add_if_valid_conv(conv.arg_position, conv.conv.Char())) return false;
+  }
+  return used.size() == convs.size() || allow_ignored;
+}
+
+}  // namespace str_format_internal
+}  // namespace absl
diff --git a/absl/strings/internal/str_format/parser.h b/absl/strings/internal/str_format/parser.h
new file mode 100644
index 000000000000..5bebc95540e6
--- /dev/null
+++ b/absl/strings/internal/str_format/parser.h
@@ -0,0 +1,291 @@
+#ifndef ABSL_STRINGS_INTERNAL_STR_FORMAT_PARSER_H_
+#define ABSL_STRINGS_INTERNAL_STR_FORMAT_PARSER_H_
+
+#include <limits.h>
+#include <stddef.h>
+#include <stdlib.h>
+
+#include <cassert>
+#include <initializer_list>
+#include <iosfwd>
+#include <iterator>
+#include <memory>
+#include <vector>
+
+#include "absl/strings/internal/str_format/checker.h"
+#include "absl/strings/internal/str_format/extension.h"
+
+namespace absl {
+namespace str_format_internal {
+
+// The analyzed properties of a single specified conversion.
+struct UnboundConversion {
+  UnboundConversion()
+      : flags() /* This is required to zero all the fields of flags. */ {
+    flags.basic = true;
+  }
+
+  class InputValue {
+   public:
+    void set_value(int value) {
+      assert(value >= 0);
+      value_ = value;
+    }
+    int value() const { return value_; }
+
+    // Marks the value as "from arg". aka the '*' format.
+    // Requires `value >= 1`.
+    // When set, is_from_arg() return true and get_from_arg() returns the
+    // original value.
+    // `value()`'s return value is unspecfied in this state.
+    void set_from_arg(int value) {
+      assert(value > 0);
+      value_ = -value - 1;
+    }
+    bool is_from_arg() const { return value_ < -1; }
+    int get_from_arg() const {
+      assert(is_from_arg());
+      return -value_ - 1;
+    }
+
+   private:
+    int value_ = -1;
+  };
+
+  // No need to initialize. It will always be set in the parser.
+  int arg_position;
+
+  InputValue width;
+  InputValue precision;
+
+  Flags flags;
+  LengthMod length_mod;
+  ConversionChar conv;
+};
+
+// Consume conversion spec prefix (not including '%') of '*src' if valid.
+// Examples of valid specs would be e.g.: "s", "d", "-12.6f".
+// If valid, the front of src is advanced such that src becomes the
+// part following the conversion spec, and the spec part is broken down and
+// returned in 'conv'.
+// If invalid, returns false and leaves 'src' unmodified.
+// For example:
+//   Given "d9", returns "d", and leaves src="9",
+//   Given "!", returns "" and leaves src="!".
+bool ConsumeUnboundConversion(string_view* src, UnboundConversion* conv,
+                              int* next_arg);
+
+// Parse the format std::string provided in 'src' and pass the identified items into
+// 'consumer'.
+// Text runs will be passed by calling
+//   Consumer::Append(string_view);
+// ConversionItems will be passed by calling
+//   Consumer::ConvertOne(UnboundConversion, string_view);
+// In the case of ConvertOne, the string_view that is passed is the
+// portion of the format std::string corresponding to the conversion, not including
+// the leading %. On success, it returns true. On failure, it stops and returns
+// false.
+template <typename Consumer>
+bool ParseFormatString(string_view src, Consumer consumer) {
+  int next_arg = 0;
+  while (!src.empty()) {
+    const char* percent =
+        static_cast<const char*>(memchr(src.begin(), '%', src.size()));
+    if (!percent) {
+      // We found the last substring.
+      return consumer.Append(src);
+    }
+    // We found a percent, so push the text run then process the percent.
+    size_t percent_loc = percent - src.data();
+    if (!consumer.Append(string_view(src.data(), percent_loc))) return false;
+    if (percent + 1 >= src.end()) return false;
+
+    UnboundConversion conv;
+
+    switch (percent[1]) {
+      case '%':
+        if (!consumer.Append("%")) return false;
+        src.remove_prefix(percent_loc + 2);
+        continue;
+
+#define PARSER_CASE(ch)                                     \
+  case #ch[0]:                                              \
+    src.remove_prefix(percent_loc + 2);                     \
+    conv.conv = ConversionChar::FromId(ConversionChar::ch); \
+    conv.arg_position = ++next_arg;                         \
+    break;
+        ABSL_CONVERSION_CHARS_EXPAND_(PARSER_CASE, );
+#undef PARSER_CASE
+
+      default:
+        src.remove_prefix(percent_loc + 1);
+        if (!ConsumeUnboundConversion(&src, &conv, &next_arg)) return false;
+        break;
+    }
+    if (next_arg == 0) {
+      // This indicates an error in the format std::string.
+      // The only way to get next_arg == 0 is to have a positional argument
+      // first which sets next_arg to -1 and then a non-positional argument
+      // which does ++next_arg.
+      // Checking here seems to be the cheapeast place to do it.
+      return false;
+    }
+    if (!consumer.ConvertOne(
+            conv, string_view(percent + 1, src.data() - (percent + 1)))) {
+      return false;
+    }
+  }
+  return true;
+}
+
+// Always returns true, or fails to compile in a constexpr context if s does not
+// point to a constexpr char array.
+constexpr bool EnsureConstexpr(string_view s) {
+  return s.empty() || s[0] == s[0];
+}
+
+class ParsedFormatBase {
+ public:
+  explicit ParsedFormatBase(string_view format, bool allow_ignored,
+                            std::initializer_list<Conv> convs);
+
+  ParsedFormatBase(const ParsedFormatBase& other) { *this = other; }
+
+  ParsedFormatBase(ParsedFormatBase&& other) { *this = std::move(other); }
+
+  ParsedFormatBase& operator=(const ParsedFormatBase& other) {
+    if (this == &other) return *this;
+    has_error_ = other.has_error_;
+    items_ = other.items_;
+    size_t text_size = items_.empty() ? 0 : items_.back().text_end;
+    data_.reset(new char[text_size]);
+    memcpy(data_.get(), other.data_.get(), text_size);
+    return *this;
+  }
+
+  ParsedFormatBase& operator=(ParsedFormatBase&& other) {
+    if (this == &other) return *this;
+    has_error_ = other.has_error_;
+    data_ = std::move(other.data_);
+    items_ = std::move(other.items_);
+    // Reset the vector to make sure the invariants hold.
+    other.items_.clear();
+    return *this;
+  }
+
+  template <typename Consumer>
+  bool ProcessFormat(Consumer consumer) const {
+    const char* const base = data_.get();
+    string_view text(base, 0);
+    for (const auto& item : items_) {
+      text = string_view(text.end(), (base + item.text_end) - text.end());
+      if (item.is_conversion) {
+        if (!consumer.ConvertOne(item.conv, text)) return false;
+      } else {
+        if (!consumer.Append(text)) return false;
+      }
+    }
+    return !has_error_;
+  }
+
+  bool has_error() const { return has_error_; }
+
+ private:
+  // Returns whether the conversions match and if !allow_ignored it verifies
+  // that all conversions are used by the format.
+  bool MatchesConversions(bool allow_ignored,
+                          std::initializer_list<Conv> convs) const;
+
+  struct ParsedFormatConsumer;
+
+  struct ConversionItem {
+    bool is_conversion;
+    // Points to the past-the-end location of this element in the data_ array.
+    size_t text_end;
+    UnboundConversion conv;
+  };
+
+  bool has_error_;
+  std::unique_ptr<char[]> data_;
+  std::vector<ConversionItem> items_;
+};
+
+
+// A value type representing a preparsed format.  These can be created, copied
+// around, and reused to speed up formatting loops.
+// The user must specify through the template arguments the conversion
+// characters used in the format. This will be checked at compile time.
+//
+// This class uses Conv enum values to specify each argument.
+// This allows for more flexibility as you can specify multiple possible
+// conversion characters for each argument.
+// ParsedFormat<char...> is a simplified alias for when the user only
+// needs to specify a single conversion character for each argument.
+//
+// Example:
+//   // Extended format supports multiple characters per argument:
+//   using MyFormat = ExtendedParsedFormat<Conv::d | Conv::x>;
+//   MyFormat GetFormat(bool use_hex) {
+//     if (use_hex) return MyFormat("foo %x bar");
+//     return MyFormat("foo %d bar");
+//   }
+//   // 'format' can be used with any value that supports 'd' and 'x',
+//   // like `int`.
+//   auto format = GetFormat(use_hex);
+//   value = StringF(format, i);
+//
+// This class also supports runtime format checking with the ::New() and
+// ::NewAllowIgnored() factory functions.
+// This is the only API that allows the user to pass a runtime specified format
+// std::string. These factory functions will return NULL if the format does not match
+// the conversions requested by the user.
+template <str_format_internal::Conv... C>
+class ExtendedParsedFormat : public str_format_internal::ParsedFormatBase {
+ public:
+  explicit ExtendedParsedFormat(string_view format)
+#if ABSL_INTERNAL_ENABLE_FORMAT_CHECKER
+      __attribute__((
+          enable_if(str_format_internal::EnsureConstexpr(format),
+                    "Format std::string is not constexpr."),
+          enable_if(str_format_internal::ValidFormatImpl<C...>(format),
+                    "Format specified does not match the template arguments.")))
+#endif  // ABSL_INTERNAL_ENABLE_FORMAT_CHECKER
+      : ExtendedParsedFormat(format, false) {
+  }
+
+  // ExtendedParsedFormat factory function.
+  // The user still has to specify the conversion characters, but they will not
+  // be checked at compile time. Instead, it will be checked at runtime.
+  // This delays the checking to runtime, but allows the user to pass
+  // dynamically sourced formats.
+  // It returns NULL if the format does not match the conversion characters.
+  // The user is responsible for checking the return value before using it.
+  //
+  // The 'New' variant will check that all the specified arguments are being
+  // consumed by the format and return NULL if any argument is being ignored.
+  // The 'NewAllowIgnored' variant will not verify this and will allow formats
+  // that ignore arguments.
+  static std::unique_ptr<ExtendedParsedFormat> New(string_view format) {
+    return New(format, false);
+  }
+  static std::unique_ptr<ExtendedParsedFormat> NewAllowIgnored(
+      string_view format) {
+    return New(format, true);
+  }
+
+ private:
+  static std::unique_ptr<ExtendedParsedFormat> New(string_view format,
+                                                   bool allow_ignored) {
+    std::unique_ptr<ExtendedParsedFormat> conv(
+        new ExtendedParsedFormat(format, allow_ignored));
+    if (conv->has_error()) return nullptr;
+    return conv;
+  }
+
+  ExtendedParsedFormat(string_view s, bool allow_ignored)
+      : ParsedFormatBase(s, allow_ignored, {C...}) {}
+};
+}  // namespace str_format_internal
+}  // namespace absl
+
+#endif  // ABSL_STRINGS_INTERNAL_STR_FORMAT_PARSER_H_
diff --git a/absl/strings/internal/str_format/parser_test.cc b/absl/strings/internal/str_format/parser_test.cc
new file mode 100644
index 000000000000..e698020b1aba
--- /dev/null
+++ b/absl/strings/internal/str_format/parser_test.cc
@@ -0,0 +1,379 @@
+#include "absl/strings/internal/str_format/parser.h"
+
+#include <string.h>
+#include "gtest/gtest.h"
+#include "absl/base/macros.h"
+
+namespace absl {
+namespace str_format_internal {
+
+namespace {
+
+TEST(LengthModTest, Names) {
+  struct Expectation {
+    int line;
+    LengthMod::Id id;
+    const char *name;
+  };
+  const Expectation kExpect[] = {
+    {__LINE__, LengthMod::none, ""  },
+    {__LINE__, LengthMod::h,    "h" },
+    {__LINE__, LengthMod::hh,   "hh"},
+    {__LINE__, LengthMod::l,    "l" },
+    {__LINE__, LengthMod::ll,   "ll"},
+    {__LINE__, LengthMod::L,    "L" },
+    {__LINE__, LengthMod::j,    "j" },
+    {__LINE__, LengthMod::z,    "z" },
+    {__LINE__, LengthMod::t,    "t" },
+    {__LINE__, LengthMod::q,    "q" },
+  };
+  EXPECT_EQ(ABSL_ARRAYSIZE(kExpect), LengthMod::kNumValues);
+  for (auto e : kExpect) {
+    SCOPED_TRACE(e.line);
+    LengthMod mod = LengthMod::FromId(e.id);
+    EXPECT_EQ(e.id, mod.id());
+    EXPECT_EQ(e.name, mod.name());
+  }
+}
+
+TEST(ConversionCharTest, Names) {
+  struct Expectation {
+    ConversionChar::Id id;
+    char name;
+  };
+  // clang-format off
+  const Expectation kExpect[] = {
+#define X(c) {ConversionChar::c, #c[0]}
+    X(c), X(C), X(s), X(S),                          // text
+    X(d), X(i), X(o), X(u), X(x), X(X),              // int
+    X(f), X(F), X(e), X(E), X(g), X(G), X(a), X(A),  // float
+    X(n), X(p),                                      // misc
+#undef X
+    {ConversionChar::none, '\0'},
+  };
+  // clang-format on
+  EXPECT_EQ(ABSL_ARRAYSIZE(kExpect), ConversionChar::kNumValues);
+  for (auto e : kExpect) {
+    SCOPED_TRACE(e.name);
+    ConversionChar v = ConversionChar::FromId(e.id);
+    EXPECT_EQ(e.id, v.id());
+    EXPECT_EQ(e.name, v.Char());
+  }
+}
+
+class ConsumeUnboundConversionTest : public ::testing::Test {
+ public:
+  typedef UnboundConversion Props;
+  string_view Consume(string_view* src) {
+    int next = 0;
+    const char* prev_begin = src->begin();
+    o = UnboundConversion();  // refresh
+    ConsumeUnboundConversion(src, &o, &next);
+    return {prev_begin, static_cast<size_t>(src->begin() - prev_begin)};
+  }
+
+  bool Run(const char *fmt, bool force_positional = false) {
+    string_view src = fmt;
+    int next = force_positional ? -1 : 0;
+    o = UnboundConversion();  // refresh
+    return ConsumeUnboundConversion(&src, &o, &next) && src.empty();
+  }
+  UnboundConversion o;
+};
+
+TEST_F(ConsumeUnboundConversionTest, ConsumeSpecification) {
+  struct Expectation {
+    int line;
+    const char *src;
+    const char *out;
+    const char *src_post;
+  };
+  const Expectation kExpect[] = {
+    {__LINE__, "",     "",     ""  },
+    {__LINE__, "b",    "",     "b" },  // 'b' is invalid
+    {__LINE__, "ba",   "",     "ba"},  // 'b' is invalid
+    {__LINE__, "l",    "",     "l" },  // just length mod isn't okay
+    {__LINE__, "d",    "d",    ""  },  // basic
+    {__LINE__, "d ",   "d",    " " },  // leave suffix
+    {__LINE__, "dd",   "d",    "d" },  // don't be greedy
+    {__LINE__, "d9",   "d",    "9" },  // leave non-space suffix
+    {__LINE__, "dzz",  "d",    "zz"},  // length mod as suffix
+    {__LINE__, "1$*2$d", "1$*2$d", ""  },  // arg indexing and * allowed.
+    {__LINE__, "0-14.3hhd", "0-14.3hhd", ""},  // precision, width
+    {__LINE__, " 0-+#14.3hhd", " 0-+#14.3hhd", ""},  // flags
+  };
+  for (const auto& e : kExpect) {
+    SCOPED_TRACE(e.line);
+    string_view src = e.src;
+    EXPECT_EQ(e.src, src);
+    string_view out = Consume(&src);
+    EXPECT_EQ(e.out, out);
+    EXPECT_EQ(e.src_post, src);
+  }
+}
+
+TEST_F(ConsumeUnboundConversionTest, BasicConversion) {
+  EXPECT_FALSE(Run(""));
+  EXPECT_FALSE(Run("z"));
+
+  EXPECT_FALSE(Run("dd"));  // no excess allowed
+
+  EXPECT_TRUE(Run("d"));
+  EXPECT_EQ('d', o.conv.Char());
+  EXPECT_FALSE(o.width.is_from_arg());
+  EXPECT_LT(o.width.value(), 0);
+  EXPECT_FALSE(o.precision.is_from_arg());
+  EXPECT_LT(o.precision.value(), 0);
+  EXPECT_EQ(1, o.arg_position);
+  EXPECT_EQ(LengthMod::none, o.length_mod.id());
+}
+
+TEST_F(ConsumeUnboundConversionTest, ArgPosition) {
+  EXPECT_TRUE(Run("d"));
+  EXPECT_EQ(1, o.arg_position);
+  EXPECT_TRUE(Run("3$d"));
+  EXPECT_EQ(3, o.arg_position);
+  EXPECT_TRUE(Run("1$d"));
+  EXPECT_EQ(1, o.arg_position);
+  EXPECT_TRUE(Run("1$d", true));
+  EXPECT_EQ(1, o.arg_position);
+  EXPECT_TRUE(Run("123$d"));
+  EXPECT_EQ(123, o.arg_position);
+  EXPECT_TRUE(Run("123$d", true));
+  EXPECT_EQ(123, o.arg_position);
+  EXPECT_TRUE(Run("10$d"));
+  EXPECT_EQ(10, o.arg_position);
+  EXPECT_TRUE(Run("10$d", true));
+  EXPECT_EQ(10, o.arg_position);
+
+  // Position can't be zero.
+  EXPECT_FALSE(Run("0$d"));
+  EXPECT_FALSE(Run("0$d", true));
+  EXPECT_FALSE(Run("1$*0$d"));
+  EXPECT_FALSE(Run("1$.*0$d"));
+
+  // Position can't start with a zero digit at all. That is not a 'decimal'.
+  EXPECT_FALSE(Run("01$p"));
+  EXPECT_FALSE(Run("01$p", true));
+  EXPECT_FALSE(Run("1$*01$p"));
+  EXPECT_FALSE(Run("1$.*01$p"));
+}
+
+TEST_F(ConsumeUnboundConversionTest, WidthAndPrecision) {
+  EXPECT_TRUE(Run("14d"));
+  EXPECT_EQ('d', o.conv.Char());
+  EXPECT_FALSE(o.width.is_from_arg());
+  EXPECT_EQ(14, o.width.value());
+  EXPECT_FALSE(o.precision.is_from_arg());
+  EXPECT_LT(o.precision.value(), 0);
+
+  EXPECT_TRUE(Run("14.d"));
+  EXPECT_FALSE(o.width.is_from_arg());
+  EXPECT_FALSE(o.precision.is_from_arg());
+  EXPECT_EQ(14, o.width.value());
+  EXPECT_EQ(0, o.precision.value());
+
+  EXPECT_TRUE(Run(".d"));
+  EXPECT_FALSE(o.width.is_from_arg());
+  EXPECT_LT(o.width.value(), 0);
+  EXPECT_FALSE(o.precision.is_from_arg());
+  EXPECT_EQ(0, o.precision.value());
+
+  EXPECT_TRUE(Run(".5d"));
+  EXPECT_FALSE(o.width.is_from_arg());
+  EXPECT_LT(o.width.value(), 0);
+  EXPECT_FALSE(o.precision.is_from_arg());
+  EXPECT_EQ(5, o.precision.value());
+
+  EXPECT_TRUE(Run(".0d"));
+  EXPECT_FALSE(o.width.is_from_arg());
+  EXPECT_LT(o.width.value(), 0);
+  EXPECT_FALSE(o.precision.is_from_arg());
+  EXPECT_EQ(0, o.precision.value());
+
+  EXPECT_TRUE(Run("14.5d"));
+  EXPECT_FALSE(o.width.is_from_arg());
+  EXPECT_FALSE(o.precision.is_from_arg());
+  EXPECT_EQ(14, o.width.value());
+  EXPECT_EQ(5, o.precision.value());
+
+  EXPECT_TRUE(Run("*.*d"));
+  EXPECT_TRUE(o.width.is_from_arg());
+  EXPECT_EQ(1, o.width.get_from_arg());
+  EXPECT_TRUE(o.precision.is_from_arg());
+  EXPECT_EQ(2, o.precision.get_from_arg());
+  EXPECT_EQ(3, o.arg_position);
+
+  EXPECT_TRUE(Run("*d"));
+  EXPECT_TRUE(o.width.is_from_arg());
+  EXPECT_EQ(1, o.width.get_from_arg());
+  EXPECT_FALSE(o.precision.is_from_arg());
+  EXPECT_LT(o.precision.value(), 0);
+  EXPECT_EQ(2, o.arg_position);
+
+  EXPECT_TRUE(Run(".*d"));
+  EXPECT_FALSE(o.width.is_from_arg());
+  EXPECT_LT(o.width.value(), 0);
+  EXPECT_TRUE(o.precision.is_from_arg());
+  EXPECT_EQ(1, o.precision.get_from_arg());
+  EXPECT_EQ(2, o.arg_position);
+
+  // mixed implicit and explicit: didn't specify arg position.
+  EXPECT_FALSE(Run("*23$.*34$d"));
+
+  EXPECT_TRUE(Run("12$*23$.*34$d"));
+  EXPECT_EQ(12, o.arg_position);
+  EXPECT_TRUE(o.width.is_from_arg());
+  EXPECT_EQ(23, o.width.get_from_arg());
+  EXPECT_TRUE(o.precision.is_from_arg());
+  EXPECT_EQ(34, o.precision.get_from_arg());
+
+  EXPECT_TRUE(Run("2$*5$.*9$d"));
+  EXPECT_EQ(2, o.arg_position);
+  EXPECT_TRUE(o.width.is_from_arg());
+  EXPECT_EQ(5, o.width.get_from_arg());
+  EXPECT_TRUE(o.precision.is_from_arg());
+  EXPECT_EQ(9, o.precision.get_from_arg());
+
+  EXPECT_FALSE(Run(".*0$d")) << "no arg 0";
+}
+
+TEST_F(ConsumeUnboundConversionTest, Flags) {
+  static const char kAllFlags[] = "-+ #0";
+  static const int kNumFlags = ABSL_ARRAYSIZE(kAllFlags) - 1;
+  for (int rev = 0; rev < 2; ++rev) {
+    for (int i = 0; i < 1 << kNumFlags; ++i) {
+      std::string fmt;
+      for (int k = 0; k < kNumFlags; ++k)
+        if ((i >> k) & 1) fmt += kAllFlags[k];
+      // flag order shouldn't matter
+      if (rev == 1) { std::reverse(fmt.begin(), fmt.end()); }
+      fmt += 'd';
+      SCOPED_TRACE(fmt);
+      EXPECT_TRUE(Run(fmt.c_str()));
+      EXPECT_EQ(fmt.find('-') == std::string::npos, !o.flags.left);
+      EXPECT_EQ(fmt.find('+') == std::string::npos, !o.flags.show_pos);
+      EXPECT_EQ(fmt.find(' ') == std::string::npos, !o.flags.sign_col);
+      EXPECT_EQ(fmt.find('#') == std::string::npos, !o.flags.alt);
+      EXPECT_EQ(fmt.find('0') == std::string::npos, !o.flags.zero);
+    }
+  }
+}
+
+TEST_F(ConsumeUnboundConversionTest, BasicFlag) {
+  // Flag is on
+  for (const char* fmt : {"d", "llx", "G", "1$X"}) {
+    SCOPED_TRACE(fmt);
+    EXPECT_TRUE(Run(fmt));
+    EXPECT_TRUE(o.flags.basic);
+  }
+
+  // Flag is off
+  for (const char* fmt : {"3d", ".llx", "-G", "1$#X"}) {
+    SCOPED_TRACE(fmt);
+    EXPECT_TRUE(Run(fmt));
+    EXPECT_FALSE(o.flags.basic);
+  }
+}
+
+struct SummarizeConsumer {
+  std::string* out;
+  explicit SummarizeConsumer(std::string* out) : out(out) {}
+
+  bool Append(string_view s) {
+    *out += "[" + std::string(s) + "]";
+    return true;
+  }
+
+  bool ConvertOne(const UnboundConversion& conv, string_view s) {
+    *out += "{";
+    *out += std::string(s);
+    *out += ":";
+    *out += std::to_string(conv.arg_position) + "$";
+    if (conv.width.is_from_arg()) {
+      *out += std::to_string(conv.width.get_from_arg()) + "$*";
+    }
+    if (conv.precision.is_from_arg()) {
+      *out += "." + std::to_string(conv.precision.get_from_arg()) + "$*";
+    }
+    *out += conv.conv.Char();
+    *out += "}";
+    return true;
+  }
+};
+
+std::string SummarizeParsedFormat(const ParsedFormatBase& pc) {
+  std::string out;
+  if (!pc.ProcessFormat(SummarizeConsumer(&out))) out += "!";
+  return out;
+}
+
+class ParsedFormatTest : public testing::Test {};
+
+TEST_F(ParsedFormatTest, ValueSemantics) {
+  ParsedFormatBase p1({}, true, {});  // empty format
+  EXPECT_EQ("", SummarizeParsedFormat(p1));
+
+  ParsedFormatBase p2 = p1;  // copy construct (empty)
+  EXPECT_EQ(SummarizeParsedFormat(p1), SummarizeParsedFormat(p2));
+
+  p1 = ParsedFormatBase("hello%s", true, {Conv::s});  // move assign
+  EXPECT_EQ("[hello]{s:1$s}", SummarizeParsedFormat(p1));
+
+  ParsedFormatBase p3 = p1;  // copy construct (nonempty)
+  EXPECT_EQ(SummarizeParsedFormat(p1), SummarizeParsedFormat(p3));
+
+  using std::swap;
+  swap(p1, p2);
+  EXPECT_EQ("", SummarizeParsedFormat(p1));
+  EXPECT_EQ("[hello]{s:1$s}", SummarizeParsedFormat(p2));
+  swap(p1, p2);  // undo
+
+  p2 = p1;  // copy assign
+  EXPECT_EQ(SummarizeParsedFormat(p1), SummarizeParsedFormat(p2));
+}
+
+struct ExpectParse {
+  const char* in;
+  std::initializer_list<Conv> conv_set;
+  const char* out;
+};
+
+TEST_F(ParsedFormatTest, Parsing) {
+  // Parse should be equivalent to that obtained by ConversionParseIterator.
+  // No need to retest the parsing edge cases here.
+  const ExpectParse kExpect[] = {
+      {"", {}, ""},
+      {"ab", {}, "[ab]"},
+      {"a%d", {Conv::d}, "[a]{d:1$d}"},
+      {"a%+d", {Conv::d}, "[a]{+d:1$d}"},
+      {"a% d", {Conv::d}, "[a]{ d:1$d}"},
+      {"a%b %d", {}, "[a]!"},  // stop after error
+  };
+  for (const auto& e : kExpect) {
+    SCOPED_TRACE(e.in);
+    EXPECT_EQ(e.out,
+              SummarizeParsedFormat(ParsedFormatBase(e.in, false, e.conv_set)));
+  }
+}
+
+TEST_F(ParsedFormatTest, ParsingFlagOrder) {
+  const ExpectParse kExpect[] = {
+      {"a%+ 0d", {Conv::d}, "[a]{+ 0d:1$d}"},
+      {"a%+0 d", {Conv::d}, "[a]{+0 d:1$d}"},
+      {"a%0+ d", {Conv::d}, "[a]{0+ d:1$d}"},
+      {"a% +0d", {Conv::d}, "[a]{ +0d:1$d}"},
+      {"a%0 +d", {Conv::d}, "[a]{0 +d:1$d}"},
+      {"a% 0+d", {Conv::d}, "[a]{ 0+d:1$d}"},
+      {"a%+   0+d", {Conv::d}, "[a]{+   0+d:1$d}"},
+  };
+  for (const auto& e : kExpect) {
+    SCOPED_TRACE(e.in);
+    EXPECT_EQ(e.out,
+              SummarizeParsedFormat(ParsedFormatBase(e.in, false, e.conv_set)));
+  }
+}
+
+}  // namespace
+}  // namespace str_format_internal
+}  // namespace absl
diff --git a/absl/strings/str_format.h b/absl/strings/str_format.h
new file mode 100644
index 000000000000..98e0fef4906b
--- /dev/null
+++ b/absl/strings/str_format.h
@@ -0,0 +1,512 @@
+//
+// Copyright 2018 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// -----------------------------------------------------------------------------
+// File: str_format.h
+// -----------------------------------------------------------------------------
+//
+// The `str_format` library is a typesafe replacement for the family of
+// `printf()` std::string formatting routines within the `<cstdio>` standard library
+// header. Like the `printf` family, the `str_format` uses a "format string" to
+// perform argument substitutions based on types.
+//
+// Example:
+//
+//   std::string s = absl::StrFormat("%s %s You have $%d!", "Hello", name, dollars);
+//
+// The library consists of the following basic utilities:
+//
+//   * `absl::StrFormat()`, a type-safe replacement for `std::sprintf()`, to
+//     write a format std::string to a `string` value.
+//   * `absl::StrAppendFormat()` to append a format std::string to a `string`
+//   * `absl::StreamFormat()` to more efficiently write a format std::string to a
+//     stream, such as`std::cout`.
+//   * `absl::PrintF()`, `absl::FPrintF()` and `absl::SNPrintF()` as
+//     replacements for `std::printf()`, `std::fprintf()` and `std::snprintf()`.
+//
+//     Note: a version of `std::sprintf()` is not supported as it is
+//     generally unsafe due to buffer overflows.
+//
+// Additionally, you can provide a format std::string (and its associated arguments)
+// using one of the following abstractions:
+//
+//   * A `FormatSpec` class template fully encapsulates a format std::string and its
+//     type arguments and is usually provided to `str_format` functions as a
+//     variadic argument of type `FormatSpec<Arg...>`. The `FormatSpec<Args...>`
+//     template is evaluated at compile-time, providing type safety.
+//   * A `ParsedFormat` instance, which encapsulates a specific, pre-compiled
+//     format std::string for a specific set of type(s), and which can be passed
+//     between API boundaries. (The `FormatSpec` type should not be used
+//     directly.)
+//
+// The `str_format` library provides the ability to output its format strings to
+// arbitrary sink types:
+//
+//   * A generic `Format()` function to write outputs to arbitrary sink types,
+//     which must implement a `RawSinkFormat` interface. (See
+//     `str_format_sink.h` for more information.)
+//
+//   * A `FormatUntyped()` function that is similar to `Format()` except it is
+//     loosely typed. `FormatUntyped()` is not a template and does not perform
+//     any compile-time checking of the format std::string; instead, it returns a
+//     boolean from a runtime check.
+//
+// In addition, the `str_format` library provides extension points for
+// augmenting formatting to new types. These extensions are fully documented
+// within the `str_format_extension.h` header file.
+#ifndef ABSL_STRINGS_STR_FORMAT_H_
+#define ABSL_STRINGS_STR_FORMAT_H_
+
+#include <cstdio>
+#include <string>
+
+#include "absl/strings/internal/str_format/arg.h"  // IWYU pragma: export
+#include "absl/strings/internal/str_format/bind.h"  // IWYU pragma: export
+#include "absl/strings/internal/str_format/checker.h"  // IWYU pragma: export
+#include "absl/strings/internal/str_format/extension.h"  // IWYU pragma: export
+#include "absl/strings/internal/str_format/parser.h"  // IWYU pragma: export
+
+namespace absl {
+
+// UntypedFormatSpec
+//
+// A type-erased class that can be used directly within untyped API entry
+// points. An `UntypedFormatSpec` is specifically used as an argument to
+// `FormatUntyped()`.
+//
+// Example:
+//
+//   absl::UntypedFormatSpec format("%d");
+//   std::string out;
+//   CHECK(absl::FormatUntyped(&out, format, {absl::FormatArg(1)}));
+class UntypedFormatSpec {
+ public:
+  UntypedFormatSpec() = delete;
+  UntypedFormatSpec(const UntypedFormatSpec&) = delete;
+  UntypedFormatSpec& operator=(const UntypedFormatSpec&) = delete;
+
+  explicit UntypedFormatSpec(string_view s) : spec_(s) {}
+
+ protected:
+  explicit UntypedFormatSpec(const str_format_internal::ParsedFormatBase* pc)
+      : spec_(pc) {}
+
+ private:
+  friend str_format_internal::UntypedFormatSpecImpl;
+  str_format_internal::UntypedFormatSpecImpl spec_;
+};
+
+// FormatStreamed()
+//
+// Takes a streamable argument and returns an object that can print it
+// with '%s'. Allows printing of types that have an `operator<<` but no
+// intrinsic type support within `StrFormat()` itself.
+//
+// Example:
+//
+//   absl::StrFormat("%s", absl::FormatStreamed(obj));
+template <typename T>
+str_format_internal::StreamedWrapper<T> FormatStreamed(const T& v) {
+  return str_format_internal::StreamedWrapper<T>(v);
+}
+
+// FormatCountCapture
+//
+// This class provides a way to safely wrap `StrFormat()` captures of `%n`
+// conversions, which denote the number of characters written by a formatting
+// operation to this point, into an integer value.
+//
+// This wrapper is designed to allow safe usage of `%n` within `StrFormat(); in
+// the `printf()` family of functions, `%n` is not safe to use, as the `int *`
+// buffer can be used to capture arbitrary data.
+//
+// Example:
+//
+//   int n = 0;
+//   std::string s = absl::StrFormat("%s%d%n", "hello", 123,
+//                   absl::FormatCountCapture(&n));
+//   EXPECT_EQ(8, n);
+class FormatCountCapture {
+ public:
+  explicit FormatCountCapture(int* p) : p_(p) {}
+
+ private:
+  // FormatCountCaptureHelper is used to define FormatConvertImpl() for this
+  // class.
+  friend struct str_format_internal::FormatCountCaptureHelper;
+  // Unused() is here because of the false positive from -Wunused-private-field
+  // p_ is used in the templated function of the friend FormatCountCaptureHelper
+  // class.
+  int* Unused() { return p_; }
+  int* p_;
+};
+
+// FormatSpec
+//
+// The `FormatSpec` type defines the makeup of a format std::string within the
+// `str_format` library. You should not need to use or manipulate this type
+// directly. A `FormatSpec` is a variadic class template that is evaluated at
+// compile-time, according to the format std::string and arguments that are passed
+// to it.
+//
+// For a `FormatSpec` to be valid at compile-time, it must be provided as
+// either:
+//
+// * A `constexpr` literal or `absl::string_view`, which is how it most often
+//   used.
+// * A `ParsedFormat` instantiation, which ensures the format std::string is
+//   valid before use. (See below.)
+//
+// Example:
+//
+//   // Provided as a std::string literal.
+//   absl::StrFormat("Welcome to %s, Number %d!", "The Village", 6);
+//
+//   // Provided as a constexpr absl::string_view.
+//   constexpr absl::string_view formatString = "Welcome to %s, Number %d!";
+//   absl::StrFormat(formatString, "The Village", 6);
+//
+//   // Provided as a pre-compiled ParsedFormat object.
+//   // Note that this example is useful only for illustration purposes.
+//   absl::ParsedFormat<'s', 'd'> formatString("Welcome to %s, Number %d!");
+//   absl::StrFormat(formatString, "TheVillage", 6);
+//
+// A format std::string generally follows the POSIX syntax as used within the POSIX
+// `printf` specification.
+//
+// (See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/printf.html.)
+//
+// In specific, the `FormatSpec` supports the following type specifiers:
+//   * `c` for characters
+//   * `s` for strings
+//   * `d` or `i` for integers
+//   * `o` for unsigned integer conversions into octal
+//   * `x` or `X` for unsigned integer conversions into hex
+//   * `u` for unsigned integers
+//   * `f` or `F` for floating point values into decimal notation
+//   * `e` or `E` for floating point values into exponential notation
+//   * `a` or `A` for floating point values into hex exponential notation
+//   * `g` or `G` for floating point values into decimal or exponential
+//     notation based on their precision
+//   * `p` for pointer address values
+//   * `n` for the special case of writing out the number of characters
+//     written to this point. The resulting value must be captured within an
+//     `absl::FormatCountCapture` type.
+//
+// NOTE: `o`, `x\X` and `u` will convert signed values to their unsigned
+// counterpart before formatting.
+//
+// Examples:
+//     "%c", 'a'                -> "a"
+//     "%c", 32                 -> " "
+//     "%s", "C"                -> "C"
+//     "%s", std::string("C++") -> "C++"
+//     "%d", -10                -> "-10"
+//     "%o", 10                 -> "12"
+//     "%x", 16                 -> "10"
+//     "%f", 123456789          -> "123456789.000000"
+//     "%e", .01                -> "1.00000e-2"
+//     "%a", -3.0               -> "-0x1.8p+1"
+//     "%g", .01                -> "1e-2"
+//     "%p", *int               -> "0x7ffdeb6ad2a4"
+//
+//     int n = 0;
+//     std::string s = absl::StrFormat(
+//         "%s%d%n", "hello", 123, absl::FormatCountCapture(&n));
+//     EXPECT_EQ(8, n);
+//
+// The `FormatSpec` intrinsically supports all of these fundamental C++ types:
+//
+// *   Characters: `char`, `signed char`, `unsigned char`
+// *   Integers: `int`, `short`, `unsigned short`, `unsigned`, `long`,
+//         `unsigned long`, `long long`, `unsigned long long`
+// *   Floating-point: `float`, `double`, `long double`
+//
+// However, in the `str_format` library, a format conversion specifies a broader
+// C++ conceptual category instead of an exact type. For example, `%s` binds to
+// any std::string-like argument, so `std::string`, `absl::string_view`, and
+// `const char*` are all accepted. Likewise, `%d` accepts any integer-like
+// argument, etc.
+
+template <typename... Args>
+using FormatSpec =
+    typename str_format_internal::FormatSpecDeductionBarrier<Args...>::type;
+
+using absl::str_format_internal::ExtendedParsedFormat;
+
+// ParsedFormat
+//
+// A `ParsedFormat` is a class template representing a preparsed `FormatSpec`,
+// with template arguments specifying the conversion characters used within the
+// format std::string. Such characters must be valid format type specifiers, and
+// these type specifiers are checked at compile-time.
+//
+// Instances of `ParsedFormat` can be created, copied, and reused to speed up
+// formatting loops. A `ParsedFormat` may either be constructed statically, or
+// dynamically through its `New()` factory function, which only constructs a
+// runtime object if the format is valid at that time.
+//
+// Example:
+//
+//   // Verified at compile time.
+//   absl::ParsedFormat<'s', 'd'> formatString("Welcome to %s, Number %d!");
+//   absl::StrFormat(formatString, "TheVillage", 6);
+//
+//   // Verified at runtime.
+//   auto format_runtime = absl::ParsedFormat<'d'>::New(format_string);
+//   if (format_runtime) {
+//     value = absl::StrFormat(*format_runtime, i);
+//   } else {
+//     ... error case ...
+//   }
+template <char... Conv>
+using ParsedFormat = str_format_internal::ExtendedParsedFormat<
+    str_format_internal::ConversionCharToConv(Conv)...>;
+
+// StrFormat()
+//
+// Returns a `string` given a `printf()`-style format std::string and zero or more
+// additional arguments. Use it as you would `sprintf()`. `StrFormat()` is the
+// primary formatting function within the `str_format` library, and should be
+// used in most cases where you need type-safe conversion of types into
+// formatted strings.
+//
+// The format std::string generally consists of ordinary character data along with
+// one or more format conversion specifiers (denoted by the `%` character).
+// Ordinary character data is returned unchanged into the result std::string, while
+// each conversion specification performs a type substitution from
+// `StrFormat()`'s other arguments. See the comments for `FormatSpec` for full
+// information on the makeup of this format std::string.
+//
+// Example:
+//
+//   std::string s = absl::StrFormat(
+//       "Welcome to %s, Number %d!", "The Village", 6);
+//   EXPECT_EQ("Welcome to The Village, Number 6!", s);
+//
+// Returns an empty std::string in case of error.
+template <typename... Args>
+ABSL_MUST_USE_RESULT std::string StrFormat(const FormatSpec<Args...>& format,
+                                      const Args&... args) {
+  return str_format_internal::FormatPack(
+      str_format_internal::UntypedFormatSpecImpl::Extract(format),
+      {str_format_internal::FormatArgImpl(args)...});
+}
+
+// StrAppendFormat()
+//
+// Appends to a `dst` std::string given a format std::string, and zero or more additional
+// arguments, returning `*dst` as a convenience for chaining purposes. Appends
+// nothing in case of error (but possibly alters its capacity).
+//
+// Example:
+//
+//   std::string orig("For example PI is approximately ");
+//   std::cout << StrAppendFormat(&orig, "%12.6f", 3.14);
+template <typename... Args>
+std::string& StrAppendFormat(std::string* dst, const FormatSpec<Args...>& format,
+                        const Args&... args) {
+  return str_format_internal::AppendPack(
+      dst, str_format_internal::UntypedFormatSpecImpl::Extract(format),
+      {str_format_internal::FormatArgImpl(args)...});
+}
+
+// StreamFormat()
+//
+// Writes to an output stream given a format std::string and zero or more arguments,
+// generally in a manner that is more efficient than streaming the result of
+// `absl:: StrFormat()`. The returned object must be streamed before the full
+// expression ends.
+//
+// Example:
+//
+//   std::cout << StreamFormat("%12.6f", 3.14);
+template <typename... Args>
+ABSL_MUST_USE_RESULT str_format_internal::Streamable StreamFormat(
+    const FormatSpec<Args...>& format, const Args&... args) {
+  return str_format_internal::Streamable(
+      str_format_internal::UntypedFormatSpecImpl::Extract(format),
+      {str_format_internal::FormatArgImpl(args)...});
+}
+
+// PrintF()
+//
+// Writes to stdout given a format std::string and zero or more arguments. This
+// function is functionally equivalent to `std::print()` (and type-safe); prefer
+// `absl::PrintF()` over `std::printf()`.
+//
+// Example:
+//
+//   std::string_view s = "Ulaanbaatar";
+//   absl::PrintF("The capital of Mongolia is: %s \n", s);
+//
+//   Outputs: "The capital of Mongolia is Ulaanbaatar"
+//
+template <typename... Args>
+int PrintF(const FormatSpec<Args...>& format, const Args&... args) {
+  return str_format_internal::FprintF(
+      stdout, str_format_internal::UntypedFormatSpecImpl::Extract(format),
+      {str_format_internal::FormatArgImpl(args)...});
+}
+
+// FPrintF()
+//
+// Writes to a file given a format std::string and zero or more arguments. This
+// function is functionally equivalent to `std::fprint()` (and type-safe);
+// prefer `absl::FPrintF()` over `std::fprintf()`.
+//
+// Example:
+//
+//   std::string_view s = "Ulaanbaatar";
+//   absl::FPrintF("The capital of Mongolia is: %s \n", s);
+//
+//   Outputs: "The capital of Mongolia is Ulaanbaatar"
+//
+template <typename... Args>
+int FPrintF(std::FILE* output, const FormatSpec<Args...>& format,
+            const Args&... args) {
+  return str_format_internal::FprintF(
+      output, str_format_internal::UntypedFormatSpecImpl::Extract(format),
+      {str_format_internal::FormatArgImpl(args)...});
+}
+
+// SNPrintF()
+//
+// Writes to a sized buffer given a format std::string and zero or more arguments.
+// This function is functionally equivalent to `std::snprint()` (and type-safe);
+// prefer `absl::SNPrintF()` over `std::snprintf()`.
+//
+// Example:
+//
+//   std::string_view s = "Ulaanbaatar";
+//   absl::FPrintF("The capital of Mongolia is: %s \n", s);
+//
+//   Outputs: "The capital of Mongolia is Ulaanbaatar"
+//
+template <typename... Args>
+int SNPrintF(char* output, std::size_t size, const FormatSpec<Args...>& format,
+             const Args&... args) {
+  return str_format_internal::SnprintF(
+      output, size, str_format_internal::UntypedFormatSpecImpl::Extract(format),
+      {str_format_internal::FormatArgImpl(args)...});
+}
+
+// -----------------------------------------------------------------------------
+// Custom Output Formatting Functions
+// -----------------------------------------------------------------------------
+
+// FormatRawSink
+//
+// FormatRawSink is a type erased wrapper around arbitrary sink objects
+// specifically used as an argument to `Format()`.
+// FormatRawSink does not own the passed sink object. The passed object must
+// outlive the FormatRawSink.
+class FormatRawSink {
+ public:
+  // Implicitly convert from any type that provides the hook function as
+  // described above.
+  template <typename T,
+            typename = typename std::enable_if<std::is_constructible<
+                str_format_internal::FormatRawSinkImpl, T*>::value>::type>
+  FormatRawSink(T* raw)  // NOLINT
+      : sink_(raw) {}
+
+ private:
+  friend str_format_internal::FormatRawSinkImpl;
+  str_format_internal::FormatRawSinkImpl sink_;
+};
+
+// Format()
+//
+// Writes a formatted std::string to an arbitrary sink object (implementing the
+// `absl::FormatRawSink` interface), using a format std::string and zero or more
+// additional arguments.
+//
+// By default, `string` and `std::ostream` are supported as destination objects.
+//
+// `absl::Format()` is a generic version of `absl::StrFormat(), for custom
+// sinks. The format std::string, like format strings for `StrFormat()`, is checked
+// at compile-time.
+//
+// On failure, this function returns `false` and the state of the sink is
+// unspecified.
+template <typename... Args>
+bool Format(FormatRawSink raw_sink, const FormatSpec<Args...>& format,
+            const Args&... args) {
+  return str_format_internal::FormatUntyped(
+      str_format_internal::FormatRawSinkImpl::Extract(raw_sink),
+      str_format_internal::UntypedFormatSpecImpl::Extract(format),
+      {str_format_internal::FormatArgImpl(args)...});
+}
+
+// FormatArg
+//
+// A type-erased handle to a format argument specifically used as an argument to
+// `FormatUntyped()`. You may construct `FormatArg` by passing
+// reference-to-const of any printable type. `FormatArg` is both copyable and
+// assignable. The source data must outlive the `FormatArg` instance. See
+// example below.
+//
+using FormatArg = str_format_internal::FormatArgImpl;
+
+// FormatUntyped()
+//
+// Writes a formatted std::string to an arbitrary sink object (implementing the
+// `absl::FormatRawSink` interface), using an `UntypedFormatSpec` and zero or
+// more additional arguments.
+//
+// This function acts as the most generic formatting function in the
+// `str_format` library. The caller provides a raw sink, an unchecked format
+// std::string, and (usually) a runtime specified list of arguments; no compile-time
+// checking of formatting is performed within this function. As a result, a
+// caller should check the return value to verify that no error occurred.
+// On failure, this function returns `false` and the state of the sink is
+// unspecified.
+//
+// The arguments are provided in an `absl::Span<const absl::FormatArg>`.
+// Each `absl::FormatArg` object binds to a single argument and keeps a
+// reference to it. The values used to create the `FormatArg` objects must
+// outlive this function call. (See `str_format_arg.h` for information on
+// the `FormatArg` class.)_
+//
+// Example:
+//
+//   std::optional<std::string> FormatDynamic(const std::string& in_format,
+//                                       const vector<std::string>& in_args) {
+//     std::string out;
+//     std::vector<absl::FormatArg> args;
+//     for (const auto& v : in_args) {
+//       // It is important that 'v' is a reference to the objects in in_args.
+//       // The values we pass to FormatArg must outlive the call to
+//       // FormatUntyped.
+//       args.emplace_back(v);
+//     }
+//     absl::UntypedFormatSpec format(in_format);
+//     if (!absl::FormatUntyped(&out, format, args)) {
+//       return std::nullopt;
+//     }
+//     return std::move(out);
+//   }
+//
+ABSL_MUST_USE_RESULT inline bool FormatUntyped(
+    FormatRawSink raw_sink, const UntypedFormatSpec& format,
+    absl::Span<const FormatArg> args) {
+  return str_format_internal::FormatUntyped(
+      str_format_internal::FormatRawSinkImpl::Extract(raw_sink),
+      str_format_internal::UntypedFormatSpecImpl::Extract(format), args);
+}
+
+}  // namespace absl
+#endif  // ABSL_STRINGS_STR_FORMAT_H_
diff --git a/absl/strings/str_format_test.cc b/absl/strings/str_format_test.cc
new file mode 100644
index 000000000000..fe742bf99533
--- /dev/null
+++ b/absl/strings/str_format_test.cc
@@ -0,0 +1,603 @@
+
+#include <cstdarg>
+#include <cstdint>
+#include <cstdio>
+#include <string>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "absl/strings/str_format.h"
+#include "absl/strings/string_view.h"
+
+namespace absl {
+namespace {
+using str_format_internal::FormatArgImpl;
+
+class FormatEntryPointTest : public ::testing::Test { };
+
+TEST_F(FormatEntryPointTest, Format) {
+  std::string sink;
+  EXPECT_TRUE(Format(&sink, "A format %d", 123));
+  EXPECT_EQ("A format 123", sink);
+  sink.clear();
+
+  ParsedFormat<'d'> pc("A format %d");
+  EXPECT_TRUE(Format(&sink, pc, 123));
+  EXPECT_EQ("A format 123", sink);
+}
+TEST_F(FormatEntryPointTest, UntypedFormat) {
+  constexpr const char* formats[] = {
+    "",
+    "a",
+    "%80d",
+#if !defined(_MSC_VER) && !defined(__ANDROID__)
+    // MSVC and Android don't support positional syntax.
+    "complicated multipart %% %1$d format %1$0999d",
+#endif  // _MSC_VER
+  };
+  for (const char* fmt : formats) {
+    std::string actual;
+    int i = 123;
+    FormatArgImpl arg_123(i);
+    absl::Span<const FormatArgImpl> args(&arg_123, 1);
+    UntypedFormatSpec format(fmt);
+
+    EXPECT_TRUE(FormatUntyped(&actual, format, args));
+    char buf[4096]{};
+    snprintf(buf, sizeof(buf), fmt, 123);
+    EXPECT_EQ(
+        str_format_internal::FormatPack(
+            str_format_internal::UntypedFormatSpecImpl::Extract(format), args),
+        buf);
+    EXPECT_EQ(actual, buf);
+  }
+  // The internal version works with a preparsed format.
+  ParsedFormat<'d'> pc("A format %d");
+  int i = 345;
+  FormatArg arg(i);
+  std::string out;
+  EXPECT_TRUE(str_format_internal::FormatUntyped(
+      &out, str_format_internal::UntypedFormatSpecImpl(&pc), {&arg, 1}));
+  EXPECT_EQ("A format 345", out);
+}
+
+TEST_F(FormatEntryPointTest, StringFormat) {
+  EXPECT_EQ("123", StrFormat("%d", 123));
+  constexpr absl::string_view view("=%d=", 4);
+  EXPECT_EQ("=123=", StrFormat(view, 123));
+}
+
+TEST_F(FormatEntryPointTest, AppendFormat) {
+  std::string s;
+  std::string& r = StrAppendFormat(&s, "%d", 123);
+  EXPECT_EQ(&s, &r);  // should be same object
+  EXPECT_EQ("123", r);
+}
+
+TEST_F(FormatEntryPointTest, AppendFormatFail) {
+  std::string s = "orig";
+
+  UntypedFormatSpec format(" more %d");
+  FormatArgImpl arg("not an int");
+
+  EXPECT_EQ("orig",
+            str_format_internal::AppendPack(
+                &s, str_format_internal::UntypedFormatSpecImpl::Extract(format),
+                {&arg, 1}));
+}
+
+
+TEST_F(FormatEntryPointTest, ManyArgs) {
+  EXPECT_EQ("24", StrFormat("%24$d", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
+                            14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24));
+  EXPECT_EQ("60", StrFormat("%60$d", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
+                            14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26,
+                            27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39,
+                            40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52,
+                            53, 54, 55, 56, 57, 58, 59, 60));
+}
+
+TEST_F(FormatEntryPointTest, Preparsed) {
+  ParsedFormat<'d'> pc("%d");
+  EXPECT_EQ("123", StrFormat(pc, 123));
+  // rvalue ok?
+  EXPECT_EQ("123", StrFormat(ParsedFormat<'d'>("%d"), 123));
+  constexpr absl::string_view view("=%d=", 4);
+  EXPECT_EQ("=123=", StrFormat(ParsedFormat<'d'>(view), 123));
+}
+
+TEST_F(FormatEntryPointTest, FormatCountCapture) {
+  int n = 0;
+  EXPECT_EQ("", StrFormat("%n", FormatCountCapture(&n)));
+  EXPECT_EQ(0, n);
+  EXPECT_EQ("123", StrFormat("%d%n", 123, FormatCountCapture(&n)));
+  EXPECT_EQ(3, n);
+}
+
+TEST_F(FormatEntryPointTest, FormatCountCaptureWrongType) {
+  // Should reject int*.
+  int n = 0;
+  UntypedFormatSpec format("%d%n");
+  int i = 123, *ip = &n;
+  FormatArgImpl args[2] = {FormatArgImpl(i), FormatArgImpl(ip)};
+
+  EXPECT_EQ("", str_format_internal::FormatPack(
+                    str_format_internal::UntypedFormatSpecImpl::Extract(format),
+                    absl::MakeSpan(args)));
+}
+
+TEST_F(FormatEntryPointTest, FormatCountCaptureMultiple) {
+  int n1 = 0;
+  int n2 = 0;
+  EXPECT_EQ("    1         2",
+            StrFormat("%5d%n%10d%n", 1, FormatCountCapture(&n1), 2,
+                      FormatCountCapture(&n2)));
+  EXPECT_EQ(5, n1);
+  EXPECT_EQ(15, n2);
+}
+
+TEST_F(FormatEntryPointTest, FormatCountCaptureExample) {
+  int n;
+  std::string s;
+  StrAppendFormat(&s, "%s: %n%s\n", "(1,1)", FormatCountCapture(&n), "(1,2)");
+  StrAppendFormat(&s, "%*s%s\n", n, "", "(2,2)");
+  EXPECT_EQ(7, n);
+  EXPECT_EQ(
+      "(1,1): (1,2)\n"
+      "       (2,2)\n",
+      s);
+}
+
+TEST_F(FormatEntryPointTest, Stream) {
+  const std::string formats[] = {
+    "",
+    "a",
+    "%80d",
+#if !defined(_MSC_VER) && !defined(__ANDROID__)
+    // MSVC doesn't support positional syntax.
+    "complicated multipart %% %1$d format %1$080d",
+#endif  // _MSC_VER
+  };
+  std::string buf(4096, '\0');
+  for (const auto& fmt : formats) {
+    const auto parsed = ParsedFormat<'d'>::NewAllowIgnored(fmt);
+    std::ostringstream oss;
+    oss << StreamFormat(*parsed, 123);
+    int fmt_result = snprintf(&*buf.begin(), buf.size(), fmt.c_str(), 123);
+    ASSERT_TRUE(oss) << fmt;
+    ASSERT_TRUE(fmt_result >= 0 && static_cast<size_t>(fmt_result) < buf.size())
+        << fmt_result;
+    EXPECT_EQ(buf.c_str(), oss.str());
+  }
+}
+
+TEST_F(FormatEntryPointTest, StreamOk) {
+  std::ostringstream oss;
+  oss << StreamFormat("hello %d", 123);
+  EXPECT_EQ("hello 123", oss.str());
+  EXPECT_TRUE(oss.good());
+}
+
+TEST_F(FormatEntryPointTest, StreamFail) {
+  std::ostringstream oss;
+  UntypedFormatSpec format("hello %d");
+  FormatArgImpl arg("non-numeric");
+  oss << str_format_internal::Streamable(
+      str_format_internal::UntypedFormatSpecImpl::Extract(format), {&arg, 1});
+  EXPECT_EQ("hello ", oss.str());  // partial write
+  EXPECT_TRUE(oss.fail());
+}
+
+std::string WithSnprintf(const char* fmt, ...) {
+  std::string buf;
+  buf.resize(128);
+  va_list va;
+  va_start(va, fmt);
+  int r = vsnprintf(&*buf.begin(), buf.size(), fmt, va);
+  va_end(va);
+  EXPECT_GE(r, 0);
+  EXPECT_LT(r, buf.size());
+  buf.resize(r);
+  return buf;
+}
+
+TEST_F(FormatEntryPointTest, FloatPrecisionArg) {
+  // Test that positional parameters for width and precision
+  // are indexed to precede the value.
+  // Also sanity check the same formats against snprintf.
+  EXPECT_EQ("0.1", StrFormat("%.1f", 0.1));
+  EXPECT_EQ("0.1", WithSnprintf("%.1f", 0.1));
+  EXPECT_EQ("  0.1", StrFormat("%*.1f", 5, 0.1));
+  EXPECT_EQ("  0.1", WithSnprintf("%*.1f", 5, 0.1));
+  EXPECT_EQ("0.1", StrFormat("%.*f", 1, 0.1));
+  EXPECT_EQ("0.1", WithSnprintf("%.*f", 1, 0.1));
+  EXPECT_EQ("  0.1", StrFormat("%*.*f", 5, 1, 0.1));
+  EXPECT_EQ("  0.1", WithSnprintf("%*.*f", 5, 1, 0.1));
+}
+namespace streamed_test {
+struct X {};
+std::ostream& operator<<(std::ostream& os, const X&) {
+  return os << "X";
+}
+}  // streamed_test
+
+TEST_F(FormatEntryPointTest, FormatStreamed) {
+  EXPECT_EQ("123", StrFormat("%s", FormatStreamed(123)));
+  EXPECT_EQ("  123", StrFormat("%5s", FormatStreamed(123)));
+  EXPECT_EQ("123  ", StrFormat("%-5s", FormatStreamed(123)));
+  EXPECT_EQ("X", StrFormat("%s", FormatStreamed(streamed_test::X())));
+  EXPECT_EQ("123", StrFormat("%s", FormatStreamed(StreamFormat("%d", 123))));
+}
+
+// Helper class that creates a temporary file and exposes a FILE* to it.
+// It will close the file on destruction.
+class TempFile {
+ public:
+  TempFile() : file_(std::tmpfile()) {}
+  ~TempFile() { std::fclose(file_); }
+
+  std::FILE* file() const { return file_; }
+
+  // Read the file into a std::string.
+  std::string ReadFile() {
+    std::fseek(file_, 0, SEEK_END);
+    int size = std::ftell(file_);
+    std::rewind(file_);
+    std::string str(2 * size, ' ');
+    int read_bytes = std::fread(&str[0], 1, str.size(), file_);
+    EXPECT_EQ(read_bytes, size);
+    str.resize(read_bytes);
+    EXPECT_TRUE(std::feof(file_));
+    return str;
+  }
+
+ private:
+  std::FILE* file_;
+};
+
+TEST_F(FormatEntryPointTest, FPrintF) {
+  TempFile tmp;
+  int result =
+      FPrintF(tmp.file(), "STRING: %s NUMBER: %010d", std::string("ABC"), -19);
+  EXPECT_EQ(result, 30);
+  EXPECT_EQ(tmp.ReadFile(), "STRING: ABC NUMBER: -000000019");
+}
+
+TEST_F(FormatEntryPointTest, FPrintFError) {
+  errno = 0;
+  int result = FPrintF(stdin, "ABC");
+  EXPECT_LT(result, 0);
+  EXPECT_EQ(errno, EBADF);
+}
+
+#if __GNUC__
+TEST_F(FormatEntryPointTest, FprintfTooLarge) {
+  std::FILE* f = std::fopen("/dev/null", "w");
+  int width = 2000000000;
+  errno = 0;
+  int result = FPrintF(f, "%*d %*d", width, 0, width, 0);
+  EXPECT_LT(result, 0);
+  EXPECT_EQ(errno, EFBIG);
+  std::fclose(f);
+}
+
+TEST_F(FormatEntryPointTest, PrintF) {
+  int stdout_tmp = dup(STDOUT_FILENO);
+
+  TempFile tmp;
+  std::fflush(stdout);
+  dup2(fileno(tmp.file()), STDOUT_FILENO);
+
+  int result = PrintF("STRING: %s NUMBER: %010d", std::string("ABC"), -19);
+
+  std::fflush(stdout);
+  dup2(stdout_tmp, STDOUT_FILENO);
+  close(stdout_tmp);
+
+  EXPECT_EQ(result, 30);
+  EXPECT_EQ(tmp.ReadFile(), "STRING: ABC NUMBER: -000000019");
+}
+#endif  // __GNUC__
+
+TEST_F(FormatEntryPointTest, SNPrintF) {
+  char buffer[16];
+  int result =
+      SNPrintF(buffer, sizeof(buffer), "STRING: %s", std::string("ABC"));
+  EXPECT_EQ(result, 11);
+  EXPECT_EQ(std::string(buffer), "STRING: ABC");
+
+  result = SNPrintF(buffer, sizeof(buffer), "NUMBER: %d", 123456);
+  EXPECT_EQ(result, 14);
+  EXPECT_EQ(std::string(buffer), "NUMBER: 123456");
+
+  result = SNPrintF(buffer, sizeof(buffer), "NUMBER: %d", 1234567);
+  EXPECT_EQ(result, 15);
+  EXPECT_EQ(std::string(buffer), "NUMBER: 1234567");
+
+  result = SNPrintF(buffer, sizeof(buffer), "NUMBER: %d", 12345678);
+  EXPECT_EQ(result, 16);
+  EXPECT_EQ(std::string(buffer), "NUMBER: 1234567");
+
+  result = SNPrintF(buffer, sizeof(buffer), "NUMBER: %d", 123456789);
+  EXPECT_EQ(result, 17);
+  EXPECT_EQ(std::string(buffer), "NUMBER: 1234567");
+
+  result = SNPrintF(nullptr, 0, "Just checking the %s of the output.", "size");
+  EXPECT_EQ(result, 37);
+}
+
+TEST(StrFormat, BehavesAsDocumented) {
+  std::string s = absl::StrFormat("%s, %d!", "Hello", 123);
+  EXPECT_EQ("Hello, 123!", s);
+  // The format of a replacement is
+  // '%'[position][flags][width['.'precision]][length_modifier][format]
+  EXPECT_EQ(absl::StrFormat("%1$+3.2Lf", 1.1), "+1.10");
+  // Text conversion:
+  //     "c" - Character.              Eg: 'a' -> "A", 20 -> " "
+  EXPECT_EQ(StrFormat("%c", 'a'), "a");
+  EXPECT_EQ(StrFormat("%c", 0x20), " ");
+  //           Formats char and integral types: int, long, uint64_t, etc.
+  EXPECT_EQ(StrFormat("%c", int{'a'}), "a");
+  EXPECT_EQ(StrFormat("%c", long{'a'}), "a");  // NOLINT
+  EXPECT_EQ(StrFormat("%c", uint64_t{'a'}), "a");
+  //     "s" - std::string                  Eg: "C" -> "C", std::string("C++") -> "C++"
+  //           Formats std::string, char*, string_view, and Cord.
+  EXPECT_EQ(StrFormat("%s", "C"), "C");
+  EXPECT_EQ(StrFormat("%s", std::string("C++")), "C++");
+  EXPECT_EQ(StrFormat("%s", string_view("view")), "view");
+  // Integral Conversion
+  //     These format integral types: char, int, long, uint64_t, etc.
+  EXPECT_EQ(StrFormat("%d", char{10}), "10");
+  EXPECT_EQ(StrFormat("%d", int{10}), "10");
+  EXPECT_EQ(StrFormat("%d", long{10}), "10");  // NOLINT
+  EXPECT_EQ(StrFormat("%d", uint64_t{10}), "10");
+  //     d,i - signed decimal          Eg: -10 -> "-10"
+  EXPECT_EQ(StrFormat("%d", -10), "-10");
+  EXPECT_EQ(StrFormat("%i", -10), "-10");
+  //      o  - octal                   Eg:  10 -> "12"
+  EXPECT_EQ(StrFormat("%o", 10), "12");
+  //      u  - unsigned decimal        Eg:  10 -> "10"
+  EXPECT_EQ(StrFormat("%u", 10), "10");
+  //     x/X - lower,upper case hex    Eg:  10 -> "a"/"A"
+  EXPECT_EQ(StrFormat("%x", 10), "a");
+  EXPECT_EQ(StrFormat("%X", 10), "A");
+  // Floating-point, with upper/lower-case output.
+  //     These format floating points types: float, double, long double, etc.
+  EXPECT_EQ(StrFormat("%.1f", float{1}), "1.0");
+  EXPECT_EQ(StrFormat("%.1f", double{1}), "1.0");
+  const long double long_double = 1.0;
+  EXPECT_EQ(StrFormat("%.1f", long_double), "1.0");
+  //     These also format integral types: char, int, long, uint64_t, etc.:
+  EXPECT_EQ(StrFormat("%.1f", char{1}), "1.0");
+  EXPECT_EQ(StrFormat("%.1f", int{1}), "1.0");
+  EXPECT_EQ(StrFormat("%.1f", long{1}), "1.0");  // NOLINT
+  EXPECT_EQ(StrFormat("%.1f", uint64_t{1}), "1.0");
+  //     f/F - decimal.                Eg: 123456789 -> "123456789.000000"
+  EXPECT_EQ(StrFormat("%f", 123456789), "123456789.000000");
+  EXPECT_EQ(StrFormat("%F", 123456789), "123456789.000000");
+  //     e/E - exponentiated           Eg: .01 -> "1.00000e-2"/"1.00000E-2"
+  EXPECT_EQ(StrFormat("%e", .01), "1.000000e-02");
+  EXPECT_EQ(StrFormat("%E", .01), "1.000000E-02");
+  //     g/G - exponentiate to fit     Eg: .01 -> "0.01", 1e10 ->"1e+10"/"1E+10"
+  EXPECT_EQ(StrFormat("%g", .01), "0.01");
+  EXPECT_EQ(StrFormat("%g", 1e10), "1e+10");
+  EXPECT_EQ(StrFormat("%G", 1e10), "1E+10");
+  //     a/A - lower,upper case hex    Eg: -3.0 -> "-0x1.8p+1"/"-0X1.8P+1"
+
+// On NDK r16, there is a regression in hexfloat formatting.
+#if !defined(__NDK_MAJOR__) || __NDK_MAJOR__ != 16
+  EXPECT_EQ(StrFormat("%.1a", -3.0), "-0x1.8p+1");  // .1 to fix MSVC output
+  EXPECT_EQ(StrFormat("%.1A", -3.0), "-0X1.8P+1");  // .1 to fix MSVC output
+#endif
+
+  // Other conversion
+  int64_t value = 0x7ffdeb6;
+  auto ptr_value = static_cast<uintptr_t>(value);
+  const int& something = *reinterpret_cast<const int*>(ptr_value);
+  EXPECT_EQ(StrFormat("%p", &something), StrFormat("0x%x", ptr_value));
+
+  // Output widths are supported, with optional flags.
+  EXPECT_EQ(StrFormat("%3d", 1), "  1");
+  EXPECT_EQ(StrFormat("%3d", 123456), "123456");
+  EXPECT_EQ(StrFormat("%06.2f", 1.234), "001.23");
+  EXPECT_EQ(StrFormat("%+d", 1), "+1");
+  EXPECT_EQ(StrFormat("% d", 1), " 1");
+  EXPECT_EQ(StrFormat("%-4d", -1), "-1  ");
+  EXPECT_EQ(StrFormat("%#o", 10), "012");
+  EXPECT_EQ(StrFormat("%#x", 15), "0xf");
+  EXPECT_EQ(StrFormat("%04d", 8), "0008");
+  // Posix positional substitution.
+  EXPECT_EQ(absl::StrFormat("%2$s, %3$s, %1$s!", "vici", "veni", "vidi"),
+            "veni, vidi, vici!");
+  // Length modifiers are ignored.
+  EXPECT_EQ(StrFormat("%hhd", int{1}), "1");
+  EXPECT_EQ(StrFormat("%hd", int{1}), "1");
+  EXPECT_EQ(StrFormat("%ld", int{1}), "1");
+  EXPECT_EQ(StrFormat("%lld", int{1}), "1");
+  EXPECT_EQ(StrFormat("%Ld", int{1}), "1");
+  EXPECT_EQ(StrFormat("%jd", int{1}), "1");
+  EXPECT_EQ(StrFormat("%zd", int{1}), "1");
+  EXPECT_EQ(StrFormat("%td", int{1}), "1");
+  EXPECT_EQ(StrFormat("%qd", int{1}), "1");
+}
+
+using str_format_internal::ExtendedParsedFormat;
+using str_format_internal::ParsedFormatBase;
+
+struct SummarizeConsumer {
+  std::string* out;
+  explicit SummarizeConsumer(std::string* out) : out(out) {}
+
+  bool Append(string_view s) {
+    *out += "[" + std::string(s) + "]";
+    return true;
+  }
+
+  bool ConvertOne(const str_format_internal::UnboundConversion& conv,
+                  string_view s) {
+    *out += "{";
+    *out += std::string(s);
+    *out += ":";
+    *out += std::to_string(conv.arg_position) + "$";
+    if (conv.width.is_from_arg()) {
+      *out += std::to_string(conv.width.get_from_arg()) + "$*";
+    }
+    if (conv.precision.is_from_arg()) {
+      *out += "." + std::to_string(conv.precision.get_from_arg()) + "$*";
+    }
+    *out += conv.conv.Char();
+    *out += "}";
+    return true;
+  }
+};
+
+std::string SummarizeParsedFormat(const ParsedFormatBase& pc) {
+  std::string out;
+  if (!pc.ProcessFormat(SummarizeConsumer(&out))) out += "!";
+  return out;
+}
+
+class ParsedFormatTest : public testing::Test {};
+
+TEST_F(ParsedFormatTest, SimpleChecked) {
+  EXPECT_EQ("[ABC]{d:1$d}[DEF]",
+            SummarizeParsedFormat(ParsedFormat<'d'>("ABC%dDEF")));
+  EXPECT_EQ("{s:1$s}[FFF]{d:2$d}[ZZZ]{f:3$f}",
+            SummarizeParsedFormat(ParsedFormat<'s', 'd', 'f'>("%sFFF%dZZZ%f")));
+  EXPECT_EQ("{s:1$s}[ ]{.*d:3$.2$*d}",
+            SummarizeParsedFormat(ParsedFormat<'s', '*', 'd'>("%s %.*d")));
+}
+
+TEST_F(ParsedFormatTest, SimpleUncheckedCorrect) {
+  auto f = ParsedFormat<'d'>::New("ABC%dDEF");
+  ASSERT_TRUE(f);
+  EXPECT_EQ("[ABC]{d:1$d}[DEF]", SummarizeParsedFormat(*f));
+
+  std::string format = "%sFFF%dZZZ%f";
+  auto f2 = ParsedFormat<'s', 'd', 'f'>::New(format);
+
+  ASSERT_TRUE(f2);
+  EXPECT_EQ("{s:1$s}[FFF]{d:2$d}[ZZZ]{f:3$f}", SummarizeParsedFormat(*f2));
+
+  f2 = ParsedFormat<'s', 'd', 'f'>::New("%s %d %f");
+
+  ASSERT_TRUE(f2);
+  EXPECT_EQ("{s:1$s}[ ]{d:2$d}[ ]{f:3$f}", SummarizeParsedFormat(*f2));
+
+  auto star = ParsedFormat<'*', 'd'>::New("%*d");
+  ASSERT_TRUE(star);
+  EXPECT_EQ("{*d:2$1$*d}", SummarizeParsedFormat(*star));
+
+  auto dollar = ParsedFormat<'d', 's'>::New("%2$s %1$d");
+  ASSERT_TRUE(dollar);
+  EXPECT_EQ("{2$s:2$s}[ ]{1$d:1$d}", SummarizeParsedFormat(*dollar));
+  // with reuse
+  dollar = ParsedFormat<'d', 's'>::New("%2$s %1$d %1$d");
+  ASSERT_TRUE(dollar);
+  EXPECT_EQ("{2$s:2$s}[ ]{1$d:1$d}[ ]{1$d:1$d}",
+            SummarizeParsedFormat(*dollar));
+}
+
+TEST_F(ParsedFormatTest, SimpleUncheckedIgnoredArgs) {
+  EXPECT_FALSE((ParsedFormat<'d', 's'>::New("ABC")));
+  EXPECT_FALSE((ParsedFormat<'d', 's'>::New("%dABC")));
+  EXPECT_FALSE((ParsedFormat<'d', 's'>::New("ABC%2$s")));
+  auto f = ParsedFormat<'d', 's'>::NewAllowIgnored("ABC");
+  ASSERT_TRUE(f);
+  EXPECT_EQ("[ABC]", SummarizeParsedFormat(*f));
+  f = ParsedFormat<'d', 's'>::NewAllowIgnored("%dABC");
+  ASSERT_TRUE(f);
+  EXPECT_EQ("{d:1$d}[ABC]", SummarizeParsedFormat(*f));
+  f = ParsedFormat<'d', 's'>::NewAllowIgnored("ABC%2$s");
+  ASSERT_TRUE(f);
+  EXPECT_EQ("[ABC]{2$s:2$s}", SummarizeParsedFormat(*f));
+}
+
+TEST_F(ParsedFormatTest, SimpleUncheckedUnsupported) {
+  EXPECT_FALSE(ParsedFormat<'d'>::New("%1$d %1$x"));
+  EXPECT_FALSE(ParsedFormat<'x'>::New("%1$d %1$x"));
+}
+
+TEST_F(ParsedFormatTest, SimpleUncheckedIncorrect) {
+  EXPECT_FALSE(ParsedFormat<'d'>::New(""));
+
+  EXPECT_FALSE(ParsedFormat<'d'>::New("ABC%dDEF%d"));
+
+  std::string format = "%sFFF%dZZZ%f";
+  EXPECT_FALSE((ParsedFormat<'s', 'd', 'g'>::New(format)));
+}
+
+using str_format_internal::Conv;
+
+TEST_F(ParsedFormatTest, UncheckedCorrect) {
+  auto f = ExtendedParsedFormat<Conv::d>::New("ABC%dDEF");
+  ASSERT_TRUE(f);
+  EXPECT_EQ("[ABC]{d:1$d}[DEF]", SummarizeParsedFormat(*f));
+
+  std::string format = "%sFFF%dZZZ%f";
+  auto f2 =
+      ExtendedParsedFormat<Conv::string, Conv::d, Conv::floating>::New(format);
+
+  ASSERT_TRUE(f2);
+  EXPECT_EQ("{s:1$s}[FFF]{d:2$d}[ZZZ]{f:3$f}", SummarizeParsedFormat(*f2));
+
+  f2 = ExtendedParsedFormat<Conv::string, Conv::d, Conv::floating>::New(
+      "%s %d %f");
+
+  ASSERT_TRUE(f2);
+  EXPECT_EQ("{s:1$s}[ ]{d:2$d}[ ]{f:3$f}", SummarizeParsedFormat(*f2));
+
+  auto star = ExtendedParsedFormat<Conv::star, Conv::d>::New("%*d");
+  ASSERT_TRUE(star);
+  EXPECT_EQ("{*d:2$1$*d}", SummarizeParsedFormat(*star));
+
+  auto dollar = ExtendedParsedFormat<Conv::d, Conv::s>::New("%2$s %1$d");
+  ASSERT_TRUE(dollar);
+  EXPECT_EQ("{2$s:2$s}[ ]{1$d:1$d}", SummarizeParsedFormat(*dollar));
+  // with reuse
+  dollar = ExtendedParsedFormat<Conv::d, Conv::s>::New("%2$s %1$d %1$d");
+  ASSERT_TRUE(dollar);
+  EXPECT_EQ("{2$s:2$s}[ ]{1$d:1$d}[ ]{1$d:1$d}",
+            SummarizeParsedFormat(*dollar));
+}
+
+TEST_F(ParsedFormatTest, UncheckedIgnoredArgs) {
+  EXPECT_FALSE((ExtendedParsedFormat<Conv::d, Conv::s>::New("ABC")));
+  EXPECT_FALSE((ExtendedParsedFormat<Conv::d, Conv::s>::New("%dABC")));
+  EXPECT_FALSE((ExtendedParsedFormat<Conv::d, Conv::s>::New("ABC%2$s")));
+  auto f = ExtendedParsedFormat<Conv::d, Conv::s>::NewAllowIgnored("ABC");
+  ASSERT_TRUE(f);
+  EXPECT_EQ("[ABC]", SummarizeParsedFormat(*f));
+  f = ExtendedParsedFormat<Conv::d, Conv::s>::NewAllowIgnored("%dABC");
+  ASSERT_TRUE(f);
+  EXPECT_EQ("{d:1$d}[ABC]", SummarizeParsedFormat(*f));
+  f = ExtendedParsedFormat<Conv::d, Conv::s>::NewAllowIgnored("ABC%2$s");
+  ASSERT_TRUE(f);
+  EXPECT_EQ("[ABC]{2$s:2$s}", SummarizeParsedFormat(*f));
+}
+
+TEST_F(ParsedFormatTest, UncheckedMultipleTypes) {
+  auto dx = ExtendedParsedFormat<Conv::d | Conv::x>::New("%1$d %1$x");
+  EXPECT_TRUE(dx);
+  EXPECT_EQ("{1$d:1$d}[ ]{1$x:1$x}", SummarizeParsedFormat(*dx));
+
+  dx = ExtendedParsedFormat<Conv::d | Conv::x>::New("%1$d");
+  EXPECT_TRUE(dx);
+  EXPECT_EQ("{1$d:1$d}", SummarizeParsedFormat(*dx));
+}
+
+TEST_F(ParsedFormatTest, UncheckedIncorrect) {
+  EXPECT_FALSE(ExtendedParsedFormat<Conv::d>::New(""));
+
+  EXPECT_FALSE(ExtendedParsedFormat<Conv::d>::New("ABC%dDEF%d"));
+
+  std::string format = "%sFFF%dZZZ%f";
+  EXPECT_FALSE((ExtendedParsedFormat<Conv::s, Conv::d, Conv::g>::New(format)));
+}
+
+TEST_F(ParsedFormatTest, RegressionMixPositional) {
+  EXPECT_FALSE((ExtendedParsedFormat<Conv::d, Conv::o>::New("%1$d %o")));
+}
+
+}  // namespace
+}  // namespace absl
diff --git a/absl/time/format.cc b/absl/time/format.cc
index 6edf2b5f03e5..e98e60a372bf 100644
--- a/absl/time/format.cc
+++ b/absl/time/format.cc
@@ -34,15 +34,13 @@ namespace {
 const char kInfiniteFutureStr[] = "infinite-future";
 const char kInfinitePastStr[] = "infinite-past";
 
-using cctz_sec = cctz::time_point<cctz::sys_seconds>;
-using cctz_fem = cctz::detail::femtoseconds;
 struct cctz_parts {
-  cctz_sec sec;
-  cctz_fem fem;
+  cctz::time_point<cctz::seconds> sec;
+  cctz::detail::femtoseconds fem;
 };
 
-inline cctz_sec unix_epoch() {
-  return std::chrono::time_point_cast<cctz::sys_seconds>(
+inline cctz::time_point<cctz::seconds> unix_epoch() {
+  return std::chrono::time_point_cast<cctz::seconds>(
       std::chrono::system_clock::from_time_t(0));
 }
 
@@ -53,8 +51,8 @@ cctz_parts Split(absl::Time t) {
   const auto d = time_internal::ToUnixDuration(t);
   const int64_t rep_hi = time_internal::GetRepHi(d);
   const int64_t rep_lo = time_internal::GetRepLo(d);
-  const auto sec = unix_epoch() + cctz::sys_seconds(rep_hi);
-  const auto fem = cctz_fem(rep_lo * (1000 * 1000 / 4));
+  const auto sec = unix_epoch() + cctz::seconds(rep_hi);
+  const auto fem = cctz::detail::femtoseconds(rep_lo * (1000 * 1000 / 4));
   return {sec, fem};
 }
 
diff --git a/absl/time/internal/cctz/include/cctz/time_zone.h b/absl/time/internal/cctz/include/cctz/time_zone.h
index 31abc2c4bbdf..55804ba68abb 100644
--- a/absl/time/internal/cctz/include/cctz/time_zone.h
+++ b/absl/time/internal/cctz/include/cctz/time_zone.h
@@ -34,23 +34,24 @@ namespace cctz {
 // Convenience aliases. Not intended as public API points.
 template <typename D>
 using time_point = std::chrono::time_point<std::chrono::system_clock, D>;
-using sys_seconds = std::chrono::duration<std::int_fast64_t>;
+using seconds = std::chrono::duration<std::int_fast64_t>;
+using sys_seconds = seconds;  // Deprecated.  Use cctz::seconds instead.
 
 namespace detail {
 template <typename D>
-inline std::pair<time_point<sys_seconds>, D>
+inline std::pair<time_point<seconds>, D>
 split_seconds(const time_point<D>& tp) {
-  auto sec = std::chrono::time_point_cast<sys_seconds>(tp);
+  auto sec = std::chrono::time_point_cast<seconds>(tp);
   auto sub = tp - sec;
   if (sub.count() < 0) {
-    sec -= sys_seconds(1);
-    sub += sys_seconds(1);
+    sec -= seconds(1);
+    sub += seconds(1);
   }
   return {sec, std::chrono::duration_cast<D>(sub)};
 }
-inline std::pair<time_point<sys_seconds>, sys_seconds>
-split_seconds(const time_point<sys_seconds>& tp) {
-  return {tp, sys_seconds(0)};
+inline std::pair<time_point<seconds>, seconds>
+split_seconds(const time_point<seconds>& tp) {
+  return {tp, seconds::zero()};
 }
 }  // namespace detail
 
@@ -99,7 +100,7 @@ class time_zone {
     bool is_dst;       // is offset non-standard?
     const char* abbr;  // time-zone abbreviation (e.g., "PST")
   };
-  absolute_lookup lookup(const time_point<sys_seconds>& tp) const;
+  absolute_lookup lookup(const time_point<seconds>& tp) const;
   template <typename D>
   absolute_lookup lookup(const time_point<D>& tp) const {
     return lookup(detail::split_seconds(tp).first);
@@ -120,7 +121,7 @@ class time_zone {
   // offset, the transition point itself, and the post-transition offset,
   // respectively (all three times are equal if kind == UNIQUE).  If any
   // of these three absolute times is outside the representable range of a
-  // time_point<sys_seconds> the field is set to its maximum/minimum value.
+  // time_point<seconds> the field is set to its maximum/minimum value.
   //
   // Example:
   //   cctz::time_zone lax;
@@ -152,9 +153,9 @@ class time_zone {
       SKIPPED,   // the civil time did not exist (pre >= trans > post)
       REPEATED,  // the civil time was ambiguous (pre < trans <= post)
     } kind;
-    time_point<sys_seconds> pre;    // uses the pre-transition offset
-    time_point<sys_seconds> trans;  // instant of civil-offset change
-    time_point<sys_seconds> post;   // uses the post-transition offset
+    time_point<seconds> pre;    // uses the pre-transition offset
+    time_point<seconds> trans;  // instant of civil-offset change
+    time_point<seconds> post;   // uses the post-transition offset
   };
   civil_lookup lookup(const civil_second& cs) const;
 
@@ -180,7 +181,7 @@ time_zone utc_time_zone();
 // Returns a time zone that is a fixed offset (seconds east) from UTC.
 // Note: If the absolute value of the offset is greater than 24 hours
 // you'll get UTC (i.e., zero offset) instead.
-time_zone fixed_time_zone(const sys_seconds& offset);
+time_zone fixed_time_zone(const seconds& offset);
 
 // Returns a time zone representing the local time zone. Falls back to UTC.
 time_zone local_time_zone();
@@ -199,8 +200,8 @@ inline civil_second convert(const time_point<D>& tp, const time_zone& tz) {
 // it was either repeated or non-existent), then the returned time_point is
 // the best estimate that preserves relative order. That is, this function
 // guarantees that if cs1 < cs2, then convert(cs1, tz) <= convert(cs2, tz).
-inline time_point<sys_seconds> convert(const civil_second& cs,
-                                       const time_zone& tz) {
+inline time_point<seconds> convert(const civil_second& cs,
+                                   const time_zone& tz) {
   const time_zone::civil_lookup cl = tz.lookup(cs);
   if (cl.kind == time_zone::civil_lookup::SKIPPED) return cl.trans;
   return cl.pre;
@@ -208,10 +209,10 @@ inline time_point<sys_seconds> convert(const civil_second& cs,
 
 namespace detail {
 using femtoseconds = std::chrono::duration<std::int_fast64_t, std::femto>;
-std::string format(const std::string&, const time_point<sys_seconds>&,
+std::string format(const std::string&, const time_point<seconds>&,
                    const femtoseconds&, const time_zone&);
 bool parse(const std::string&, const std::string&, const time_zone&,
-           time_point<sys_seconds>*, femtoseconds*, std::string* err = nullptr);
+           time_point<seconds>*, femtoseconds*, std::string* err = nullptr);
 }  // namespace detail
 
 // Formats the given time_point in the given cctz::time_zone according to
@@ -298,7 +299,7 @@ inline std::string format(const std::string& fmt, const time_point<D>& tp,
 template <typename D>
 inline bool parse(const std::string& fmt, const std::string& input,
                   const time_zone& tz, time_point<D>* tpp) {
-  time_point<sys_seconds> sec;
+  time_point<seconds> sec;
   detail::femtoseconds fs;
   const bool b = detail::parse(fmt, input, tz, &sec, &fs);
   if (b) {
diff --git a/absl/time/internal/cctz/src/time_zone_fixed.cc b/absl/time/internal/cctz/src/time_zone_fixed.cc
index 65eba3569d97..598b08fde422 100644
--- a/absl/time/internal/cctz/src/time_zone_fixed.cc
+++ b/absl/time/internal/cctz/src/time_zone_fixed.cc
@@ -42,9 +42,9 @@ int Parse02d(const char* p) {
 
 }  // namespace
 
-bool FixedOffsetFromName(const std::string& name, sys_seconds* offset) {
+bool FixedOffsetFromName(const std::string& name, seconds* offset) {
   if (name.compare(0, std::string::npos, "UTC", 3) == 0) {
-    *offset = sys_seconds::zero();
+    *offset = seconds::zero();
     return true;
   }
 
@@ -69,12 +69,12 @@ bool FixedOffsetFromName(const std::string& name, sys_seconds* offset) {
 
   secs += ((hours * 60) + mins) * 60;
   if (secs > 24 * 60 * 60) return false;  // outside supported offset range
-  *offset = sys_seconds(secs * (np[0] == '-' ? -1 : 1));  // "-" means west
+  *offset = seconds(secs * (np[0] == '-' ? -1 : 1));  // "-" means west
   return true;
 }
 
-std::string FixedOffsetToName(const sys_seconds& offset) {
-  if (offset == sys_seconds::zero()) return "UTC";
+std::string FixedOffsetToName(const seconds& offset) {
+  if (offset == seconds::zero()) return "UTC";
   if (offset < std::chrono::hours(-24) || offset > std::chrono::hours(24)) {
     // We don't support fixed-offset zones more than 24 hours
     // away from UTC to avoid complications in rendering such
@@ -101,7 +101,7 @@ std::string FixedOffsetToName(const sys_seconds& offset) {
   return buf;
 }
 
-std::string FixedOffsetToAbbr(const sys_seconds& offset) {
+std::string FixedOffsetToAbbr(const seconds& offset) {
   std::string abbr = FixedOffsetToName(offset);
   const std::size_t prefix_len = sizeof(kFixedOffsetPrefix) - 1;
   if (abbr.size() == prefix_len + 9) {         // <prefix>+99:99:99
diff --git a/absl/time/internal/cctz/src/time_zone_fixed.h b/absl/time/internal/cctz/src/time_zone_fixed.h
index 7c9d11db9c5b..489b857d5df3 100644
--- a/absl/time/internal/cctz/src/time_zone_fixed.h
+++ b/absl/time/internal/cctz/src/time_zone_fixed.h
@@ -38,9 +38,9 @@ namespace cctz {
 // Note: FixedOffsetFromName() fails on syntax errors or when the parsed
 // offset exceeds 24 hours.  FixedOffsetToName() and FixedOffsetToAbbr()
 // both produce "UTC" when the argument offset exceeds 24 hours.
-bool FixedOffsetFromName(const std::string& name, sys_seconds* offset);
-std::string FixedOffsetToName(const sys_seconds& offset);
-std::string FixedOffsetToAbbr(const sys_seconds& offset);
+bool FixedOffsetFromName(const std::string& name, seconds* offset);
+std::string FixedOffsetToName(const seconds& offset);
+std::string FixedOffsetToAbbr(const seconds& offset);
 
 }  // namespace cctz
 }  // namespace time_internal
diff --git a/absl/time/internal/cctz/src/time_zone_format.cc b/absl/time/internal/cctz/src/time_zone_format.cc
index 6d5ccba1ce49..592ab7d3cfdf 100644
--- a/absl/time/internal/cctz/src/time_zone_format.cc
+++ b/absl/time/internal/cctz/src/time_zone_format.cc
@@ -277,7 +277,7 @@ const std::int_fast64_t kExp10[kDigits10_64 + 1] = {
 // not support the tm_gmtoff and tm_zone extensions to std::tm.
 //
 // Requires that zero() <= fs < seconds(1).
-std::string format(const std::string& format, const time_point<sys_seconds>& tp,
+std::string format(const std::string& format, const time_point<seconds>& tp,
                    const detail::femtoseconds& fs, const time_zone& tz) {
   std::string result;
   result.reserve(format.size());  // A reasonable guess for the result size.
@@ -555,7 +555,7 @@ const char* ParseTM(const char* dp, const char* fmt, std::tm* tm) {
 // We also handle the %z specifier to accommodate platforms that do not
 // support the tm_gmtoff extension to std::tm.  %Z is parsed but ignored.
 bool parse(const std::string& format, const std::string& input,
-           const time_zone& tz, time_point<sys_seconds>* sec,
+           const time_zone& tz, time_point<seconds>* sec,
            detail::femtoseconds* fs, std::string* err) {
   // The unparsed input.
   const char* data = input.c_str();  // NUL terminated
@@ -822,15 +822,15 @@ bool parse(const std::string& format, const std::string& input,
 
   const auto tp = ptz.lookup(cs).pre;
   // Checks for overflow/underflow and returns an error as necessary.
-  if (tp == time_point<sys_seconds>::max()) {
-    const auto al = ptz.lookup(time_point<sys_seconds>::max());
+  if (tp == time_point<seconds>::max()) {
+    const auto al = ptz.lookup(time_point<seconds>::max());
     if (cs > al.cs) {
       if (err != nullptr) *err = "Out-of-range field";
       return false;
     }
   }
-  if (tp == time_point<sys_seconds>::min()) {
-    const auto al = ptz.lookup(time_point<sys_seconds>::min());
+  if (tp == time_point<seconds>::min()) {
+    const auto al = ptz.lookup(time_point<seconds>::min());
     if (cs < al.cs) {
       if (err != nullptr) *err = "Out-of-range field";
       return false;
diff --git a/absl/time/internal/cctz/src/time_zone_format_test.cc b/absl/time/internal/cctz/src/time_zone_format_test.cc
index 7d5b02ad3a68..33c239841118 100644
--- a/absl/time/internal/cctz/src/time_zone_format_test.cc
+++ b/absl/time/internal/cctz/src/time_zone_format_test.cc
@@ -23,15 +23,7 @@
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
 
-using std::chrono::time_point_cast;
-using std::chrono::system_clock;
-using std::chrono::nanoseconds;
-using std::chrono::microseconds;
-using std::chrono::milliseconds;
-using std::chrono::seconds;
-using std::chrono::minutes;
-using std::chrono::hours;
-using testing::HasSubstr;
+namespace chrono = std::chrono;
 
 namespace absl {
 namespace time_internal {
@@ -81,33 +73,36 @@ void TestFormatSpecifier(time_point<D> tp, time_zone tz, const std::string& fmt,
 TEST(Format, TimePointResolution) {
   const char kFmt[] = "%H:%M:%E*S";
   const time_zone utc = utc_time_zone();
-  const time_point<nanoseconds> t0 = system_clock::from_time_t(1420167845) +
-                                     milliseconds(123) + microseconds(456) +
-                                     nanoseconds(789);
-  EXPECT_EQ("03:04:05.123456789",
-            format(kFmt, time_point_cast<nanoseconds>(t0), utc));
-  EXPECT_EQ("03:04:05.123456",
-            format(kFmt, time_point_cast<microseconds>(t0), utc));
-  EXPECT_EQ("03:04:05.123",
-            format(kFmt, time_point_cast<milliseconds>(t0), utc));
+  const time_point<chrono::nanoseconds> t0 =
+      chrono::system_clock::from_time_t(1420167845) +
+      chrono::milliseconds(123) + chrono::microseconds(456) +
+      chrono::nanoseconds(789);
+  EXPECT_EQ(
+      "03:04:05.123456789",
+      format(kFmt, chrono::time_point_cast<chrono::nanoseconds>(t0), utc));
+  EXPECT_EQ(
+      "03:04:05.123456",
+      format(kFmt, chrono::time_point_cast<chrono::microseconds>(t0), utc));
+  EXPECT_EQ(
+      "03:04:05.123",
+      format(kFmt, chrono::time_point_cast<chrono::milliseconds>(t0), utc));
   EXPECT_EQ("03:04:05",
-            format(kFmt, time_point_cast<seconds>(t0), utc));
+            format(kFmt, chrono::time_point_cast<chrono::seconds>(t0), utc));
   EXPECT_EQ("03:04:05",
-            format(kFmt, time_point_cast<sys_seconds>(t0), utc));
+            format(kFmt, chrono::time_point_cast<absl::time_internal::cctz::seconds>(t0), utc));
   EXPECT_EQ("03:04:00",
-            format(kFmt, time_point_cast<minutes>(t0), utc));
+            format(kFmt, chrono::time_point_cast<chrono::minutes>(t0), utc));
   EXPECT_EQ("03:00:00",
-            format(kFmt, time_point_cast<hours>(t0), utc));
+            format(kFmt, chrono::time_point_cast<chrono::hours>(t0), utc));
 }
 
 TEST(Format, TimePointExtendedResolution) {
   const char kFmt[] = "%H:%M:%E*S";
   const time_zone utc = utc_time_zone();
-  const time_point<sys_seconds> tp =
-      std::chrono::time_point_cast<sys_seconds>(
-          std::chrono::system_clock::from_time_t(0)) +
-      std::chrono::hours(12) + std::chrono::minutes(34) +
-      std::chrono::seconds(56);
+  const time_point<absl::time_internal::cctz::seconds> tp =
+      chrono::time_point_cast<absl::time_internal::cctz::seconds>(
+          chrono::system_clock::from_time_t(0)) +
+      chrono::hours(12) + chrono::minutes(34) + chrono::seconds(56);
 
   EXPECT_EQ(
       "12:34:56.123456789012345",
@@ -132,7 +127,7 @@ TEST(Format, TimePointExtendedResolution) {
 
 TEST(Format, Basics) {
   time_zone tz = utc_time_zone();
-  time_point<nanoseconds> tp = system_clock::from_time_t(0);
+  time_point<chrono::nanoseconds> tp = chrono::system_clock::from_time_t(0);
 
   // Starts with a couple basic edge cases.
   EXPECT_EQ("", format("", tp, tz));
@@ -145,8 +140,9 @@ TEST(Format, Basics) {
   std::string bigger(100000, 'x');
   EXPECT_EQ(bigger, format(bigger, tp, tz));
 
-  tp += hours(13) + minutes(4) + seconds(5);
-  tp += milliseconds(6) + microseconds(7) + nanoseconds(8);
+  tp += chrono::hours(13) + chrono::minutes(4) + chrono::seconds(5);
+  tp += chrono::milliseconds(6) + chrono::microseconds(7) +
+        chrono::nanoseconds(8);
   EXPECT_EQ("1970-01-01", format("%Y-%m-%d", tp, tz));
   EXPECT_EQ("13:04:05", format("%H:%M:%S", tp, tz));
   EXPECT_EQ("13:04:05.006", format("%H:%M:%E3S", tp, tz));
@@ -156,7 +152,7 @@ TEST(Format, Basics) {
 
 TEST(Format, PosixConversions) {
   const time_zone tz = utc_time_zone();
-  auto tp = system_clock::from_time_t(0);
+  auto tp = chrono::system_clock::from_time_t(0);
 
   TestFormatSpecifier(tp, tz, "%d", "01");
   TestFormatSpecifier(tp, tz, "%e", " 1");  // extension but internal support
@@ -196,7 +192,7 @@ TEST(Format, PosixConversions) {
 
 TEST(Format, LocaleSpecific) {
   const time_zone tz = utc_time_zone();
-  auto tp = system_clock::from_time_t(0);
+  auto tp = chrono::system_clock::from_time_t(0);
 
   TestFormatSpecifier(tp, tz, "%a", "Thu");
   TestFormatSpecifier(tp, tz, "%A", "Thursday");
@@ -205,8 +201,8 @@ TEST(Format, LocaleSpecific) {
 
   // %c should at least produce the numeric year and time-of-day.
   const std::string s = format("%c", tp, utc_time_zone());
-  EXPECT_THAT(s, HasSubstr("1970"));
-  EXPECT_THAT(s, HasSubstr("00:00:00"));
+  EXPECT_THAT(s, testing::HasSubstr("1970"));
+  EXPECT_THAT(s, testing::HasSubstr("00:00:00"));
 
   TestFormatSpecifier(tp, tz, "%p", "AM");
   TestFormatSpecifier(tp, tz, "%x", "01/01/70");
@@ -245,7 +241,7 @@ TEST(Format, LocaleSpecific) {
 
 TEST(Format, Escaping) {
   const time_zone tz = utc_time_zone();
-  auto tp = system_clock::from_time_t(0);
+  auto tp = chrono::system_clock::from_time_t(0);
 
   TestFormatSpecifier(tp, tz, "%%", "%");
   TestFormatSpecifier(tp, tz, "%%a", "%a");
@@ -266,8 +262,8 @@ TEST(Format, ExtendedSeconds) {
   const time_zone tz = utc_time_zone();
 
   // No subseconds.
-  time_point<nanoseconds> tp = system_clock::from_time_t(0);
-  tp += seconds(5);
+  time_point<chrono::nanoseconds> tp = chrono::system_clock::from_time_t(0);
+  tp += chrono::seconds(5);
   EXPECT_EQ("05", format("%E*S", tp, tz));
   EXPECT_EQ("05", format("%E0S", tp, tz));
   EXPECT_EQ("05.0", format("%E1S", tp, tz));
@@ -287,7 +283,8 @@ TEST(Format, ExtendedSeconds) {
   EXPECT_EQ("05.000000000000000", format("%E15S", tp, tz));
 
   // With subseconds.
-  tp += milliseconds(6) + microseconds(7) + nanoseconds(8);
+  tp += chrono::milliseconds(6) + chrono::microseconds(7) +
+        chrono::nanoseconds(8);
   EXPECT_EQ("05.006007008", format("%E*S", tp, tz));
   EXPECT_EQ("05", format("%E0S", tp, tz));
   EXPECT_EQ("05.0", format("%E1S", tp, tz));
@@ -307,17 +304,18 @@ TEST(Format, ExtendedSeconds) {
   EXPECT_EQ("05.006007008000000", format("%E15S", tp, tz));
 
   // Times before the Unix epoch.
-  tp = system_clock::from_time_t(0) + microseconds(-1);
+  tp = chrono::system_clock::from_time_t(0) + chrono::microseconds(-1);
   EXPECT_EQ("1969-12-31 23:59:59.999999",
             format("%Y-%m-%d %H:%M:%E*S", tp, tz));
 
   // Here is a "%E*S" case we got wrong for a while.  While the first
   // instant below is correctly rendered as "...:07.333304", the second
   // one used to appear as "...:07.33330499999999999".
-  tp = system_clock::from_time_t(0) + microseconds(1395024427333304);
+  tp = chrono::system_clock::from_time_t(0) +
+       chrono::microseconds(1395024427333304);
   EXPECT_EQ("2014-03-17 02:47:07.333304",
             format("%Y-%m-%d %H:%M:%E*S", tp, tz));
-  tp += microseconds(1);
+  tp += chrono::microseconds(1);
   EXPECT_EQ("2014-03-17 02:47:07.333305",
             format("%Y-%m-%d %H:%M:%E*S", tp, tz));
 }
@@ -326,8 +324,8 @@ TEST(Format, ExtendedSubeconds) {
   const time_zone tz = utc_time_zone();
 
   // No subseconds.
-  time_point<nanoseconds> tp = system_clock::from_time_t(0);
-  tp += seconds(5);
+  time_point<chrono::nanoseconds> tp = chrono::system_clock::from_time_t(0);
+  tp += chrono::seconds(5);
   EXPECT_EQ("0", format("%E*f", tp, tz));
   EXPECT_EQ("", format("%E0f", tp, tz));
   EXPECT_EQ("0", format("%E1f", tp, tz));
@@ -347,7 +345,8 @@ TEST(Format, ExtendedSubeconds) {
   EXPECT_EQ("000000000000000", format("%E15f", tp, tz));
 
   // With subseconds.
-  tp += milliseconds(6) + microseconds(7) + nanoseconds(8);
+  tp += chrono::milliseconds(6) + chrono::microseconds(7) +
+        chrono::nanoseconds(8);
   EXPECT_EQ("006007008", format("%E*f", tp, tz));
   EXPECT_EQ("", format("%E0f", tp, tz));
   EXPECT_EQ("0", format("%E1f", tp, tz));
@@ -367,17 +366,18 @@ TEST(Format, ExtendedSubeconds) {
   EXPECT_EQ("006007008000000", format("%E15f", tp, tz));
 
   // Times before the Unix epoch.
-  tp = system_clock::from_time_t(0) + microseconds(-1);
+  tp = chrono::system_clock::from_time_t(0) + chrono::microseconds(-1);
   EXPECT_EQ("1969-12-31 23:59:59.999999",
             format("%Y-%m-%d %H:%M:%S.%E*f", tp, tz));
 
   // Here is a "%E*S" case we got wrong for a while.  While the first
   // instant below is correctly rendered as "...:07.333304", the second
   // one used to appear as "...:07.33330499999999999".
-  tp = system_clock::from_time_t(0) + microseconds(1395024427333304);
+  tp = chrono::system_clock::from_time_t(0) +
+       chrono::microseconds(1395024427333304);
   EXPECT_EQ("2014-03-17 02:47:07.333304",
             format("%Y-%m-%d %H:%M:%S.%E*f", tp, tz));
-  tp += microseconds(1);
+  tp += chrono::microseconds(1);
   EXPECT_EQ("2014-03-17 02:47:07.333305",
             format("%Y-%m-%d %H:%M:%S.%E*f", tp, tz));
 }
@@ -392,8 +392,8 @@ TEST(Format, CompareExtendSecondsVsSubseconds) {
   auto fmt_B = [](const std::string& prec) { return "%S.%E" + prec + "f"; };
 
   // No subseconds:
-  time_point<nanoseconds> tp = system_clock::from_time_t(0);
-  tp += seconds(5);
+  time_point<chrono::nanoseconds> tp = chrono::system_clock::from_time_t(0);
+  tp += chrono::seconds(5);
   // ... %E*S and %S.%E*f are different.
   EXPECT_EQ("05", format(fmt_A("*"), tp, tz));
   EXPECT_EQ("05.0", format(fmt_B("*"), tp, tz));
@@ -409,7 +409,8 @@ TEST(Format, CompareExtendSecondsVsSubseconds) {
 
   // With subseconds:
   // ... %E*S and %S.%E*f are the same.
-  tp += milliseconds(6) + microseconds(7) + nanoseconds(8);
+  tp += chrono::milliseconds(6) + chrono::microseconds(7) +
+        chrono::nanoseconds(8);
   EXPECT_EQ("05.006007008", format(fmt_A("*"), tp, tz));
   EXPECT_EQ("05.006007008", format(fmt_B("*"), tp, tz));
   // ... %E0S and %S.%E0f are different.
@@ -424,7 +425,7 @@ TEST(Format, CompareExtendSecondsVsSubseconds) {
 }
 
 TEST(Format, ExtendedOffset) {
-  auto tp = system_clock::from_time_t(0);
+  auto tp = chrono::system_clock::from_time_t(0);
 
   time_zone tz = utc_time_zone();
   TestFormatSpecifier(tp, tz, "%Ez", "+00:00");
@@ -446,7 +447,7 @@ TEST(Format, ExtendedOffset) {
 
 TEST(Format, ExtendedSecondOffset) {
   const time_zone utc = utc_time_zone();
-  time_point<seconds> tp;
+  time_point<chrono::seconds> tp;
   time_zone tz;
 
   EXPECT_TRUE(load_time_zone("America/New_York", &tz));
@@ -458,7 +459,7 @@ TEST(Format, ExtendedSecondOffset) {
     TestFormatSpecifier(tp, tz, "%E*z", "-04:56:02");
     TestFormatSpecifier(tp, tz, "%Ez", "-04:56");
   }
-  tp += seconds(1);
+  tp += chrono::seconds(1);
   TestFormatSpecifier(tp, tz, "%E*z", "-05:00:00");
 
   EXPECT_TRUE(load_time_zone("Europe/Moscow", &tz));
@@ -469,7 +470,7 @@ TEST(Format, ExtendedSecondOffset) {
   TestFormatSpecifier(tp, tz, "%E*z", "+04:31:19");
   TestFormatSpecifier(tp, tz, "%Ez", "+04:31");
 #endif
-  tp += seconds(1);
+  tp += chrono::seconds(1);
   TestFormatSpecifier(tp, tz, "%E*z", "+04:00:00");
 }
 
@@ -510,44 +511,44 @@ TEST(Format, RFC3339Format) {
   time_zone tz;
   EXPECT_TRUE(load_time_zone("America/Los_Angeles", &tz));
 
-  time_point<nanoseconds> tp =
+  time_point<chrono::nanoseconds> tp =
       convert(civil_second(1977, 6, 28, 9, 8, 7), tz);
   EXPECT_EQ("1977-06-28T09:08:07-07:00", format(RFC3339_full, tp, tz));
   EXPECT_EQ("1977-06-28T09:08:07-07:00", format(RFC3339_sec, tp, tz));
 
-  tp += milliseconds(100);
+  tp += chrono::milliseconds(100);
   EXPECT_EQ("1977-06-28T09:08:07.1-07:00", format(RFC3339_full, tp, tz));
   EXPECT_EQ("1977-06-28T09:08:07-07:00", format(RFC3339_sec, tp, tz));
 
-  tp += milliseconds(20);
+  tp += chrono::milliseconds(20);
   EXPECT_EQ("1977-06-28T09:08:07.12-07:00", format(RFC3339_full, tp, tz));
   EXPECT_EQ("1977-06-28T09:08:07-07:00", format(RFC3339_sec, tp, tz));
 
-  tp += milliseconds(3);
+  tp += chrono::milliseconds(3);
   EXPECT_EQ("1977-06-28T09:08:07.123-07:00", format(RFC3339_full, tp, tz));
   EXPECT_EQ("1977-06-28T09:08:07-07:00", format(RFC3339_sec, tp, tz));
 
-  tp += microseconds(400);
+  tp += chrono::microseconds(400);
   EXPECT_EQ("1977-06-28T09:08:07.1234-07:00", format(RFC3339_full, tp, tz));
   EXPECT_EQ("1977-06-28T09:08:07-07:00", format(RFC3339_sec, tp, tz));
 
-  tp += microseconds(50);
+  tp += chrono::microseconds(50);
   EXPECT_EQ("1977-06-28T09:08:07.12345-07:00", format(RFC3339_full, tp, tz));
   EXPECT_EQ("1977-06-28T09:08:07-07:00", format(RFC3339_sec, tp, tz));
 
-  tp += microseconds(6);
+  tp += chrono::microseconds(6);
   EXPECT_EQ("1977-06-28T09:08:07.123456-07:00", format(RFC3339_full, tp, tz));
   EXPECT_EQ("1977-06-28T09:08:07-07:00", format(RFC3339_sec, tp, tz));
 
-  tp += nanoseconds(700);
+  tp += chrono::nanoseconds(700);
   EXPECT_EQ("1977-06-28T09:08:07.1234567-07:00", format(RFC3339_full, tp, tz));
   EXPECT_EQ("1977-06-28T09:08:07-07:00", format(RFC3339_sec, tp, tz));
 
-  tp += nanoseconds(80);
+  tp += chrono::nanoseconds(80);
   EXPECT_EQ("1977-06-28T09:08:07.12345678-07:00", format(RFC3339_full, tp, tz));
   EXPECT_EQ("1977-06-28T09:08:07-07:00", format(RFC3339_sec, tp, tz));
 
-  tp += nanoseconds(9);
+  tp += chrono::nanoseconds(9);
   EXPECT_EQ("1977-06-28T09:08:07.123456789-07:00",
             format(RFC3339_full, tp, tz));
   EXPECT_EQ("1977-06-28T09:08:07-07:00", format(RFC3339_sec, tp, tz));
@@ -570,13 +571,13 @@ TEST(Parse, TimePointResolution) {
   const char kFmt[] = "%H:%M:%E*S";
   const time_zone utc = utc_time_zone();
 
-  time_point<nanoseconds> tp_ns;
+  time_point<chrono::nanoseconds> tp_ns;
   EXPECT_TRUE(parse(kFmt, "03:04:05.123456789", utc, &tp_ns));
   EXPECT_EQ("03:04:05.123456789", format(kFmt, tp_ns, utc));
   EXPECT_TRUE(parse(kFmt, "03:04:05.123456", utc, &tp_ns));
   EXPECT_EQ("03:04:05.123456", format(kFmt, tp_ns, utc));
 
-  time_point<microseconds> tp_us;
+  time_point<chrono::microseconds> tp_us;
   EXPECT_TRUE(parse(kFmt, "03:04:05.123456789", utc, &tp_us));
   EXPECT_EQ("03:04:05.123456", format(kFmt, tp_us, utc));
   EXPECT_TRUE(parse(kFmt, "03:04:05.123456", utc, &tp_us));
@@ -584,7 +585,7 @@ TEST(Parse, TimePointResolution) {
   EXPECT_TRUE(parse(kFmt, "03:04:05.123", utc, &tp_us));
   EXPECT_EQ("03:04:05.123", format(kFmt, tp_us, utc));
 
-  time_point<milliseconds> tp_ms;
+  time_point<chrono::milliseconds> tp_ms;
   EXPECT_TRUE(parse(kFmt, "03:04:05.123456", utc, &tp_ms));
   EXPECT_EQ("03:04:05.123", format(kFmt, tp_ms, utc));
   EXPECT_TRUE(parse(kFmt, "03:04:05.123", utc, &tp_ms));
@@ -592,17 +593,17 @@ TEST(Parse, TimePointResolution) {
   EXPECT_TRUE(parse(kFmt, "03:04:05", utc, &tp_ms));
   EXPECT_EQ("03:04:05", format(kFmt, tp_ms, utc));
 
-  time_point<seconds> tp_s;
+  time_point<chrono::seconds> tp_s;
   EXPECT_TRUE(parse(kFmt, "03:04:05.123", utc, &tp_s));
   EXPECT_EQ("03:04:05", format(kFmt, tp_s, utc));
   EXPECT_TRUE(parse(kFmt, "03:04:05", utc, &tp_s));
   EXPECT_EQ("03:04:05", format(kFmt, tp_s, utc));
 
-  time_point<minutes> tp_m;
+  time_point<chrono::minutes> tp_m;
   EXPECT_TRUE(parse(kFmt, "03:04:05", utc, &tp_m));
   EXPECT_EQ("03:04:00", format(kFmt, tp_m, utc));
 
-  time_point<hours> tp_h;
+  time_point<chrono::hours> tp_h;
   EXPECT_TRUE(parse(kFmt, "03:04:05", utc, &tp_h));
   EXPECT_EQ("03:00:00", format(kFmt, tp_h, utc));
 }
@@ -611,7 +612,7 @@ TEST(Parse, TimePointExtendedResolution) {
   const char kFmt[] = "%H:%M:%E*S";
   const time_zone utc = utc_time_zone();
 
-  time_point<sys_seconds> tp;
+  time_point<absl::time_internal::cctz::seconds> tp;
   detail::femtoseconds fs;
   EXPECT_TRUE(detail::parse(kFmt, "12:34:56.123456789012345", utc, &tp, &fs));
   EXPECT_EQ("12:34:56.123456789012345", detail::format(kFmt, tp, fs, utc));
@@ -629,11 +630,12 @@ TEST(Parse, TimePointExtendedResolution) {
 
 TEST(Parse, Basics) {
   time_zone tz = utc_time_zone();
-  time_point<nanoseconds> tp = system_clock::from_time_t(1234567890);
+  time_point<chrono::nanoseconds> tp =
+      chrono::system_clock::from_time_t(1234567890);
 
   // Simple edge cases.
   EXPECT_TRUE(parse("", "", tz, &tp));
-  EXPECT_EQ(system_clock::from_time_t(0), tp);  // everything defaulted
+  EXPECT_EQ(chrono::system_clock::from_time_t(0), tp);  // everything defaulted
   EXPECT_TRUE(parse(" ", " ", tz, &tp));
   EXPECT_TRUE(parse("  ", "  ", tz, &tp));
   EXPECT_TRUE(parse("x", "x", tz, &tp));
@@ -647,7 +649,7 @@ TEST(Parse, Basics) {
 TEST(Parse, WithTimeZone) {
   time_zone tz;
   EXPECT_TRUE(load_time_zone("America/Los_Angeles", &tz));
-  time_point<nanoseconds> tp;
+  time_point<chrono::nanoseconds> tp;
 
   // We can parse a std::string without a UTC offset if we supply a timezone.
   EXPECT_TRUE(parse("%Y-%m-%d %H:%M:%S", "2013-06-28 19:08:09", tz, &tp));
@@ -672,7 +674,7 @@ TEST(Parse, WithTimeZone) {
 TEST(Parse, LeapSecond) {
   time_zone tz;
   EXPECT_TRUE(load_time_zone("America/Los_Angeles", &tz));
-  time_point<nanoseconds> tp;
+  time_point<chrono::nanoseconds> tp;
 
   // ":59" -> ":59"
   EXPECT_TRUE(parse(RFC3339_full, "2013-06-28T07:08:59-08:00", tz, &tp));
@@ -696,7 +698,7 @@ TEST(Parse, LeapSecond) {
 
 TEST(Parse, ErrorCases) {
   const time_zone tz = utc_time_zone();
-  auto tp = system_clock::from_time_t(0);
+  auto tp = chrono::system_clock::from_time_t(0);
 
   // Illegal trailing data.
   EXPECT_FALSE(parse("%S", "123", tz, &tp));
@@ -739,7 +741,7 @@ TEST(Parse, ErrorCases) {
 
 TEST(Parse, PosixConversions) {
   time_zone tz = utc_time_zone();
-  auto tp = system_clock::from_time_t(0);
+  auto tp = chrono::system_clock::from_time_t(0);
   const auto reset = convert(civil_second(1977, 6, 28, 9, 8, 7), tz);
 
   tp = reset;
@@ -828,14 +830,14 @@ TEST(Parse, PosixConversions) {
 
   tp = reset;
   EXPECT_TRUE(parse("%s", "1234567890", tz, &tp));
-  EXPECT_EQ(system_clock::from_time_t(1234567890), tp);
+  EXPECT_EQ(chrono::system_clock::from_time_t(1234567890), tp);
 
   // %s conversion, like %z/%Ez, pays no heed to the optional zone.
   time_zone lax;
   EXPECT_TRUE(load_time_zone("America/Los_Angeles", &lax));
   tp = reset;
   EXPECT_TRUE(parse("%s", "1234567890", lax, &tp));
-  EXPECT_EQ(system_clock::from_time_t(1234567890), tp);
+  EXPECT_EQ(chrono::system_clock::from_time_t(1234567890), tp);
 
   // This is most important when the time has the same YMDhms
   // breakdown in the zone as some other time.  For example, ...
@@ -843,16 +845,16 @@ TEST(Parse, PosixConversions) {
   //  1414920600 in US/Pacific -> Sun Nov 2 01:30:00 2014 (PST)
   tp = reset;
   EXPECT_TRUE(parse("%s", "1414917000", lax, &tp));
-  EXPECT_EQ(system_clock::from_time_t(1414917000), tp);
+  EXPECT_EQ(chrono::system_clock::from_time_t(1414917000), tp);
   tp = reset;
   EXPECT_TRUE(parse("%s", "1414920600", lax, &tp));
-  EXPECT_EQ(system_clock::from_time_t(1414920600), tp);
+  EXPECT_EQ(chrono::system_clock::from_time_t(1414920600), tp);
 #endif
 }
 
 TEST(Parse, LocaleSpecific) {
   time_zone tz = utc_time_zone();
-  auto tp = system_clock::from_time_t(0);
+  auto tp = chrono::system_clock::from_time_t(0);
   const auto reset = convert(civil_second(1977, 6, 28, 9, 8, 7), tz);
 
   // %a is parsed but ignored.
@@ -983,7 +985,8 @@ TEST(Parse, LocaleSpecific) {
 
 TEST(Parse, ExtendedSeconds) {
   const time_zone tz = utc_time_zone();
-  const time_point<nanoseconds> unix_epoch = system_clock::from_time_t(0);
+  const time_point<chrono::nanoseconds> unix_epoch =
+      chrono::system_clock::from_time_t(0);
 
   // All %E<prec>S cases are treated the same as %E*S on input.
   auto precisions = {"*", "0", "1",  "2",  "3",  "4",  "5",  "6", "7",
@@ -991,47 +994,47 @@ TEST(Parse, ExtendedSeconds) {
   for (const std::string& prec : precisions) {
     const std::string fmt = "%E" + prec + "S";
     SCOPED_TRACE(fmt);
-    time_point<nanoseconds> tp = unix_epoch;
+    time_point<chrono::nanoseconds> tp = unix_epoch;
     EXPECT_TRUE(parse(fmt, "5", tz, &tp));
-    EXPECT_EQ(unix_epoch + seconds(5), tp);
+    EXPECT_EQ(unix_epoch + chrono::seconds(5), tp);
     tp = unix_epoch;
     EXPECT_TRUE(parse(fmt, "05", tz, &tp));
-    EXPECT_EQ(unix_epoch + seconds(5), tp);
+    EXPECT_EQ(unix_epoch + chrono::seconds(5), tp);
     tp = unix_epoch;
     EXPECT_TRUE(parse(fmt, "05.0", tz, &tp));
-    EXPECT_EQ(unix_epoch + seconds(5), tp);
+    EXPECT_EQ(unix_epoch + chrono::seconds(5), tp);
     tp = unix_epoch;
     EXPECT_TRUE(parse(fmt, "05.00", tz, &tp));
-    EXPECT_EQ(unix_epoch + seconds(5), tp);
+    EXPECT_EQ(unix_epoch + chrono::seconds(5), tp);
     tp = unix_epoch;
     EXPECT_TRUE(parse(fmt, "05.6", tz, &tp));
-    EXPECT_EQ(unix_epoch + seconds(5) + milliseconds(600), tp);
+    EXPECT_EQ(unix_epoch + chrono::seconds(5) + chrono::milliseconds(600), tp);
     tp = unix_epoch;
     EXPECT_TRUE(parse(fmt, "05.60", tz, &tp));
-    EXPECT_EQ(unix_epoch + seconds(5) + milliseconds(600), tp);
+    EXPECT_EQ(unix_epoch + chrono::seconds(5) + chrono::milliseconds(600), tp);
     tp = unix_epoch;
     EXPECT_TRUE(parse(fmt, "05.600", tz, &tp));
-    EXPECT_EQ(unix_epoch + seconds(5) + milliseconds(600), tp);
+    EXPECT_EQ(unix_epoch + chrono::seconds(5) + chrono::milliseconds(600), tp);
     tp = unix_epoch;
     EXPECT_TRUE(parse(fmt, "05.67", tz, &tp));
-    EXPECT_EQ(unix_epoch + seconds(5) + milliseconds(670), tp);
+    EXPECT_EQ(unix_epoch + chrono::seconds(5) + chrono::milliseconds(670), tp);
     tp = unix_epoch;
     EXPECT_TRUE(parse(fmt, "05.670", tz, &tp));
-    EXPECT_EQ(unix_epoch + seconds(5) + milliseconds(670), tp);
+    EXPECT_EQ(unix_epoch + chrono::seconds(5) + chrono::milliseconds(670), tp);
     tp = unix_epoch;
     EXPECT_TRUE(parse(fmt, "05.678", tz, &tp));
-    EXPECT_EQ(unix_epoch + seconds(5) + milliseconds(678), tp);
+    EXPECT_EQ(unix_epoch + chrono::seconds(5) + chrono::milliseconds(678), tp);
   }
 
   // Here is a "%E*S" case we got wrong for a while.  The fractional
   // part of the first instant is less than 2^31 and was correctly
   // parsed, while the second (and any subsecond field >=2^31) failed.
-  time_point<nanoseconds> tp = unix_epoch;
+  time_point<chrono::nanoseconds> tp = unix_epoch;
   EXPECT_TRUE(parse("%E*S", "0.2147483647", tz, &tp));
-  EXPECT_EQ(unix_epoch + nanoseconds(214748364), tp);
+  EXPECT_EQ(unix_epoch + chrono::nanoseconds(214748364), tp);
   tp = unix_epoch;
   EXPECT_TRUE(parse("%E*S", "0.2147483648", tz, &tp));
-  EXPECT_EQ(unix_epoch + nanoseconds(214748364), tp);
+  EXPECT_EQ(unix_epoch + chrono::nanoseconds(214748364), tp);
 
   // We should also be able to specify long strings of digits far
   // beyond the current resolution and have them convert the same way.
@@ -1039,18 +1042,18 @@ TEST(Parse, ExtendedSeconds) {
   EXPECT_TRUE(parse(
       "%E*S", "0.214748364801234567890123456789012345678901234567890123456789",
       tz, &tp));
-  EXPECT_EQ(unix_epoch + nanoseconds(214748364), tp);
+  EXPECT_EQ(unix_epoch + chrono::nanoseconds(214748364), tp);
 }
 
 TEST(Parse, ExtendedSecondsScan) {
   const time_zone tz = utc_time_zone();
-  time_point<nanoseconds> tp;
+  time_point<chrono::nanoseconds> tp;
   for (int ms = 0; ms < 1000; ms += 111) {
     for (int us = 0; us < 1000; us += 27) {
       const int micros = ms * 1000 + us;
       for (int ns = 0; ns < 1000; ns += 9) {
-        const auto expected =
-            system_clock::from_time_t(0) + nanoseconds(micros * 1000 + ns);
+        const auto expected = chrono::system_clock::from_time_t(0) +
+                              chrono::nanoseconds(micros * 1000 + ns);
         std::ostringstream oss;
         oss << "0." << std::setfill('0') << std::setw(3);
         oss << ms << std::setw(3) << us << std::setw(3) << ns;
@@ -1064,7 +1067,8 @@ TEST(Parse, ExtendedSecondsScan) {
 
 TEST(Parse, ExtendedSubeconds) {
   const time_zone tz = utc_time_zone();
-  const time_point<nanoseconds> unix_epoch = system_clock::from_time_t(0);
+  const time_point<chrono::nanoseconds> unix_epoch =
+      chrono::system_clock::from_time_t(0);
 
   // All %E<prec>f cases are treated the same as %E*f on input.
   auto precisions = {"*", "0", "1",  "2",  "3",  "4",  "5",  "6", "7",
@@ -1072,41 +1076,42 @@ TEST(Parse, ExtendedSubeconds) {
   for (const std::string& prec : precisions) {
     const std::string fmt = "%E" + prec + "f";
     SCOPED_TRACE(fmt);
-    time_point<nanoseconds> tp = unix_epoch - seconds(1);
+    time_point<chrono::nanoseconds> tp = unix_epoch - chrono::seconds(1);
     EXPECT_TRUE(parse(fmt, "", tz, &tp));
     EXPECT_EQ(unix_epoch, tp);
     tp = unix_epoch;
     EXPECT_TRUE(parse(fmt, "6", tz, &tp));
-    EXPECT_EQ(unix_epoch + milliseconds(600), tp);
+    EXPECT_EQ(unix_epoch + chrono::milliseconds(600), tp);
     tp = unix_epoch;
     EXPECT_TRUE(parse(fmt, "60", tz, &tp));
-    EXPECT_EQ(unix_epoch + milliseconds(600), tp);
+    EXPECT_EQ(unix_epoch + chrono::milliseconds(600), tp);
     tp = unix_epoch;
     EXPECT_TRUE(parse(fmt, "600", tz, &tp));
-    EXPECT_EQ(unix_epoch + milliseconds(600), tp);
+    EXPECT_EQ(unix_epoch + chrono::milliseconds(600), tp);
     tp = unix_epoch;
     EXPECT_TRUE(parse(fmt, "67", tz, &tp));
-    EXPECT_EQ(unix_epoch + milliseconds(670), tp);
+    EXPECT_EQ(unix_epoch + chrono::milliseconds(670), tp);
     tp = unix_epoch;
     EXPECT_TRUE(parse(fmt, "670", tz, &tp));
-    EXPECT_EQ(unix_epoch + milliseconds(670), tp);
+    EXPECT_EQ(unix_epoch + chrono::milliseconds(670), tp);
     tp = unix_epoch;
     EXPECT_TRUE(parse(fmt, "678", tz, &tp));
-    EXPECT_EQ(unix_epoch + milliseconds(678), tp);
+    EXPECT_EQ(unix_epoch + chrono::milliseconds(678), tp);
     tp = unix_epoch;
     EXPECT_TRUE(parse(fmt, "6789", tz, &tp));
-    EXPECT_EQ(unix_epoch + milliseconds(678) + microseconds(900), tp);
+    EXPECT_EQ(
+        unix_epoch + chrono::milliseconds(678) + chrono::microseconds(900), tp);
   }
 
   // Here is a "%E*f" case we got wrong for a while.  The fractional
   // part of the first instant is less than 2^31 and was correctly
   // parsed, while the second (and any subsecond field >=2^31) failed.
-  time_point<nanoseconds> tp = unix_epoch;
+  time_point<chrono::nanoseconds> tp = unix_epoch;
   EXPECT_TRUE(parse("%E*f", "2147483647", tz, &tp));
-  EXPECT_EQ(unix_epoch + nanoseconds(214748364), tp);
+  EXPECT_EQ(unix_epoch + chrono::nanoseconds(214748364), tp);
   tp = unix_epoch;
   EXPECT_TRUE(parse("%E*f", "2147483648", tz, &tp));
-  EXPECT_EQ(unix_epoch + nanoseconds(214748364), tp);
+  EXPECT_EQ(unix_epoch + chrono::nanoseconds(214748364), tp);
 
   // We should also be able to specify long strings of digits far
   // beyond the current resolution and have them convert the same way.
@@ -1114,11 +1119,11 @@ TEST(Parse, ExtendedSubeconds) {
   EXPECT_TRUE(parse(
       "%E*f", "214748364801234567890123456789012345678901234567890123456789",
       tz, &tp));
-  EXPECT_EQ(unix_epoch + nanoseconds(214748364), tp);
+  EXPECT_EQ(unix_epoch + chrono::nanoseconds(214748364), tp);
 }
 
 TEST(Parse, ExtendedSubecondsScan) {
-  time_point<nanoseconds> tp;
+  time_point<chrono::nanoseconds> tp;
   const time_zone tz = utc_time_zone();
   for (int ms = 0; ms < 1000; ms += 111) {
     for (int us = 0; us < 1000; us += 27) {
@@ -1128,14 +1133,14 @@ TEST(Parse, ExtendedSubecondsScan) {
         oss << std::setfill('0') << std::setw(3) << ms;
         oss << std::setw(3) << us << std::setw(3) << ns;
         const std::string nanos = oss.str();
-        const auto expected =
-            system_clock::from_time_t(0) + nanoseconds(micros * 1000 + ns);
+        const auto expected = chrono::system_clock::from_time_t(0) +
+                              chrono::nanoseconds(micros * 1000 + ns);
         for (int ps = 0; ps < 1000; ps += 250) {
           std::ostringstream oss;
           oss << std::setfill('0') << std::setw(3) << ps;
           const std::string input = nanos + oss.str() + "999";
           EXPECT_TRUE(parse("%E*f", input, tz, &tp));
-          EXPECT_EQ(expected + nanoseconds(ps) / 1000, tp) << input;
+          EXPECT_EQ(expected + chrono::nanoseconds(ps) / 1000, tp) << input;
         }
       }
     }
@@ -1144,7 +1149,7 @@ TEST(Parse, ExtendedSubecondsScan) {
 
 TEST(Parse, ExtendedOffset) {
   const time_zone utc = utc_time_zone();
-  time_point<sys_seconds> tp;
+  time_point<absl::time_internal::cctz::seconds> tp;
 
   // %z against +-HHMM.
   EXPECT_TRUE(parse("%z", "+0000", utc, &tp));
@@ -1194,7 +1199,7 @@ TEST(Parse, ExtendedOffset) {
 
 TEST(Parse, ExtendedSecondOffset) {
   const time_zone utc = utc_time_zone();
-  time_point<sys_seconds> tp;
+  time_point<absl::time_internal::cctz::seconds> tp;
 
   // %Ez against +-HH:MM:SS.
   EXPECT_TRUE(parse("%Ez", "+00:00:00", utc, &tp));
@@ -1263,7 +1268,7 @@ TEST(Parse, ExtendedSecondOffset) {
 TEST(Parse, ExtendedYears) {
   const time_zone utc = utc_time_zone();
   const char e4y_fmt[] = "%E4Y%m%d";  // no separators
-  time_point<sys_seconds> tp;
+  time_point<absl::time_internal::cctz::seconds> tp;
 
   // %E4Y consumes exactly four chars, including any sign.
   EXPECT_TRUE(parse(e4y_fmt, "-9991127", utc, &tp));
@@ -1294,45 +1299,45 @@ TEST(Parse, ExtendedYears) {
 
 TEST(Parse, RFC3339Format) {
   const time_zone tz = utc_time_zone();
-  time_point<nanoseconds> tp;
+  time_point<chrono::nanoseconds> tp;
   EXPECT_TRUE(parse(RFC3339_sec, "2014-02-12T20:21:00+00:00", tz, &tp));
   ExpectTime(tp, tz, 2014, 2, 12, 20, 21, 0, 0, false, "UTC");
 
   // Check that %Ez also accepts "Z" as a synonym for "+00:00".
-  time_point<nanoseconds> tp2;
+  time_point<chrono::nanoseconds> tp2;
   EXPECT_TRUE(parse(RFC3339_sec, "2014-02-12T20:21:00Z", tz, &tp2));
   EXPECT_EQ(tp, tp2);
 }
 
 TEST(Parse, MaxRange) {
   const time_zone utc = utc_time_zone();
-  time_point<sys_seconds> tp;
+  time_point<absl::time_internal::cctz::seconds> tp;
 
   // tests the upper limit using +00:00 offset
   EXPECT_TRUE(
       parse(RFC3339_sec, "292277026596-12-04T15:30:07+00:00", utc, &tp));
-  EXPECT_EQ(tp, time_point<sys_seconds>::max());
+  EXPECT_EQ(tp, time_point<absl::time_internal::cctz::seconds>::max());
   EXPECT_FALSE(
       parse(RFC3339_sec, "292277026596-12-04T15:30:08+00:00", utc, &tp));
 
   // tests the upper limit using -01:00 offset
   EXPECT_TRUE(
       parse(RFC3339_sec, "292277026596-12-04T14:30:07-01:00", utc, &tp));
-  EXPECT_EQ(tp, time_point<sys_seconds>::max());
+  EXPECT_EQ(tp, time_point<absl::time_internal::cctz::seconds>::max());
   EXPECT_FALSE(
       parse(RFC3339_sec, "292277026596-12-04T15:30:07-01:00", utc, &tp));
 
   // tests the lower limit using +00:00 offset
   EXPECT_TRUE(
       parse(RFC3339_sec, "-292277022657-01-27T08:29:52+00:00", utc, &tp));
-  EXPECT_EQ(tp, time_point<sys_seconds>::min());
+  EXPECT_EQ(tp, time_point<absl::time_internal::cctz::seconds>::min());
   EXPECT_FALSE(
       parse(RFC3339_sec, "-292277022657-01-27T08:29:51+00:00", utc, &tp));
 
   // tests the lower limit using +01:00 offset
   EXPECT_TRUE(
       parse(RFC3339_sec, "-292277022657-01-27T09:29:52+01:00", utc, &tp));
-  EXPECT_EQ(tp, time_point<sys_seconds>::min());
+  EXPECT_EQ(tp, time_point<absl::time_internal::cctz::seconds>::min());
   EXPECT_FALSE(
       parse(RFC3339_sec, "-292277022657-01-27T08:29:51+01:00", utc, &tp));
 
@@ -1355,11 +1360,11 @@ TEST(FormatParse, RoundTrip) {
   time_zone lax;
   EXPECT_TRUE(load_time_zone("America/Los_Angeles", &lax));
   const auto in = convert(civil_second(1977, 6, 28, 9, 8, 7), lax);
-  const auto subseconds = nanoseconds(654321);
+  const auto subseconds = chrono::nanoseconds(654321);
 
   // RFC3339, which renders subseconds.
   {
-    time_point<nanoseconds> out;
+    time_point<chrono::nanoseconds> out;
     const std::string s = format(RFC3339_full, in + subseconds, lax);
     EXPECT_TRUE(parse(RFC3339_full, s, lax, &out)) << s;
     EXPECT_EQ(in + subseconds, out);  // RFC3339_full includes %Ez
@@ -1367,7 +1372,7 @@ TEST(FormatParse, RoundTrip) {
 
   // RFC1123, which only does whole seconds.
   {
-    time_point<nanoseconds> out;
+    time_point<chrono::nanoseconds> out;
     const std::string s = format(RFC1123_full, in, lax);
     EXPECT_TRUE(parse(RFC1123_full, s, lax, &out)) << s;
     EXPECT_EQ(in, out);  // RFC1123_full includes %z
@@ -1380,7 +1385,7 @@ TEST(FormatParse, RoundTrip) {
   // Even though we don't know what %c will produce, it should roundtrip,
   // but only in the 0-offset timezone.
   {
-    time_point<nanoseconds> out;
+    time_point<chrono::nanoseconds> out;
     time_zone utc = utc_time_zone();
     const std::string s = format("%c", in, utc);
     EXPECT_TRUE(parse("%c", s, utc, &out)) << s;
@@ -1391,18 +1396,18 @@ TEST(FormatParse, RoundTrip) {
 
 TEST(FormatParse, RoundTripDistantFuture) {
   const time_zone utc = utc_time_zone();
-  const time_point<sys_seconds> in = time_point<sys_seconds>::max();
+  const time_point<absl::time_internal::cctz::seconds> in = time_point<absl::time_internal::cctz::seconds>::max();
   const std::string s = format(RFC3339_full, in, utc);
-  time_point<sys_seconds> out;
+  time_point<absl::time_internal::cctz::seconds> out;
   EXPECT_TRUE(parse(RFC3339_full, s, utc, &out)) << s;
   EXPECT_EQ(in, out);
 }
 
 TEST(FormatParse, RoundTripDistantPast) {
   const time_zone utc = utc_time_zone();
-  const time_point<sys_seconds> in = time_point<sys_seconds>::min();
+  const time_point<absl::time_internal::cctz::seconds> in = time_point<absl::time_internal::cctz::seconds>::min();
   const std::string s = format(RFC3339_full, in, utc);
-  time_point<sys_seconds> out;
+  time_point<absl::time_internal::cctz::seconds> out;
   EXPECT_TRUE(parse(RFC3339_full, s, utc, &out)) << s;
   EXPECT_EQ(in, out);
 }
diff --git a/absl/time/internal/cctz/src/time_zone_if.h b/absl/time/internal/cctz/src/time_zone_if.h
index ce4da1b728b6..f10972ae2b0f 100644
--- a/absl/time/internal/cctz/src/time_zone_if.h
+++ b/absl/time/internal/cctz/src/time_zone_if.h
@@ -37,30 +37,28 @@ class TimeZoneIf {
   virtual ~TimeZoneIf();
 
   virtual time_zone::absolute_lookup BreakTime(
-      const time_point<sys_seconds>& tp) const = 0;
+      const time_point<seconds>& tp) const = 0;
   virtual time_zone::civil_lookup MakeTime(
       const civil_second& cs) const = 0;
 
   virtual std::string Description() const = 0;
-  virtual bool NextTransition(time_point<sys_seconds>* tp) const = 0;
-  virtual bool PrevTransition(time_point<sys_seconds>* tp) const = 0;
+  virtual bool NextTransition(time_point<seconds>* tp) const = 0;
+  virtual bool PrevTransition(time_point<seconds>* tp) const = 0;
 
  protected:
   TimeZoneIf() {}
 };
 
-// Convert between time_point<sys_seconds> and a count of seconds since
-// the Unix epoch.  We assume that the std::chrono::system_clock and the
+// Convert between time_point<seconds> and a count of seconds since the
+// Unix epoch.  We assume that the std::chrono::system_clock and the
 // Unix clock are second aligned, but not that they share an epoch.
-inline std::int_fast64_t ToUnixSeconds(const time_point<sys_seconds>& tp) {
-  return (tp - std::chrono::time_point_cast<sys_seconds>(
-                   std::chrono::system_clock::from_time_t(0)))
-      .count();
+inline std::int_fast64_t ToUnixSeconds(const time_point<seconds>& tp) {
+  return (tp - std::chrono::time_point_cast<seconds>(
+                   std::chrono::system_clock::from_time_t(0))).count();
 }
-inline time_point<sys_seconds> FromUnixSeconds(std::int_fast64_t t) {
-  return std::chrono::time_point_cast<sys_seconds>(
-             std::chrono::system_clock::from_time_t(0)) +
-         sys_seconds(t);
+inline time_point<seconds> FromUnixSeconds(std::int_fast64_t t) {
+  return std::chrono::time_point_cast<seconds>(
+             std::chrono::system_clock::from_time_t(0)) + seconds(t);
 }
 
 }  // namespace cctz
diff --git a/absl/time/internal/cctz/src/time_zone_impl.cc b/absl/time/internal/cctz/src/time_zone_impl.cc
index b3f635f786ff..eb96c7ef5995 100644
--- a/absl/time/internal/cctz/src/time_zone_impl.cc
+++ b/absl/time/internal/cctz/src/time_zone_impl.cc
@@ -45,8 +45,8 @@ bool time_zone::Impl::LoadTimeZone(const std::string& name, time_zone* tz) {
   const time_zone::Impl* const utc_impl = UTCImpl();
 
   // First check for UTC (which is never a key in time_zone_map).
-  auto offset = sys_seconds::zero();
-  if (FixedOffsetFromName(name, &offset) && offset == sys_seconds::zero()) {
+  auto offset = seconds::zero();
+  if (FixedOffsetFromName(name, &offset) && offset == seconds::zero()) {
     *tz = time_zone(utc_impl);
     return true;
   }
diff --git a/absl/time/internal/cctz/src/time_zone_impl.h b/absl/time/internal/cctz/src/time_zone_impl.h
index 2c1c30b690e5..fef7f22672dc 100644
--- a/absl/time/internal/cctz/src/time_zone_impl.h
+++ b/absl/time/internal/cctz/src/time_zone_impl.h
@@ -48,8 +48,7 @@ class time_zone::Impl {
   const std::string& name() const { return name_; }
 
   // Breaks a time_point down to civil-time components in this time zone.
-  time_zone::absolute_lookup BreakTime(
-      const time_point<sys_seconds>& tp) const {
+  time_zone::absolute_lookup BreakTime(const time_point<seconds>& tp) const {
     return zone_->BreakTime(tp);
   }
 
@@ -75,10 +74,10 @@ class time_zone::Impl {
   // to NextTransition()/PrevTransition() will eventually return false,
   // but it is unspecified exactly when NextTransition(&tp) jumps to false,
   // or what time is set by PrevTransition(&tp) for a very distant tp.
-  bool NextTransition(time_point<sys_seconds>* tp) const {
+  bool NextTransition(time_point<seconds>* tp) const {
     return zone_->NextTransition(tp);
   }
-  bool PrevTransition(time_point<sys_seconds>* tp) const {
+  bool PrevTransition(time_point<seconds>* tp) const {
     return zone_->PrevTransition(tp);
   }
 
diff --git a/absl/time/internal/cctz/src/time_zone_info.cc b/absl/time/internal/cctz/src/time_zone_info.cc
index 20bba28b363b..cdd11810583a 100644
--- a/absl/time/internal/cctz/src/time_zone_info.cc
+++ b/absl/time/internal/cctz/src/time_zone_info.cc
@@ -140,7 +140,7 @@ std::int_fast64_t TransOffset(bool leap_year, int jan1_weekday,
   return (days * kSecsPerDay) + pt.time.offset;
 }
 
-inline time_zone::civil_lookup MakeUnique(const time_point<sys_seconds>& tp) {
+inline time_zone::civil_lookup MakeUnique(const time_point<seconds>& tp) {
   time_zone::civil_lookup cl;
   cl.kind = time_zone::civil_lookup::UNIQUE;
   cl.pre = cl.trans = cl.post = tp;
@@ -179,7 +179,7 @@ inline civil_second YearShift(const civil_second& cs, year_t shift) {
 }  // namespace
 
 // What (no leap-seconds) UTC+seconds zoneinfo would look like.
-bool TimeZoneInfo::ResetToBuiltinUTC(const sys_seconds& offset) {
+bool TimeZoneInfo::ResetToBuiltinUTC(const seconds& offset) {
   transition_types_.resize(1);
   TransitionType& tt(transition_types_.back());
   tt.utc_offset = static_cast<std::int_least32_t>(offset.count());
@@ -218,8 +218,8 @@ bool TimeZoneInfo::ResetToBuiltinUTC(const sys_seconds& offset) {
   future_spec_.clear();  // never needed for a fixed-offset zone
   extended_ = false;
 
-  tt.civil_max = LocalTime(sys_seconds::max().count(), tt).cs;
-  tt.civil_min = LocalTime(sys_seconds::min().count(), tt).cs;
+  tt.civil_max = LocalTime(seconds::max().count(), tt).cs;
+  tt.civil_min = LocalTime(seconds::min().count(), tt).cs;
 
   transitions_.shrink_to_fit();
   return true;
@@ -565,10 +565,10 @@ bool TimeZoneInfo::Load(const std::string& name, ZoneInfoSource* zip) {
   }
 
   // Compute the maximum/minimum civil times that can be converted to a
-  // time_point<sys_seconds> for each of the zone's transition types.
+  // time_point<seconds> for each of the zone's transition types.
   for (auto& tt : transition_types_) {
-    tt.civil_max = LocalTime(sys_seconds::max().count(), tt).cs;
-    tt.civil_min = LocalTime(sys_seconds::min().count(), tt).cs;
+    tt.civil_max = LocalTime(seconds::max().count(), tt).cs;
+    tt.civil_min = LocalTime(seconds::min().count(), tt).cs;
   }
 
   transitions_.shrink_to_fit();
@@ -713,7 +713,7 @@ bool TimeZoneInfo::Load(const std::string& name) {
   // zone never fails because the simple, fixed-offset state can be
   // internally generated. Note that this depends on our choice to not
   // accept leap-second encoded ("right") zoneinfo.
-  auto offset = sys_seconds::zero();
+  auto offset = seconds::zero();
   if (FixedOffsetFromName(name, &offset)) {
     return ResetToBuiltinUTC(offset);
   }
@@ -755,14 +755,14 @@ time_zone::civil_lookup TimeZoneInfo::TimeLocal(const civil_second& cs,
                                                 year_t c4_shift) const {
   assert(last_year_ - 400 < cs.year() && cs.year() <= last_year_);
   time_zone::civil_lookup cl = MakeTime(cs);
-  if (c4_shift > sys_seconds::max().count() / kSecsPer400Years) {
-    cl.pre = cl.trans = cl.post = time_point<sys_seconds>::max();
+  if (c4_shift > seconds::max().count() / kSecsPer400Years) {
+    cl.pre = cl.trans = cl.post = time_point<seconds>::max();
   } else {
-    const auto offset = sys_seconds(c4_shift * kSecsPer400Years);
-    const auto limit = time_point<sys_seconds>::max() - offset;
+    const auto offset = seconds(c4_shift * kSecsPer400Years);
+    const auto limit = time_point<seconds>::max() - offset;
     for (auto* tp : {&cl.pre, &cl.trans, &cl.post}) {
       if (*tp > limit) {
-        *tp = time_point<sys_seconds>::max();
+        *tp = time_point<seconds>::max();
       } else {
         *tp += offset;
       }
@@ -772,7 +772,7 @@ time_zone::civil_lookup TimeZoneInfo::TimeLocal(const civil_second& cs,
 }
 
 time_zone::absolute_lookup TimeZoneInfo::BreakTime(
-    const time_point<sys_seconds>& tp) const {
+    const time_point<seconds>& tp) const {
   std::int_fast64_t unix_time = ToUnixSeconds(tp);
   const std::size_t timecnt = transitions_.size();
   assert(timecnt != 0);  // We always add a transition.
@@ -788,7 +788,7 @@ time_zone::absolute_lookup TimeZoneInfo::BreakTime(
       const std::int_fast64_t diff =
           unix_time - transitions_[timecnt - 1].unix_time;
       const year_t shift = diff / kSecsPer400Years + 1;
-      const auto d = sys_seconds(shift * kSecsPer400Years);
+      const auto d = seconds(shift * kSecsPer400Years);
       time_zone::absolute_lookup al = BreakTime(tp - d);
       al.cs = YearShift(al.cs, shift * 400);
       return al;
@@ -847,7 +847,7 @@ time_zone::civil_lookup TimeZoneInfo::MakeTime(const civil_second& cs) const {
     if (tr->prev_civil_sec >= cs) {
       // Before first transition, so use the default offset.
       const TransitionType& tt(transition_types_[default_transition_type_]);
-      if (cs < tt.civil_min) return MakeUnique(time_point<sys_seconds>::min());
+      if (cs < tt.civil_min) return MakeUnique(time_point<seconds>::min());
       return MakeUnique(cs - (civil_second() + tt.utc_offset));
     }
     // tr->prev_civil_sec < cs < tr->civil_sec
@@ -864,7 +864,7 @@ time_zone::civil_lookup TimeZoneInfo::MakeTime(const civil_second& cs) const {
         return TimeLocal(YearShift(cs, shift * -400), shift);
       }
       const TransitionType& tt(transition_types_[tr->type_index]);
-      if (cs > tt.civil_max) return MakeUnique(time_point<sys_seconds>::max());
+      if (cs > tt.civil_max) return MakeUnique(time_point<seconds>::max());
       return MakeUnique(tr->unix_time + (cs - tr->civil_sec));
     }
     // tr->civil_sec <= cs <= tr->prev_civil_sec
@@ -895,7 +895,7 @@ std::string TimeZoneInfo::Description() const {
   return oss.str();
 }
 
-bool TimeZoneInfo::NextTransition(time_point<sys_seconds>* tp) const {
+bool TimeZoneInfo::NextTransition(time_point<seconds>* tp) const {
   if (transitions_.empty()) return false;
   const Transition* begin = &transitions_[0];
   const Transition* end = begin + transitions_.size();
@@ -919,7 +919,7 @@ bool TimeZoneInfo::NextTransition(time_point<sys_seconds>* tp) const {
   return true;
 }
 
-bool TimeZoneInfo::PrevTransition(time_point<sys_seconds>* tp) const {
+bool TimeZoneInfo::PrevTransition(time_point<seconds>* tp) const {
   if (transitions_.empty()) return false;
   const Transition* begin = &transitions_[0];
   const Transition* end = begin + transitions_.size();
diff --git a/absl/time/internal/cctz/src/time_zone_info.h b/absl/time/internal/cctz/src/time_zone_info.h
index b4d1696bf61b..d28443e26398 100644
--- a/absl/time/internal/cctz/src/time_zone_info.h
+++ b/absl/time/internal/cctz/src/time_zone_info.h
@@ -71,12 +71,12 @@ class TimeZoneInfo : public TimeZoneIf {
 
   // TimeZoneIf implementations.
   time_zone::absolute_lookup BreakTime(
-      const time_point<sys_seconds>& tp) const override;
+      const time_point<seconds>& tp) const override;
   time_zone::civil_lookup MakeTime(
       const civil_second& cs) const override;
   std::string Description() const override;
-  bool NextTransition(time_point<sys_seconds>* tp) const override;
-  bool PrevTransition(time_point<sys_seconds>* tp) const override;
+  bool NextTransition(time_point<seconds>* tp) const override;
+  bool PrevTransition(time_point<seconds>* tp) const override;
 
  private:
   struct Header {  // counts of:
@@ -98,7 +98,7 @@ class TimeZoneInfo : public TimeZoneIf {
                         std::uint_fast8_t tt2_index) const;
   void ExtendTransitions(const std::string& name, const Header& hdr);
 
-  bool ResetToBuiltinUTC(const sys_seconds& offset);
+  bool ResetToBuiltinUTC(const seconds& offset);
   bool Load(const std::string& name, ZoneInfoSource* zip);
 
   // Helpers for BreakTime() and MakeTime().
diff --git a/absl/time/internal/cctz/src/time_zone_libc.cc b/absl/time/internal/cctz/src/time_zone_libc.cc
index b0b56a522332..1d727bded982 100644
--- a/absl/time/internal/cctz/src/time_zone_libc.cc
+++ b/absl/time/internal/cctz/src/time_zone_libc.cc
@@ -91,7 +91,7 @@ TimeZoneLibC::TimeZoneLibC(const std::string& name)
     : local_(name == "localtime") {}
 
 time_zone::absolute_lookup TimeZoneLibC::BreakTime(
-    const time_point<sys_seconds>& tp) const {
+    const time_point<seconds>& tp) const {
   time_zone::absolute_lookup al;
   std::time_t t = ToUnixSeconds(tp);
   std::tm tm;
@@ -143,11 +143,11 @@ std::string TimeZoneLibC::Description() const {
   return local_ ? "localtime" : "UTC";
 }
 
-bool TimeZoneLibC::NextTransition(time_point<sys_seconds>* tp) const {
+bool TimeZoneLibC::NextTransition(time_point<seconds>* tp) const {
   return false;
 }
 
-bool TimeZoneLibC::PrevTransition(time_point<sys_seconds>* tp) const {
+bool TimeZoneLibC::PrevTransition(time_point<seconds>* tp) const {
   return false;
 }
 
diff --git a/absl/time/internal/cctz/src/time_zone_libc.h b/absl/time/internal/cctz/src/time_zone_libc.h
index 41f7dde2b06a..4c64cd3462f8 100644
--- a/absl/time/internal/cctz/src/time_zone_libc.h
+++ b/absl/time/internal/cctz/src/time_zone_libc.h
@@ -32,12 +32,12 @@ class TimeZoneLibC : public TimeZoneIf {
 
   // TimeZoneIf implementations.
   time_zone::absolute_lookup BreakTime(
-      const time_point<sys_seconds>& tp) const override;
+      const time_point<seconds>& tp) const override;
   time_zone::civil_lookup MakeTime(
       const civil_second& cs) const override;
   std::string Description() const override;
-  bool NextTransition(time_point<sys_seconds>* tp) const override;
-  bool PrevTransition(time_point<sys_seconds>* tp) const override;
+  bool NextTransition(time_point<seconds>* tp) const override;
+  bool PrevTransition(time_point<seconds>* tp) const override;
 
  private:
   const bool local_;  // localtime or UTC
diff --git a/absl/time/internal/cctz/src/time_zone_lookup.cc b/absl/time/internal/cctz/src/time_zone_lookup.cc
index d549d862a769..2f6cd98b9b43 100644
--- a/absl/time/internal/cctz/src/time_zone_lookup.cc
+++ b/absl/time/internal/cctz/src/time_zone_lookup.cc
@@ -65,7 +65,7 @@ std::string time_zone::name() const {
 }
 
 time_zone::absolute_lookup time_zone::lookup(
-    const time_point<sys_seconds>& tp) const {
+    const time_point<seconds>& tp) const {
   return time_zone::Impl::get(*this).BreakTime(tp);
 }
 
@@ -85,7 +85,7 @@ time_zone utc_time_zone() {
   return time_zone::Impl::UTC();  // avoid name lookup
 }
 
-time_zone fixed_time_zone(const sys_seconds& offset) {
+time_zone fixed_time_zone(const seconds& offset) {
   time_zone tz;
   load_time_zone(FixedOffsetToName(offset), &tz);
   return tz;
diff --git a/absl/time/internal/cctz/src/time_zone_lookup_test.cc b/absl/time/internal/cctz/src/time_zone_lookup_test.cc
index 06b172a80323..cd9fc2362752 100644
--- a/absl/time/internal/cctz/src/time_zone_lookup_test.cc
+++ b/absl/time/internal/cctz/src/time_zone_lookup_test.cc
@@ -24,14 +24,7 @@
 #include "absl/time/internal/cctz/include/cctz/civil_time.h"
 #include "gtest/gtest.h"
 
-using std::chrono::time_point_cast;
-using std::chrono::system_clock;
-using std::chrono::nanoseconds;
-using std::chrono::microseconds;
-using std::chrono::milliseconds;
-using std::chrono::seconds;
-using std::chrono::minutes;
-using std::chrono::hours;
+namespace chrono = std::chrono;
 
 namespace absl {
 namespace time_internal {
@@ -715,13 +708,13 @@ TEST(TimeZone, NamedTimeZones) {
   EXPECT_EQ("America/New_York", nyc.name());
   const time_zone syd = LoadZone("Australia/Sydney");
   EXPECT_EQ("Australia/Sydney", syd.name());
-  const time_zone fixed0 = fixed_time_zone(sys_seconds::zero());
+  const time_zone fixed0 = fixed_time_zone(absl::time_internal::cctz::seconds::zero());
   EXPECT_EQ("UTC", fixed0.name());
-  const time_zone fixed_pos =
-      fixed_time_zone(hours(3) + minutes(25) + seconds(45));
+  const time_zone fixed_pos = fixed_time_zone(
+      chrono::hours(3) + chrono::minutes(25) + chrono::seconds(45));
   EXPECT_EQ("Fixed/UTC+03:25:45", fixed_pos.name());
-  const time_zone fixed_neg =
-      fixed_time_zone(-(hours(12) + minutes(34) + seconds(56)));
+  const time_zone fixed_neg = fixed_time_zone(
+      -(chrono::hours(12) + chrono::minutes(34) + chrono::seconds(56)));
   EXPECT_EQ("Fixed/UTC-12:34:56", fixed_neg.name());
 }
 
@@ -731,19 +724,19 @@ TEST(TimeZone, Failures) {
 
   tz = LoadZone("America/Los_Angeles");
   EXPECT_FALSE(load_time_zone("Invalid/TimeZone", &tz));
-  EXPECT_EQ(system_clock::from_time_t(0),
+  EXPECT_EQ(chrono::system_clock::from_time_t(0),
             convert(civil_second(1970, 1, 1, 0, 0, 0), tz));  // UTC
 
   // Ensures that the load still fails on a subsequent attempt.
   tz = LoadZone("America/Los_Angeles");
   EXPECT_FALSE(load_time_zone("Invalid/TimeZone", &tz));
-  EXPECT_EQ(system_clock::from_time_t(0),
+  EXPECT_EQ(chrono::system_clock::from_time_t(0),
             convert(civil_second(1970, 1, 1, 0, 0, 0), tz));  // UTC
 
   // Loading an empty std::string timezone should fail.
   tz = LoadZone("America/Los_Angeles");
   EXPECT_FALSE(load_time_zone("", &tz));
-  EXPECT_EQ(system_clock::from_time_t(0),
+  EXPECT_EQ(chrono::system_clock::from_time_t(0),
             convert(civil_second(1970, 1, 1, 0, 0, 0), tz));  // UTC
 }
 
@@ -758,7 +751,7 @@ TEST(TimeZone, Equality) {
   EXPECT_EQ(implicit_utc, explicit_utc);
   EXPECT_EQ(implicit_utc.name(), explicit_utc.name());
 
-  const time_zone fixed_zero = fixed_time_zone(sys_seconds::zero());
+  const time_zone fixed_zero = fixed_time_zone(absl::time_internal::cctz::seconds::zero());
   EXPECT_EQ(fixed_zero, LoadZone(fixed_zero.name()));
   EXPECT_EQ(fixed_zero, explicit_utc);
 
@@ -766,23 +759,25 @@ TEST(TimeZone, Equality) {
   EXPECT_EQ(fixed_utc, LoadZone(fixed_utc.name()));
   EXPECT_EQ(fixed_utc, explicit_utc);
 
-  const time_zone fixed_pos =
-      fixed_time_zone(hours(3) + minutes(25) + seconds(45));
+  const time_zone fixed_pos = fixed_time_zone(
+      chrono::hours(3) + chrono::minutes(25) + chrono::seconds(45));
   EXPECT_EQ(fixed_pos, LoadZone(fixed_pos.name()));
   EXPECT_NE(fixed_pos, explicit_utc);
-  const time_zone fixed_neg =
-      fixed_time_zone(-(hours(12) + minutes(34) + seconds(56)));
+  const time_zone fixed_neg = fixed_time_zone(
+      -(chrono::hours(12) + chrono::minutes(34) + chrono::seconds(56)));
   EXPECT_EQ(fixed_neg, LoadZone(fixed_neg.name()));
   EXPECT_NE(fixed_neg, explicit_utc);
 
-  const time_zone fixed_lim = fixed_time_zone(hours(24));
+  const time_zone fixed_lim = fixed_time_zone(chrono::hours(24));
   EXPECT_EQ(fixed_lim, LoadZone(fixed_lim.name()));
   EXPECT_NE(fixed_lim, explicit_utc);
-  const time_zone fixed_ovfl = fixed_time_zone(hours(24) + seconds(1));
+  const time_zone fixed_ovfl =
+      fixed_time_zone(chrono::hours(24) + chrono::seconds(1));
   EXPECT_EQ(fixed_ovfl, LoadZone(fixed_ovfl.name()));
   EXPECT_EQ(fixed_ovfl, explicit_utc);
 
-  EXPECT_EQ(fixed_time_zone(seconds(1)), fixed_time_zone(seconds(1)));
+  EXPECT_EQ(fixed_time_zone(chrono::seconds(1)),
+            fixed_time_zone(chrono::seconds(1)));
 
   const time_zone local = local_time_zone();
   EXPECT_EQ(local, LoadZone(local.name()));
@@ -795,40 +790,43 @@ TEST(TimeZone, Equality) {
 TEST(StdChronoTimePoint, TimeTAlignment) {
   // Ensures that the Unix epoch and the system clock epoch are an integral
   // number of seconds apart. This simplifies conversions to/from time_t.
-  auto diff = system_clock::time_point() - system_clock::from_time_t(0);
-  EXPECT_EQ(system_clock::time_point::duration::zero(), diff % seconds(1));
+  auto diff = chrono::system_clock::time_point() -
+              chrono::system_clock::from_time_t(0);
+  EXPECT_EQ(chrono::system_clock::time_point::duration::zero(),
+            diff % chrono::seconds(1));
 }
 
 TEST(BreakTime, TimePointResolution) {
   const time_zone utc = utc_time_zone();
-  const auto t0 = system_clock::from_time_t(0);
+  const auto t0 = chrono::system_clock::from_time_t(0);
 
-  ExpectTime(time_point_cast<nanoseconds>(t0), utc,
+  ExpectTime(chrono::time_point_cast<chrono::nanoseconds>(t0), utc,
              1970, 1, 1, 0, 0, 0, 0, false, "UTC");
-  ExpectTime(time_point_cast<microseconds>(t0), utc,
+  ExpectTime(chrono::time_point_cast<chrono::microseconds>(t0), utc,
              1970, 1, 1, 0, 0, 0, 0, false, "UTC");
-  ExpectTime(time_point_cast<milliseconds>(t0), utc,
+  ExpectTime(chrono::time_point_cast<chrono::milliseconds>(t0), utc,
              1970, 1, 1, 0, 0, 0, 0, false, "UTC");
-  ExpectTime(time_point_cast<seconds>(t0), utc,
+  ExpectTime(chrono::time_point_cast<chrono::seconds>(t0), utc,
              1970, 1, 1, 0, 0, 0, 0, false, "UTC");
-  ExpectTime(time_point_cast<sys_seconds>(t0), utc,
+  ExpectTime(chrono::time_point_cast<absl::time_internal::cctz::seconds>(t0), utc,
              1970, 1, 1, 0, 0, 0, 0, false, "UTC");
-  ExpectTime(time_point_cast<minutes>(t0), utc,
+  ExpectTime(chrono::time_point_cast<chrono::minutes>(t0), utc,
              1970, 1, 1, 0, 0, 0, 0, false, "UTC");
-  ExpectTime(time_point_cast<hours>(t0), utc,
+  ExpectTime(chrono::time_point_cast<chrono::hours>(t0), utc,
              1970, 1, 1, 0, 0, 0, 0, false, "UTC");
 }
 
 TEST(BreakTime, LocalTimeInUTC) {
   const time_zone tz = utc_time_zone();
-  const auto tp = system_clock::from_time_t(0);
+  const auto tp = chrono::system_clock::from_time_t(0);
   ExpectTime(tp, tz, 1970, 1, 1, 0, 0, 0, 0, false, "UTC");
   EXPECT_EQ(weekday::thursday, get_weekday(civil_day(convert(tp, tz))));
 }
 
 TEST(BreakTime, LocalTimeInUTCUnaligned) {
   const time_zone tz = utc_time_zone();
-  const auto tp = system_clock::from_time_t(0) - milliseconds(500);
+  const auto tp =
+      chrono::system_clock::from_time_t(0) - chrono::milliseconds(500);
   ExpectTime(tp, tz, 1969, 12, 31, 23, 59, 59, 0, false, "UTC");
   EXPECT_EQ(weekday::wednesday, get_weekday(civil_day(convert(tp, tz))));
 }
@@ -836,15 +834,16 @@ TEST(BreakTime, LocalTimeInUTCUnaligned) {
 TEST(BreakTime, LocalTimePosix) {
   // See IEEE Std 1003.1-1988 B.2.3 General Terms, Epoch.
   const time_zone tz = utc_time_zone();
-  const auto tp = system_clock::from_time_t(536457599);
+  const auto tp = chrono::system_clock::from_time_t(536457599);
   ExpectTime(tp, tz, 1986, 12, 31, 23, 59, 59, 0, false, "UTC");
   EXPECT_EQ(weekday::wednesday, get_weekday(civil_day(convert(tp, tz))));
 }
 
 TEST(TimeZoneImpl, LocalTimeInFixed) {
-  const sys_seconds offset = -(hours(8) + minutes(33) + seconds(47));
+  const absl::time_internal::cctz::seconds offset =
+      -(chrono::hours(8) + chrono::minutes(33) + chrono::seconds(47));
   const time_zone tz = fixed_time_zone(offset);
-  const auto tp = system_clock::from_time_t(0);
+  const auto tp = chrono::system_clock::from_time_t(0);
   ExpectTime(tp, tz, 1969, 12, 31, 15, 26, 13, offset.count(), false,
              "-083347");
   EXPECT_EQ(weekday::wednesday, get_weekday(civil_day(convert(tp, tz))));
@@ -852,52 +851,52 @@ TEST(TimeZoneImpl, LocalTimeInFixed) {
 
 TEST(BreakTime, LocalTimeInNewYork) {
   const time_zone tz = LoadZone("America/New_York");
-  const auto tp = system_clock::from_time_t(45);
+  const auto tp = chrono::system_clock::from_time_t(45);
   ExpectTime(tp, tz, 1969, 12, 31, 19, 0, 45, -5 * 60 * 60, false, "EST");
   EXPECT_EQ(weekday::wednesday, get_weekday(civil_day(convert(tp, tz))));
 }
 
 TEST(BreakTime, LocalTimeInMTV) {
   const time_zone tz = LoadZone("America/Los_Angeles");
-  const auto tp = system_clock::from_time_t(1380855729);
+  const auto tp = chrono::system_clock::from_time_t(1380855729);
   ExpectTime(tp, tz, 2013, 10, 3, 20, 2, 9, -7 * 60 * 60, true, "PDT");
   EXPECT_EQ(weekday::thursday, get_weekday(civil_day(convert(tp, tz))));
 }
 
 TEST(BreakTime, LocalTimeInSydney) {
   const time_zone tz = LoadZone("Australia/Sydney");
-  const auto tp = system_clock::from_time_t(90);
+  const auto tp = chrono::system_clock::from_time_t(90);
   ExpectTime(tp, tz, 1970, 1, 1, 10, 1, 30, 10 * 60 * 60, false, "AEST");
   EXPECT_EQ(weekday::thursday, get_weekday(civil_day(convert(tp, tz))));
 }
 
 TEST(MakeTime, TimePointResolution) {
   const time_zone utc = utc_time_zone();
-  const time_point<nanoseconds> tp_ns =
+  const time_point<chrono::nanoseconds> tp_ns =
       convert(civil_second(2015, 1, 2, 3, 4, 5), utc);
   EXPECT_EQ("04:05", format("%M:%E*S", tp_ns, utc));
-  const time_point<microseconds> tp_us =
+  const time_point<chrono::microseconds> tp_us =
       convert(civil_second(2015, 1, 2, 3, 4, 5), utc);
   EXPECT_EQ("04:05", format("%M:%E*S", tp_us, utc));
-  const time_point<milliseconds> tp_ms =
+  const time_point<chrono::milliseconds> tp_ms =
       convert(civil_second(2015, 1, 2, 3, 4, 5), utc);
   EXPECT_EQ("04:05", format("%M:%E*S", tp_ms, utc));
-  const time_point<seconds> tp_s =
+  const time_point<chrono::seconds> tp_s =
       convert(civil_second(2015, 1, 2, 3, 4, 5), utc);
   EXPECT_EQ("04:05", format("%M:%E*S", tp_s, utc));
-  const time_point<sys_seconds> tp_s64 =
+  const time_point<absl::time_internal::cctz::seconds> tp_s64 =
       convert(civil_second(2015, 1, 2, 3, 4, 5), utc);
   EXPECT_EQ("04:05", format("%M:%E*S", tp_s64, utc));
 
-  // These next two require time_point_cast because the conversion from a
-  // resolution of seconds (the return value of convert()) to a coarser
-  // resolution requires an explicit cast.
-  const time_point<minutes> tp_m =
-      time_point_cast<minutes>(
+  // These next two require chrono::time_point_cast because the conversion
+  // from a resolution of seconds (the return value of convert()) to a
+  // coarser resolution requires an explicit cast.
+  const time_point<chrono::minutes> tp_m =
+      chrono::time_point_cast<chrono::minutes>(
           convert(civil_second(2015, 1, 2, 3, 4, 5), utc));
   EXPECT_EQ("04:00", format("%M:%E*S", tp_m, utc));
-  const time_point<hours> tp_h =
-      time_point_cast<hours>(
+  const time_point<chrono::hours> tp_h =
+      chrono::time_point_cast<chrono::hours>(
           convert(civil_second(2015, 1, 2, 3, 4, 5), utc));
   EXPECT_EQ("00:00", format("%M:%E*S", tp_h, utc));
 }
@@ -905,7 +904,7 @@ TEST(MakeTime, TimePointResolution) {
 TEST(MakeTime, Normalization) {
   const time_zone tz = LoadZone("America/New_York");
   const auto tp = convert(civil_second(2009, 2, 13, 18, 31, 30), tz);
-  EXPECT_EQ(system_clock::from_time_t(1234567890), tp);
+  EXPECT_EQ(chrono::system_clock::from_time_t(1234567890), tp);
 
   // Now requests for the same time_point but with out-of-range fields.
   EXPECT_EQ(tp, convert(civil_second(2008, 14, 13, 18, 31, 30), tz));  // month
@@ -919,67 +918,67 @@ TEST(MakeTime, Normalization) {
 TEST(MakeTime, SysSecondsLimits) {
   const char RFC3339[] =  "%Y-%m-%dT%H:%M:%S%Ez";
   const time_zone utc = utc_time_zone();
-  const time_zone east = fixed_time_zone(hours(14));
-  const time_zone west = fixed_time_zone(-hours(14));
-  time_point<sys_seconds> tp;
+  const time_zone east = fixed_time_zone(chrono::hours(14));
+  const time_zone west = fixed_time_zone(-chrono::hours(14));
+  time_point<absl::time_internal::cctz::seconds> tp;
 
-  // Approach the maximal time_point<sys_seconds> value from below.
+  // Approach the maximal time_point<cctz::seconds> value from below.
   tp = convert(civil_second(292277026596, 12, 4, 15, 30, 6), utc);
   EXPECT_EQ("292277026596-12-04T15:30:06+00:00", format(RFC3339, tp, utc));
   tp = convert(civil_second(292277026596, 12, 4, 15, 30, 7), utc);
   EXPECT_EQ("292277026596-12-04T15:30:07+00:00", format(RFC3339, tp, utc));
-  EXPECT_EQ(time_point<sys_seconds>::max(), tp);
+  EXPECT_EQ(time_point<absl::time_internal::cctz::seconds>::max(), tp);
   tp = convert(civil_second(292277026596, 12, 4, 15, 30, 8), utc);
-  EXPECT_EQ(time_point<sys_seconds>::max(), tp);
+  EXPECT_EQ(time_point<absl::time_internal::cctz::seconds>::max(), tp);
   tp = convert(civil_second::max(), utc);
-  EXPECT_EQ(time_point<sys_seconds>::max(), tp);
+  EXPECT_EQ(time_point<absl::time_internal::cctz::seconds>::max(), tp);
 
   // Checks that we can also get the maximal value for a far-east zone.
   tp = convert(civil_second(292277026596, 12, 5, 5, 30, 7), east);
   EXPECT_EQ("292277026596-12-05T05:30:07+14:00", format(RFC3339, tp, east));
-  EXPECT_EQ(time_point<sys_seconds>::max(), tp);
+  EXPECT_EQ(time_point<absl::time_internal::cctz::seconds>::max(), tp);
   tp = convert(civil_second(292277026596, 12, 5, 5, 30, 8), east);
-  EXPECT_EQ(time_point<sys_seconds>::max(), tp);
+  EXPECT_EQ(time_point<absl::time_internal::cctz::seconds>::max(), tp);
   tp = convert(civil_second::max(), east);
-  EXPECT_EQ(time_point<sys_seconds>::max(), tp);
+  EXPECT_EQ(time_point<absl::time_internal::cctz::seconds>::max(), tp);
 
   // Checks that we can also get the maximal value for a far-west zone.
   tp = convert(civil_second(292277026596, 12, 4, 1, 30, 7), west);
   EXPECT_EQ("292277026596-12-04T01:30:07-14:00", format(RFC3339, tp, west));
-  EXPECT_EQ(time_point<sys_seconds>::max(), tp);
+  EXPECT_EQ(time_point<absl::time_internal::cctz::seconds>::max(), tp);
   tp = convert(civil_second(292277026596, 12, 4, 7, 30, 8), west);
-  EXPECT_EQ(time_point<sys_seconds>::max(), tp);
+  EXPECT_EQ(time_point<absl::time_internal::cctz::seconds>::max(), tp);
   tp = convert(civil_second::max(), west);
-  EXPECT_EQ(time_point<sys_seconds>::max(), tp);
+  EXPECT_EQ(time_point<absl::time_internal::cctz::seconds>::max(), tp);
 
-  // Approach the minimal time_point<sys_seconds> value from above.
+  // Approach the minimal time_point<cctz::seconds> value from above.
   tp = convert(civil_second(-292277022657, 1, 27, 8, 29, 53), utc);
   EXPECT_EQ("-292277022657-01-27T08:29:53+00:00", format(RFC3339, tp, utc));
   tp = convert(civil_second(-292277022657, 1, 27, 8, 29, 52), utc);
   EXPECT_EQ("-292277022657-01-27T08:29:52+00:00", format(RFC3339, tp, utc));
-  EXPECT_EQ(time_point<sys_seconds>::min(), tp);
+  EXPECT_EQ(time_point<absl::time_internal::cctz::seconds>::min(), tp);
   tp = convert(civil_second(-292277022657, 1, 27, 8, 29, 51), utc);
-  EXPECT_EQ(time_point<sys_seconds>::min(), tp);
+  EXPECT_EQ(time_point<absl::time_internal::cctz::seconds>::min(), tp);
   tp = convert(civil_second::min(), utc);
-  EXPECT_EQ(time_point<sys_seconds>::min(), tp);
+  EXPECT_EQ(time_point<absl::time_internal::cctz::seconds>::min(), tp);
 
   // Checks that we can also get the minimal value for a far-east zone.
   tp = convert(civil_second(-292277022657, 1, 27, 22, 29, 52), east);
   EXPECT_EQ("-292277022657-01-27T22:29:52+14:00", format(RFC3339, tp, east));
-  EXPECT_EQ(time_point<sys_seconds>::min(), tp);
+  EXPECT_EQ(time_point<absl::time_internal::cctz::seconds>::min(), tp);
   tp = convert(civil_second(-292277022657, 1, 27, 22, 29, 51), east);
-  EXPECT_EQ(time_point<sys_seconds>::min(), tp);
+  EXPECT_EQ(time_point<absl::time_internal::cctz::seconds>::min(), tp);
   tp = convert(civil_second::min(), east);
-  EXPECT_EQ(time_point<sys_seconds>::min(), tp);
+  EXPECT_EQ(time_point<absl::time_internal::cctz::seconds>::min(), tp);
 
   // Checks that we can also get the minimal value for a far-west zone.
   tp = convert(civil_second(-292277022657, 1, 26, 18, 29, 52), west);
   EXPECT_EQ("-292277022657-01-26T18:29:52-14:00", format(RFC3339, tp, west));
-  EXPECT_EQ(time_point<sys_seconds>::min(), tp);
+  EXPECT_EQ(time_point<absl::time_internal::cctz::seconds>::min(), tp);
   tp = convert(civil_second(-292277022657, 1, 26, 18, 29, 51), west);
-  EXPECT_EQ(time_point<sys_seconds>::min(), tp);
+  EXPECT_EQ(time_point<absl::time_internal::cctz::seconds>::min(), tp);
   tp = convert(civil_second::min(), west);
-  EXPECT_EQ(time_point<sys_seconds>::min(), tp);
+  EXPECT_EQ(time_point<absl::time_internal::cctz::seconds>::min(), tp);
 }
 
 TEST(TimeZoneEdgeCase, AmericaNewYork) {
@@ -988,13 +987,13 @@ TEST(TimeZoneEdgeCase, AmericaNewYork) {
   // Spring 1:59:59 -> 3:00:00
   auto tp = convert(civil_second(2013, 3, 10, 1, 59, 59), tz);
   ExpectTime(tp, tz, 2013, 3, 10, 1, 59, 59, -5 * 3600, false, "EST");
-  tp += seconds(1);
+  tp += absl::time_internal::cctz::seconds(1);
   ExpectTime(tp, tz, 2013, 3, 10, 3, 0, 0, -4 * 3600, true, "EDT");
 
   // Fall 1:59:59 -> 1:00:00
   tp = convert(civil_second(2013, 11, 3, 1, 59, 59), tz);
   ExpectTime(tp, tz, 2013, 11, 3, 1, 59, 59, -4 * 3600, true, "EDT");
-  tp += seconds(1);
+  tp += absl::time_internal::cctz::seconds(1);
   ExpectTime(tp, tz, 2013, 11, 3, 1, 0, 0, -5 * 3600, false, "EST");
 }
 
@@ -1004,13 +1003,13 @@ TEST(TimeZoneEdgeCase, AmericaLosAngeles) {
   // Spring 1:59:59 -> 3:00:00
   auto tp = convert(civil_second(2013, 3, 10, 1, 59, 59), tz);
   ExpectTime(tp, tz, 2013, 3, 10, 1, 59, 59, -8 * 3600, false, "PST");
-  tp += seconds(1);
+  tp += absl::time_internal::cctz::seconds(1);
   ExpectTime(tp, tz, 2013, 3, 10, 3, 0, 0, -7 * 3600, true, "PDT");
 
   // Fall 1:59:59 -> 1:00:00
   tp = convert(civil_second(2013, 11, 3, 1, 59, 59), tz);
   ExpectTime(tp, tz, 2013, 11, 3, 1, 59, 59, -7 * 3600, true, "PDT");
-  tp += seconds(1);
+  tp += absl::time_internal::cctz::seconds(1);
   ExpectTime(tp, tz, 2013, 11, 3, 1, 0, 0, -8 * 3600, false, "PST");
 }
 
@@ -1020,13 +1019,13 @@ TEST(TimeZoneEdgeCase, ArizonaNoTransition) {
   // No transition in Spring.
   auto tp = convert(civil_second(2013, 3, 10, 1, 59, 59), tz);
   ExpectTime(tp, tz, 2013, 3, 10, 1, 59, 59, -7 * 3600, false, "MST");
-  tp += seconds(1);
+  tp += absl::time_internal::cctz::seconds(1);
   ExpectTime(tp, tz, 2013, 3, 10, 2, 0, 0, -7 * 3600, false, "MST");
 
   // No transition in Fall.
   tp = convert(civil_second(2013, 11, 3, 1, 59, 59), tz);
   ExpectTime(tp, tz, 2013, 11, 3, 1, 59, 59, -7 * 3600, false, "MST");
-  tp += seconds(1);
+  tp += absl::time_internal::cctz::seconds(1);
   ExpectTime(tp, tz, 2013, 11, 3, 2, 0, 0, -7 * 3600, false, "MST");
 }
 
@@ -1039,7 +1038,7 @@ TEST(TimeZoneEdgeCase, AsiaKathmandu) {
   //   504901800 == Wed,  1 Jan 1986 00:15:00 +0545 (+0545)
   auto tp = convert(civil_second(1985, 12, 31, 23, 59, 59), tz);
   ExpectTime(tp, tz, 1985, 12, 31, 23, 59, 59, 5.5 * 3600, false, "+0530");
-  tp += seconds(1);
+  tp += absl::time_internal::cctz::seconds(1);
   ExpectTime(tp, tz, 1986, 1, 1, 0, 15, 0, 5.75 * 3600, false, "+0545");
 }
 
@@ -1052,14 +1051,14 @@ TEST(TimeZoneEdgeCase, PacificChatham) {
   //   1365256800 == Sun,  7 Apr 2013 02:45:00 +1245 (+1245)
   auto tp = convert(civil_second(2013, 4, 7, 3, 44, 59), tz);
   ExpectTime(tp, tz, 2013, 4, 7, 3, 44, 59, 13.75 * 3600, true, "+1345");
-  tp += seconds(1);
+  tp += absl::time_internal::cctz::seconds(1);
   ExpectTime(tp, tz, 2013, 4, 7, 2, 45, 0, 12.75 * 3600, false, "+1245");
 
   //   1380376799 == Sun, 29 Sep 2013 02:44:59 +1245 (+1245)
   //   1380376800 == Sun, 29 Sep 2013 03:45:00 +1345 (+1345)
   tp = convert(civil_second(2013, 9, 29, 2, 44, 59), tz);
   ExpectTime(tp, tz, 2013, 9, 29, 2, 44, 59, 12.75 * 3600, false, "+1245");
-  tp += seconds(1);
+  tp += absl::time_internal::cctz::seconds(1);
   ExpectTime(tp, tz, 2013, 9, 29, 3, 45, 0, 13.75 * 3600, true, "+1345");
 }
 
@@ -1072,14 +1071,14 @@ TEST(TimeZoneEdgeCase, AustraliaLordHowe) {
   //   1365260400 == Sun,  7 Apr 2013 01:30:00 +1030 (+1030)
   auto tp = convert(civil_second(2013, 4, 7, 1, 59, 59), tz);
   ExpectTime(tp, tz, 2013, 4, 7, 1, 59, 59, 11 * 3600, true, "+11");
-  tp += seconds(1);
+  tp += absl::time_internal::cctz::seconds(1);
   ExpectTime(tp, tz, 2013, 4, 7, 1, 30, 0, 10.5 * 3600, false, "+1030");
 
   //   1380986999 == Sun,  6 Oct 2013 01:59:59 +1030 (+1030)
   //   1380987000 == Sun,  6 Oct 2013 02:30:00 +1100 (+11)
   tp = convert(civil_second(2013, 10, 6, 1, 59, 59), tz);
   ExpectTime(tp, tz, 2013, 10, 6, 1, 59, 59, 10.5 * 3600, false, "+1030");
-  tp += seconds(1);
+  tp += absl::time_internal::cctz::seconds(1);
   ExpectTime(tp, tz, 2013, 10, 6, 2, 30, 0, 11 * 3600, true, "+11");
 }
 
@@ -1097,7 +1096,7 @@ TEST(TimeZoneEdgeCase, PacificApia) {
   auto tp = convert(civil_second(2011, 12, 29, 23, 59, 59), tz);
   ExpectTime(tp, tz, 2011, 12, 29, 23, 59, 59, -10 * 3600, true, "-10");
   EXPECT_EQ(363, get_yearday(civil_day(convert(tp, tz))));
-  tp += seconds(1);
+  tp += absl::time_internal::cctz::seconds(1);
   ExpectTime(tp, tz, 2011, 12, 31, 0, 0, 0, 14 * 3600, true, "+14");
   EXPECT_EQ(365, get_yearday(civil_day(convert(tp, tz))));
 }
@@ -1114,7 +1113,7 @@ TEST(TimeZoneEdgeCase, AfricaCairo) {
   //   1400191200 == Fri, 16 May 2014 01:00:00 +0300 (EEST)
   auto tp = convert(civil_second(2014, 5, 15, 23, 59, 59), tz);
   ExpectTime(tp, tz, 2014, 5, 15, 23, 59, 59, 2 * 3600, false, "EET");
-  tp += seconds(1);
+  tp += absl::time_internal::cctz::seconds(1);
   ExpectTime(tp, tz, 2014, 5, 16, 1, 0, 0, 3 * 3600, true, "EEST");
 #endif
 }
@@ -1131,7 +1130,7 @@ TEST(TimeZoneEdgeCase, AfricaMonrovia) {
   //   63593070 == Fri,  7 Jan 1972 00:44:30 +0000 (GMT)
   auto tp = convert(civil_second(1972, 1, 6, 23, 59, 59), tz);
   ExpectTime(tp, tz, 1972, 1, 6, 23, 59, 59, -44.5 * 60, false, "MMT");
-  tp += seconds(1);
+  tp += absl::time_internal::cctz::seconds(1);
   ExpectTime(tp, tz, 1972, 1, 7, 0, 44, 30, 0 * 60, false, "GMT");
 #endif
 }
@@ -1159,7 +1158,7 @@ TEST(TimeZoneEdgeCase, AmericaJamaica) {
   tp = convert(civil_second(1889, 12, 31, 23, 59, 59), tz);
   ExpectTime(tp, tz, 1889, 12, 31, 23, 59, 59, -18430, false,
              tz.lookup(tp).abbr);
-  tp += seconds(1);
+  tp += absl::time_internal::cctz::seconds(1);
   ExpectTime(tp, tz, 1890, 1, 1, 0, 0, 0, -18430, false, "KMT");
 #endif
 
@@ -1168,7 +1167,7 @@ TEST(TimeZoneEdgeCase, AmericaJamaica) {
   //     436341600 == Sun, 30 Oct 1983 01:00:00 -0500 (EST)
   tp = convert(civil_second(1983, 10, 30, 1, 59, 59), tz);
   ExpectTime(tp, tz, 1983, 10, 30, 1, 59, 59, -4 * 3600, true, "EDT");
-  tp += seconds(1);
+  tp += absl::time_internal::cctz::seconds(1);
   ExpectTime(tp, tz, 1983, 10, 30, 1, 0, 0, -5 * 3600, false, "EST");
 
   // After the last transition.
@@ -1189,7 +1188,7 @@ TEST(TimeZoneEdgeCase, WET) {
   //     228877200 == Sun,  3 Apr 1977 02:00:00 +0100 (WEST)
   tp = convert(civil_second(1977, 4, 3, 0, 59, 59), tz);
   ExpectTime(tp, tz, 1977, 4, 3, 0, 59, 59, 0, false, "WET");
-  tp += seconds(1);
+  tp += absl::time_internal::cctz::seconds(1);
   ExpectTime(tp, tz, 1977, 4, 3, 2, 0, 0, 1 * 3600, true, "WEST");
 
   // A non-existent time within the first transition.
@@ -1211,12 +1210,12 @@ TEST(TimeZoneEdgeCase, FixedOffsets) {
   const time_zone gmtm5 = LoadZone("Etc/GMT+5");  // -0500
   auto tp = convert(civil_second(1970, 1, 1, 0, 0, 0), gmtm5);
   ExpectTime(tp, gmtm5, 1970, 1, 1, 0, 0, 0, -5 * 3600, false, "-05");
-  EXPECT_EQ(system_clock::from_time_t(5 * 3600), tp);
+  EXPECT_EQ(chrono::system_clock::from_time_t(5 * 3600), tp);
 
   const time_zone gmtp5 = LoadZone("Etc/GMT-5");  // +0500
   tp = convert(civil_second(1970, 1, 1, 0, 0, 0), gmtp5);
   ExpectTime(tp, gmtp5, 1970, 1, 1, 0, 0, 0, 5 * 3600, false, "+05");
-  EXPECT_EQ(system_clock::from_time_t(-5 * 3600), tp);
+  EXPECT_EQ(chrono::system_clock::from_time_t(-5 * 3600), tp);
 }
 
 TEST(TimeZoneEdgeCase, NegativeYear) {
@@ -1225,7 +1224,7 @@ TEST(TimeZoneEdgeCase, NegativeYear) {
   auto tp = convert(civil_second(0, 1, 1, 0, 0, 0), tz);
   ExpectTime(tp, tz, 0, 1, 1, 0, 0, 0, 0 * 3600, false, "UTC");
   EXPECT_EQ(weekday::saturday, get_weekday(civil_day(convert(tp, tz))));
-  tp -= seconds(1);
+  tp -= absl::time_internal::cctz::seconds(1);
   ExpectTime(tp, tz, -1, 12, 31, 23, 59, 59, 0 * 3600, false, "UTC");
   EXPECT_EQ(weekday::friday, get_weekday(civil_day(convert(tp, tz))));
 }
@@ -1239,7 +1238,7 @@ TEST(TimeZoneEdgeCase, UTC32bitLimit) {
   //   2147483648 == Tue, 19 Jan 2038 03:14:08 +0000 (UTC)
   auto tp = convert(civil_second(2038, 1, 19, 3, 14, 7), tz);
   ExpectTime(tp, tz, 2038, 1, 19, 3, 14, 7, 0 * 3600, false, "UTC");
-  tp += seconds(1);
+  tp += absl::time_internal::cctz::seconds(1);
   ExpectTime(tp, tz, 2038, 1, 19, 3, 14, 8, 0 * 3600, false, "UTC");
 }
 
@@ -1252,7 +1251,7 @@ TEST(TimeZoneEdgeCase, UTC5DigitYear) {
   //   253402300800 == Sat,  1 Jan 1000 00:00:00 +0000 (UTC)
   auto tp = convert(civil_second(9999, 12, 31, 23, 59, 59), tz);
   ExpectTime(tp, tz, 9999, 12, 31, 23, 59, 59, 0 * 3600, false, "UTC");
-  tp += seconds(1);
+  tp += absl::time_internal::cctz::seconds(1);
   ExpectTime(tp, tz, 10000, 1, 1, 0, 0, 0, 0 * 3600, false, "UTC");
 }
 
diff --git a/absl/time/internal/cctz/src/zone_info_source.cc b/absl/time/internal/cctz/src/zone_info_source.cc
index b77c0a585a12..ee7500b64f28 100644
--- a/absl/time/internal/cctz/src/zone_info_source.cc
+++ b/absl/time/internal/cctz/src/zone_info_source.cc
@@ -60,9 +60,17 @@ ZoneInfoSourceFactory default_factory = DefaultFactory;
 #else
 #error Unsupported MSVC platform
 #endif
-#else
+#else  // _MSC_VER
+#if !defined(__has_attribute)
+#define __has_attribute(x) 0
+#endif
+#if __has_attribute(weak) || defined(__GNUC__)
 ZoneInfoSourceFactory zone_info_source_factory
     __attribute__((weak)) = DefaultFactory;
+#else
+// Make it a "strong" definition if we have no other choice.
+ZoneInfoSourceFactory zone_info_source_factory = DefaultFactory;
+#endif
 #endif  // _MSC_VER
 
 }  // namespace cctz_extension
diff --git a/absl/time/time.cc b/absl/time/time.cc
index 03720f62b007..71fd8ee6e743 100644
--- a/absl/time/time.cc
+++ b/absl/time/time.cc
@@ -44,8 +44,8 @@ namespace absl {
 
 namespace {
 
-inline cctz::time_point<cctz::sys_seconds> unix_epoch() {
-  return std::chrono::time_point_cast<cctz::sys_seconds>(
+inline cctz::time_point<cctz::seconds> unix_epoch() {
+  return std::chrono::time_point_cast<cctz::seconds>(
       std::chrono::system_clock::from_time_t(0));
 }
 
@@ -110,12 +110,12 @@ inline TimeConversion InfinitePastTimeConversion() {
 
 // Makes a Time from sec, overflowing to InfiniteFuture/InfinitePast as
 // necessary. If sec is min/max, then consult cs+tz to check for overlow.
-Time MakeTimeWithOverflow(const cctz::time_point<cctz::sys_seconds>& sec,
+Time MakeTimeWithOverflow(const cctz::time_point<cctz::seconds>& sec,
                           const cctz::civil_second& cs,
                           const cctz::time_zone& tz,
                           bool* normalized = nullptr) {
-  const auto max = cctz::time_point<cctz::sys_seconds>::max();
-  const auto min = cctz::time_point<cctz::sys_seconds>::min();
+  const auto max = cctz::time_point<cctz::seconds>::max();
+  const auto min = cctz::time_point<cctz::seconds>::min();
   if (sec == max) {
     const auto al = tz.lookup(max);
     if (cs > al.cs) {
@@ -174,8 +174,7 @@ absl::Time::Breakdown Time::In(absl::TimeZone tz) const {
   if (*this == absl::InfiniteFuture()) return absl::InfiniteFutureBreakdown();
   if (*this == absl::InfinitePast()) return absl::InfinitePastBreakdown();
 
-  const auto tp =
-      unix_epoch() + cctz::sys_seconds(time_internal::GetRepHi(rep_));
+  const auto tp = unix_epoch() + cctz::seconds(time_internal::GetRepHi(rep_));
   const auto al = cctz::time_zone(tz).lookup(tp);
   const auto cs = al.cs;
   const auto cd = cctz::civil_day(cs);
diff --git a/absl/time/time_zone_test.cc b/absl/time/time_zone_test.cc
index 7138560ac2d1..43d919040445 100644
--- a/absl/time/time_zone_test.cc
+++ b/absl/time/time_zone_test.cc
@@ -59,7 +59,7 @@ TEST(TimeZone, DefaultTimeZones) {
 
 TEST(TimeZone, FixedTimeZone) {
   const absl::TimeZone tz = absl::FixedTimeZone(123);
-  const cctz::time_zone cz = cctz::fixed_time_zone(cctz::sys_seconds(123));
+  const cctz::time_zone cz = cctz::fixed_time_zone(cctz::seconds(123));
   EXPECT_EQ(tz, absl::TimeZone(cz));
 }
 
diff --git a/absl/types/internal/variant.h b/absl/types/internal/variant.h
index 3414c9142daf..7db5e0535f68 100644
--- a/absl/types/internal/variant.h
+++ b/absl/types/internal/variant.h
@@ -579,12 +579,9 @@ struct VariantCoreAccess {
     self.index_ = other.index();
   }
 
+  // Access a variant alternative, assuming the index is correct.
   template <std::size_t I, class Variant>
   static VariantAccessResult<I, Variant> Access(Variant&& self) {
-    if (ABSL_PREDICT_FALSE(self.index_ != I)) {
-      TypedThrowBadVariantAccess<VariantAccessResult<I, Variant>>();
-    }
-
     // This cast instead of invocation of AccessUnion with an rvalue is a
     // workaround for msvc. Without this there is a runtime failure when dealing
     // with rvalues.
@@ -593,6 +590,16 @@ struct VariantCoreAccess {
         variant_internal::AccessUnion(self.state_, SizeT<I>()));
   }
 
+  // Access a variant alternative, throwing if the index is incorrect.
+  template <std::size_t I, class Variant>
+  static VariantAccessResult<I, Variant> CheckedAccess(Variant&& self) {
+    if (ABSL_PREDICT_FALSE(self.index_ != I)) {
+      TypedThrowBadVariantAccess<VariantAccessResult<I, Variant>>();
+    }
+
+    return Access<I>(absl::forward<Variant>(self));
+  }
+
   // The implementation of the move-assignment operation for a variant.
   template <class VType>
   struct MoveAssignVisitor {
diff --git a/absl/types/variant.h b/absl/types/variant.h
index 55017ae194b0..fd1d49ac890a 100644
--- a/absl/types/variant.h
+++ b/absl/types/variant.h
@@ -290,7 +290,7 @@ constexpr bool holds_alternative(const variant<Types...>& v) noexcept {
 // Overload for getting a variant's lvalue by type.
 template <class T, class... Types>
 constexpr T& get(variant<Types...>& v) {  // NOLINT
-  return variant_internal::VariantCoreAccess::Access<
+  return variant_internal::VariantCoreAccess::CheckedAccess<
       variant_internal::IndexOf<T, Types...>::value>(v);
 }
 
@@ -298,14 +298,14 @@ constexpr T& get(variant<Types...>& v) {  // NOLINT
 // Note: `absl::move()` is required to allow use of constexpr in C++11.
 template <class T, class... Types>
 constexpr T&& get(variant<Types...>&& v) {
-  return variant_internal::VariantCoreAccess::Access<
+  return variant_internal::VariantCoreAccess::CheckedAccess<
       variant_internal::IndexOf<T, Types...>::value>(absl::move(v));
 }
 
 // Overload for getting a variant's const lvalue by type.
 template <class T, class... Types>
 constexpr const T& get(const variant<Types...>& v) {
-  return variant_internal::VariantCoreAccess::Access<
+  return variant_internal::VariantCoreAccess::CheckedAccess<
       variant_internal::IndexOf<T, Types...>::value>(v);
 }
 
@@ -313,7 +313,7 @@ constexpr const T& get(const variant<Types...>& v) {
 // Note: `absl::move()` is required to allow use of constexpr in C++11.
 template <class T, class... Types>
 constexpr const T&& get(const variant<Types...>&& v) {
-  return variant_internal::VariantCoreAccess::Access<
+  return variant_internal::VariantCoreAccess::CheckedAccess<
       variant_internal::IndexOf<T, Types...>::value>(absl::move(v));
 }
 
@@ -321,7 +321,7 @@ constexpr const T&& get(const variant<Types...>&& v) {
 template <std::size_t I, class... Types>
 constexpr variant_alternative_t<I, variant<Types...>>& get(
     variant<Types...>& v) {  // NOLINT
-  return variant_internal::VariantCoreAccess::Access<I>(v);
+  return variant_internal::VariantCoreAccess::CheckedAccess<I>(v);
 }
 
 // Overload for getting a variant's rvalue by index.
@@ -329,14 +329,14 @@ constexpr variant_alternative_t<I, variant<Types...>>& get(
 template <std::size_t I, class... Types>
 constexpr variant_alternative_t<I, variant<Types...>>&& get(
     variant<Types...>&& v) {
-  return variant_internal::VariantCoreAccess::Access<I>(absl::move(v));
+  return variant_internal::VariantCoreAccess::CheckedAccess<I>(absl::move(v));
 }
 
 // Overload for getting a variant's const lvalue by index.
 template <std::size_t I, class... Types>
 constexpr const variant_alternative_t<I, variant<Types...>>& get(
     const variant<Types...>& v) {
-  return variant_internal::VariantCoreAccess::Access<I>(v);
+  return variant_internal::VariantCoreAccess::CheckedAccess<I>(v);
 }
 
 // Overload for getting a variant's const rvalue by index.
@@ -344,7 +344,7 @@ constexpr const variant_alternative_t<I, variant<Types...>>& get(
 template <std::size_t I, class... Types>
 constexpr const variant_alternative_t<I, variant<Types...>>&& get(
     const variant<Types...>&& v) {
-  return variant_internal::VariantCoreAccess::Access<I>(absl::move(v));
+  return variant_internal::VariantCoreAccess::CheckedAccess<I>(absl::move(v));
 }
 
 // get_if()
@@ -362,8 +362,10 @@ constexpr const variant_alternative_t<I, variant<Types...>>&& get(
 template <std::size_t I, class... Types>
 constexpr absl::add_pointer_t<variant_alternative_t<I, variant<Types...>>>
 get_if(variant<Types...>* v) noexcept {
-  return (v != nullptr && v->index() == I) ? std::addressof(absl::get<I>(*v))
-                                           : nullptr;
+  return (v != nullptr && v->index() == I)
+             ? std::addressof(
+                   variant_internal::VariantCoreAccess::Access<I>(*v))
+             : nullptr;
 }
 
 // Overload for getting a pointer to the const value stored in the given
@@ -371,8 +373,10 @@ get_if(variant<Types...>* v) noexcept {
 template <std::size_t I, class... Types>
 constexpr absl::add_pointer_t<const variant_alternative_t<I, variant<Types...>>>
 get_if(const variant<Types...>* v) noexcept {
-  return (v != nullptr && v->index() == I) ? std::addressof(absl::get<I>(*v))
-                                           : nullptr;
+  return (v != nullptr && v->index() == I)
+             ? std::addressof(
+                   variant_internal::VariantCoreAccess::Access<I>(*v))
+             : nullptr;
 }
 
 // Overload for getting a pointer to the value stored in the given variant by