Fix overflow in time_point formatting with large dates
This fixes #3725. I am not 100% sure the new implementation is what people would want; in particular, with `FMT_SAFE_DURATION_CAST` enabled, trying to format a date that is too large to fit in `time_t` will throw an exception. Without `FMT_SAFE_DURATION_CAST` I believe this will just invoke an undefined behavior (overflow of `time_t`, which is likely a signed integer type).
{"label": "fmtlib:master", "ref": "master", "sha": "06f1c0d725855861535e9e65cd4d502aca7c61ed"}
`std::chrono::system_clock::time_point` on linux platforms uses nanosecond resolution and a 64 bit signed counter, hence is unable to represent dates beyond year 2262. This can be circumvented by using a custom `time_point` with a lower time resolution when doing time calculations, such as
```c++
using my_time_point = std::chrono::time_point<std::chrono::system_clock, std::chrono::milliseconds>;
```

Unfortunately, it seems that fmtlib is unable to correctly format such time points despite the lower time resolution. Here is an example that reproduces the problem:

```c++
#include <chrono>
#include <iostream>
#include <fmt/chrono.h>

#if defined(__cpp_lib_format) && __cpp_lib_format >= 202106L
#define HAS_STD_FORMAT
#endif

#if defined(HAS_STD_FORMAT)
#include <format>
#endif

int main() {
    std::cout << "fmt version: " << FMT_VERSION << "\n";

    using TimePointBad = std::chrono::system_clock::time_point;
    auto timeBad = TimePointBad{} + std::chrono::years{1030};
    std::cout << "bad time_point years:  " <<
        1970 + std::chrono::duration_cast<std::chrono::years>(timeBad.time_since_epoch()).count() << "\n";
    std::cout << "fmt::format:           " << fmt::format("{:%F %T}", timeBad) << "\n";
    #if defined(HAS_STD_FORMAT)
    std::cout << "std::format:           " << std::format("{:%F %T}", timeBad) << "\n";
    #else
    std::cout << "std::format:           not available\n";
    #endif

    using TimePointGood = std::chrono::time_point<std::chrono::system_clock, std::chrono::milliseconds>;
    auto timeGood = TimePointGood{} + std::chrono::years{1030};
    std::cout << "good time_point years: " <<
        1970 + std::chrono::duration_cast<std::chrono::years>(timeGood.time_since_epoch()).count() << "\n";
    std::cout << "fmt::format:           " << fmt::format("{:%F %T}", timeGood) << "\n";
    #if defined(HAS_STD_FORMAT)
    std::cout << "std::format:           " << std::format("{:%F %T}", timeGood) << "\n";
    #else
    std::cout << "std::format:           not available\n";
    #endif

    return 0;
}
```

