# What Is FMT_SAFE_DURATION_CAST? Preventing Undefined Behavior in fmtlib Chrono Conversions

> Learn how FMT_SAFE_DURATION_CAST prevents undefined behavior in fmtlib chrono conversions. Discover its bounds-checked implementation for robust duration handling.

- Repository: [Hello World Foundation/fmt](https://github.com/fmtlib/fmt)
- Tags: internals
- Published: 2026-09-05

---

**`FMT_SAFE_DURATION_CAST` replaces the standard `std::chrono::duration_cast` with a bounds-checked implementation that throws `fmt::format_error` instead of invoking undefined behavior when a duration value overflows or underflows its target representation.**

The `{fmt}` library provides comprehensive formatting support for `std::chrono` types via [`include/fmt/chrono.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/chrono.h). By default, the library defines the `FMT_SAFE_DURATION_CAST` macro to protect users from the undefined behavior (UB) risks inherent in narrowing duration conversions, ensuring that formatting operations fail predictably rather than silently corrupting data.

## Why Standard duration_cast Risks Undefined Behavior

The C++ standard library’s `std::chrono::duration_cast` performs unconditional conversions between duration types. When casting a large value to a narrower representation—such as converting nanoseconds represented in 64-bit integers to a 32-bit integer type—the operation overflows without notification. This produces **undefined behavior** according to the C++ standard, leading to potential crashes, incorrect calculations, or platform-specific results that compromise application reliability.

## How FMT_SAFE_DURATION_CAST Prevents Overflow and Underflow

When `FMT_SAFE_DURATION_CAST` is enabled (the default), `{fmt}` intercepts duration conversions through a custom `safe_duration_cast` namespace implemented in [`include/fmt/chrono.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/chrono.h). This infrastructure validates values before construction, transforming potential UB into well-defined exceptions.

### Macro Definition and CMake Control

The safeguard activates through a preprocessor macro defined at lines 28–31 of [`include/fmt/chrono.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/chrono.h):

```cpp
#ifndef FMT_SAFE_DURATION_CAST

#  define FMT_SAFE_DURATION_CAST 1

#endif

```

You can control this behavior at build time via the CMake option defined in [`CMakeLists.txt`](https://github.com/fmtlib/fmt/blob/main/CMakeLists.txt) (around line 333). Setting `-DFMT_SAFE_DURATION_CAST=OFF` disables the checks, causing the library to fall back to standard `std::chrono::duration_cast` with its inherent UB risks.

### Checked Integral Conversions

For integral duration representations, the implementation uses `safe_duration_cast::lossless_integral_conversion` defined in [`include/fmt/chrono.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/chrono.h) (lines 20–50). This function validates that the conversion factor between periods does not overflow the target type before performing the multiplication. If the value lies outside the representable range, the code invokes `throw_duration_error()`, which throws a `fmt::format_error` with the message `"cannot format duration"` rather than proceeding with the unsafe cast.

### Floating-Point Safety Checks

For floating-point representations, the code path utilizes `safe_duration_cast::safe_float_conversion` (lines 56–66 of [`include/fmt/chrono.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/chrono.h)). This routine detects out-of-range values before constructing the target duration while preserving special values like NaN and infinity. Like its integral counterpart, it triggers `throw_duration_error()` when encountering values that cannot be safely represented.

### Fallback to Standard Behavior

If you disable `FMT_SAFE_DURATION_CAST`, the library reverts to the standard behavior. The preprocessor conditional at lines 24–27 of [`include/fmt/chrono.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/chrono.h) simply forwards to `std::chrono::duration_cast<To>(from)`, inheriting all associated undefined behavior risks for overflow scenarios.

## Practical Examples of Safe Duration Casting

The following examples demonstrate how the safety check operates in practice.

### Detecting Overflow in Extreme Durations

This example attempts to format a duration that would overflow when cast to a smaller type:

```cpp
#include <fmt/chrono.h>
#include <limits>

int main() {
  try {
    using years = std::chrono::duration<std::int64_t,
                                        std::ratio<31556952>>;
    // Maximum 64-bit value representing a huge number of years
    years huge{std::numeric_limits<std::int64_t>::max()};
    
    // This triggers safe_duration_cast and detects the overflow
    fmt::print("{:%Y-%m-%d}\n", fmt::chrono::sys_time<years>(huge));
  } catch (const fmt::format_error& e) {
    fmt::print("Caught expected error: {}\n", e.what());
    // Output: Caught expected error: cannot format duration
  }
}

```

### Normal Safe Conversions

Standard conversions that fit within the target type work transparently:

```cpp
#include <fmt/chrono.h>

int main() {
  std::chrono::seconds s{42};
  
  // Safe conversion to milliseconds; no overflow risk
  auto ms = fmt::chrono::duration_cast<std::chrono::milliseconds>(s);
  fmt::print("{} seconds = {} ms\n", s.count(), ms.count());
  // Output: 42 seconds = 42000 ms
}

```

### Disabling Safety Checks

To use the unchecked standard conversion (not recommended for untrusted input), configure your build:

```bash
cmake -DFMT_SAFE_DURATION_CAST=OFF ..

```

Or add to your [`CMakeLists.txt`](https://github.com/fmtlib/fmt/blob/main/CMakeLists.txt):

```cmake
add_definitions(-DFMT_SAFE_DURATION_CAST=0)

```

With the macro disabled, the previous overflow example would invoke undefined behavior instead of throwing an exception.

## Summary

- **`FMT_SAFE_DURATION_CAST`** defaults to enabled (value `1`) in `{fmt}` installations, providing checked duration conversions.
- The implementation in [`include/fmt/chrono.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/chrono.h) intercepts `duration_cast` operations for both **integral** and **floating-point** representations.
- **Overflow, underflow, and sign-loss** scenarios trigger `throw_duration_error()`, which throws `fmt::format_error` with the message `"cannot format duration"`.
- When disabled via CMake (`-DFMT_SAFE_DURATION_CAST=OFF`), the library falls back to `std::chrono::duration_cast`, exposing applications to standard undefined behavior on invalid conversions.
- Unit tests in `test/chrono-test.cc` (lines 43–48) verify that the safety mechanism correctly identifies and rejects dangerous conversions.

## Frequently Asked Questions

### What happens when FMT_SAFE_DURATION_CAST detects an unsafe conversion?

The library invokes `throw_duration_error()` defined in [`include/fmt/chrono.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/chrono.h) at lines 16–19, which throws a `fmt::format_error` exception with the message `"cannot format duration"`. This allows applications to catch and handle the error gracefully rather than proceeding with undefined behavior.

### Can I disable the safety checks if I need maximum performance?

Yes. You can disable the feature by setting the CMake option `FMT_SAFE_DURATION_CAST` to `OFF` or by defining the macro as `0` before including [`fmt/chrono.h`](https://github.com/fmtlib/fmt/blob/main/fmt/chrono.h). However, this reverts to `std::chrono::duration_cast` and its associated UB risks for narrowing conversions.

### Does FMT_SAFE_DURATION_CAST affect all chrono duration types?

The protection applies to all duration conversions performed internally by `{fmt}`’s chrono formatting facilities. It handles both integral representations (using `lossless_integral_conversion`) and floating-point representations (using `safe_float_conversion`), ensuring comprehensive coverage across `std::chrono::duration` specializations.

### Is there a performance penalty for using FMT_SAFE_DURATION_CAST?

The safety checks add minor overhead for the bounds validation logic. For integral types, this involves checking conversion factors and multiplication results; for floating-point types, it checks range validity. These checks are typically negligible compared to the cost of formatting operations, though users with performance-critical paths can benchmark the disabled configuration if necessary.