]> git.proxmox.com Git - ceph.git/blob - ceph/src/fmt/include/fmt/chrono.h
import 15.2.0 Octopus source
[ceph.git] / ceph / src / fmt / include / fmt / chrono.h
1 // Formatting library for C++ - chrono support
2 //
3 // Copyright (c) 2012 - present, Victor Zverovich
4 // All rights reserved.
5 //
6 // For the license information refer to format.h.
7
8 #ifndef FMT_CHRONO_H_
9 #define FMT_CHRONO_H_
10
11 #include "format.h"
12 #include "locale.h"
13
14 #include <chrono>
15 #include <ctime>
16 #include <locale>
17 #include <sstream>
18
19 FMT_BEGIN_NAMESPACE
20
21 // Prevents expansion of a preceding token as a function-style macro.
22 // Usage: f FMT_NOMACRO()
23 #define FMT_NOMACRO
24
25 namespace internal {
26 inline null<> localtime_r FMT_NOMACRO(...) { return null<>(); }
27 inline null<> localtime_s(...) { return null<>(); }
28 inline null<> gmtime_r(...) { return null<>(); }
29 inline null<> gmtime_s(...) { return null<>(); }
30 } // namespace internal
31
32 // Thread-safe replacement for std::localtime
33 inline std::tm localtime(std::time_t time) {
34 struct dispatcher {
35 std::time_t time_;
36 std::tm tm_;
37
38 dispatcher(std::time_t t) : time_(t) {}
39
40 bool run() {
41 using namespace fmt::internal;
42 return handle(localtime_r(&time_, &tm_));
43 }
44
45 bool handle(std::tm* tm) { return tm != FMT_NULL; }
46
47 bool handle(internal::null<>) {
48 using namespace fmt::internal;
49 return fallback(localtime_s(&tm_, &time_));
50 }
51
52 bool fallback(int res) { return res == 0; }
53
54 #if !FMT_MSC_VER
55 bool fallback(internal::null<>) {
56 using namespace fmt::internal;
57 std::tm* tm = std::localtime(&time_);
58 if (tm) tm_ = *tm;
59 return tm != FMT_NULL;
60 }
61 #endif
62 };
63 dispatcher lt(time);
64 // Too big time values may be unsupported.
65 if (!lt.run()) FMT_THROW(format_error("time_t value out of range"));
66 return lt.tm_;
67 }
68
69 // Thread-safe replacement for std::gmtime
70 inline std::tm gmtime(std::time_t time) {
71 struct dispatcher {
72 std::time_t time_;
73 std::tm tm_;
74
75 dispatcher(std::time_t t) : time_(t) {}
76
77 bool run() {
78 using namespace fmt::internal;
79 return handle(gmtime_r(&time_, &tm_));
80 }
81
82 bool handle(std::tm* tm) { return tm != FMT_NULL; }
83
84 bool handle(internal::null<>) {
85 using namespace fmt::internal;
86 return fallback(gmtime_s(&tm_, &time_));
87 }
88
89 bool fallback(int res) { return res == 0; }
90
91 #if !FMT_MSC_VER
92 bool fallback(internal::null<>) {
93 std::tm* tm = std::gmtime(&time_);
94 if (tm) tm_ = *tm;
95 return tm != FMT_NULL;
96 }
97 #endif
98 };
99 dispatcher gt(time);
100 // Too big time values may be unsupported.
101 if (!gt.run()) FMT_THROW(format_error("time_t value out of range"));
102 return gt.tm_;
103 }
104
105 namespace internal {
106 inline std::size_t strftime(char* str, std::size_t count, const char* format,
107 const std::tm* time) {
108 return std::strftime(str, count, format, time);
109 }
110
111 inline std::size_t strftime(wchar_t* str, std::size_t count,
112 const wchar_t* format, const std::tm* time) {
113 return std::wcsftime(str, count, format, time);
114 }
115 } // namespace internal
116
117 template <typename Char> struct formatter<std::tm, Char> {
118 template <typename ParseContext>
119 auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
120 auto it = ctx.begin();
121 if (it != ctx.end() && *it == ':') ++it;
122 auto end = it;
123 while (end != ctx.end() && *end != '}') ++end;
124 tm_format.reserve(internal::to_unsigned(end - it + 1));
125 tm_format.append(it, end);
126 tm_format.push_back('\0');
127 return end;
128 }
129
130 template <typename FormatContext>
131 auto format(const std::tm& tm, FormatContext& ctx) -> decltype(ctx.out()) {
132 basic_memory_buffer<Char> buf;
133 std::size_t start = buf.size();
134 for (;;) {
135 std::size_t size = buf.capacity() - start;
136 std::size_t count =
137 internal::strftime(&buf[start], size, &tm_format[0], &tm);
138 if (count != 0) {
139 buf.resize(start + count);
140 break;
141 }
142 if (size >= tm_format.size() * 256) {
143 // If the buffer is 256 times larger than the format string, assume
144 // that `strftime` gives an empty result. There doesn't seem to be a
145 // better way to distinguish the two cases:
146 // https://github.com/fmtlib/fmt/issues/367
147 break;
148 }
149 const std::size_t MIN_GROWTH = 10;
150 buf.reserve(buf.capacity() + (size > MIN_GROWTH ? size : MIN_GROWTH));
151 }
152 return std::copy(buf.begin(), buf.end(), ctx.out());
153 }
154
155 basic_memory_buffer<Char> tm_format;
156 };
157
158 namespace internal {
159 template <typename Period> FMT_CONSTEXPR const char* get_units() {
160 return FMT_NULL;
161 }
162 template <> FMT_CONSTEXPR const char* get_units<std::atto>() { return "as"; }
163 template <> FMT_CONSTEXPR const char* get_units<std::femto>() { return "fs"; }
164 template <> FMT_CONSTEXPR const char* get_units<std::pico>() { return "ps"; }
165 template <> FMT_CONSTEXPR const char* get_units<std::nano>() { return "ns"; }
166 template <> FMT_CONSTEXPR const char* get_units<std::micro>() { return "µs"; }
167 template <> FMT_CONSTEXPR const char* get_units<std::milli>() { return "ms"; }
168 template <> FMT_CONSTEXPR const char* get_units<std::centi>() { return "cs"; }
169 template <> FMT_CONSTEXPR const char* get_units<std::deci>() { return "ds"; }
170 template <> FMT_CONSTEXPR const char* get_units<std::ratio<1>>() { return "s"; }
171 template <> FMT_CONSTEXPR const char* get_units<std::deca>() { return "das"; }
172 template <> FMT_CONSTEXPR const char* get_units<std::hecto>() { return "hs"; }
173 template <> FMT_CONSTEXPR const char* get_units<std::kilo>() { return "ks"; }
174 template <> FMT_CONSTEXPR const char* get_units<std::mega>() { return "Ms"; }
175 template <> FMT_CONSTEXPR const char* get_units<std::giga>() { return "Gs"; }
176 template <> FMT_CONSTEXPR const char* get_units<std::tera>() { return "Ts"; }
177 template <> FMT_CONSTEXPR const char* get_units<std::peta>() { return "Ps"; }
178 template <> FMT_CONSTEXPR const char* get_units<std::exa>() { return "Es"; }
179 template <> FMT_CONSTEXPR const char* get_units<std::ratio<60>>() {
180 return "m";
181 }
182 template <> FMT_CONSTEXPR const char* get_units<std::ratio<3600>>() {
183 return "h";
184 }
185
186 enum class numeric_system {
187 standard,
188 // Alternative numeric system, e.g. 十二 instead of 12 in ja_JP locale.
189 alternative
190 };
191
192 // Parses a put_time-like format string and invokes handler actions.
193 template <typename Char, typename Handler>
194 FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin,
195 const Char* end,
196 Handler&& handler) {
197 auto ptr = begin;
198 while (ptr != end) {
199 auto c = *ptr;
200 if (c == '}') break;
201 if (c != '%') {
202 ++ptr;
203 continue;
204 }
205 if (begin != ptr) handler.on_text(begin, ptr);
206 ++ptr; // consume '%'
207 if (ptr == end) FMT_THROW(format_error("invalid format"));
208 c = *ptr++;
209 switch (c) {
210 case '%':
211 handler.on_text(ptr - 1, ptr);
212 break;
213 case 'n': {
214 const char newline[] = "\n";
215 handler.on_text(newline, newline + 1);
216 break;
217 }
218 case 't': {
219 const char tab[] = "\t";
220 handler.on_text(tab, tab + 1);
221 break;
222 }
223 // Day of the week:
224 case 'a':
225 handler.on_abbr_weekday();
226 break;
227 case 'A':
228 handler.on_full_weekday();
229 break;
230 case 'w':
231 handler.on_dec0_weekday(numeric_system::standard);
232 break;
233 case 'u':
234 handler.on_dec1_weekday(numeric_system::standard);
235 break;
236 // Month:
237 case 'b':
238 handler.on_abbr_month();
239 break;
240 case 'B':
241 handler.on_full_month();
242 break;
243 // Hour, minute, second:
244 case 'H':
245 handler.on_24_hour(numeric_system::standard);
246 break;
247 case 'I':
248 handler.on_12_hour(numeric_system::standard);
249 break;
250 case 'M':
251 handler.on_minute(numeric_system::standard);
252 break;
253 case 'S':
254 handler.on_second(numeric_system::standard);
255 break;
256 // Other:
257 case 'c':
258 handler.on_datetime(numeric_system::standard);
259 break;
260 case 'x':
261 handler.on_loc_date(numeric_system::standard);
262 break;
263 case 'X':
264 handler.on_loc_time(numeric_system::standard);
265 break;
266 case 'D':
267 handler.on_us_date();
268 break;
269 case 'F':
270 handler.on_iso_date();
271 break;
272 case 'r':
273 handler.on_12_hour_time();
274 break;
275 case 'R':
276 handler.on_24_hour_time();
277 break;
278 case 'T':
279 handler.on_iso_time();
280 break;
281 case 'p':
282 handler.on_am_pm();
283 break;
284 case 'Q':
285 handler.on_duration_value();
286 break;
287 case 'q':
288 handler.on_duration_unit();
289 break;
290 case 'z':
291 handler.on_utc_offset();
292 break;
293 case 'Z':
294 handler.on_tz_name();
295 break;
296 // Alternative representation:
297 case 'E': {
298 if (ptr == end) FMT_THROW(format_error("invalid format"));
299 c = *ptr++;
300 switch (c) {
301 case 'c':
302 handler.on_datetime(numeric_system::alternative);
303 break;
304 case 'x':
305 handler.on_loc_date(numeric_system::alternative);
306 break;
307 case 'X':
308 handler.on_loc_time(numeric_system::alternative);
309 break;
310 default:
311 FMT_THROW(format_error("invalid format"));
312 }
313 break;
314 }
315 case 'O':
316 if (ptr == end) FMT_THROW(format_error("invalid format"));
317 c = *ptr++;
318 switch (c) {
319 case 'w':
320 handler.on_dec0_weekday(numeric_system::alternative);
321 break;
322 case 'u':
323 handler.on_dec1_weekday(numeric_system::alternative);
324 break;
325 case 'H':
326 handler.on_24_hour(numeric_system::alternative);
327 break;
328 case 'I':
329 handler.on_12_hour(numeric_system::alternative);
330 break;
331 case 'M':
332 handler.on_minute(numeric_system::alternative);
333 break;
334 case 'S':
335 handler.on_second(numeric_system::alternative);
336 break;
337 default:
338 FMT_THROW(format_error("invalid format"));
339 }
340 break;
341 default:
342 FMT_THROW(format_error("invalid format"));
343 }
344 begin = ptr;
345 }
346 if (begin != ptr) handler.on_text(begin, ptr);
347 return ptr;
348 }
349
350 struct chrono_format_checker {
351 void report_no_date() { FMT_THROW(format_error("no date")); }
352
353 template <typename Char> void on_text(const Char*, const Char*) {}
354 void on_abbr_weekday() { report_no_date(); }
355 void on_full_weekday() { report_no_date(); }
356 void on_dec0_weekday(numeric_system) { report_no_date(); }
357 void on_dec1_weekday(numeric_system) { report_no_date(); }
358 void on_abbr_month() { report_no_date(); }
359 void on_full_month() { report_no_date(); }
360 void on_24_hour(numeric_system) {}
361 void on_12_hour(numeric_system) {}
362 void on_minute(numeric_system) {}
363 void on_second(numeric_system) {}
364 void on_datetime(numeric_system) { report_no_date(); }
365 void on_loc_date(numeric_system) { report_no_date(); }
366 void on_loc_time(numeric_system) { report_no_date(); }
367 void on_us_date() { report_no_date(); }
368 void on_iso_date() { report_no_date(); }
369 void on_12_hour_time() {}
370 void on_24_hour_time() {}
371 void on_iso_time() {}
372 void on_am_pm() {}
373 void on_duration_value() {}
374 void on_duration_unit() {}
375 void on_utc_offset() { report_no_date(); }
376 void on_tz_name() { report_no_date(); }
377 };
378
379 template <typename Int> inline int to_int(Int value) {
380 FMT_ASSERT(value >= (std::numeric_limits<int>::min)() &&
381 value <= (std::numeric_limits<int>::max)(),
382 "invalid value");
383 return static_cast<int>(value);
384 }
385
386 template <typename Rep, typename OutputIt>
387 OutputIt static format_chrono_duration_value(OutputIt out, Rep val,
388 int precision) {
389 if (precision < 0) {
390 return format_to(out, std::is_floating_point<Rep>::value ? "{:g}" : "{}",
391 val);
392 }
393 return format_to(out, "{:.{}f}", val, precision);
394 }
395
396 template <typename Period, typename OutputIt>
397 static OutputIt format_chrono_duration_unit(OutputIt out) {
398 if (const char* unit = get_units<Period>())
399 return format_to(out, "{}", unit);
400 else if (Period::den == 1)
401 return format_to(out, "[{}]s", Period::num);
402 else
403 return format_to(out, "[{}/{}]s", Period::num, Period::den);
404 }
405
406 template <typename FormatContext, typename OutputIt, typename Rep,
407 typename Period>
408 struct chrono_formatter {
409 FormatContext& context;
410 OutputIt out;
411 int precision;
412 Rep val;
413 typedef std::chrono::duration<Rep, std::milli> milliseconds;
414 std::chrono::seconds s;
415 milliseconds ms;
416
417 typedef typename FormatContext::char_type char_type;
418
419 explicit chrono_formatter(FormatContext& ctx, OutputIt o,
420 std::chrono::duration<Rep, Period> d)
421 : context(ctx),
422 out(o),
423 val(d.count()),
424 s(std::chrono::duration_cast<std::chrono::seconds>(d)),
425 ms(std::chrono::duration_cast<milliseconds>(d - s)) {}
426
427 int hour() const { return to_int((s.count() / 3600) % 24); }
428
429 int hour12() const {
430 auto hour = to_int((s.count() / 3600) % 12);
431 return hour > 0 ? hour : 12;
432 }
433
434 int minute() const { return to_int((s.count() / 60) % 60); }
435 int second() const { return to_int(s.count() % 60); }
436
437 std::tm time() const {
438 auto time = std::tm();
439 time.tm_hour = hour();
440 time.tm_min = minute();
441 time.tm_sec = second();
442 return time;
443 }
444
445 void write(int value, int width) {
446 typedef typename int_traits<int>::main_type main_type;
447 main_type n = to_unsigned(value);
448 int num_digits = internal::count_digits(n);
449 if (width > num_digits) out = std::fill_n(out, width - num_digits, '0');
450 out = format_decimal<char_type>(out, n, num_digits);
451 }
452
453 void format_localized(const tm& time, const char* format) {
454 auto locale = context.locale().template get<std::locale>();
455 auto& facet = std::use_facet<std::time_put<char_type>>(locale);
456 std::basic_ostringstream<char_type> os;
457 os.imbue(locale);
458 facet.put(os, os, ' ', &time, format, format + std::strlen(format));
459 auto str = os.str();
460 std::copy(str.begin(), str.end(), out);
461 }
462
463 void on_text(const char_type* begin, const char_type* end) {
464 std::copy(begin, end, out);
465 }
466
467 // These are not implemented because durations don't have date information.
468 void on_abbr_weekday() {}
469 void on_full_weekday() {}
470 void on_dec0_weekday(numeric_system) {}
471 void on_dec1_weekday(numeric_system) {}
472 void on_abbr_month() {}
473 void on_full_month() {}
474 void on_datetime(numeric_system) {}
475 void on_loc_date(numeric_system) {}
476 void on_loc_time(numeric_system) {}
477 void on_us_date() {}
478 void on_iso_date() {}
479 void on_utc_offset() {}
480 void on_tz_name() {}
481
482 void on_24_hour(numeric_system ns) {
483 if (ns == numeric_system::standard) return write(hour(), 2);
484 auto time = tm();
485 time.tm_hour = hour();
486 format_localized(time, "%OH");
487 }
488
489 void on_12_hour(numeric_system ns) {
490 if (ns == numeric_system::standard) return write(hour12(), 2);
491 auto time = tm();
492 time.tm_hour = hour();
493 format_localized(time, "%OI");
494 }
495
496 void on_minute(numeric_system ns) {
497 if (ns == numeric_system::standard) return write(minute(), 2);
498 auto time = tm();
499 time.tm_min = minute();
500 format_localized(time, "%OM");
501 }
502
503 void on_second(numeric_system ns) {
504 if (ns == numeric_system::standard) {
505 write(second(), 2);
506 if (ms != std::chrono::milliseconds(0)) {
507 *out++ = '.';
508 write(to_int(ms.count()), 3);
509 }
510 return;
511 }
512 auto time = tm();
513 time.tm_sec = second();
514 format_localized(time, "%OS");
515 }
516
517 void on_12_hour_time() { format_localized(time(), "%r"); }
518
519 void on_24_hour_time() {
520 write(hour(), 2);
521 *out++ = ':';
522 write(minute(), 2);
523 }
524
525 void on_iso_time() {
526 on_24_hour_time();
527 *out++ = ':';
528 write(second(), 2);
529 }
530
531 void on_am_pm() { format_localized(time(), "%p"); }
532
533 void on_duration_value() {
534 out = format_chrono_duration_value(out, val, precision);
535 }
536
537 void on_duration_unit() { out = format_chrono_duration_unit<Period>(out); }
538 };
539 } // namespace internal
540
541 template <typename Rep, typename Period, typename Char>
542 struct formatter<std::chrono::duration<Rep, Period>, Char> {
543 private:
544 align_spec spec;
545 int precision;
546 typedef internal::arg_ref<Char> arg_ref_type;
547 arg_ref_type width_ref;
548 arg_ref_type precision_ref;
549 mutable basic_string_view<Char> format_str;
550 typedef std::chrono::duration<Rep, Period> duration;
551
552 struct spec_handler {
553 formatter& f;
554 basic_parse_context<Char>& context;
555 basic_string_view<Char> format_str;
556
557 template <typename Id> FMT_CONSTEXPR arg_ref_type make_arg_ref(Id arg_id) {
558 context.check_arg_id(arg_id);
559 return arg_ref_type(arg_id);
560 }
561
562 FMT_CONSTEXPR arg_ref_type make_arg_ref(basic_string_view<Char> arg_id) {
563 context.check_arg_id(arg_id);
564 const auto str_val = internal::string_view_metadata(format_str, arg_id);
565 return arg_ref_type(str_val);
566 }
567
568 FMT_CONSTEXPR arg_ref_type make_arg_ref(internal::auto_id) {
569 return arg_ref_type(context.next_arg_id());
570 }
571
572 void on_error(const char* msg) { FMT_THROW(format_error(msg)); }
573 void on_fill(Char fill) { f.spec.fill_ = fill; }
574 void on_align(alignment align) { f.spec.align_ = align; }
575 void on_width(unsigned width) { f.spec.width_ = width; }
576 void on_precision(unsigned precision) { f.precision = precision; }
577 void end_precision() {}
578
579 template <typename Id> void on_dynamic_width(Id arg_id) {
580 f.width_ref = make_arg_ref(arg_id);
581 }
582
583 template <typename Id> void on_dynamic_precision(Id arg_id) {
584 f.precision_ref = make_arg_ref(arg_id);
585 }
586 };
587
588 public:
589 formatter() : spec(), precision(-1) {}
590
591 FMT_CONSTEXPR auto parse(basic_parse_context<Char>& ctx)
592 -> decltype(ctx.begin()) {
593 auto begin = ctx.begin(), end = ctx.end();
594 if (begin == end) return begin;
595 spec_handler handler{*this, ctx, format_str};
596 begin = internal::parse_align(begin, end, handler);
597 if (begin == end) return begin;
598 begin = internal::parse_width(begin, end, handler);
599 if (begin == end) return begin;
600 if (*begin == '.') {
601 if (std::is_floating_point<Rep>::value)
602 begin = internal::parse_precision(begin, end, handler);
603 else
604 handler.on_error("precision not allowed for this argument type");
605 }
606 end = parse_chrono_format(begin, end, internal::chrono_format_checker());
607 format_str =
608 basic_string_view<Char>(&*begin, internal::to_unsigned(end - begin));
609 return end;
610 }
611
612 template <typename FormatContext>
613 auto format(const duration& d, FormatContext& ctx) -> decltype(ctx.out()) {
614 auto begin = format_str.begin(), end = format_str.end();
615 // As a possible future optimization, we could avoid extra copying if width
616 // is not specified.
617 basic_memory_buffer<Char> buf;
618 auto out = std::back_inserter(buf);
619 typedef output_range<decltype(ctx.out()), Char> range;
620 basic_writer<range> w(range(ctx.out()));
621 internal::handle_dynamic_spec<internal::width_checker>(
622 spec.width_, width_ref, ctx, format_str.begin());
623 internal::handle_dynamic_spec<internal::precision_checker>(
624 precision, precision_ref, ctx, format_str.begin());
625 if (begin == end || *begin == '}') {
626 out = internal::format_chrono_duration_value(out, d.count(), precision);
627 internal::format_chrono_duration_unit<Period>(out);
628 } else {
629 internal::chrono_formatter<FormatContext, decltype(out), Rep, Period> f(
630 ctx, out, d);
631 f.precision = precision;
632 parse_chrono_format(begin, end, f);
633 }
634 w.write(buf.data(), buf.size(), spec);
635 return w.out();
636 }
637 };
638
639 FMT_END_NAMESPACE
640
641 #endif // FMT_CHRONO_H_