Output from latest master commit on my system (Ubuntu GCC 11.4; doesn't have `<format>`):
```
fmt version: 100101
bad time_point years:  1831
fmt::format:           1830-11-22 19:26:52.580896768
std::format:           not available
good time_point years: 3000
fmt::format:           1830-11-22 19:26:53.000
std::format:           not available
```

Output lines 2 and 3 show the problem of the standard `std::chrono::system_clock::time_point`; this is the output I expect.

Output line 5 shows that, using `TimePointGood` with millisecond resolution, the time point itself is able to represent dates far in the future.

Output line 6 shows that fmtlib still overflows.

[Godbolt link](https://godbolt.org/z/4MYETo4qo); this is running an older version of fmtlib but it also affected. Using gcc trunk with `std::format` shows that the standard `format` doesn't have the issue and outputs what one would expect:
```
fmt version: 90100
bad time_point years:  1831
fmt::format:           1830-11-22 19:26:53
std::format:           1830-11-22 19:26:52.580896768
good time_point years: 3000
fmt::format:           1830-11-22 19:26:53
std::format:           2999-12-31 18:36:00.000
```

Date overflow with std::chrono::time_point
diff --git a/include/fmt/chrono.h b/include/fmt/chrono.h
index 7bd4206d7528..3d2007bb5cfb 100644
--- a/include/fmt/chrono.h
+++ b/include/fmt/chrono.h
@@ -430,6 +430,51 @@ auto write(OutputIt out, const std::tm& time, const std::locale& loc,
   return write_encoded_tm_str(out, string_view(buf.data(), buf.size()), loc);
 }
 
+template <typename Rep1, typename Rep2>
+struct is_same_arithmetic_type
+    : public std::integral_constant<bool,
+                                    (std::is_integral<Rep1>::value &&
+                                     std::is_integral<Rep2>::value) ||
+                                        (std::is_floating_point<Rep1>::value &&
+                                         std::is_floating_point<Rep2>::value)> {
+};
+
+template <
+    typename To, typename FromRep, typename FromPeriod,
+    FMT_ENABLE_IF(is_same_arithmetic_type<FromRep, typename To::rep>::value)>
+To fmt_duration_cast(std::chrono::duration<FromRep, FromPeriod> from) {
+#if FMT_SAFE_DURATION_CAST
+  // throwing version of safe_duration_cast
+  // only available for integer<->integer or float<->float casts
+  int ec;
+  To to = safe_duration_cast::safe_duration_cast<To>(from, ec);
+  if (ec) FMT_THROW(format_error("cannot format duration"));
+  return to;
+#else
+  // standard duration cast, may overflow and invoke undefined behavior
+  return std::chrono::duration_cast<To>(from);
+#endif
+}
+
+template <
+    typename To, typename FromRep, typename FromPeriod,
+    FMT_ENABLE_IF(!is_same_arithmetic_type<FromRep, typename To::rep>::value)>
+To fmt_duration_cast(std::chrono::duration<FromRep, FromPeriod> from) {
+  // mixed integer<->float cast is not supported with safe_duration_cast
+  // fallback to standard duration cast in this case
+  return std::chrono::duration_cast<To>(from);
+}
+
+template <typename Duration>
+std::time_t to_time_t(
+    std::chrono::time_point<std::chrono::system_clock, Duration> time_point) {
+  // cannot use std::chrono::system_clock::to_time_t() since this would first
+  // require a cast to std::chrono::system_clock::time_point, which could
+  // overflow.
+  return fmt_duration_cast<std::chrono::duration<std::time_t>>(
+             time_point.time_since_epoch())
+      .count();
+}
 }  // namespace detail
 
 FMT_BEGIN_EXPORT
@@ -478,8 +523,8 @@ inline std::tm localtime(std::time_t time) {
 #if FMT_USE_LOCAL_TIME
 template <typename Duration>
 inline auto localtime(std::chrono::local_time<Duration> time) -> std::tm {
-  return localtime(std::chrono::system_clock::to_time_t(
-      std::chrono::current_zone()->to_sys(time)));
+  return localtime(
+      detail::to_time_t(std::chrono::current_zone()->to_sys(time)));
 }
 #endif
 
@@ -523,9 +568,10 @@ inline std::tm gmtime(std::time_t time) {
   return gt.tm_;
 }
 
+template <typename Duration>
 inline std::tm gmtime(
-    std::chrono::time_point<std::chrono::system_clock> time_point) {
-  return gmtime(std::chrono::system_clock::to_time_t(time_point));
+    std::chrono::time_point<std::chrono::system_clock, Duration> time_point) {
+  return gmtime(detail::to_time_t(time_point));
 }
 
 namespace detail {
@@ -1051,13 +1097,12 @@ void write_fractional_seconds(OutputIt& out, Duration d, int precision = -1) {
                                 std::chrono::seconds::rep>::type,
       std::ratio<1, detail::pow10(num_fractional_digits)>>;
 
-  const auto fractional =
-      d - std::chrono::duration_cast<std::chrono::seconds>(d);
+  const auto fractional = d - fmt_duration_cast<std::chrono::seconds>(d);
   const auto subseconds =
       std::chrono::treat_as_floating_point<
           typename subsecond_precision::rep>::value
           ? fractional.count()
-          : std::chrono::duration_cast<subsecond_precision>(fractional).count();
+          : fmt_duration_cast<subsecond_precision>(fractional).count();
   auto n = static_cast<uint32_or_64_or_128_t<long long>>(subseconds);
   const int num_digits = detail::count_digits(n);
 
@@ -1620,17 +1665,6 @@ template <typename T> struct make_unsigned_or_unchanged<T, true> {
   using type = typename std::make_unsigned<T>::type;
 };
 
-#if FMT_SAFE_DURATION_CAST
-// throwing version of safe_duration_cast
-template <typename To, typename FromRep, typename FromPeriod>
-To fmt_safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from) {
-  int ec;
-  To to = safe_duration_cast::safe_duration_cast<To>(from, ec);
-  if (ec) FMT_THROW(format_error("cannot format duration"));
-  return to;
-}
-#endif
-
 template <typename Rep, typename Period,
           FMT_ENABLE_IF(std::is_integral<Rep>::value)>
 inline std::chrono::duration<Rep, std::milli> get_milliseconds(
@@ -1640,17 +1674,17 @@ inline std::chrono::duration<Rep, std::milli> get_milliseconds(
 #if FMT_SAFE_DURATION_CAST
   using CommonSecondsType =
       typename std::common_type<decltype(d), std::chrono::seconds>::type;
-  const auto d_as_common = fmt_safe_duration_cast<CommonSecondsType>(d);
+  const auto d_as_common = fmt_duration_cast<CommonSecondsType>(d);
   const auto d_as_whole_seconds =
-      fmt_safe_duration_cast<std::chrono::seconds>(d_as_common);
+      fmt_duration_cast<std::chrono::seconds>(d_as_common);
   // this conversion should be nonproblematic
   const auto diff = d_as_common - d_as_whole_seconds;
   const auto ms =
-      fmt_safe_duration_cast<std::chrono::duration<Rep, std::milli>>(diff);
+      fmt_duration_cast<std::chrono::duration<Rep, std::milli>>(diff);
   return ms;
 #else
-  auto s = std::chrono::duration_cast<std::chrono::seconds>(d);
-  return std::chrono::duration_cast<std::chrono::milliseconds>(d - s);
+  auto s = fmt_duration_cast<std::chrono::seconds>(d);
+  return fmt_duration_cast<std::chrono::milliseconds>(d - s);
 #endif
 }
 
@@ -1751,14 +1785,8 @@ struct chrono_formatter {
 
     // this may overflow and/or the result may not fit in the
     // target type.
-#if FMT_SAFE_DURATION_CAST
     // might need checked conversion (rep!=Rep)
-    auto tmpval = std::chrono::duration<rep, Period>(val);
-    s = fmt_safe_duration_cast<seconds>(tmpval);
-#else
-    s = std::chrono::duration_cast<seconds>(
-        std::chrono::duration<rep, Period>(val));
-#endif
+    s = fmt_duration_cast<seconds>(std::chrono::duration<rep, Period>(val));
   }
 
   // returns true if nan or inf, writes to out.
@@ -2082,25 +2110,22 @@ struct formatter<std::chrono::time_point<std::chrono::system_clock, Duration>,
             period::num != 1 || period::den != 1 ||
             std::is_floating_point<typename Duration::rep>::value)) {
       const auto epoch = val.time_since_epoch();
-      auto subsecs = std::chrono::duration_cast<Duration>(
-          epoch - std::chrono::duration_cast<std::chrono::seconds>(epoch));
+      auto subsecs = detail::fmt_duration_cast<Duration>(
+          epoch - detail::fmt_duration_cast<std::chrono::seconds>(epoch));
 
       if (subsecs.count() < 0) {
         auto second =
-            std::chrono::duration_cast<Duration>(std::chrono::seconds(1));
+            detail::fmt_duration_cast<Duration>(std::chrono::seconds(1));
         if (epoch.count() < ((Duration::min)() + second).count())
           FMT_THROW(format_error("duration is too small"));
         subsecs += second;
         val -= second;
       }
 
-      return formatter<std::tm, Char>::do_format(
-          gmtime(std::chrono::time_point_cast<std::chrono::seconds>(val)), ctx,
-          &subsecs);
+      return formatter<std::tm, Char>::do_format(gmtime(val), ctx, &subsecs);
     }
 
-    return formatter<std::tm, Char>::format(
-        gmtime(std::chrono::time_point_cast<std::chrono::seconds>(val)), ctx);
+    return formatter<std::tm, Char>::format(gmtime(val), ctx);
   }
 };
 
