Formatting %j for a duration with a small integer representation can throw or print a truncated day count. For example, duration<signed char, ratio<86400>>(-128) throws chrono value is out of range, and duration<unsigned char, ratio<172800>>(200) prints 144 instead of 400.
duration_formatter already stores the nonnegative magnitude in its promoted rep type. However, days() casts the computed count back to the original Rep, and write() also takes Rep. Keep both in rep so the magnitude is preserved until formatting. Sign handling and the existing checked conversion to int remain unchanged.
Reproduction
Reproduced on upstream f87a75e15262614ce8aa0e6a0664df9d0823fb92 with Apple Clang 17 on macOS arm64.
Save the following as repro.cpp and run clang++ -std=c++11 -DFMT_HEADER_ONLY -Iinclude repro.cpp -o repro && ./repro from the repository root.
#include "fmt/chrono.h"
#include <cstdio>
int main() {
using signed_days = std::chrono::duration<signed char, std::ratio<86400>>;
using two_days = std::chrono::duration<unsigned char, std::ratio<172800>>;
try {
std::puts(fmt::format("{:%j}", signed_days(-128)).c_str());
} catch (const fmt::format_error& error) {
std::puts(error.what());
}
std::puts(fmt::format("{:%j}", two_days(200)).c_str());
}Before the fix, this prints the range error followed by 144. After the fix, it prints -128 followed by 400.
Validation
Both new regressions failed for the reported reasons before the production edit. Both pass after the two-line fix, and the existing chrono_test.out_of_range test still passes.
cmake -S . -B build -DFMT_TEST=ON
cmake --build build -j 4
ctest --test-dir build --output-on-failure -j 4- All 22 CTest targets passed.
- A separate header-only sweep checked all 256 signed-char values and all 256 unsigned-char values with one-day and two-day periods, giving 1,024 cases per run. All matched independently computed decimal day counts in C++11, C++17 and C++20 with ASan and UBSan enabled.
- Changed lines pass clang-format 17.0.6. Whole-file checking also reports an unrelated existing formatting difference in
chrono-test.cc, reproduced on unchanged upstream.git diff --checkpassed.
Limitations
This fixes narrowing after conversion to seconds. It does not extend the existing supported duration range or change range-error behavior for genuinely oversized values.
Local validation used Apple Clang on macOS arm64. GCC, MSVC and upstream CI have not been run. No performance claim is made.
AI assistance
AI assistance was used to investigate the issue, write the patch and regression tests, and run local validation.
I reviewed the patch and regression tests before submission.