microfmt
Zero-allocation C++ formatting for constrained environments
Loading...
Searching...
No Matches
chrono.hpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2026 Jarosław Pelczar <jarek@jpelczar.com>
2//
3// SPDX-License-Identifier: BSD-2-Clause
4
5#pragma once
6
9#include "../microfmt.hpp"
10#include <chrono>
11#include <cstdint>
12#include <ratio>
13#include <string_view>
14#include <type_traits>
15
16namespace microfmt {
17
18// ============================================================================
19// Duration Unit Suffix Helpers
20// ============================================================================
21
22namespace detail {
23
24template <typename Period> struct duration_suffix {
25 static constexpr microfmt::string_view value = " [custom]";
26};
27
28template <> struct duration_suffix<std::nano> {
29 static constexpr microfmt::string_view value = "ns";
30};
31template <> struct duration_suffix<std::micro> {
32 static constexpr microfmt::string_view value = "us";
33};
34template <> struct duration_suffix<std::milli> {
35 static constexpr microfmt::string_view value = "ms";
36};
37template <> struct duration_suffix<std::ratio<1>> {
38 static constexpr microfmt::string_view value = "s";
39};
40template <> struct duration_suffix<std::ratio<60>> {
41 static constexpr microfmt::string_view value = "min";
42};
43template <> struct duration_suffix<std::ratio<3600>> {
44 static constexpr microfmt::string_view value = "h";
45};
46template <> struct duration_suffix<std::ratio<86400>> {
47 static constexpr microfmt::string_view value = "d";
48};
49
50// Days to civil date conversion (Howard Hinnant algorithm, zero-float /
51// integer-only)
53 int32_t year{};
54 uint8_t month{};
55 uint8_t day{};
56};
57
58// time-field arithmetic converts between C/POSIX integer widths whose ranges are bounded by the format.
59RELOCO_BEGIN_UNSAFE_CODE
60inline constexpr civil_date civil_from_days(int32_t z) noexcept {
61 z += 719468;
62 const int32_t era = (z >= 0 ? z : z - 146096) / 146097;
63 const uint32_t doe = static_cast<uint32_t>(z - era * 146097);
64 const uint32_t yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
65 const int32_t y = static_cast<int32_t>(yoe) + era * 400;
66 const uint32_t doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
67 const uint32_t mp = (5 * doy + 2) / 153;
68 const uint8_t d = static_cast<uint8_t>(doy - (153 * mp + 2) / 5 + 1);
69 const uint8_t m = static_cast<uint8_t>(mp < 10 ? mp + 3 : mp - 9);
70 return {y + (m <= 2 ? 1 : 0), m, d};
71}
73
74} // namespace detail
75
76// ============================================================================
77// Formatter for std::chrono::duration
78// ============================================================================
79
80template <typename Rep, typename Period> struct formatter<std::chrono::duration<Rep, Period>> {
81 bool hide_suffix{false};
82
83 constexpr void parse(format_parse_context &ctx) noexcept {
84 for (char c : ctx.spec()) {
85 if (c == 'c' || c == 'C') { // Compact/Clean: prints raw count without unit suffix
86 hide_suffix = true;
87 }
88 }
89 }
90
91 void format(const std::chrono::duration<Rep, Period> &d, const sink &out) const noexcept {
92 const auto count = d.count();
93 if constexpr (std::is_signed_v<Rep>) {
94 if (count < 0) {
95 out.put('-');
96 detail::format_unsigned<detail::radix::decimal>(out, static_cast<uint64_t>(-count), false, 0);
97 } else {
98 detail::format_unsigned<detail::radix::decimal>(out, static_cast<uint64_t>(count), false, 0);
99 }
100 } else {
101 detail::format_unsigned<detail::radix::decimal>(out, static_cast<uint64_t>(count), false, 0);
102 }
103
104 if (!hide_suffix) {
106 }
107 }
108};
109
110// ============================================================================
111// Formatter for std::chrono::time_point (System Clock / Calendar UTC)
112// ============================================================================
113
114template <typename Duration> struct formatter<std::chrono::time_point<std::chrono::system_clock, Duration>> {
115 bool time_only{false};
116 bool date_only{false};
117
118 constexpr void parse(format_parse_context &ctx) noexcept {
119 for (char c : ctx.spec()) {
120 if (c == 'T' || c == 't')
121 time_only = true; // {:t} -> "HH:MM:SS"
122 if (c == 'D' || c == 'd')
123 date_only = true; // {:d} -> "YYYY-MM-DD"
124 }
125 }
126
127 // time-field arithmetic converts between C/POSIX integer widths whose ranges are bounded by the format.
128 RELOCO_BEGIN_UNSAFE_CODE
129 void format(const std::chrono::time_point<std::chrono::system_clock, Duration> &tp, const sink &out) const noexcept {
130 using namespace std::chrono;
131 const auto dur = tp.time_since_epoch();
132 const auto total_secs = duration_cast<seconds>(dur).count();
133 const auto sub_ms = duration_cast<milliseconds>(dur).count() % 1000;
134
135 const int32_t days = static_cast<int32_t>(total_secs / 86400);
136 int32_t rem_secs = static_cast<int32_t>(total_secs % 86400);
137 if (rem_secs < 0)
138 rem_secs += 86400;
139
140 const uint32_t hours = static_cast<uint32_t>(rem_secs / 3600);
141 const uint32_t mins = static_cast<uint32_t>((rem_secs % 3600) / 60);
142 const uint32_t secs = static_cast<uint32_t>(rem_secs % 60);
143
144 if (!time_only) {
145 const auto date = detail::civil_from_days(days);
146 detail::format_unsigned<detail::radix::decimal>(out, static_cast<uint32_t>(date.year), false, 4);
147 out.put('-');
148 detail::format_unsigned<detail::radix::decimal>(out, date.month, false, 2);
149 out.put('-');
150 detail::format_unsigned<detail::radix::decimal>(out, date.day, false, 2);
151 }
152
153 if (!date_only && !time_only) {
154 out.put('T');
155 }
156
157 if (!date_only) {
158 detail::format_unsigned<detail::radix::decimal>(out, hours, false, 2);
159 out.put(':');
160 detail::format_unsigned<detail::radix::decimal>(out, mins, false, 2);
161 out.put(':');
162 detail::format_unsigned<detail::radix::decimal>(out, secs, false, 2);
163 out.put('.');
164 detail::format_unsigned<detail::radix::decimal>(out, static_cast<uint32_t>(sub_ms < 0 ? -sub_ms : sub_ms), false,
165 3);
166 out.put('Z');
167 }
168 }
170};
171
172// ============================================================================
173// Formatter for std::chrono::time_point (Steady / Monotonic Clock Uptime)
174// ============================================================================
175
176template <typename Duration> struct formatter<std::chrono::time_point<std::chrono::steady_clock, Duration>> {
177 constexpr void parse(format_parse_context &) noexcept {}
178
179 void format(const std::chrono::time_point<std::chrono::steady_clock, Duration> &tp, const sink &out) const noexcept {
180 using namespace std::chrono;
181 const auto dur = tp.time_since_epoch();
182 const auto total_secs = duration_cast<seconds>(dur).count();
183 const auto ms = duration_cast<milliseconds>(dur).count() % 1000;
184
185 const uint32_t hours = static_cast<uint32_t>(total_secs / 3600);
186 const uint32_t mins = static_cast<uint32_t>((total_secs % 3600) / 60);
187 const uint32_t secs = static_cast<uint32_t>(total_secs % 60);
188
189 // Format as [HH:MM:SS.mmm] uptime
190 detail::format_unsigned<detail::radix::decimal>(out, hours, false, 2);
191 out.put(':');
192 detail::format_unsigned<detail::radix::decimal>(out, mins, false, 2);
193 out.put(':');
194 detail::format_unsigned<detail::radix::decimal>(out, secs, false, 2);
195 out.put('.');
196 detail::format_unsigned<detail::radix::decimal>(out, static_cast<uint32_t>(ms), false, 3);
197 }
198};
199
200} // namespace microfmt
Definition microfmt.hpp:881
#define MICROFMT_API_CLASS
Definition compat.hpp:181
RELOCO_BEGIN_UNSAFE_CODE constexpr civil_date civil_from_days(int32_t z) noexcept
Definition chrono.hpp:60
Definition assert.hpp:19
constexpr auto micro(IntType raw_val) noexcept
Definition fixed_point.hpp:64
reloco::sink sink
Type-erased, zero-allocation output sink.
Definition microfmt.hpp:46
RELOCO_END_UNSAFE_CODE
Definition remote_object.hpp:132
constexpr auto milli(IntType raw_val) noexcept
Definition fixed_point.hpp:56
Definition chrono.hpp:52
Definition chrono.hpp:24
static constexpr microfmt::string_view value
Definition chrono.hpp:25
void format(const std::chrono::duration< Rep, Period > &d, const sink &out) const noexcept
Definition chrono.hpp:91
constexpr void parse(format_parse_context &ctx) noexcept
Definition chrono.hpp:83
void format(const std::chrono::time_point< std::chrono::steady_clock, Duration > &tp, const sink &out) const noexcept
Definition chrono.hpp:179
constexpr void parse(format_parse_context &) noexcept
Definition chrono.hpp:177
constexpr void parse(format_parse_context &ctx) noexcept
Definition chrono.hpp:118
RELOCO_BEGIN_UNSAFE_CODE void format(const std::chrono::time_point< std::chrono::system_clock, Duration > &tp, const sink &out) const noexcept
Definition chrono.hpp:129
Definition microfmt.hpp:985