@@ -2119,17 +2144,13 @@ struct formatter<std::chrono::local_time<Duration>, Char>
     if (period::num != 1 || period::den != 1 ||
         std::is_floating_point<typename Duration::rep>::value) {
       const auto epoch = val.time_since_epoch();
-      const auto subsecs = std::chrono::duration_cast<Duration>(
-          epoch - std::chrono::duration_cast<std::chrono::seconds>(epoch));
+      const auto subsecs = detail::fmt_duration_cast<Duration>(
+          epoch - detail::fmt_duration_cast<std::chrono::seconds>(epoch));
 
-      return formatter<std::tm, Char>::do_format(
-          localtime(std::chrono::time_point_cast<std::chrono::seconds>(val)),
-          ctx, &subsecs);
+      return formatter<std::tm, Char>::do_format(localtime(val), ctx, &subsecs);
     }
 
-    return formatter<std::tm, Char>::format(
-        localtime(std::chrono::time_point_cast<std::chrono::seconds>(val)),
-        ctx);
+    return formatter<std::tm, Char>::format(localtime(val), ctx);
   }
 };
 #endif

diff --git a/test/chrono-test.cc b/test/chrono-test.cc
index 077606887095..b562a50ea4bf 100644
--- a/test/chrono-test.cc
+++ b/test/chrono-test.cc
@@ -874,6 +874,20 @@ TEST(chrono_test, timestamps_ratios) {
       t4(std::chrono::duration<int, std::ratio<63>>(1));
 
   EXPECT_EQ(fmt::format("{:%M:%S}", t4), "01:03");
+
+  std::chrono::time_point<std::chrono::system_clock, std::chrono::milliseconds>
+      t5(std::chrono::seconds(32503680000));
+
+  EXPECT_EQ(fmt::format("{:%Y-%m-%d}", t5), "3000-01-01");
+
+#if FMT_SAFE_DURATION_CAST
+  using years = std::chrono::duration<std::int64_t, std::ratio<31556952>>;
+  std::chrono::time_point<std::chrono::system_clock, years> t6(
+      (years(std::numeric_limits<std::int64_t>::max())));
+
+  EXPECT_THROW_MSG((void)fmt::format("{:%Y-%m-%d}", t6), fmt::format_error,
+                   "cannot format duration");
+#endif
 }
 
 TEST(chrono_test, timestamps_sub_seconds) {

{"chrono-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}}
{"compile-test": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "ostream-test": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "ranges-test": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "std-test": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "core-test": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "scan-test": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "color-test": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "format-test": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "gtest-extra-test": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "args-test": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "compile-fp-test": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "unicode-test": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "assert-test": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "posix-mock-test": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "printf-test": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "enforce-checks-test": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "format-impl-test": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "os-test": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "xchar-test": {"run": "PASS", "test": "PASS", "fix": "PASS"}}
{"chrono-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}}
{"passed_count": 20, "failed_count": 0, "skipped_count": 0, "passed_tests": ["compile-test", "core-test", "chrono-test", "gtest-extra-test", "compile-fp-test", "unicode-test", "assert-test", "enforce-checks-test", "format-impl-test", "os-test", "xchar-test", "ostream-test", "ranges-test", "std-test", "scan-test", "color-test", "format-test", "args-test", "posix-mock-test", "printf-test"], "failed_tests": [], "skipped_tests": []}
{"passed_count": 19, "failed_count": 0, "skipped_count": 0, "passed_tests": ["compile-test", "core-test", "gtest-extra-test", "compile-fp-test", "unicode-test", "assert-test", "enforce-checks-test", "format-impl-test", "os-test", "xchar-test", "ostream-test", "ranges-test", "std-test", "scan-test", "color-test", "format-test", "args-test", "posix-mock-test", "printf-test"], "failed_tests": [], "skipped_tests": []}
{"passed_count": 20, "failed_count": 0, "skipped_count": 0, "passed_tests": ["compile-test", "core-test", "chrono-test", "gtest-extra-test", "compile-fp-test", "unicode-test", "assert-test", "enforce-checks-test", "format-impl-test", "os-test", "xchar-test", "ostream-test", "ranges-test", "std-test", "scan-test", "color-test", "format-test", "args-test", "posix-mock-test", "printf-test"], "failed_tests": [], "skipped_tests": []}