userver: userver/utils/time_of_day.hpp Source File
⚠️ This is the documentation for an old userver version. Click here to switch to the latest version.
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages Concepts
time_of_day.hpp
Go to the documentation of this file.
1#pragma once
2
3/// @file userver/utils/time_of_day.hpp
4/// @brief @copybrief utils::datetime::TimeOfDay
5
6#include <algorithm>
7#include <array>
8#include <chrono>
9#include <string_view>
10#include <type_traits>
11#include <vector>
12
13#include <fmt/format.h>
14
15#include <userver/utils/fmt_compat.hpp>
16
17USERVER_NAMESPACE_BEGIN
18
19namespace logging {
20class LogHelper;
21}
22
23namespace utils::datetime {
24
25/// @ingroup userver_universal userver_containers
26///
27/// @brief A simple implementation of a "time since midnight" datatype.
28///
29/// This type is time-zone ignorant.
30///
31/// Valid time range is from [00:00:00.0, 24:00:00.0)
32///
33/// Available construction:
34///
35/// from duration (since midnight, the value will be normalized, e.g. 25:00 will
36/// become 01:00, 24:00 will become 00:00)
37///
38/// from string representation HH:MM[:SS[.s]], accepted range is from 00:00 to
39/// 24:00
40///
41/// construction from int in form 1300 as a static member function
42///
43/// Accessors:
44///
45/// int Hours(); // hours since midnight
46/// int Minutes(); // minutes since midnight + Hours()
47/// int Seconds(); // seconds since midnight + Hours() + Minutes()
48/// int Subseconds(); // seconds fraction since
49/// midnight + Hours() + Minutes() + Seconds()
50///
51///
52/// Output:
53///
54/// LOG(xxx) << val
55///
56/// Formatting:
57///
58/// fmt::format("{}", val)
59///
60/// Default format for hours and minutes is HH:MM, for seconds HH:MM:SS, for
61/// subseconds HH:MM:SS.s with fractional part, but truncating trailing zeros.
62///
63/// Custom formatting:
64///
65/// fmt:format("{:%H%M%S}", val); // will output HHMMSS without separators
66///
67/// Format keys:
68/// %H 24-hour two-digit zero-padded hours
69/// %M two-digit zero-padded minutes
70/// %S two-digit zero-padded seconds
71/// %% literal %
72
73template <typename Duration>
74class TimeOfDay;
75
76template <typename Rep, typename Period>
77class TimeOfDay<std::chrono::duration<Rep, Period>> {
78 public:
79 using DurationType = std::chrono::duration<Rep, Period>;
80
81 constexpr TimeOfDay() noexcept = default;
82 constexpr explicit TimeOfDay(DurationType) noexcept;
83 template <typename ORep, typename OPeriod>
84 constexpr explicit TimeOfDay(std::chrono::duration<ORep, OPeriod>) noexcept;
85 constexpr explicit TimeOfDay(std::string_view);
86
87 //@{
88 /** @name Comparison operators */
89 constexpr bool operator==(const TimeOfDay&) const;
90 constexpr bool operator!=(const TimeOfDay&) const;
91 constexpr bool operator<(const TimeOfDay&) const;
92 constexpr bool operator<=(const TimeOfDay&) const;
93 constexpr bool operator>(const TimeOfDay&) const;
94 constexpr bool operator>=(const TimeOfDay&) const;
95 //@}
96
97 //@{
98 /** @name Accessors */
99 /// @return Hours since midnight
100 constexpr std::chrono::hours Hours() const noexcept;
101 /// @return Minutes since midnight + Hours
102 constexpr std::chrono::minutes Minutes() const noexcept;
103 /// @return Seconds since midnight + Hours + Minutes
104 constexpr std::chrono::seconds Seconds() const noexcept;
105 /// @return Fractional part of seconds since midnight + Hours + Minutes +
106 /// Seconds up to resolution
107 constexpr DurationType Subseconds() const noexcept;
108
109 /// @return Underlying duration representation
110 constexpr DurationType SinceMidnight() const noexcept;
111 //@}
112
113 /// Create time of day from integer representation, e.g.1330 => 13:30
114 constexpr static TimeOfDay FromHHMMInt(int);
115
116 private:
117 DurationType since_midnight_{};
118};
119
120//@{
121/** @name Duration arithmetic */
122
123template <typename LDuration, typename RDuration>
124auto operator-(TimeOfDay<LDuration> lhs, TimeOfDay<RDuration> rhs) {
125 return lhs.SinceMidnight() - rhs.SinceMidnight();
126}
127
128template <typename Duration, typename Rep, typename Period>
129TimeOfDay<Duration> operator+(TimeOfDay<Duration> lhs,
130 std::chrono::duration<Rep, Period> rhs) {
131 return TimeOfDay<Duration>{lhs.SinceMidnight() + rhs};
132}
133
134template <typename Duration, typename Rep, typename Period>
135TimeOfDay<Duration> operator-(TimeOfDay<Duration> lhs,
136 std::chrono::duration<Rep, Period> rhs) {
137 return TimeOfDay<Duration>{lhs.SinceMidnight() - rhs};
138}
139//@}
140
141template <typename Duration>
142logging::LogHelper& operator<<(logging::LogHelper& lh,
143 TimeOfDay<Duration> value) {
144 lh << fmt::to_string(value);
145 return lh;
146}
147
148namespace detail {
149template <typename Rep, typename Period>
150inline constexpr std::chrono::duration<Rep, Period> kTwentyFourHours =
151 std::chrono::duration_cast<std::chrono::duration<Rep, Period>>(
152 std::chrono::hours{24});
153
154template <typename Rep, typename Period, typename ORep = Rep,
155 typename OPeriod = Period>
156constexpr std::chrono::duration<Rep, Period> NormalizeTimeOfDay(
157 std::chrono::duration<ORep, OPeriod> d) {
158 auto res = std::chrono::duration_cast<std::chrono::duration<Rep, Period>>(d) %
159 kTwentyFourHours<Rep, Period>;
160 return res.count() < 0 ? res + kTwentyFourHours<Rep, Period> : res;
161}
162
163template <typename Ratio>
164inline constexpr std::size_t kDecimalPositions = 0;
165template <>
166inline constexpr std::size_t kDecimalPositions<std::milli> = 3;
167template <>
168inline constexpr std::size_t kDecimalPositions<std::micro> = 6;
169template <>
170inline constexpr const std::size_t kDecimalPositions<std::nano> = 9;
171
172constexpr std::intmax_t MissingDigits(std::size_t n) {
173 // As we support resolutions up to nano, all we need is up to 10^9
174 // clang-format off
175 constexpr std::intmax_t powers[]{
176 1,
177 10,
178 100,
179 1'000,
180 10'000,
181 100'000,
182 1'000'000,
183 10'000'000,
184 100'000'000,
185 1'000'000'000
186 };
187 // clang-format on
188
189 return powers[n];
190}
191
192template <typename Ratio>
193struct HasMinutes : std::false_type {};
194
195template <intmax_t Num, intmax_t Den>
196struct HasMinutes<std::ratio<Num, Den>>
197 : std::integral_constant<bool, (Num <= 60LL)> {};
198
199template <typename Rep, typename Period>
200struct HasMinutes<std::chrono::duration<Rep, Period>> : HasMinutes<Period> {};
201
202template <typename Duration>
203struct HasMinutes<TimeOfDay<Duration>> : HasMinutes<Duration> {};
204
205template <typename T>
206constexpr inline bool kHasMinutes = HasMinutes<T>{};
207
208template <typename Ratio>
209struct HasSeconds : std::false_type {};
210
211template <intmax_t Num, intmax_t Den>
212struct HasSeconds<std::ratio<Num, Den>>
213 : std::integral_constant<bool, (Num == 1)> {};
214
215template <typename Rep, typename Period>
216struct HasSeconds<std::chrono::duration<Rep, Period>> : HasSeconds<Period> {};
217
218template <typename Duration>
219struct HasSeconds<TimeOfDay<Duration>> : HasSeconds<Duration> {};
220
221template <typename T>
222constexpr inline bool kHasSeconds = HasSeconds<T>{};
223
224template <typename Ratio>
225struct HasSubseconds : std::false_type {};
226
227template <intmax_t Num, intmax_t Den>
228struct HasSubseconds<std::ratio<Num, Den>>
229 : std::integral_constant<bool, (Den > 1)> {};
230
231template <typename Rep, typename Period>
232struct HasSubseconds<std::chrono::duration<Rep, Period>>
233 : HasSubseconds<Period> {};
234
235template <typename Duration>
236struct HasSubseconds<TimeOfDay<Duration>> : HasSubseconds<Duration> {};
237
238template <typename T>
239constexpr inline bool kHasSubseconds = HasSubseconds<T>{};
240
241template <typename Rep, typename Period>
242class TimeOfDayParser {
243 public:
244 using DurationType = std::chrono::duration<Rep, Period>;
245
246 constexpr DurationType operator()(std::string_view str) {
247 for (auto c : str) {
248 switch (c) {
249 case ':':
250 if (position_ >= kSeconds)
251 throw std::runtime_error{
252 fmt::format("Extra colon in TimeOfDay string `{}`", str)};
253 AssignCurrentPosition(str);
254 position_ = static_cast<TimePart>(position_ + 1);
255 break;
256 case '.':
257 if (position_ != kSeconds)
258 throw std::runtime_error{fmt::format(
259 "Unexpected decimal point in TimeOfDay string `{}`", str)};
260 AssignCurrentPosition(str);
261 position_ = static_cast<TimePart>(position_ + 1);
262 break;
263 default:
264 if (!std::isdigit(c))
265 throw std::runtime_error{fmt::format(
266 "Unexpected character {} in TimeOfDay string `{}`", c, str)};
267 if (position_ == kOverflow) {
268 continue;
269 } else if (position_ == kSubseconds) {
270 if (digit_count_ >= kDecimalPositions<Period>) {
271 AssignCurrentPosition(str);
272 position_ = kOverflow;
273 continue;
274 }
275 } else if (digit_count_ >= 2) {
276 throw std::runtime_error{
277 fmt::format("Too much digits in TimeOfDay string `{}`", str)};
278 }
279 ++digit_count_;
280 current_number_ = current_number_ * 10 + (c - '0');
281 break;
282 }
283 }
284 if (position_ == kHour)
285 throw std::runtime_error{
286 fmt::format("Expected to have at least minutes after hours in "
287 "TimeOfDay string `{}`",
288 str)};
289 AssignCurrentPosition(str);
290
291 auto sum = hours_ + minutes_ + seconds_ + subseconds_;
292 if (sum > kTwentyFourHours<Rep, Period>) {
293 throw std::runtime_error(fmt::format(
294 "TimeOfDay value {} is out of range [00:00, 24:00)", str));
295 }
296 return NormalizeTimeOfDay<Rep, Period>(sum);
297 }
298
299 private:
300 enum TimePart { kHour, kMinutes, kSeconds, kSubseconds, kOverflow };
301
302 void AssignCurrentPosition(std::string_view str) {
303 switch (position_) {
304 case kHour: {
305 if (digit_count_ < 1)
306 throw std::runtime_error{fmt::format(
307 "Not enough digits for hours in TimeOfDay string `{}`", str)};
308 if (current_number_ > 24)
309 throw std::runtime_error{fmt::format(
310 "Invalid value for hours in TimeOfDay string `{}`", str)};
311 hours_ = std::chrono::hours{current_number_};
312 break;
313 }
314 case kMinutes: {
315 if (digit_count_ != 2)
316 throw std::runtime_error{fmt::format(
317 "Not enough digits for minutes in TimeOfDay string `{}`", str)};
318 if (current_number_ > 59)
319 throw std::runtime_error{fmt::format(
320 "Invalid value for minutes in TimeOfDay string `{}`", str)};
321 minutes_ = std::chrono::minutes{current_number_};
322 break;
323 }
324 case kSeconds: {
325 if (digit_count_ != 2)
326 throw std::runtime_error{fmt::format(
327 "Not enough digits for seconds in TimeOfDay string `{}`", str)};
328 if (current_number_ > 59)
329 throw std::runtime_error{fmt::format(
330 "Invalid value for seconds in TimeOfDay string `{}`", str)};
331 seconds_ = std::chrono::seconds{current_number_};
332 break;
333 }
334 case kSubseconds: {
335 if (digit_count_ < 1)
336 throw std::runtime_error{fmt::format(
337 "Not enough digits for subseconds in TimeOfDay string `{}`",
338 str)};
339 if constexpr (kHasSubseconds<Period>) {
340 // TODO check digit count and adjust if needed
341 if (digit_count_ < kDecimalPositions<Period>) {
342 current_number_ *=
343 MissingDigits(kDecimalPositions<Period> - digit_count_);
344 }
345 subseconds_ = DurationType{current_number_};
346 }
347 break;
348 }
349 case kOverflow:
350 // Just ignore this
351 break;
352 }
353 current_number_ = 0;
354 digit_count_ = 0;
355 }
356
357 TimePart position_ = kHour;
358 std::chrono::hours hours_{0};
359 std::chrono::minutes minutes_{0};
360 std::chrono::seconds seconds_{0};
361 DurationType subseconds_{0};
362
363 std::size_t digit_count_{0};
364 std::size_t current_number_{0};
365};
366
367/// Format string used for format key `%H`, two-digit 24-hour left-padded by 0
368inline constexpr std::string_view kLongHourFormat = "{0:0>#2d}";
369/// Format string used for minutes, key `%M`, no variations
370inline constexpr std::string_view kMinutesFormat = "{1:0>#2d}";
371/// Format string used for seconds, key `%S`, no variations
372inline constexpr std::string_view kSecondsFormat = "{2:0>#2d}";
373/// Format string for subseconds, keys not yet assigned
374inline constexpr std::string_view kSubsecondsFormat = "{3}";
375
376template <typename Ratio>
377constexpr inline std::string_view kSubsecondsPreformat = ".0";
378template <>
379inline constexpr std::string_view kSubsecondsPreformat<std::milli> =
380 ".{:0>#3d}";
381template <>
382inline constexpr std::string_view kSubsecondsPreformat<std::micro> =
383 ".{:0>#6d}";
384template <>
385inline constexpr std::string_view kSubsecondsPreformat<std::nano> = ".{:0>#9d}";
386
387// Default format for formatting is HH:MM:SS
388template <typename Ratio>
389inline constexpr std::array<std::string_view, 5> kDefaultFormat{
390 {kLongHourFormat, ":", kMinutesFormat, ":", kSecondsFormat}};
391
392// Default format for formatting with minutes resolution is HH:MM
393template <>
394inline constexpr std::array<std::string_view, 3>
395 kDefaultFormat<std::ratio<60, 1>>{{kLongHourFormat, ":", kMinutesFormat}};
396
397// Default format for formatting with hours resolution is HH:MM
398template <>
399inline constexpr std::array<std::string_view, 3>
400 kDefaultFormat<std::ratio<3600, 1>>{{kLongHourFormat, ":", kMinutesFormat}};
401
402} // namespace detail
403
404template <typename Rep, typename Period>
405constexpr TimeOfDay<std::chrono::duration<Rep, Period>>::TimeOfDay(
406 DurationType d) noexcept
407 : since_midnight_{detail::NormalizeTimeOfDay<Rep, Period>(d)} {}
408
409template <typename Rep, typename Period>
410template <typename ORep, typename OPeriod>
411constexpr TimeOfDay<std::chrono::duration<Rep, Period>>::TimeOfDay(
412 std::chrono::duration<ORep, OPeriod> d) noexcept
413 : since_midnight_{detail::NormalizeTimeOfDay<Rep, Period>(d)} {}
414
415template <typename Rep, typename Period>
416constexpr TimeOfDay<std::chrono::duration<Rep, Period>>::TimeOfDay(
417 std::string_view str)
418 : since_midnight_{detail::TimeOfDayParser<Rep, Period>{}(str)} {}
419
420template <typename Rep, typename Period>
421constexpr bool TimeOfDay<std::chrono::duration<Rep, Period>>::operator==(
422 const TimeOfDay& rhs) const {
423 return since_midnight_ == rhs.since_midnight_;
424}
425
426template <typename Rep, typename Period>
427constexpr bool TimeOfDay<std::chrono::duration<Rep, Period>>::operator!=(
428 const TimeOfDay& rhs) const {
429 return !(*this == rhs);
430}
431
432template <typename Rep, typename Period>
433constexpr bool TimeOfDay<std::chrono::duration<Rep, Period>>::operator<(
434 const TimeOfDay& rhs) const {
435 return since_midnight_ < rhs.since_midnight_;
436}
437
438template <typename Rep, typename Period>
439constexpr bool TimeOfDay<std::chrono::duration<Rep, Period>>::operator<=(
440 const TimeOfDay& rhs) const {
441 return since_midnight_ <= rhs.since_midnight_;
442}
443
444template <typename Rep, typename Period>
445constexpr bool TimeOfDay<std::chrono::duration<Rep, Period>>::operator>(
446 const TimeOfDay& rhs) const {
447 return since_midnight_ > rhs.since_midnight_;
448}
449
450template <typename Rep, typename Period>
451constexpr bool TimeOfDay<std::chrono::duration<Rep, Period>>::operator>=(
452 const TimeOfDay& rhs) const {
453 return since_midnight_ >= rhs.since_midnight_;
454}
455
456template <typename Rep, typename Period>
457constexpr std::chrono::hours
458TimeOfDay<std::chrono::duration<Rep, Period>>::Hours() const noexcept {
459 return std::chrono::duration_cast<std::chrono::hours>(since_midnight_);
460}
461
462template <typename Rep, typename Period>
463constexpr std::chrono::minutes
464TimeOfDay<std::chrono::duration<Rep, Period>>::Minutes() const noexcept {
465 if constexpr (detail::kHasMinutes<Period>) {
466 return std::chrono::duration_cast<std::chrono::minutes>(since_midnight_) -
467 std::chrono::duration_cast<std::chrono::minutes>(Hours());
468 } else {
469 return std::chrono::minutes{0};
470 }
471}
472
473template <typename Rep, typename Period>
474constexpr std::chrono::seconds
475TimeOfDay<std::chrono::duration<Rep, Period>>::Seconds() const noexcept {
476 if constexpr (detail::kHasSeconds<Period>) {
477 return std::chrono::duration_cast<std::chrono::seconds>(since_midnight_) -
478 std::chrono::duration_cast<std::chrono::seconds>(
479 std::chrono::duration_cast<std::chrono::minutes>(
480 since_midnight_));
481 } else {
482 return std::chrono::seconds{0};
483 }
484}
485
486template <typename Rep, typename Period>
487constexpr typename TimeOfDay<std::chrono::duration<Rep, Period>>::DurationType
488TimeOfDay<std::chrono::duration<Rep, Period>>::Subseconds() const noexcept {
489 if constexpr (detail::kHasSubseconds<Period>) {
490 return since_midnight_ -
491 std::chrono::duration_cast<std::chrono::seconds>(since_midnight_);
492 } else {
493 return DurationType{0};
494 }
495}
496
497template <typename Rep, typename Period>
498constexpr typename TimeOfDay<std::chrono::duration<Rep, Period>>::DurationType
499TimeOfDay<std::chrono::duration<Rep, Period>>::SinceMidnight() const noexcept {
500 return since_midnight_;
501}
502
503template <typename Rep, typename Period>
504constexpr TimeOfDay<std::chrono::duration<Rep, Period>>
505TimeOfDay<std::chrono::duration<Rep, Period>>::FromHHMMInt(int hh_mm) {
506 auto mm = hh_mm % 100;
507 if (mm >= 60)
508 throw std::runtime_error{fmt::format(
509 "Invalid value for minutes {} in int representation {}", mm, hh_mm)};
510 return TimeOfDay{std::chrono::minutes{hh_mm / 100 * 60 + mm}};
511}
512
513} // namespace utils::datetime
514
515USERVER_NAMESPACE_END
516
517namespace fmt {
518
519template <typename Duration>
520class formatter<USERVER_NAMESPACE::utils::datetime::TimeOfDay<Duration>> {
521 /// Format string used for format key `%H`, two-digit 24-hour left-padded by 0
522 static constexpr auto kLongHourFormat =
523 USERVER_NAMESPACE::utils::datetime::detail::kLongHourFormat;
524 /// Format string used for minutes, key `%M`, no variations
525 static constexpr auto kMinutesFormat =
526 USERVER_NAMESPACE::utils::datetime::detail::kMinutesFormat;
527 /// Format string used for seconds, key `%S`, no variations
528 static constexpr auto kSecondsFormat =
529 USERVER_NAMESPACE::utils::datetime::detail::kSecondsFormat;
530 /// Format string for subseconds, keys not yet assigned
531 /// for use in representation
532 static constexpr auto kSubsecondsFormat =
533 USERVER_NAMESPACE::utils::datetime::detail::kSubsecondsFormat;
534
535 static constexpr auto kSubsecondsPreformat =
536 USERVER_NAMESPACE::utils::datetime::detail::kSubsecondsPreformat<
537 typename Duration::period>;
538
539 static constexpr std::string_view kLiteralPercent = "%";
540
541 static constexpr auto kDefaultFormat =
542 USERVER_NAMESPACE::utils::datetime::detail::kDefaultFormat<
543 typename Duration::period>;
544
545 constexpr std::string_view GetFormatForKey(char key) {
546 // TODO Check if time part already seen
547 switch (key) {
548 case 'H':
549 return kLongHourFormat;
550 case 'M':
551 return kMinutesFormat;
552 case 'S':
553 return kSecondsFormat;
554 default:
555 throw format_error{fmt::format("Unsupported format key {}", key)};
556 }
557 }
558
559 public:
560 constexpr auto parse(format_parse_context& ctx) {
561 enum { kChar, kPercent, kKey } state = kChar;
562 const auto* it = ctx.begin();
563 const auto* end = ctx.end();
564 const auto* begin = it;
565
566 bool custom_format = false;
567 std::size_t size = 0;
568 for (; it != end && *it != '}'; ++it) {
569 if (!custom_format) {
570 representation_size_ = 0;
571 custom_format = true;
572 }
573 if (*it == '%') {
574 if (state == kPercent) {
575 PushBackFmt(kLiteralPercent);
576 state = kKey;
577 } else {
578 if (state == kChar && size > 0) {
579 PushBackFmt({begin, size});
580 }
581 state = kPercent;
582 }
583 } else {
584 if (state == kPercent) {
585 PushBackFmt(GetFormatForKey(*it));
586 state = kKey;
587 } else if (state == kKey) {
588 // start new literal
589 begin = it;
590 size = 1;
591 state = kChar;
592 } else {
593 ++size;
594 }
595 }
596 }
597 if (!custom_format) {
598 for (const auto fmt : kDefaultFormat) {
599 PushBackFmt(fmt);
600 }
601 }
602 if (state == kChar) {
603 if (size > 0) {
604 PushBackFmt({begin, size});
605 }
606 } else if (state == kPercent) {
607 throw format_error{"No format key after percent character"};
608 }
609 return it;
610 }
611
612 template <typename FormatContext>
613 constexpr auto format(
614 const USERVER_NAMESPACE::utils::datetime::TimeOfDay<Duration>& value,
615 FormatContext& ctx) const {
616 auto hours = value.Hours().count();
617 auto mins = value.Minutes().count();
618 auto secs = value.Seconds().count();
619
620 auto ss = value.Subseconds().count();
621
622 // Number of decimal positions (min 1) + point + null terminator
623 constexpr std::size_t buffer_size =
624 std::max(USERVER_NAMESPACE::utils::datetime::detail::kDecimalPositions<
625 typename Duration::period>,
626 std::size_t{1}) +
627 2;
628 char subseconds[buffer_size];
629 subseconds[0] = 0;
630 if (ss > 0 || !truncate_trailing_subseconds_) {
631 fmt::format_to(subseconds, kSubsecondsPreformat, ss);
632 subseconds[buffer_size - 1] = 0;
633 if (truncate_trailing_subseconds_) {
634 // Truncate trailing zeros
635 for (auto last = buffer_size - 2; last > 0 && subseconds[last] == '0';
636 --last)
637 subseconds[last] = 0;
638 }
639 }
640
641 auto res = ctx.out();
642 for (std::size_t i = 0; i < representation_size_; ++i) {
643 res = format_to(ctx.out(), fmt::runtime(representation_[i]), hours, mins,
644 secs, subseconds);
645 }
646 return res;
647 }
648
649 private:
650 constexpr void PushBackFmt(std::string_view fmt) {
651 if (representation_size_ >= kRepresentationCapacity) {
652 throw format_error("Format string complexity exceeds TimeOfDay limits");
653 }
654 representation_[representation_size_++] = fmt;
655 }
656
657 // Enough for hours, minutes, seconds, text and some % literals.
658 static constexpr std::size_t kRepresentationCapacity = 10;
659
660 std::string_view representation_[kRepresentationCapacity]{};
661 std::size_t representation_size_{0};
662 bool truncate_trailing_subseconds_{true};
663};
664
665} // namespace fmt