091ca2aa03
f5e54359d Update version a003ab93c Update changelog 9b7416081 `atomic_flag` formatting (#3594) a21690bdf Coding conventions c9efd8968 Merge branch 'MathewBensonCode-master' e78682405 Refactor Improvement on umul128 function 2b20d7be6 Refactor To remove friend function private access 1f38ebbdb Add condition to include friend function in win32 e41817969 Fix for FMT_MODULE not compiling on GCC 0bffed895 Bump actions/checkout from 3.1.0 to 3.5.3 (#3602) cc077a5e3 Fix `FMT_NO_UNIQUE_ADDRESS` warning with clang-cl. (#3600) a992b3d1f Fix for Compilation Error When Using FMT_MODULE (#3597) af1b768cc Update changelog 2a7c45b54 Update changelog bd868f3a5 Improve compatibility with GBK #3598 (#3599) bbb784fb1 Fix a gcc error about partial specialization after instantiation 6c845f57e Fix `formatted_size` with `FMT_COMPILE` and format specs (#3588) a379595c5 Bump actions/upload-artifact from 3.1.0 to 3.1.2 (#3593) 9dfde7871 Bump ossf/scorecard-action from 2.1.2 to 2.2.0 (#3592) e92a705bd Bump github/codeql-action from 2.2.4 to 2.21.4 (#3591) 5a866fe85 Add formatter for std::atomic (#3574) e150ea0cc to_string supports types with format_as 29ce2ff8a Update README.rst 2e1362add Create scorecard.yml e57ca2e36 Update version 4c56612c6 Update changelog e1acd5f4d Bump version 6ffee2f75 Handle new rst nodes ee475d640 Update changelog ecc914125 Update changelog d42486231 Fix fixed precision handling during rounding in long double 3c1b3337d Update changelog 35fb69ebe Update changelog f92be35c0 Update changelog 7a2f6ac21 Fix a comment 744ff5554 Fix docs 70ae48b00 Update changelog ebb10347c Merge branch 'master' of github.com:fmtlib/fmt 100572016 Clarify that visit_format_arg is deprecated aeb6ad4dd Added formatter for bit_reference-like types (#3570) 96d1fa22d Update changelog 8a4bec5cf fix ambiguous formatter lookup for flat_set (#3561) eacd51c24 cmake: fix FMT_PKGCONFIG_DIR path (#3563) 757564f5c add missing inline specifier (#3552) f4214ae8d Use the U literal for unsigned integer constants. (#3549) aecf80d30 Update a comment 503d49286 expose detail::throw_format_error (#3551) 4f46cb82f Remove std::copy usage. (#3550) 3dec65b7f Use replacement character in path dbabb305c Turn off error-producing NVCC workaround when using c++20 (#3544) ac0ab8eff Improve path formatter 40f35d6f0 Cleanup std formatters 31c2c5679 Cleanup formatters b2728a317 Improve path formatter 77e0b0e22 Cleanup chrono formatter e47585904 Fix overspecified tests 436c131d4 Optimize compiled format_to_n 388bc296b Fix fixed formatting of small long doubles 95e1ea573 Clarify requirement on compiled formatter fb97cb231 Suppress a bogus stringop-overflow warning dd5a9691f Clarify that data is not null-terminated 72dc4491e Fix format_string_checker initialisation order (#3542) 9bea6ec04 Don't use deprecated checked_array_iterator 661b23ede Call parse on empty specs at compile time 8e87d3a8b Fix include a47491656 Update CMakeLists.txt (#3523) de4705f84 Trying to improve errors in the unformattable case (#3478) e4c8cfe38 README: Add link to merged clang-tidy check (#3515) 606f85f8b Workaround brain-damaged conversions a331dbfb6 Fix type in assert message. (#3508) 13156e54b Revert "add ability to build Apple framework using CMAKE_FRAMEWORK" (#3496) 9158bea1e Remove old grisu tests dd17f89a1 Fix for issue #3492 (#3493) 1daae555b Optimize format string compilation 6ad301235 Optimize code unit output in compiled format 8732ad877 Update docs 5afb1821a Update docs a81135f2c Fix a link dfc34821a Update docs 0765e7284 Update docs 977d887a4 Suppress a bogus warning c86fe0b8d Give basic_memory_buffer allocator [[no_unique_address]] (#3485) 5dbe0ff95 Bump actions/checkout from 3.5.2 to 3.5.3 (#3486) de0757b57 Use FMT_TRY and FMT_CATCH in std.h (#3482) 8fe893c0a Update README.rst 0f823df46 Remove detail namespace hack 60fd9941c Use correct Char type in std::filesystem::path (#3476) 8abfc145b Fix MSVC warning in std::chrono::time_point formatter (#3475) 686b3353a Simplify typeid check b2106f363 Tweak comments 35547d600 Use const_check to silence MSVC warning 179c7e5a6 Use typeid() only if it's available 61fb3a15f Add VxWorks7 user space and kernel space support (#3467) bd393456e Remove FMT_*_DETAIL_NAMESPACE 6be36af0d Remove udl_formatter 2a35eeed8 Remove unused data 256a826d6 Consolidate formatters 6369af37d Simplify formatters 0b8404918 Remove get_cached_power 171a020c8 Pass correct Char to base format_as formatter (#3457) d8f04e399 Simplify ceil 5e988f8df Remove is_constant_evaluated() check 19b17618a Make constexpr ceil c684a06d5 New CI: macOS 13, Xcode 14.3, C++ 20 abdb7fdf8 Update api.rst 8b09fe2a0 Fix example for user-defined types in documentation for 10.0.0 (#3461) adad18a74 Update ChangeLog.rst 858e528ab Use dragon in constexpr a54cb108d CMake: Do not fail on unknown compiler features (#3453) ef55d4f52 Suppress a bogus gcc warning 70b6a6fa4 Update README.rst (#3454) 6fe895410 Remove invalid_arg_index d0652d225 Create dependabot.yml (#3452) de8d0171a Simplify FMT_FORMAT_AS 7401fe046 Hash pin Github Workflows (#3451) 08ef0d084 fix formatter<char*> (#3432) d60b907f8 Replace mod_inv_25 by explicit value (#3450) 4ce086f73 remove code duplication (#3448) 821f8cdb4 Detemplatize printf more 0bf6ed7e1 Cleanup printf e40e04fac Detemplatize printf_arg_formatter 0a1c27281 Detemplatize basic_printf_context 2f605cc89 Deprecate wide printf 1d54499ac Apply coding conventions 3c6053c53 Cleanup basic_printf_context 4a392adaa Remove basic_printf_parse_context b14913fae FMT_MODULE_EXPORT -> FMT_EXPORT 2117df299 format-inl.h: address implicit int to bool conversion (#3446) 616a49378 Revert Char* formatter removal 9a034b0d5 Always assert in FMT_THROW (#3439) e0fc0e85e Remove unneeded FMT_API 552c43aba Improve long formatters d6846f4ac Clarify why byte formatter is in core aeedac588 Remove unneeded specialization eaa630769 Make hex float test more stable (#3434) e82bf41a1 Update README.rst e077396f5 Update docs 861facad0 Fix a typo 75bfe5761 Improve docs 697e76ba3 Don't call init_named_args unnecessarily a425e0ff3 Cleanup xchar API c36dd825b Remove unnecessary forwarding 130b8fcdb Reduce template instantiations a47e8419b Cleanup the core API ea49c91cd Cleanup argument construction d7592ad8b Fix time_point formatting for durations with certain ratios (#3430) ebfb2e677 Remove unused alias 5780269d5 Improve API safety b47119216 Fix a comment 8f18e72df Improve API safety 93d7cb12f Fix formatting 0e4278717 Remove unused macro 93a30a074 unicode_to_utf8 -> to_utf8 since both sides of conversion are Unicode a08196b14 Update ChangeLog.rst 0398ba42c Update ChangeLog.rst a0b8a92e3 Update version 5cf2342aa Bump version fe9d39d7c Update changelog 4c9856197 Update changelog 403b271ed Update changelog 2c991e1af Update changelog c984df981 Remove an unused function from internal class fbf21ed22 Update changelog 575583144 Update changelog e7f6888c7 Update changelog 39db2dfd0 Update changelog 9b7829e26 Update changelog 1e0ce567e Fix formatting of paths containing invalid Unicode dde8cf3bb Unification utf16/utf32 to utf8 conversion e84b00e01 Workaround to error: variable 'n' set but not used [-Werror,-Wunused-but-set-variable] b12ffea4f Add filesystem_error test back f61f15cc5 Suppress a false positive in gcc 192df93d7 modules missing pieces (#3399) d8973bf16 Add FMT_STRING for format_to() call (#3413) d7a8e50cb Improve module testing (#3397) 02cae7e48 Improve handling of Unicode in paths 53162142b Remove .bazelrc mention from Bazel related readme (#3411) 5bcf0d7f9 Bazel support (#3406) f8c9fabd9 Fix spelling (#3404) 62ff4e1db Remove foonathan from maintainer list (#3402) f449ca052 Name `vfprintf` clashes with the identically named declaration in 'stdio.h' if that happens to be #included into the same TU. Fix this by using qualified name lookup instead of unqualified lookup that also enables ADL. (#3400) eafcd3c8e Optionally attach declarations to the `global module` rather than `module fmt` (#3387) 18154cc90 Simplify print 0de789cf2 Update changelog c03938922 export names only once (#3392) 93e81bb5d Fix C4365 (signed/unsigned mismatch) warning on 32-bit Windows (#3398) e7d6eb679 Update tests to use recommended MOCK_METHOD (#3395) 18e7a2532 Remove obsolete msvc workarounds (#3388) 0489c19dc fix and improve module (#3386) 8ec94ac6a Use full path to pcm d97d8cea6 Push module check to test d8a2698e6 Fix compilation as a C++20 module with gcc 13 d9c19940a Update add_module_library 4b5ae0b0e Remove unnecessary module support check 75f3b1c09 Use add_module_library faf83406a Workaround cmake issue 165814d57 Add module support to CMake 33f715077 Fix error C2668 on msvc (#3378) c98e5a08a Fix modular build on clang 119c6bd16 Move the modules check 77eeb7183 Remove unused headers 13bf99f9d Enable modules in clang 16 1d0257e4c FMT_MODULE_EXPORT_* -> FMT_EXPORT_* 4613d48fd FMT_EXPORT -> FMT_LIB_EXPORT 4a4a2a2bd Fix diagnostics fce74caa1 Disable problematic implicit conversions 02bf4d1c1 Disable to_string_view ADL 466e0650e Remove problematic workaround 029caa8ea Update changelog e406ddbfa Remove broken part of the config 909567953 Update changelog 7f46cb75b ranges: Fix extra semi (#3374) 4e3f38105 Update changelog d3c10f516 fix compilation for MSDOS (#3369) ab956f600 Update changelog 97aedeab4 Workaround a double-double hexfloat format (#3366) bce8d4ed0 Remove stray comment a91c7b286 Cleanup the core API 19c074e47 Remove deprecated fallback formatter 41cfc739f Generalize format_as f6276a2c2 Force use a signed char (On ARM char is unsigned by default) (#3362) 6002ddf82 Remove a deprecated option 6549ffde8 Improve format_as safety d9bc5f132 Fix code causing spurious Wstringop-overflow warning 9c5cd998d Remove unused functions 93bfa0538 %T is %H:%M:%S (#3349) d8e1c4265 fix case of variant which is valueless by exception (#3347) e1720c0e5 Fix CUDA nvcc warning fmt/include/fmt/core.h(295): warning #1675-D: unrecognized GCC pragma (#3352) 7f882918e `write_floating_seconds`: Fall back to `::round` (#3343) cbc7b8d5c Cleanup dead variable (#3338) 050293646 Path is not escaped twice in the debug mode (#3321) 3daf33837 Enable consteval for Xcode 14.0.1 and later (#3331) e0748e61d Fix recursion check in range formatting b94e1016f chore: set permission to cifuzz.yml (#3328) 98699719f Make # handling consistent with std::format 48dfbcaa9 Improve license and PR template wording c644c753d Add '🐙 userver framework' to the projects 73b7cee7f Fix for issue #3325 (#3326) 5b8302079 Add optional support (#3303) 3a69529e8 Fix various typos (#3312) 76f520835 Call element parse in tuple parse 507c3042d class -> typename 1741e90de Always call parse in range formatter d646fd0da Minor cleanup b5c2f74f4 change sopen_s to wsopen_s (fmtlib#3234) (#3293) e03753c4a Add ubuntu mirrors (#3302) 6e6eb6377 [msvc] fix warning about non-inline variable 3c5464ba1 Fix OpenBSD build error (#3295) 655046d24 Fix container adaptor formatting 581c6292c Add formatters for container adapters (#3279) 7718eeeac Implement glibc ext for sec, min, and hour (#3271) 44e0eea94 Use FMT_HAS_INCLUDE and apply clang-format 99070899b Fix errors setting of FMT_USE_FLOAT128 (#3259) 05e3a9233 Allowing formatting non-copyable ranges. (#3290) 70db193f0 Visual Studio 2022: fmt/format-inl.h(1145,60): warning C4310: cast truncates constant value #3287 (#3288) a2c05a10e Workaround a bug in MSVC <= 19.22 cae9bf45b Simplify apidoc comments 87c066a35 Implement `println` (#3267) 9409b2e4d Workaround for incompatibility between libstdc++ consteval-based std::is_constant_evaluated() implementation and clang-14 (#3281) f89cd276f Refactor countl_zero fallback (#3276) 240b728d8 Report an error on overflow dfbb952b2 Fix empty spec for time point (#3275) 39971eb33 Fix localized format for float-point numbers (#3272) 0f42c17d8 Implement a new formatting algorithm for small given precision (#3269) bfc0924ea Bugfix for fmt::printf on Power9 architecture with the XL compiler (#3256) 676c2a107 Fix negative subsec for time_point (#3261) 2c80cedc3 Fix standard default installation target presence (#3264) dda53082b Support fill, align & width for time point (#3260) 2622cd23e Simplify arg_mapper 9e4a54fa6 Disable remaining implicit conversions 9ce648067 Disble deprecated implicit enum conversions 9121f9b1d Enable format_as for classes b7535365b Enable format_as for non-integral types 09ed1ddb9 Cleanup tests 0ec65d99a Merge parse_presentation_type into parse_format_specs 71e4e0272 Cleanup vprintf aad546baa Simplify presentation type parsing 14a69fcc5 Use parse_align bf34ffd33 Refactor format string parsing 6056e0712 Simplify symbols aa99b8640 Minor cleanup 6ade2eb4e Inline all_int_set caa697494 Simplify parse functions a73a9b6a8 Refactor format string checks 72785a3ab Cleanup write 0c3dd5ddd Remove redundant check 739b600f4 Remove iterator shenanigans 3710c4d38 Link to dragonbox a05ba44df Simplify format string parsing ffb9b1d13 Improve handling of signed types 32190859e Fix handling of char 8fe4d97d5 Reduce template instantiations 7e5a95956 Fix build with MSVC C++20 modules (#3254) 9e6030486 Clarify that unused args are allowed 7ad48c1f6 Cleanup core.h a921a596e Cleanup core.h 3e762fdf5 Use ignore_unused 79981a252 Cleanup ranges formatting bd12aaa98 Simplify format string parsing b8f36207c Simplify format string parsing d907786f0 Move anchor to where it belongs f2355bbe5 Fix docs f398c9476 Fix docs 4841784e8 Simplify C99 strftime detection conditions cb72c23e9 Improve timezone tests 583f2d820 Set timezone for chrono tests 32c439070 Minor cleanup 3a5e19fbf Minor cleanup dfb857ebe Refactor format spec parsing 9ea9b6bcb Cleanup arg id parsing 2b0ff62a7 Remove unused template arg from format_string_checker d1745084e Simplify parse context 407e7b7b6 basic_format_specs -> format_specs 3cf979475 Cleanup format string parsing 934c8e5f7 Refactor precision parsing fc9693834 Remove empty semicolon f0ab112c3 Cleanup parsing 9660e5b95 Remove redundant tests a585571e9 Ignore 0 character with align 840ec8569 Cleanup width handlers 1dadeb8a3 Refactor width parsing 275b4b341 Remove obsolete parse-benchmark e004f1d69 Fix for issue #3241 bde1a6070 Simplify fill and alignment parsing 040dc2a5d small typo in syntax.rst 6a186bcd6 Localize FMT_USE_LOCAL_TIME 8c56919bd Check chrono spec starts with % 115001a3b Formatting of system clocks ought to be to UTC, not to local time. This improves standards conformance of fmt. b90895412 Fix formatting of named arguments with locale d072f1dc6 Fix for issue #3228 3999fd193 Workaround an ADL issue c06e0b4ed Extract timezone offset from timezone conversion functions 1bf302a4e Implement %Ez, %Oz for chrono formatter f1733afd4 Pin godbolt example to specific version f61dcccc6 Update README.rst f9bcbdcbc Update README.rst 1a854b4aa Clarify what mod_inv_5 is 62ceb181b fix #3105 - Compile-time error when mixing named argument with automatic indexing b0c8263cb include/fmt/core.h: copy constructors removal d24be2e95 Add countl_zero function 8d50d814d Fix a chrono formatting issue found by fuzzing 115ca96e0 Bump tested CMake version to 3.25 886491625 Remove workaround for GTest bug 74c51ff37 Skip only strptime dependent test in scan-test 69ffedfe5 Use `target_compile_features` to specify C++ standard requirement fae6f7e08 Optimize range formatter a69e43c9d Update benchmark results 91c024ed3 Rename leading_v -> leading_xdigit 649aa102d CI linux: add CIFuzz Github action 31364732d Replace snprintf-based hex float formatter with internal implementation 74d55a493 Add missing operators 8276f1a20 Fix warning: the implicit by-copy capture of "this" is deprecated (EDG frontend) 81ebe70b9 Fix warning: a class type that is not trivially copyable passed through ellipsis (EDG frontend) 3160847eb Enable C++17 tests on macOS 6a95f8c7e Add missing env CTEST_OUTPUT_ON_FAILURE c7980542d Skip the scan-test if strptime isn't defined (#3184) 7df30f91a Format unique_ptr with custom deleter (#3177) d2e89c8b0 Document more chrono specs bd1959320 Document more chrono specs fd0d0ec8d Document more chrono specs 8e93434ed Update README.rst fc07217d8 Make utf-8 detection compatible with gbk cb7373b46 Replace format with FMT_STRING. (#3162) 795ed8abf Add precision modifier for seconds in chrono format (#3148) 66d71a1b3 Fixing formatting of range of range of char. (#3158) 80f8d3442 fmt::ostream - aggregate buffer instead of inheriting it (#3139) 64965bdc9 Add locale getter in tm_writer (#3147) e1ffa7655 Fix warning: conditional expression is constant. (#3150) 8c19bf3f2 Mention MariaDB amongst the projects that use fmt (#3145) f67dbc981 Remove duplicate implementation (#3144) cd7202e03 Fix overflow error (#3143) 51d3685ef Remove duplicate template parameter. (#3142) 9254cfa6f Support formatting of subseconds (#3115) cfb34a060 Avoid using `uint` as a type name (#3137) 5ad7b7138 Fix options for C++20 experimental module in CMake (#3134) d2c47c0df Fix broken condition (#3129) 491c32cbd Workaround gcc bug 103879 662adf4f3 Move formatter<std::error_code> from fmt/os.h to fmt/std.h (#3125) ad91cab37 Normalization of stdlib inline namespace names (#3119) 0ccaed3a6 Set CMAKE_RUNTIME_OUTPUT_DIRECTORY relative to CMAKE_CURRENT_BINARY_DIR instead of CMAKE_BINARY_DIR (#3120) ad719619c Support formatting of std time_point with utc_clock (#3110) 6e0a5f7fb Update CI to Ubuntu 20.04 and to newer versions of actions 48f525d02 Add basic_format_string::get() 0b5cb18b7 Use buffering in to_string to avoid bloat 4c4f99a58 Update a godbolt link 3272a7a3c Update an example a48e3355a Improve docs afcf42429 Update docs ac85afaab Simplify format_error 3178bb9a2 Update docs cf58f64c5 Update docs e4e0ae391 Use fmt/core.h in examples d65acc4e6 Improve docs c9f790b06 Update docs 6b8144a5a Update locale docs 2d66ad5d3 Suppress -Wshadow 042af5332 Suppress -Wfloat-equal 192859c2b Optimize writing to buffers via back_insert_iterator e2f6d7665 fix gcc <= 7.1 compile errors 61844b6b6 Fix build error on GCC-9 7a752e75f New CI: GCC-9 94ceb38a0 Improve locale API 58c4c012f Disable slow windows build and simplify write_loc c3494ae36 Refactor float localization 8ae56161c Fix compilation with FMT_ENFORCE_COMPILE_STRING and FMT_WERROR (#3091) 76705fc2e Update doc 21c2137e7 Add class name output to formatter for std::exception (#3076) ecffca672 Don't parse '}' as fill 3176e0fad Add locale overload for formatted_size (#3084) (#3087) 1feb430fa Fix intellisense on Windows (#3082) b98ffb7db Improve locale handling bac53951b Add starts_with to basic_string_view. (#3080) d59b89e9c More locale 58a5563a9 Implement grouping 1b94271ff Add support for UTF-8 digit separators 768d79a83 Implement format_facet 91ecb38a3 Localize negative integers aec3bb5d0 Workaround C complex.h idiocy 29c600013 Simplify is_variant_like_ check, fix compile error before GCC 11 (#3072) fec5515c5 num_format_facet -> format_facet f187274d3 Add loc_value fc5e59fe4 Don't use stringstream d6a870460 Improve locale support 56c72a671 Reduce locale dependency 4191477b9 Add formatter for std::exception (#3062) 75383a87f Inline trivial functions 48327a82e Make format.h compile faster b79ed4105 Remove unnecessary type_identity 64e29893c Improve locale support 0b0f7cfbf hip workaround 40e414d82 Fix compilation error with gcc-7.2.0 33b4c33c5 Requires FMT_CONSTEXPR20 support a07411c2b Disable compile-time checks for dynamic width/precision test for LCC and compiler without std::is_constant_evaluated() 797d82b21 Disable non-type template args for LCC a553521d6 Disable "GCC optimize" pragma for LCC a33701196 Update version 1f575fd5c Bump version c7635288f Fix docs, take 2 c8ed78e31 Fix docs e07cfb206 Update changelog 1dc7af569 Fix markup f7d21c3a1 Update changelog a55bcb24b Update changelog 30cb2b312 Remove appveyor config cf8d3c322 Update changelog 3c3cb6f6b Update changelog 91481f255 Detemplatize code_point_length_impl f98048b62 Fix bugs in utf8 decoder (#3056) 4a8e2949b Fix formatting of ranges of code unit types 3a3b0709e Disable bogus -Wstringop-overflow on GCC 11 (#3054) e724bbea1 Fix wchar_t corner cases 665d9779e Disable non-type template args for nvhpc (#3053) 13d07c6a3 Apply doc fixes retroactively 391f922ac Improve error reporting in format string compilation dc59d3df3 Fix a warning in gtest 489dabbd3 Fix decoder on broken utf8 sequences. (#3044) 541cd2183 Fix locale name (thanks Mikhail Paulyshka) 1f95c3438 Fix sign handling with large code units 779449fd9 Belarusify test fbb568bce nvhpc/22.3: workaround for c++17 mode. (#3043) 36c23bd5f Prepare for deprecating FindPythonInterp module. (#3040) 9ff0f3a7d Fix docs fd41110d3 Add MinGW to CI fc23cfbf4 Fix testsuite on MinGW + MSVCRT fd93b633b Constexpr formatted_size (#3026) 7fb8d33f9 Fix compile-time width/precision type check 8bd02e93b Reduce conditional compilation d9c1c7353 Use is_utf8() in print(std::ostream&, ...) 682e097be Remove -Wl,--as-needed linker option b9087ee58 Suppress unused typedef warning df56fdf88 Clarify docs for compile-time checks 90c48b852 Remove double code execution 5a8b7cd74 Add comment 36a25d75b Reuse detail::string_literal in the chrono formatter 6c9304b2c Replace ParseContext with basic_format_parse_context 24ab9dd19 Remove duplicate method a95dc1701 Remove unused condition 5f774c0ae I shouldn't be there 6567df7f2 Update README.rst 6c6b1fbf6 Workaround a bug in MSVC 9beddd08f Improve CI on Windows 6452e3c9e Fix a typo in example 756822ba3 Fix Unicode handling for ostream under Windows with libc++. (#3001) 0b2862a1e Range formatter (#2983) 258000064 Add fmt:: namespace to doc (#3009) e9ca7ea47 Suppress a bogus warning 81f1cc74a Improve Unicode handling when writing to an ostream on Windows (#2994) bbcb129e0 Reduce filesize of the tests on MinGW (#2995) 48e0a5922 Implement compile-time checks for dynamic width/precision type bc5c7c50f Fixes IBM XLC behavior with uint128 fallback (#2985) 00adc7120 Add a regression test for here be dragons c48be439f Add additional search paths for doxygen on Windows 371f9c71c Fix even in format_dragon 91abfcd6c Suppress an msvc warning deeab54b4 Remove unused include 688a627d6 Remove unused constants. 9bb1605f1 Remove some branches 8061d9afb Simplify Dragonbox implementation d82e1a108 Make sure the correct fmod overload is called defa04e73 Fix for EDG frontend (Intel, NVHPC compilers) (#2982) 92d36e82c The n specifier for ranges (#2981) 0db43cf7f Pointless comparison warnings (#2971) 05be7a076 Use FMT_USE_FLOAT128 instead of __SIZEOF_FLOAT128__ 2a1b3ac62 Fix large shift in uint128_fallback e1d3d3a32 Exclude recursive ranges from the formatter specialization for ranges (#2974) b761f1279 Improve forward using the idea from Jonathan Müller cc1926942 Inline std::forward d5e9166f5 Fixed typo in changelog example. b31d1a75a Add xchar support for fmt::streamed(). git-subtree-dir: externals/fmt git-subtree-split: f5e54359df4c26b6230fc61d38aa294581393084
2208 lines
71 KiB
C++
2208 lines
71 KiB
C++
// Formatting library for C++ - chrono support
|
|
//
|
|
// Copyright (c) 2012 - present, Victor Zverovich
|
|
// All rights reserved.
|
|
//
|
|
// For the license information refer to format.h.
|
|
|
|
#ifndef FMT_CHRONO_H_
|
|
#define FMT_CHRONO_H_
|
|
|
|
#include <algorithm>
|
|
#include <chrono>
|
|
#include <cmath> // std::isfinite
|
|
#include <cstring> // std::memcpy
|
|
#include <ctime>
|
|
#include <iterator>
|
|
#include <locale>
|
|
#include <ostream>
|
|
#include <type_traits>
|
|
|
|
#include "format.h"
|
|
|
|
FMT_BEGIN_NAMESPACE
|
|
|
|
// Check if std::chrono::local_t is available.
|
|
#ifndef FMT_USE_LOCAL_TIME
|
|
# ifdef __cpp_lib_chrono
|
|
# define FMT_USE_LOCAL_TIME (__cpp_lib_chrono >= 201907L)
|
|
# else
|
|
# define FMT_USE_LOCAL_TIME 0
|
|
# endif
|
|
#endif
|
|
|
|
// Check if std::chrono::utc_timestamp is available.
|
|
#ifndef FMT_USE_UTC_TIME
|
|
# ifdef __cpp_lib_chrono
|
|
# define FMT_USE_UTC_TIME (__cpp_lib_chrono >= 201907L)
|
|
# else
|
|
# define FMT_USE_UTC_TIME 0
|
|
# endif
|
|
#endif
|
|
|
|
// Enable tzset.
|
|
#ifndef FMT_USE_TZSET
|
|
// UWP doesn't provide _tzset.
|
|
# if FMT_HAS_INCLUDE("winapifamily.h")
|
|
# include <winapifamily.h>
|
|
# endif
|
|
# if defined(_WIN32) && (!defined(WINAPI_FAMILY) || \
|
|
(WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP))
|
|
# define FMT_USE_TZSET 1
|
|
# else
|
|
# define FMT_USE_TZSET 0
|
|
# endif
|
|
#endif
|
|
|
|
// Enable safe chrono durations, unless explicitly disabled.
|
|
#ifndef FMT_SAFE_DURATION_CAST
|
|
# define FMT_SAFE_DURATION_CAST 1
|
|
#endif
|
|
#if FMT_SAFE_DURATION_CAST
|
|
|
|
// For conversion between std::chrono::durations without undefined
|
|
// behaviour or erroneous results.
|
|
// This is a stripped down version of duration_cast, for inclusion in fmt.
|
|
// See https://github.com/pauldreik/safe_duration_cast
|
|
//
|
|
// Copyright Paul Dreik 2019
|
|
namespace safe_duration_cast {
|
|
|
|
template <typename To, typename From,
|
|
FMT_ENABLE_IF(!std::is_same<From, To>::value &&
|
|
std::numeric_limits<From>::is_signed ==
|
|
std::numeric_limits<To>::is_signed)>
|
|
FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) {
|
|
ec = 0;
|
|
using F = std::numeric_limits<From>;
|
|
using T = std::numeric_limits<To>;
|
|
static_assert(F::is_integer, "From must be integral");
|
|
static_assert(T::is_integer, "To must be integral");
|
|
|
|
// A and B are both signed, or both unsigned.
|
|
if (detail::const_check(F::digits <= T::digits)) {
|
|
// From fits in To without any problem.
|
|
} else {
|
|
// From does not always fit in To, resort to a dynamic check.
|
|
if (from < (T::min)() || from > (T::max)()) {
|
|
// outside range.
|
|
ec = 1;
|
|
return {};
|
|
}
|
|
}
|
|
return static_cast<To>(from);
|
|
}
|
|
|
|
/**
|
|
* converts From to To, without loss. If the dynamic value of from
|
|
* can't be converted to To without loss, ec is set.
|
|
*/
|
|
template <typename To, typename From,
|
|
FMT_ENABLE_IF(!std::is_same<From, To>::value &&
|
|
std::numeric_limits<From>::is_signed !=
|
|
std::numeric_limits<To>::is_signed)>
|
|
FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) {
|
|
ec = 0;
|
|
using F = std::numeric_limits<From>;
|
|
using T = std::numeric_limits<To>;
|
|
static_assert(F::is_integer, "From must be integral");
|
|
static_assert(T::is_integer, "To must be integral");
|
|
|
|
if (detail::const_check(F::is_signed && !T::is_signed)) {
|
|
// From may be negative, not allowed!
|
|
if (fmt::detail::is_negative(from)) {
|
|
ec = 1;
|
|
return {};
|
|
}
|
|
// From is positive. Can it always fit in To?
|
|
if (detail::const_check(F::digits > T::digits) &&
|
|
from > static_cast<From>(detail::max_value<To>())) {
|
|
ec = 1;
|
|
return {};
|
|
}
|
|
}
|
|
|
|
if (detail::const_check(!F::is_signed && T::is_signed &&
|
|
F::digits >= T::digits) &&
|
|
from > static_cast<From>(detail::max_value<To>())) {
|
|
ec = 1;
|
|
return {};
|
|
}
|
|
return static_cast<To>(from); // Lossless conversion.
|
|
}
|
|
|
|
template <typename To, typename From,
|
|
FMT_ENABLE_IF(std::is_same<From, To>::value)>
|
|
FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) {
|
|
ec = 0;
|
|
return from;
|
|
} // function
|
|
|
|
// clang-format off
|
|
/**
|
|
* converts From to To if possible, otherwise ec is set.
|
|
*
|
|
* input | output
|
|
* ---------------------------------|---------------
|
|
* NaN | NaN
|
|
* Inf | Inf
|
|
* normal, fits in output | converted (possibly lossy)
|
|
* normal, does not fit in output | ec is set
|
|
* subnormal | best effort
|
|
* -Inf | -Inf
|
|
*/
|
|
// clang-format on
|
|
template <typename To, typename From,
|
|
FMT_ENABLE_IF(!std::is_same<From, To>::value)>
|
|
FMT_CONSTEXPR To safe_float_conversion(const From from, int& ec) {
|
|
ec = 0;
|
|
using T = std::numeric_limits<To>;
|
|
static_assert(std::is_floating_point<From>::value, "From must be floating");
|
|
static_assert(std::is_floating_point<To>::value, "To must be floating");
|
|
|
|
// catch the only happy case
|
|
if (std::isfinite(from)) {
|
|
if (from >= T::lowest() && from <= (T::max)()) {
|
|
return static_cast<To>(from);
|
|
}
|
|
// not within range.
|
|
ec = 1;
|
|
return {};
|
|
}
|
|
|
|
// nan and inf will be preserved
|
|
return static_cast<To>(from);
|
|
} // function
|
|
|
|
template <typename To, typename From,
|
|
FMT_ENABLE_IF(std::is_same<From, To>::value)>
|
|
FMT_CONSTEXPR To safe_float_conversion(const From from, int& ec) {
|
|
ec = 0;
|
|
static_assert(std::is_floating_point<From>::value, "From must be floating");
|
|
return from;
|
|
}
|
|
|
|
/**
|
|
* safe duration cast between integral durations
|
|
*/
|
|
template <typename To, typename FromRep, typename FromPeriod,
|
|
FMT_ENABLE_IF(std::is_integral<FromRep>::value),
|
|
FMT_ENABLE_IF(std::is_integral<typename To::rep>::value)>
|
|
To safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from,
|
|
int& ec) {
|
|
using From = std::chrono::duration<FromRep, FromPeriod>;
|
|
ec = 0;
|
|
// the basic idea is that we need to convert from count() in the from type
|
|
// to count() in the To type, by multiplying it with this:
|
|
struct Factor
|
|
: std::ratio_divide<typename From::period, typename To::period> {};
|
|
|
|
static_assert(Factor::num > 0, "num must be positive");
|
|
static_assert(Factor::den > 0, "den must be positive");
|
|
|
|
// the conversion is like this: multiply from.count() with Factor::num
|
|
// /Factor::den and convert it to To::rep, all this without
|
|
// overflow/underflow. let's start by finding a suitable type that can hold
|
|
// both To, From and Factor::num
|
|
using IntermediateRep =
|
|
typename std::common_type<typename From::rep, typename To::rep,
|
|
decltype(Factor::num)>::type;
|
|
|
|
// safe conversion to IntermediateRep
|
|
IntermediateRep count =
|
|
lossless_integral_conversion<IntermediateRep>(from.count(), ec);
|
|
if (ec) return {};
|
|
// multiply with Factor::num without overflow or underflow
|
|
if (detail::const_check(Factor::num != 1)) {
|
|
const auto max1 = detail::max_value<IntermediateRep>() / Factor::num;
|
|
if (count > max1) {
|
|
ec = 1;
|
|
return {};
|
|
}
|
|
const auto min1 =
|
|
(std::numeric_limits<IntermediateRep>::min)() / Factor::num;
|
|
if (detail::const_check(!std::is_unsigned<IntermediateRep>::value) &&
|
|
count < min1) {
|
|
ec = 1;
|
|
return {};
|
|
}
|
|
count *= Factor::num;
|
|
}
|
|
|
|
if (detail::const_check(Factor::den != 1)) count /= Factor::den;
|
|
auto tocount = lossless_integral_conversion<typename To::rep>(count, ec);
|
|
return ec ? To() : To(tocount);
|
|
}
|
|
|
|
/**
|
|
* safe duration_cast between floating point durations
|
|
*/
|
|
template <typename To, typename FromRep, typename FromPeriod,
|
|
FMT_ENABLE_IF(std::is_floating_point<FromRep>::value),
|
|
FMT_ENABLE_IF(std::is_floating_point<typename To::rep>::value)>
|
|
To safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from,
|
|
int& ec) {
|
|
using From = std::chrono::duration<FromRep, FromPeriod>;
|
|
ec = 0;
|
|
if (std::isnan(from.count())) {
|
|
// nan in, gives nan out. easy.
|
|
return To{std::numeric_limits<typename To::rep>::quiet_NaN()};
|
|
}
|
|
// maybe we should also check if from is denormal, and decide what to do about
|
|
// it.
|
|
|
|
// +-inf should be preserved.
|
|
if (std::isinf(from.count())) {
|
|
return To{from.count()};
|
|
}
|
|
|
|
// the basic idea is that we need to convert from count() in the from type
|
|
// to count() in the To type, by multiplying it with this:
|
|
struct Factor
|
|
: std::ratio_divide<typename From::period, typename To::period> {};
|
|
|
|
static_assert(Factor::num > 0, "num must be positive");
|
|
static_assert(Factor::den > 0, "den must be positive");
|
|
|
|
// the conversion is like this: multiply from.count() with Factor::num
|
|
// /Factor::den and convert it to To::rep, all this without
|
|
// overflow/underflow. let's start by finding a suitable type that can hold
|
|
// both To, From and Factor::num
|
|
using IntermediateRep =
|
|
typename std::common_type<typename From::rep, typename To::rep,
|
|
decltype(Factor::num)>::type;
|
|
|
|
// force conversion of From::rep -> IntermediateRep to be safe,
|
|
// even if it will never happen be narrowing in this context.
|
|
IntermediateRep count =
|
|
safe_float_conversion<IntermediateRep>(from.count(), ec);
|
|
if (ec) {
|
|
return {};
|
|
}
|
|
|
|
// multiply with Factor::num without overflow or underflow
|
|
if (detail::const_check(Factor::num != 1)) {
|
|
constexpr auto max1 = detail::max_value<IntermediateRep>() /
|
|
static_cast<IntermediateRep>(Factor::num);
|
|
if (count > max1) {
|
|
ec = 1;
|
|
return {};
|
|
}
|
|
constexpr auto min1 = std::numeric_limits<IntermediateRep>::lowest() /
|
|
static_cast<IntermediateRep>(Factor::num);
|
|
if (count < min1) {
|
|
ec = 1;
|
|
return {};
|
|
}
|
|
count *= static_cast<IntermediateRep>(Factor::num);
|
|
}
|
|
|
|
// this can't go wrong, right? den>0 is checked earlier.
|
|
if (detail::const_check(Factor::den != 1)) {
|
|
using common_t = typename std::common_type<IntermediateRep, intmax_t>::type;
|
|
count /= static_cast<common_t>(Factor::den);
|
|
}
|
|
|
|
// convert to the to type, safely
|
|
using ToRep = typename To::rep;
|
|
|
|
const ToRep tocount = safe_float_conversion<ToRep>(count, ec);
|
|
if (ec) {
|
|
return {};
|
|
}
|
|
return To{tocount};
|
|
}
|
|
} // namespace safe_duration_cast
|
|
#endif
|
|
|
|
// Prevents expansion of a preceding token as a function-style macro.
|
|
// Usage: f FMT_NOMACRO()
|
|
#define FMT_NOMACRO
|
|
|
|
namespace detail {
|
|
template <typename T = void> struct null {};
|
|
inline null<> localtime_r FMT_NOMACRO(...) { return null<>(); }
|
|
inline null<> localtime_s(...) { return null<>(); }
|
|
inline null<> gmtime_r(...) { return null<>(); }
|
|
inline null<> gmtime_s(...) { return null<>(); }
|
|
|
|
inline const std::locale& get_classic_locale() {
|
|
static const auto& locale = std::locale::classic();
|
|
return locale;
|
|
}
|
|
|
|
template <typename CodeUnit> struct codecvt_result {
|
|
static constexpr const size_t max_size = 32;
|
|
CodeUnit buf[max_size];
|
|
CodeUnit* end;
|
|
};
|
|
template <typename CodeUnit>
|
|
constexpr const size_t codecvt_result<CodeUnit>::max_size;
|
|
|
|
template <typename CodeUnit>
|
|
void write_codecvt(codecvt_result<CodeUnit>& out, string_view in_buf,
|
|
const std::locale& loc) {
|
|
#if FMT_CLANG_VERSION
|
|
# pragma clang diagnostic push
|
|
# pragma clang diagnostic ignored "-Wdeprecated"
|
|
auto& f = std::use_facet<std::codecvt<CodeUnit, char, std::mbstate_t>>(loc);
|
|
# pragma clang diagnostic pop
|
|
#else
|
|
auto& f = std::use_facet<std::codecvt<CodeUnit, char, std::mbstate_t>>(loc);
|
|
#endif
|
|
auto mb = std::mbstate_t();
|
|
const char* from_next = nullptr;
|
|
auto result = f.in(mb, in_buf.begin(), in_buf.end(), from_next,
|
|
std::begin(out.buf), std::end(out.buf), out.end);
|
|
if (result != std::codecvt_base::ok)
|
|
FMT_THROW(format_error("failed to format time"));
|
|
}
|
|
|
|
template <typename OutputIt>
|
|
auto write_encoded_tm_str(OutputIt out, string_view in, const std::locale& loc)
|
|
-> OutputIt {
|
|
if (detail::is_utf8() && loc != get_classic_locale()) {
|
|
// char16_t and char32_t codecvts are broken in MSVC (linkage errors) and
|
|
// gcc-4.
|
|
#if FMT_MSC_VERSION != 0 || \
|
|
(defined(__GLIBCXX__) && !defined(_GLIBCXX_USE_DUAL_ABI))
|
|
// The _GLIBCXX_USE_DUAL_ABI macro is always defined in libstdc++ from gcc-5
|
|
// and newer.
|
|
using code_unit = wchar_t;
|
|
#else
|
|
using code_unit = char32_t;
|
|
#endif
|
|
|
|
using unit_t = codecvt_result<code_unit>;
|
|
unit_t unit;
|
|
write_codecvt(unit, in, loc);
|
|
// In UTF-8 is used one to four one-byte code units.
|
|
auto u =
|
|
to_utf8<code_unit, basic_memory_buffer<char, unit_t::max_size * 4>>();
|
|
if (!u.convert({unit.buf, to_unsigned(unit.end - unit.buf)}))
|
|
FMT_THROW(format_error("failed to format time"));
|
|
return copy_str<char>(u.c_str(), u.c_str() + u.size(), out);
|
|
}
|
|
return copy_str<char>(in.data(), in.data() + in.size(), out);
|
|
}
|
|
|
|
template <typename Char, typename OutputIt,
|
|
FMT_ENABLE_IF(!std::is_same<Char, char>::value)>
|
|
auto write_tm_str(OutputIt out, string_view sv, const std::locale& loc)
|
|
-> OutputIt {
|
|
codecvt_result<Char> unit;
|
|
write_codecvt(unit, sv, loc);
|
|
return copy_str<Char>(unit.buf, unit.end, out);
|
|
}
|
|
|
|
template <typename Char, typename OutputIt,
|
|
FMT_ENABLE_IF(std::is_same<Char, char>::value)>
|
|
auto write_tm_str(OutputIt out, string_view sv, const std::locale& loc)
|
|
-> OutputIt {
|
|
return write_encoded_tm_str(out, sv, loc);
|
|
}
|
|
|
|
template <typename Char>
|
|
inline void do_write(buffer<Char>& buf, const std::tm& time,
|
|
const std::locale& loc, char format, char modifier) {
|
|
auto&& format_buf = formatbuf<std::basic_streambuf<Char>>(buf);
|
|
auto&& os = std::basic_ostream<Char>(&format_buf);
|
|
os.imbue(loc);
|
|
using iterator = std::ostreambuf_iterator<Char>;
|
|
const auto& facet = std::use_facet<std::time_put<Char, iterator>>(loc);
|
|
auto end = facet.put(os, os, Char(' '), &time, format, modifier);
|
|
if (end.failed()) FMT_THROW(format_error("failed to format time"));
|
|
}
|
|
|
|
template <typename Char, typename OutputIt,
|
|
FMT_ENABLE_IF(!std::is_same<Char, char>::value)>
|
|
auto write(OutputIt out, const std::tm& time, const std::locale& loc,
|
|
char format, char modifier = 0) -> OutputIt {
|
|
auto&& buf = get_buffer<Char>(out);
|
|
do_write<Char>(buf, time, loc, format, modifier);
|
|
return get_iterator(buf, out);
|
|
}
|
|
|
|
template <typename Char, typename OutputIt,
|
|
FMT_ENABLE_IF(std::is_same<Char, char>::value)>
|
|
auto write(OutputIt out, const std::tm& time, const std::locale& loc,
|
|
char format, char modifier = 0) -> OutputIt {
|
|
auto&& buf = basic_memory_buffer<Char>();
|
|
do_write<char>(buf, time, loc, format, modifier);
|
|
return write_encoded_tm_str(out, string_view(buf.data(), buf.size()), loc);
|
|
}
|
|
|
|
} // namespace detail
|
|
|
|
FMT_BEGIN_EXPORT
|
|
|
|
/**
|
|
Converts given time since epoch as ``std::time_t`` value into calendar time,
|
|
expressed in local time. Unlike ``std::localtime``, this function is
|
|
thread-safe on most platforms.
|
|
*/
|
|
inline std::tm localtime(std::time_t time) {
|
|
struct dispatcher {
|
|
std::time_t time_;
|
|
std::tm tm_;
|
|
|
|
dispatcher(std::time_t t) : time_(t) {}
|
|
|
|
bool run() {
|
|
using namespace fmt::detail;
|
|
return handle(localtime_r(&time_, &tm_));
|
|
}
|
|
|
|
bool handle(std::tm* tm) { return tm != nullptr; }
|
|
|
|
bool handle(detail::null<>) {
|
|
using namespace fmt::detail;
|
|
return fallback(localtime_s(&tm_, &time_));
|
|
}
|
|
|
|
bool fallback(int res) { return res == 0; }
|
|
|
|
#if !FMT_MSC_VERSION
|
|
bool fallback(detail::null<>) {
|
|
using namespace fmt::detail;
|
|
std::tm* tm = std::localtime(&time_);
|
|
if (tm) tm_ = *tm;
|
|
return tm != nullptr;
|
|
}
|
|
#endif
|
|
};
|
|
dispatcher lt(time);
|
|
// Too big time values may be unsupported.
|
|
if (!lt.run()) FMT_THROW(format_error("time_t value out of range"));
|
|
return lt.tm_;
|
|
}
|
|
|
|
#if FMT_USE_LOCAL_TIME
|
|
template <typename Duration>
|
|
inline auto localtime(std::chrono::local_time<Duration> time) -> std::tm {
|
|
return localtime(std::chrono::system_clock::to_time_t(
|
|
std::chrono::current_zone()->to_sys(time)));
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
Converts given time since epoch as ``std::time_t`` value into calendar time,
|
|
expressed in Coordinated Universal Time (UTC). Unlike ``std::gmtime``, this
|
|
function is thread-safe on most platforms.
|
|
*/
|
|
inline std::tm gmtime(std::time_t time) {
|
|
struct dispatcher {
|
|
std::time_t time_;
|
|
std::tm tm_;
|
|
|
|
dispatcher(std::time_t t) : time_(t) {}
|
|
|
|
bool run() {
|
|
using namespace fmt::detail;
|
|
return handle(gmtime_r(&time_, &tm_));
|
|
}
|
|
|
|
bool handle(std::tm* tm) { return tm != nullptr; }
|
|
|
|
bool handle(detail::null<>) {
|
|
using namespace fmt::detail;
|
|
return fallback(gmtime_s(&tm_, &time_));
|
|
}
|
|
|
|
bool fallback(int res) { return res == 0; }
|
|
|
|
#if !FMT_MSC_VERSION
|
|
bool fallback(detail::null<>) {
|
|
std::tm* tm = std::gmtime(&time_);
|
|
if (tm) tm_ = *tm;
|
|
return tm != nullptr;
|
|
}
|
|
#endif
|
|
};
|
|
auto gt = dispatcher(time);
|
|
// Too big time values may be unsupported.
|
|
if (!gt.run()) FMT_THROW(format_error("time_t value out of range"));
|
|
return gt.tm_;
|
|
}
|
|
|
|
inline std::tm gmtime(
|
|
std::chrono::time_point<std::chrono::system_clock> time_point) {
|
|
return gmtime(std::chrono::system_clock::to_time_t(time_point));
|
|
}
|
|
|
|
namespace detail {
|
|
|
|
// Writes two-digit numbers a, b and c separated by sep to buf.
|
|
// The method by Pavel Novikov based on
|
|
// https://johnnylee-sde.github.io/Fast-unsigned-integer-to-time-string/.
|
|
inline void write_digit2_separated(char* buf, unsigned a, unsigned b,
|
|
unsigned c, char sep) {
|
|
unsigned long long digits =
|
|
a | (b << 24) | (static_cast<unsigned long long>(c) << 48);
|
|
// Convert each value to BCD.
|
|
// We have x = a * 10 + b and we want to convert it to BCD y = a * 16 + b.
|
|
// The difference is
|
|
// y - x = a * 6
|
|
// a can be found from x:
|
|
// a = floor(x / 10)
|
|
// then
|
|
// y = x + a * 6 = x + floor(x / 10) * 6
|
|
// floor(x / 10) is (x * 205) >> 11 (needs 16 bits).
|
|
digits += (((digits * 205) >> 11) & 0x000f00000f00000f) * 6;
|
|
// Put low nibbles to high bytes and high nibbles to low bytes.
|
|
digits = ((digits & 0x00f00000f00000f0) >> 4) |
|
|
((digits & 0x000f00000f00000f) << 8);
|
|
auto usep = static_cast<unsigned long long>(sep);
|
|
// Add ASCII '0' to each digit byte and insert separators.
|
|
digits |= 0x3030003030003030 | (usep << 16) | (usep << 40);
|
|
|
|
constexpr const size_t len = 8;
|
|
if (const_check(is_big_endian())) {
|
|
char tmp[len];
|
|
std::memcpy(tmp, &digits, len);
|
|
std::reverse_copy(tmp, tmp + len, buf);
|
|
} else {
|
|
std::memcpy(buf, &digits, len);
|
|
}
|
|
}
|
|
|
|
template <typename Period> FMT_CONSTEXPR inline const char* get_units() {
|
|
if (std::is_same<Period, std::atto>::value) return "as";
|
|
if (std::is_same<Period, std::femto>::value) return "fs";
|
|
if (std::is_same<Period, std::pico>::value) return "ps";
|
|
if (std::is_same<Period, std::nano>::value) return "ns";
|
|
if (std::is_same<Period, std::micro>::value) return "µs";
|
|
if (std::is_same<Period, std::milli>::value) return "ms";
|
|
if (std::is_same<Period, std::centi>::value) return "cs";
|
|
if (std::is_same<Period, std::deci>::value) return "ds";
|
|
if (std::is_same<Period, std::ratio<1>>::value) return "s";
|
|
if (std::is_same<Period, std::deca>::value) return "das";
|
|
if (std::is_same<Period, std::hecto>::value) return "hs";
|
|
if (std::is_same<Period, std::kilo>::value) return "ks";
|
|
if (std::is_same<Period, std::mega>::value) return "Ms";
|
|
if (std::is_same<Period, std::giga>::value) return "Gs";
|
|
if (std::is_same<Period, std::tera>::value) return "Ts";
|
|
if (std::is_same<Period, std::peta>::value) return "Ps";
|
|
if (std::is_same<Period, std::exa>::value) return "Es";
|
|
if (std::is_same<Period, std::ratio<60>>::value) return "m";
|
|
if (std::is_same<Period, std::ratio<3600>>::value) return "h";
|
|
return nullptr;
|
|
}
|
|
|
|
enum class numeric_system {
|
|
standard,
|
|
// Alternative numeric system, e.g. 十二 instead of 12 in ja_JP locale.
|
|
alternative
|
|
};
|
|
|
|
// Glibc extensions for formatting numeric values.
|
|
enum class pad_type {
|
|
unspecified,
|
|
// Do not pad a numeric result string.
|
|
none,
|
|
// Pad a numeric result string with zeros even if the conversion specifier
|
|
// character uses space-padding by default.
|
|
zero,
|
|
// Pad a numeric result string with spaces.
|
|
space,
|
|
};
|
|
|
|
template <typename OutputIt>
|
|
auto write_padding(OutputIt out, pad_type pad, int width) -> OutputIt {
|
|
if (pad == pad_type::none) return out;
|
|
return std::fill_n(out, width, pad == pad_type::space ? ' ' : '0');
|
|
}
|
|
|
|
template <typename OutputIt>
|
|
auto write_padding(OutputIt out, pad_type pad) -> OutputIt {
|
|
if (pad != pad_type::none) *out++ = pad == pad_type::space ? ' ' : '0';
|
|
return out;
|
|
}
|
|
|
|
// Parses a put_time-like format string and invokes handler actions.
|
|
template <typename Char, typename Handler>
|
|
FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin,
|
|
const Char* end,
|
|
Handler&& handler) {
|
|
if (begin == end || *begin == '}') return begin;
|
|
if (*begin != '%') FMT_THROW(format_error("invalid format"));
|
|
auto ptr = begin;
|
|
pad_type pad = pad_type::unspecified;
|
|
while (ptr != end) {
|
|
auto c = *ptr;
|
|
if (c == '}') break;
|
|
if (c != '%') {
|
|
++ptr;
|
|
continue;
|
|
}
|
|
if (begin != ptr) handler.on_text(begin, ptr);
|
|
++ptr; // consume '%'
|
|
if (ptr == end) FMT_THROW(format_error("invalid format"));
|
|
c = *ptr;
|
|
switch (c) {
|
|
case '_':
|
|
pad = pad_type::space;
|
|
++ptr;
|
|
break;
|
|
case '-':
|
|
pad = pad_type::none;
|
|
++ptr;
|
|
break;
|
|
case '0':
|
|
pad = pad_type::zero;
|
|
++ptr;
|
|
break;
|
|
}
|
|
if (ptr == end) FMT_THROW(format_error("invalid format"));
|
|
c = *ptr++;
|
|
switch (c) {
|
|
case '%':
|
|
handler.on_text(ptr - 1, ptr);
|
|
break;
|
|
case 'n': {
|
|
const Char newline[] = {'\n'};
|
|
handler.on_text(newline, newline + 1);
|
|
break;
|
|
}
|
|
case 't': {
|
|
const Char tab[] = {'\t'};
|
|
handler.on_text(tab, tab + 1);
|
|
break;
|
|
}
|
|
// Year:
|
|
case 'Y':
|
|
handler.on_year(numeric_system::standard);
|
|
break;
|
|
case 'y':
|
|
handler.on_short_year(numeric_system::standard);
|
|
break;
|
|
case 'C':
|
|
handler.on_century(numeric_system::standard);
|
|
break;
|
|
case 'G':
|
|
handler.on_iso_week_based_year();
|
|
break;
|
|
case 'g':
|
|
handler.on_iso_week_based_short_year();
|
|
break;
|
|
// Day of the week:
|
|
case 'a':
|
|
handler.on_abbr_weekday();
|
|
break;
|
|
case 'A':
|
|
handler.on_full_weekday();
|
|
break;
|
|
case 'w':
|
|
handler.on_dec0_weekday(numeric_system::standard);
|
|
break;
|
|
case 'u':
|
|
handler.on_dec1_weekday(numeric_system::standard);
|
|
break;
|
|
// Month:
|
|
case 'b':
|
|
case 'h':
|
|
handler.on_abbr_month();
|
|
break;
|
|
case 'B':
|
|
handler.on_full_month();
|
|
break;
|
|
case 'm':
|
|
handler.on_dec_month(numeric_system::standard);
|
|
break;
|
|
// Day of the year/month:
|
|
case 'U':
|
|
handler.on_dec0_week_of_year(numeric_system::standard);
|
|
break;
|
|
case 'W':
|
|
handler.on_dec1_week_of_year(numeric_system::standard);
|
|
break;
|
|
case 'V':
|
|
handler.on_iso_week_of_year(numeric_system::standard);
|
|
break;
|
|
case 'j':
|
|
handler.on_day_of_year();
|
|
break;
|
|
case 'd':
|
|
handler.on_day_of_month(numeric_system::standard);
|
|
break;
|
|
case 'e':
|
|
handler.on_day_of_month_space(numeric_system::standard);
|
|
break;
|
|
// Hour, minute, second:
|
|
case 'H':
|
|
handler.on_24_hour(numeric_system::standard, pad);
|
|
break;
|
|
case 'I':
|
|
handler.on_12_hour(numeric_system::standard, pad);
|
|
break;
|
|
case 'M':
|
|
handler.on_minute(numeric_system::standard, pad);
|
|
break;
|
|
case 'S':
|
|
handler.on_second(numeric_system::standard, pad);
|
|
break;
|
|
// Other:
|
|
case 'c':
|
|
handler.on_datetime(numeric_system::standard);
|
|
break;
|
|
case 'x':
|
|
handler.on_loc_date(numeric_system::standard);
|
|
break;
|
|
case 'X':
|
|
handler.on_loc_time(numeric_system::standard);
|
|
break;
|
|
case 'D':
|
|
handler.on_us_date();
|
|
break;
|
|
case 'F':
|
|
handler.on_iso_date();
|
|
break;
|
|
case 'r':
|
|
handler.on_12_hour_time();
|
|
break;
|
|
case 'R':
|
|
handler.on_24_hour_time();
|
|
break;
|
|
case 'T':
|
|
handler.on_iso_time();
|
|
break;
|
|
case 'p':
|
|
handler.on_am_pm();
|
|
break;
|
|
case 'Q':
|
|
handler.on_duration_value();
|
|
break;
|
|
case 'q':
|
|
handler.on_duration_unit();
|
|
break;
|
|
case 'z':
|
|
handler.on_utc_offset(numeric_system::standard);
|
|
break;
|
|
case 'Z':
|
|
handler.on_tz_name();
|
|
break;
|
|
// Alternative representation:
|
|
case 'E': {
|
|
if (ptr == end) FMT_THROW(format_error("invalid format"));
|
|
c = *ptr++;
|
|
switch (c) {
|
|
case 'Y':
|
|
handler.on_year(numeric_system::alternative);
|
|
break;
|
|
case 'y':
|
|
handler.on_offset_year();
|
|
break;
|
|
case 'C':
|
|
handler.on_century(numeric_system::alternative);
|
|
break;
|
|
case 'c':
|
|
handler.on_datetime(numeric_system::alternative);
|
|
break;
|
|
case 'x':
|
|
handler.on_loc_date(numeric_system::alternative);
|
|
break;
|
|
case 'X':
|
|
handler.on_loc_time(numeric_system::alternative);
|
|
break;
|
|
case 'z':
|
|
handler.on_utc_offset(numeric_system::alternative);
|
|
break;
|
|
default:
|
|
FMT_THROW(format_error("invalid format"));
|
|
}
|
|
break;
|
|
}
|
|
case 'O':
|
|
if (ptr == end) FMT_THROW(format_error("invalid format"));
|
|
c = *ptr++;
|
|
switch (c) {
|
|
case 'y':
|
|
handler.on_short_year(numeric_system::alternative);
|
|
break;
|
|
case 'm':
|
|
handler.on_dec_month(numeric_system::alternative);
|
|
break;
|
|
case 'U':
|
|
handler.on_dec0_week_of_year(numeric_system::alternative);
|
|
break;
|
|
case 'W':
|
|
handler.on_dec1_week_of_year(numeric_system::alternative);
|
|
break;
|
|
case 'V':
|
|
handler.on_iso_week_of_year(numeric_system::alternative);
|
|
break;
|
|
case 'd':
|
|
handler.on_day_of_month(numeric_system::alternative);
|
|
break;
|
|
case 'e':
|
|
handler.on_day_of_month_space(numeric_system::alternative);
|
|
break;
|
|
case 'w':
|
|
handler.on_dec0_weekday(numeric_system::alternative);
|
|
break;
|
|
case 'u':
|
|
handler.on_dec1_weekday(numeric_system::alternative);
|
|
break;
|
|
case 'H':
|
|
handler.on_24_hour(numeric_system::alternative, pad);
|
|
break;
|
|
case 'I':
|
|
handler.on_12_hour(numeric_system::alternative, pad);
|
|
break;
|
|
case 'M':
|
|
handler.on_minute(numeric_system::alternative, pad);
|
|
break;
|
|
case 'S':
|
|
handler.on_second(numeric_system::alternative, pad);
|
|
break;
|
|
case 'z':
|
|
handler.on_utc_offset(numeric_system::alternative);
|
|
break;
|
|
default:
|
|
FMT_THROW(format_error("invalid format"));
|
|
}
|
|
break;
|
|
default:
|
|
FMT_THROW(format_error("invalid format"));
|
|
}
|
|
begin = ptr;
|
|
}
|
|
if (begin != ptr) handler.on_text(begin, ptr);
|
|
return ptr;
|
|
}
|
|
|
|
template <typename Derived> struct null_chrono_spec_handler {
|
|
FMT_CONSTEXPR void unsupported() {
|
|
static_cast<Derived*>(this)->unsupported();
|
|
}
|
|
FMT_CONSTEXPR void on_year(numeric_system) { unsupported(); }
|
|
FMT_CONSTEXPR void on_short_year(numeric_system) { unsupported(); }
|
|
FMT_CONSTEXPR void on_offset_year() { unsupported(); }
|
|
FMT_CONSTEXPR void on_century(numeric_system) { unsupported(); }
|
|
FMT_CONSTEXPR void on_iso_week_based_year() { unsupported(); }
|
|
FMT_CONSTEXPR void on_iso_week_based_short_year() { unsupported(); }
|
|
FMT_CONSTEXPR void on_abbr_weekday() { unsupported(); }
|
|
FMT_CONSTEXPR void on_full_weekday() { unsupported(); }
|
|
FMT_CONSTEXPR void on_dec0_weekday(numeric_system) { unsupported(); }
|
|
FMT_CONSTEXPR void on_dec1_weekday(numeric_system) { unsupported(); }
|
|
FMT_CONSTEXPR void on_abbr_month() { unsupported(); }
|
|
FMT_CONSTEXPR void on_full_month() { unsupported(); }
|
|
FMT_CONSTEXPR void on_dec_month(numeric_system) { unsupported(); }
|
|
FMT_CONSTEXPR void on_dec0_week_of_year(numeric_system) { unsupported(); }
|
|
FMT_CONSTEXPR void on_dec1_week_of_year(numeric_system) { unsupported(); }
|
|
FMT_CONSTEXPR void on_iso_week_of_year(numeric_system) { unsupported(); }
|
|
FMT_CONSTEXPR void on_day_of_year() { unsupported(); }
|
|
FMT_CONSTEXPR void on_day_of_month(numeric_system) { unsupported(); }
|
|
FMT_CONSTEXPR void on_day_of_month_space(numeric_system) { unsupported(); }
|
|
FMT_CONSTEXPR void on_24_hour(numeric_system) { unsupported(); }
|
|
FMT_CONSTEXPR void on_12_hour(numeric_system) { unsupported(); }
|
|
FMT_CONSTEXPR void on_minute(numeric_system) { unsupported(); }
|
|
FMT_CONSTEXPR void on_second(numeric_system) { unsupported(); }
|
|
FMT_CONSTEXPR void on_datetime(numeric_system) { unsupported(); }
|
|
FMT_CONSTEXPR void on_loc_date(numeric_system) { unsupported(); }
|
|
FMT_CONSTEXPR void on_loc_time(numeric_system) { unsupported(); }
|
|
FMT_CONSTEXPR void on_us_date() { unsupported(); }
|
|
FMT_CONSTEXPR void on_iso_date() { unsupported(); }
|
|
FMT_CONSTEXPR void on_12_hour_time() { unsupported(); }
|
|
FMT_CONSTEXPR void on_24_hour_time() { unsupported(); }
|
|
FMT_CONSTEXPR void on_iso_time() { unsupported(); }
|
|
FMT_CONSTEXPR void on_am_pm() { unsupported(); }
|
|
FMT_CONSTEXPR void on_duration_value() { unsupported(); }
|
|
FMT_CONSTEXPR void on_duration_unit() { unsupported(); }
|
|
FMT_CONSTEXPR void on_utc_offset(numeric_system) { unsupported(); }
|
|
FMT_CONSTEXPR void on_tz_name() { unsupported(); }
|
|
};
|
|
|
|
struct tm_format_checker : null_chrono_spec_handler<tm_format_checker> {
|
|
FMT_NORETURN void unsupported() { FMT_THROW(format_error("no format")); }
|
|
|
|
template <typename Char>
|
|
FMT_CONSTEXPR void on_text(const Char*, const Char*) {}
|
|
FMT_CONSTEXPR void on_year(numeric_system) {}
|
|
FMT_CONSTEXPR void on_short_year(numeric_system) {}
|
|
FMT_CONSTEXPR void on_offset_year() {}
|
|
FMT_CONSTEXPR void on_century(numeric_system) {}
|
|
FMT_CONSTEXPR void on_iso_week_based_year() {}
|
|
FMT_CONSTEXPR void on_iso_week_based_short_year() {}
|
|
FMT_CONSTEXPR void on_abbr_weekday() {}
|
|
FMT_CONSTEXPR void on_full_weekday() {}
|
|
FMT_CONSTEXPR void on_dec0_weekday(numeric_system) {}
|
|
FMT_CONSTEXPR void on_dec1_weekday(numeric_system) {}
|
|
FMT_CONSTEXPR void on_abbr_month() {}
|
|
FMT_CONSTEXPR void on_full_month() {}
|
|
FMT_CONSTEXPR void on_dec_month(numeric_system) {}
|
|
FMT_CONSTEXPR void on_dec0_week_of_year(numeric_system) {}
|
|
FMT_CONSTEXPR void on_dec1_week_of_year(numeric_system) {}
|
|
FMT_CONSTEXPR void on_iso_week_of_year(numeric_system) {}
|
|
FMT_CONSTEXPR void on_day_of_year() {}
|
|
FMT_CONSTEXPR void on_day_of_month(numeric_system) {}
|
|
FMT_CONSTEXPR void on_day_of_month_space(numeric_system) {}
|
|
FMT_CONSTEXPR void on_24_hour(numeric_system, pad_type) {}
|
|
FMT_CONSTEXPR void on_12_hour(numeric_system, pad_type) {}
|
|
FMT_CONSTEXPR void on_minute(numeric_system, pad_type) {}
|
|
FMT_CONSTEXPR void on_second(numeric_system, pad_type) {}
|
|
FMT_CONSTEXPR void on_datetime(numeric_system) {}
|
|
FMT_CONSTEXPR void on_loc_date(numeric_system) {}
|
|
FMT_CONSTEXPR void on_loc_time(numeric_system) {}
|
|
FMT_CONSTEXPR void on_us_date() {}
|
|
FMT_CONSTEXPR void on_iso_date() {}
|
|
FMT_CONSTEXPR void on_12_hour_time() {}
|
|
FMT_CONSTEXPR void on_24_hour_time() {}
|
|
FMT_CONSTEXPR void on_iso_time() {}
|
|
FMT_CONSTEXPR void on_am_pm() {}
|
|
FMT_CONSTEXPR void on_utc_offset(numeric_system) {}
|
|
FMT_CONSTEXPR void on_tz_name() {}
|
|
};
|
|
|
|
inline const char* tm_wday_full_name(int wday) {
|
|
static constexpr const char* full_name_list[] = {
|
|
"Sunday", "Monday", "Tuesday", "Wednesday",
|
|
"Thursday", "Friday", "Saturday"};
|
|
return wday >= 0 && wday <= 6 ? full_name_list[wday] : "?";
|
|
}
|
|
inline const char* tm_wday_short_name(int wday) {
|
|
static constexpr const char* short_name_list[] = {"Sun", "Mon", "Tue", "Wed",
|
|
"Thu", "Fri", "Sat"};
|
|
return wday >= 0 && wday <= 6 ? short_name_list[wday] : "???";
|
|
}
|
|
|
|
inline const char* tm_mon_full_name(int mon) {
|
|
static constexpr const char* full_name_list[] = {
|
|
"January", "February", "March", "April", "May", "June",
|
|
"July", "August", "September", "October", "November", "December"};
|
|
return mon >= 0 && mon <= 11 ? full_name_list[mon] : "?";
|
|
}
|
|
inline const char* tm_mon_short_name(int mon) {
|
|
static constexpr const char* short_name_list[] = {
|
|
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
|
|
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
|
|
};
|
|
return mon >= 0 && mon <= 11 ? short_name_list[mon] : "???";
|
|
}
|
|
|
|
template <typename T, typename = void>
|
|
struct has_member_data_tm_gmtoff : std::false_type {};
|
|
template <typename T>
|
|
struct has_member_data_tm_gmtoff<T, void_t<decltype(T::tm_gmtoff)>>
|
|
: std::true_type {};
|
|
|
|
template <typename T, typename = void>
|
|
struct has_member_data_tm_zone : std::false_type {};
|
|
template <typename T>
|
|
struct has_member_data_tm_zone<T, void_t<decltype(T::tm_zone)>>
|
|
: std::true_type {};
|
|
|
|
#if FMT_USE_TZSET
|
|
inline void tzset_once() {
|
|
static bool init = []() -> bool {
|
|
_tzset();
|
|
return true;
|
|
}();
|
|
ignore_unused(init);
|
|
}
|
|
#endif
|
|
|
|
// Converts value to Int and checks that it's in the range [0, upper).
|
|
template <typename T, typename Int, FMT_ENABLE_IF(std::is_integral<T>::value)>
|
|
inline Int to_nonnegative_int(T value, Int upper) {
|
|
FMT_ASSERT(std::is_unsigned<Int>::value ||
|
|
(value >= 0 && to_unsigned(value) <= to_unsigned(upper)),
|
|
"invalid value");
|
|
(void)upper;
|
|
return static_cast<Int>(value);
|
|
}
|
|
template <typename T, typename Int, FMT_ENABLE_IF(!std::is_integral<T>::value)>
|
|
inline Int to_nonnegative_int(T value, Int upper) {
|
|
if (value < 0 || value > static_cast<T>(upper))
|
|
FMT_THROW(format_error("invalid value"));
|
|
return static_cast<Int>(value);
|
|
}
|
|
|
|
constexpr long long pow10(std::uint32_t n) {
|
|
return n == 0 ? 1 : 10 * pow10(n - 1);
|
|
}
|
|
|
|
// Counts the number of fractional digits in the range [0, 18] according to the
|
|
// C++20 spec. If more than 18 fractional digits are required then returns 6 for
|
|
// microseconds precision.
|
|
template <long long Num, long long Den, int N = 0,
|
|
bool Enabled = (N < 19) && (Num <= max_value<long long>() / 10)>
|
|
struct count_fractional_digits {
|
|
static constexpr int value =
|
|
Num % Den == 0 ? N : count_fractional_digits<Num * 10, Den, N + 1>::value;
|
|
};
|
|
|
|
// Base case that doesn't instantiate any more templates
|
|
// in order to avoid overflow.
|
|
template <long long Num, long long Den, int N>
|
|
struct count_fractional_digits<Num, Den, N, false> {
|
|
static constexpr int value = (Num % Den == 0) ? N : 6;
|
|
};
|
|
|
|
// Format subseconds which are given as an integer type with an appropriate
|
|
// number of digits.
|
|
template <typename Char, typename OutputIt, typename Duration>
|
|
void write_fractional_seconds(OutputIt& out, Duration d, int precision = -1) {
|
|
constexpr auto num_fractional_digits =
|
|
count_fractional_digits<Duration::period::num,
|
|
Duration::period::den>::value;
|
|
|
|
using subsecond_precision = std::chrono::duration<
|
|
typename std::common_type<typename Duration::rep,
|
|
std::chrono::seconds::rep>::type,
|
|
std::ratio<1, detail::pow10(num_fractional_digits)>>;
|
|
|
|
const auto fractional =
|
|
d - std::chrono::duration_cast<std::chrono::seconds>(d);
|
|
const auto subseconds =
|
|
std::chrono::treat_as_floating_point<
|
|
typename subsecond_precision::rep>::value
|
|
? fractional.count()
|
|
: std::chrono::duration_cast<subsecond_precision>(fractional).count();
|
|
auto n = static_cast<uint32_or_64_or_128_t<long long>>(subseconds);
|
|
const int num_digits = detail::count_digits(n);
|
|
|
|
int leading_zeroes = (std::max)(0, num_fractional_digits - num_digits);
|
|
if (precision < 0) {
|
|
FMT_ASSERT(!std::is_floating_point<typename Duration::rep>::value, "");
|
|
if (std::ratio_less<typename subsecond_precision::period,
|
|
std::chrono::seconds::period>::value) {
|
|
*out++ = '.';
|
|
out = std::fill_n(out, leading_zeroes, '0');
|
|
out = format_decimal<Char>(out, n, num_digits).end;
|
|
}
|
|
} else {
|
|
*out++ = '.';
|
|
leading_zeroes = (std::min)(leading_zeroes, precision);
|
|
out = std::fill_n(out, leading_zeroes, '0');
|
|
int remaining = precision - leading_zeroes;
|
|
if (remaining != 0 && remaining < num_digits) {
|
|
n /= to_unsigned(detail::pow10(to_unsigned(num_digits - remaining)));
|
|
out = format_decimal<Char>(out, n, remaining).end;
|
|
return;
|
|
}
|
|
out = format_decimal<Char>(out, n, num_digits).end;
|
|
remaining -= num_digits;
|
|
out = std::fill_n(out, remaining, '0');
|
|
}
|
|
}
|
|
|
|
// Format subseconds which are given as a floating point type with an
|
|
// appropriate number of digits. We cannot pass the Duration here, as we
|
|
// explicitly need to pass the Rep value in the chrono_formatter.
|
|
template <typename Duration>
|
|
void write_floating_seconds(memory_buffer& buf, Duration duration,
|
|
int num_fractional_digits = -1) {
|
|
using rep = typename Duration::rep;
|
|
FMT_ASSERT(std::is_floating_point<rep>::value, "");
|
|
|
|
auto val = duration.count();
|
|
|
|
if (num_fractional_digits < 0) {
|
|
// For `std::round` with fallback to `round`:
|
|
// On some toolchains `std::round` is not available (e.g. GCC 6).
|
|
using namespace std;
|
|
num_fractional_digits =
|
|
count_fractional_digits<Duration::period::num,
|
|
Duration::period::den>::value;
|
|
if (num_fractional_digits < 6 && static_cast<rep>(round(val)) != val)
|
|
num_fractional_digits = 6;
|
|
}
|
|
|
|
format_to(std::back_inserter(buf), FMT_STRING("{:.{}f}"),
|
|
std::fmod(val * static_cast<rep>(Duration::period::num) /
|
|
static_cast<rep>(Duration::period::den),
|
|
static_cast<rep>(60)),
|
|
num_fractional_digits);
|
|
}
|
|
|
|
template <typename OutputIt, typename Char,
|
|
typename Duration = std::chrono::seconds>
|
|
class tm_writer {
|
|
private:
|
|
static constexpr int days_per_week = 7;
|
|
|
|
const std::locale& loc_;
|
|
const bool is_classic_;
|
|
OutputIt out_;
|
|
const Duration* subsecs_;
|
|
const std::tm& tm_;
|
|
|
|
auto tm_sec() const noexcept -> int {
|
|
FMT_ASSERT(tm_.tm_sec >= 0 && tm_.tm_sec <= 61, "");
|
|
return tm_.tm_sec;
|
|
}
|
|
auto tm_min() const noexcept -> int {
|
|
FMT_ASSERT(tm_.tm_min >= 0 && tm_.tm_min <= 59, "");
|
|
return tm_.tm_min;
|
|
}
|
|
auto tm_hour() const noexcept -> int {
|
|
FMT_ASSERT(tm_.tm_hour >= 0 && tm_.tm_hour <= 23, "");
|
|
return tm_.tm_hour;
|
|
}
|
|
auto tm_mday() const noexcept -> int {
|
|
FMT_ASSERT(tm_.tm_mday >= 1 && tm_.tm_mday <= 31, "");
|
|
return tm_.tm_mday;
|
|
}
|
|
auto tm_mon() const noexcept -> int {
|
|
FMT_ASSERT(tm_.tm_mon >= 0 && tm_.tm_mon <= 11, "");
|
|
return tm_.tm_mon;
|
|
}
|
|
auto tm_year() const noexcept -> long long { return 1900ll + tm_.tm_year; }
|
|
auto tm_wday() const noexcept -> int {
|
|
FMT_ASSERT(tm_.tm_wday >= 0 && tm_.tm_wday <= 6, "");
|
|
return tm_.tm_wday;
|
|
}
|
|
auto tm_yday() const noexcept -> int {
|
|
FMT_ASSERT(tm_.tm_yday >= 0 && tm_.tm_yday <= 365, "");
|
|
return tm_.tm_yday;
|
|
}
|
|
|
|
auto tm_hour12() const noexcept -> int {
|
|
const auto h = tm_hour();
|
|
const auto z = h < 12 ? h : h - 12;
|
|
return z == 0 ? 12 : z;
|
|
}
|
|
|
|
// POSIX and the C Standard are unclear or inconsistent about what %C and %y
|
|
// do if the year is negative or exceeds 9999. Use the convention that %C
|
|
// concatenated with %y yields the same output as %Y, and that %Y contains at
|
|
// least 4 characters, with more only if necessary.
|
|
auto split_year_lower(long long year) const noexcept -> int {
|
|
auto l = year % 100;
|
|
if (l < 0) l = -l; // l in [0, 99]
|
|
return static_cast<int>(l);
|
|
}
|
|
|
|
// Algorithm:
|
|
// https://en.wikipedia.org/wiki/ISO_week_date#Calculating_the_week_number_from_a_month_and_day_of_the_month_or_ordinal_date
|
|
auto iso_year_weeks(long long curr_year) const noexcept -> int {
|
|
const auto prev_year = curr_year - 1;
|
|
const auto curr_p =
|
|
(curr_year + curr_year / 4 - curr_year / 100 + curr_year / 400) %
|
|
days_per_week;
|
|
const auto prev_p =
|
|
(prev_year + prev_year / 4 - prev_year / 100 + prev_year / 400) %
|
|
days_per_week;
|
|
return 52 + ((curr_p == 4 || prev_p == 3) ? 1 : 0);
|
|
}
|
|
auto iso_week_num(int tm_yday, int tm_wday) const noexcept -> int {
|
|
return (tm_yday + 11 - (tm_wday == 0 ? days_per_week : tm_wday)) /
|
|
days_per_week;
|
|
}
|
|
auto tm_iso_week_year() const noexcept -> long long {
|
|
const auto year = tm_year();
|
|
const auto w = iso_week_num(tm_yday(), tm_wday());
|
|
if (w < 1) return year - 1;
|
|
if (w > iso_year_weeks(year)) return year + 1;
|
|
return year;
|
|
}
|
|
auto tm_iso_week_of_year() const noexcept -> int {
|
|
const auto year = tm_year();
|
|
const auto w = iso_week_num(tm_yday(), tm_wday());
|
|
if (w < 1) return iso_year_weeks(year - 1);
|
|
if (w > iso_year_weeks(year)) return 1;
|
|
return w;
|
|
}
|
|
|
|
void write1(int value) {
|
|
*out_++ = static_cast<char>('0' + to_unsigned(value) % 10);
|
|
}
|
|
void write2(int value) {
|
|
const char* d = digits2(to_unsigned(value) % 100);
|
|
*out_++ = *d++;
|
|
*out_++ = *d;
|
|
}
|
|
void write2(int value, pad_type pad) {
|
|
unsigned int v = to_unsigned(value) % 100;
|
|
if (v >= 10) {
|
|
const char* d = digits2(v);
|
|
*out_++ = *d++;
|
|
*out_++ = *d;
|
|
} else {
|
|
out_ = detail::write_padding(out_, pad);
|
|
*out_++ = static_cast<char>('0' + v);
|
|
}
|
|
}
|
|
|
|
void write_year_extended(long long year) {
|
|
// At least 4 characters.
|
|
int width = 4;
|
|
if (year < 0) {
|
|
*out_++ = '-';
|
|
year = 0 - year;
|
|
--width;
|
|
}
|
|
uint32_or_64_or_128_t<long long> n = to_unsigned(year);
|
|
const int num_digits = count_digits(n);
|
|
if (width > num_digits) out_ = std::fill_n(out_, width - num_digits, '0');
|
|
out_ = format_decimal<Char>(out_, n, num_digits).end;
|
|
}
|
|
void write_year(long long year) {
|
|
if (year >= 0 && year < 10000) {
|
|
write2(static_cast<int>(year / 100));
|
|
write2(static_cast<int>(year % 100));
|
|
} else {
|
|
write_year_extended(year);
|
|
}
|
|
}
|
|
|
|
void write_utc_offset(long offset, numeric_system ns) {
|
|
if (offset < 0) {
|
|
*out_++ = '-';
|
|
offset = -offset;
|
|
} else {
|
|
*out_++ = '+';
|
|
}
|
|
offset /= 60;
|
|
write2(static_cast<int>(offset / 60));
|
|
if (ns != numeric_system::standard) *out_++ = ':';
|
|
write2(static_cast<int>(offset % 60));
|
|
}
|
|
template <typename T, FMT_ENABLE_IF(has_member_data_tm_gmtoff<T>::value)>
|
|
void format_utc_offset_impl(const T& tm, numeric_system ns) {
|
|
write_utc_offset(tm.tm_gmtoff, ns);
|
|
}
|
|
template <typename T, FMT_ENABLE_IF(!has_member_data_tm_gmtoff<T>::value)>
|
|
void format_utc_offset_impl(const T& tm, numeric_system ns) {
|
|
#if defined(_WIN32) && defined(_UCRT)
|
|
# if FMT_USE_TZSET
|
|
tzset_once();
|
|
# endif
|
|
long offset = 0;
|
|
_get_timezone(&offset);
|
|
if (tm.tm_isdst) {
|
|
long dstbias = 0;
|
|
_get_dstbias(&dstbias);
|
|
offset += dstbias;
|
|
}
|
|
write_utc_offset(-offset, ns);
|
|
#else
|
|
if (ns == numeric_system::standard) return format_localized('z');
|
|
|
|
// Extract timezone offset from timezone conversion functions.
|
|
std::tm gtm = tm;
|
|
std::time_t gt = std::mktime(>m);
|
|
std::tm ltm = gmtime(gt);
|
|
std::time_t lt = std::mktime(<m);
|
|
long offset = gt - lt;
|
|
write_utc_offset(offset, ns);
|
|
#endif
|
|
}
|
|
|
|
template <typename T, FMT_ENABLE_IF(has_member_data_tm_zone<T>::value)>
|
|
void format_tz_name_impl(const T& tm) {
|
|
if (is_classic_)
|
|
out_ = write_tm_str<Char>(out_, tm.tm_zone, loc_);
|
|
else
|
|
format_localized('Z');
|
|
}
|
|
template <typename T, FMT_ENABLE_IF(!has_member_data_tm_zone<T>::value)>
|
|
void format_tz_name_impl(const T&) {
|
|
format_localized('Z');
|
|
}
|
|
|
|
void format_localized(char format, char modifier = 0) {
|
|
out_ = write<Char>(out_, tm_, loc_, format, modifier);
|
|
}
|
|
|
|
public:
|
|
tm_writer(const std::locale& loc, OutputIt out, const std::tm& tm,
|
|
const Duration* subsecs = nullptr)
|
|
: loc_(loc),
|
|
is_classic_(loc_ == get_classic_locale()),
|
|
out_(out),
|
|
subsecs_(subsecs),
|
|
tm_(tm) {}
|
|
|
|
OutputIt out() const { return out_; }
|
|
|
|
FMT_CONSTEXPR void on_text(const Char* begin, const Char* end) {
|
|
out_ = copy_str<Char>(begin, end, out_);
|
|
}
|
|
|
|
void on_abbr_weekday() {
|
|
if (is_classic_)
|
|
out_ = write(out_, tm_wday_short_name(tm_wday()));
|
|
else
|
|
format_localized('a');
|
|
}
|
|
void on_full_weekday() {
|
|
if (is_classic_)
|
|
out_ = write(out_, tm_wday_full_name(tm_wday()));
|
|
else
|
|
format_localized('A');
|
|
}
|
|
void on_dec0_weekday(numeric_system ns) {
|
|
if (is_classic_ || ns == numeric_system::standard) return write1(tm_wday());
|
|
format_localized('w', 'O');
|
|
}
|
|
void on_dec1_weekday(numeric_system ns) {
|
|
if (is_classic_ || ns == numeric_system::standard) {
|
|
auto wday = tm_wday();
|
|
write1(wday == 0 ? days_per_week : wday);
|
|
} else {
|
|
format_localized('u', 'O');
|
|
}
|
|
}
|
|
|
|
void on_abbr_month() {
|
|
if (is_classic_)
|
|
out_ = write(out_, tm_mon_short_name(tm_mon()));
|
|
else
|
|
format_localized('b');
|
|
}
|
|
void on_full_month() {
|
|
if (is_classic_)
|
|
out_ = write(out_, tm_mon_full_name(tm_mon()));
|
|
else
|
|
format_localized('B');
|
|
}
|
|
|
|
void on_datetime(numeric_system ns) {
|
|
if (is_classic_) {
|
|
on_abbr_weekday();
|
|
*out_++ = ' ';
|
|
on_abbr_month();
|
|
*out_++ = ' ';
|
|
on_day_of_month_space(numeric_system::standard);
|
|
*out_++ = ' ';
|
|
on_iso_time();
|
|
*out_++ = ' ';
|
|
on_year(numeric_system::standard);
|
|
} else {
|
|
format_localized('c', ns == numeric_system::standard ? '\0' : 'E');
|
|
}
|
|
}
|
|
void on_loc_date(numeric_system ns) {
|
|
if (is_classic_)
|
|
on_us_date();
|
|
else
|
|
format_localized('x', ns == numeric_system::standard ? '\0' : 'E');
|
|
}
|
|
void on_loc_time(numeric_system ns) {
|
|
if (is_classic_)
|
|
on_iso_time();
|
|
else
|
|
format_localized('X', ns == numeric_system::standard ? '\0' : 'E');
|
|
}
|
|
void on_us_date() {
|
|
char buf[8];
|
|
write_digit2_separated(buf, to_unsigned(tm_mon() + 1),
|
|
to_unsigned(tm_mday()),
|
|
to_unsigned(split_year_lower(tm_year())), '/');
|
|
out_ = copy_str<Char>(std::begin(buf), std::end(buf), out_);
|
|
}
|
|
void on_iso_date() {
|
|
auto year = tm_year();
|
|
char buf[10];
|
|
size_t offset = 0;
|
|
if (year >= 0 && year < 10000) {
|
|
copy2(buf, digits2(static_cast<size_t>(year / 100)));
|
|
} else {
|
|
offset = 4;
|
|
write_year_extended(year);
|
|
year = 0;
|
|
}
|
|
write_digit2_separated(buf + 2, static_cast<unsigned>(year % 100),
|
|
to_unsigned(tm_mon() + 1), to_unsigned(tm_mday()),
|
|
'-');
|
|
out_ = copy_str<Char>(std::begin(buf) + offset, std::end(buf), out_);
|
|
}
|
|
|
|
void on_utc_offset(numeric_system ns) { format_utc_offset_impl(tm_, ns); }
|
|
void on_tz_name() { format_tz_name_impl(tm_); }
|
|
|
|
void on_year(numeric_system ns) {
|
|
if (is_classic_ || ns == numeric_system::standard)
|
|
return write_year(tm_year());
|
|
format_localized('Y', 'E');
|
|
}
|
|
void on_short_year(numeric_system ns) {
|
|
if (is_classic_ || ns == numeric_system::standard)
|
|
return write2(split_year_lower(tm_year()));
|
|
format_localized('y', 'O');
|
|
}
|
|
void on_offset_year() {
|
|
if (is_classic_) return write2(split_year_lower(tm_year()));
|
|
format_localized('y', 'E');
|
|
}
|
|
|
|
void on_century(numeric_system ns) {
|
|
if (is_classic_ || ns == numeric_system::standard) {
|
|
auto year = tm_year();
|
|
auto upper = year / 100;
|
|
if (year >= -99 && year < 0) {
|
|
// Zero upper on negative year.
|
|
*out_++ = '-';
|
|
*out_++ = '0';
|
|
} else if (upper >= 0 && upper < 100) {
|
|
write2(static_cast<int>(upper));
|
|
} else {
|
|
out_ = write<Char>(out_, upper);
|
|
}
|
|
} else {
|
|
format_localized('C', 'E');
|
|
}
|
|
}
|
|
|
|
void on_dec_month(numeric_system ns) {
|
|
if (is_classic_ || ns == numeric_system::standard)
|
|
return write2(tm_mon() + 1);
|
|
format_localized('m', 'O');
|
|
}
|
|
|
|
void on_dec0_week_of_year(numeric_system ns) {
|
|
if (is_classic_ || ns == numeric_system::standard)
|
|
return write2((tm_yday() + days_per_week - tm_wday()) / days_per_week);
|
|
format_localized('U', 'O');
|
|
}
|
|
void on_dec1_week_of_year(numeric_system ns) {
|
|
if (is_classic_ || ns == numeric_system::standard) {
|
|
auto wday = tm_wday();
|
|
write2((tm_yday() + days_per_week -
|
|
(wday == 0 ? (days_per_week - 1) : (wday - 1))) /
|
|
days_per_week);
|
|
} else {
|
|
format_localized('W', 'O');
|
|
}
|
|
}
|
|
void on_iso_week_of_year(numeric_system ns) {
|
|
if (is_classic_ || ns == numeric_system::standard)
|
|
return write2(tm_iso_week_of_year());
|
|
format_localized('V', 'O');
|
|
}
|
|
|
|
void on_iso_week_based_year() { write_year(tm_iso_week_year()); }
|
|
void on_iso_week_based_short_year() {
|
|
write2(split_year_lower(tm_iso_week_year()));
|
|
}
|
|
|
|
void on_day_of_year() {
|
|
auto yday = tm_yday() + 1;
|
|
write1(yday / 100);
|
|
write2(yday % 100);
|
|
}
|
|
void on_day_of_month(numeric_system ns) {
|
|
if (is_classic_ || ns == numeric_system::standard) return write2(tm_mday());
|
|
format_localized('d', 'O');
|
|
}
|
|
void on_day_of_month_space(numeric_system ns) {
|
|
if (is_classic_ || ns == numeric_system::standard) {
|
|
auto mday = to_unsigned(tm_mday()) % 100;
|
|
const char* d2 = digits2(mday);
|
|
*out_++ = mday < 10 ? ' ' : d2[0];
|
|
*out_++ = d2[1];
|
|
} else {
|
|
format_localized('e', 'O');
|
|
}
|
|
}
|
|
|
|
void on_24_hour(numeric_system ns, pad_type pad) {
|
|
if (is_classic_ || ns == numeric_system::standard)
|
|
return write2(tm_hour(), pad);
|
|
format_localized('H', 'O');
|
|
}
|
|
void on_12_hour(numeric_system ns, pad_type pad) {
|
|
if (is_classic_ || ns == numeric_system::standard)
|
|
return write2(tm_hour12(), pad);
|
|
format_localized('I', 'O');
|
|
}
|
|
void on_minute(numeric_system ns, pad_type pad) {
|
|
if (is_classic_ || ns == numeric_system::standard)
|
|
return write2(tm_min(), pad);
|
|
format_localized('M', 'O');
|
|
}
|
|
|
|
void on_second(numeric_system ns, pad_type pad) {
|
|
if (is_classic_ || ns == numeric_system::standard) {
|
|
write2(tm_sec(), pad);
|
|
if (subsecs_) {
|
|
if (std::is_floating_point<typename Duration::rep>::value) {
|
|
auto buf = memory_buffer();
|
|
write_floating_seconds(buf, *subsecs_);
|
|
if (buf.size() > 1) {
|
|
// Remove the leading "0", write something like ".123".
|
|
out_ = std::copy(buf.begin() + 1, buf.end(), out_);
|
|
}
|
|
} else {
|
|
write_fractional_seconds<Char>(out_, *subsecs_);
|
|
}
|
|
}
|
|
} else {
|
|
// Currently no formatting of subseconds when a locale is set.
|
|
format_localized('S', 'O');
|
|
}
|
|
}
|
|
|
|
void on_12_hour_time() {
|
|
if (is_classic_) {
|
|
char buf[8];
|
|
write_digit2_separated(buf, to_unsigned(tm_hour12()),
|
|
to_unsigned(tm_min()), to_unsigned(tm_sec()), ':');
|
|
out_ = copy_str<Char>(std::begin(buf), std::end(buf), out_);
|
|
*out_++ = ' ';
|
|
on_am_pm();
|
|
} else {
|
|
format_localized('r');
|
|
}
|
|
}
|
|
void on_24_hour_time() {
|
|
write2(tm_hour());
|
|
*out_++ = ':';
|
|
write2(tm_min());
|
|
}
|
|
void on_iso_time() {
|
|
on_24_hour_time();
|
|
*out_++ = ':';
|
|
on_second(numeric_system::standard, pad_type::unspecified);
|
|
}
|
|
|
|
void on_am_pm() {
|
|
if (is_classic_) {
|
|
*out_++ = tm_hour() < 12 ? 'A' : 'P';
|
|
*out_++ = 'M';
|
|
} else {
|
|
format_localized('p');
|
|
}
|
|
}
|
|
|
|
// These apply to chrono durations but not tm.
|
|
void on_duration_value() {}
|
|
void on_duration_unit() {}
|
|
};
|
|
|
|
struct chrono_format_checker : null_chrono_spec_handler<chrono_format_checker> {
|
|
bool has_precision_integral = false;
|
|
|
|
FMT_NORETURN void unsupported() { FMT_THROW(format_error("no date")); }
|
|
|
|
template <typename Char>
|
|
FMT_CONSTEXPR void on_text(const Char*, const Char*) {}
|
|
FMT_CONSTEXPR void on_24_hour(numeric_system, pad_type) {}
|
|
FMT_CONSTEXPR void on_12_hour(numeric_system, pad_type) {}
|
|
FMT_CONSTEXPR void on_minute(numeric_system, pad_type) {}
|
|
FMT_CONSTEXPR void on_second(numeric_system, pad_type) {}
|
|
FMT_CONSTEXPR void on_12_hour_time() {}
|
|
FMT_CONSTEXPR void on_24_hour_time() {}
|
|
FMT_CONSTEXPR void on_iso_time() {}
|
|
FMT_CONSTEXPR void on_am_pm() {}
|
|
FMT_CONSTEXPR void on_duration_value() const {
|
|
if (has_precision_integral) {
|
|
FMT_THROW(format_error("precision not allowed for this argument type"));
|
|
}
|
|
}
|
|
FMT_CONSTEXPR void on_duration_unit() {}
|
|
};
|
|
|
|
template <typename T,
|
|
FMT_ENABLE_IF(std::is_integral<T>::value&& has_isfinite<T>::value)>
|
|
inline bool isfinite(T) {
|
|
return true;
|
|
}
|
|
|
|
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
|
|
inline T mod(T x, int y) {
|
|
return x % static_cast<T>(y);
|
|
}
|
|
template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)>
|
|
inline T mod(T x, int y) {
|
|
return std::fmod(x, static_cast<T>(y));
|
|
}
|
|
|
|
// If T is an integral type, maps T to its unsigned counterpart, otherwise
|
|
// leaves it unchanged (unlike std::make_unsigned).
|
|
template <typename T, bool INTEGRAL = std::is_integral<T>::value>
|
|
struct make_unsigned_or_unchanged {
|
|
using type = T;
|
|
};
|
|
|
|
template <typename T> struct make_unsigned_or_unchanged<T, true> {
|
|
using type = typename std::make_unsigned<T>::type;
|
|
};
|
|
|
|
#if FMT_SAFE_DURATION_CAST
|
|
// throwing version of safe_duration_cast
|
|
template <typename To, typename FromRep, typename FromPeriod>
|
|
To fmt_safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from) {
|
|
int ec;
|
|
To to = safe_duration_cast::safe_duration_cast<To>(from, ec);
|
|
if (ec) FMT_THROW(format_error("cannot format duration"));
|
|
return to;
|
|
}
|
|
#endif
|
|
|
|
template <typename Rep, typename Period,
|
|
FMT_ENABLE_IF(std::is_integral<Rep>::value)>
|
|
inline std::chrono::duration<Rep, std::milli> get_milliseconds(
|
|
std::chrono::duration<Rep, Period> d) {
|
|
// this may overflow and/or the result may not fit in the
|
|
// target type.
|
|
#if FMT_SAFE_DURATION_CAST
|
|
using CommonSecondsType =
|
|
typename std::common_type<decltype(d), std::chrono::seconds>::type;
|
|
const auto d_as_common = fmt_safe_duration_cast<CommonSecondsType>(d);
|
|
const auto d_as_whole_seconds =
|
|
fmt_safe_duration_cast<std::chrono::seconds>(d_as_common);
|
|
// this conversion should be nonproblematic
|
|
const auto diff = d_as_common - d_as_whole_seconds;
|
|
const auto ms =
|
|
fmt_safe_duration_cast<std::chrono::duration<Rep, std::milli>>(diff);
|
|
return ms;
|
|
#else
|
|
auto s = std::chrono::duration_cast<std::chrono::seconds>(d);
|
|
return std::chrono::duration_cast<std::chrono::milliseconds>(d - s);
|
|
#endif
|
|
}
|
|
|
|
template <typename Char, typename Rep, typename OutputIt,
|
|
FMT_ENABLE_IF(std::is_integral<Rep>::value)>
|
|
OutputIt format_duration_value(OutputIt out, Rep val, int) {
|
|
return write<Char>(out, val);
|
|
}
|
|
|
|
template <typename Char, typename Rep, typename OutputIt,
|
|
FMT_ENABLE_IF(std::is_floating_point<Rep>::value)>
|
|
OutputIt format_duration_value(OutputIt out, Rep val, int precision) {
|
|
auto specs = format_specs<Char>();
|
|
specs.precision = precision;
|
|
specs.type = precision >= 0 ? presentation_type::fixed_lower
|
|
: presentation_type::general_lower;
|
|
return write<Char>(out, val, specs);
|
|
}
|
|
|
|
template <typename Char, typename OutputIt>
|
|
OutputIt copy_unit(string_view unit, OutputIt out, Char) {
|
|
return std::copy(unit.begin(), unit.end(), out);
|
|
}
|
|
|
|
template <typename OutputIt>
|
|
OutputIt copy_unit(string_view unit, OutputIt out, wchar_t) {
|
|
// This works when wchar_t is UTF-32 because units only contain characters
|
|
// that have the same representation in UTF-16 and UTF-32.
|
|
utf8_to_utf16 u(unit);
|
|
return std::copy(u.c_str(), u.c_str() + u.size(), out);
|
|
}
|
|
|
|
template <typename Char, typename Period, typename OutputIt>
|
|
OutputIt format_duration_unit(OutputIt out) {
|
|
if (const char* unit = get_units<Period>())
|
|
return copy_unit(string_view(unit), out, Char());
|
|
*out++ = '[';
|
|
out = write<Char>(out, Period::num);
|
|
if (const_check(Period::den != 1)) {
|
|
*out++ = '/';
|
|
out = write<Char>(out, Period::den);
|
|
}
|
|
*out++ = ']';
|
|
*out++ = 's';
|
|
return out;
|
|
}
|
|
|
|
class get_locale {
|
|
private:
|
|
union {
|
|
std::locale locale_;
|
|
};
|
|
bool has_locale_ = false;
|
|
|
|
public:
|
|
get_locale(bool localized, locale_ref loc) : has_locale_(localized) {
|
|
if (localized)
|
|
::new (&locale_) std::locale(loc.template get<std::locale>());
|
|
}
|
|
~get_locale() {
|
|
if (has_locale_) locale_.~locale();
|
|
}
|
|
operator const std::locale&() const {
|
|
return has_locale_ ? locale_ : get_classic_locale();
|
|
}
|
|
};
|
|
|
|
template <typename FormatContext, typename OutputIt, typename Rep,
|
|
typename Period>
|
|
struct chrono_formatter {
|
|
FormatContext& context;
|
|
OutputIt out;
|
|
int precision;
|
|
bool localized = false;
|
|
// rep is unsigned to avoid overflow.
|
|
using rep =
|
|
conditional_t<std::is_integral<Rep>::value && sizeof(Rep) < sizeof(int),
|
|
unsigned, typename make_unsigned_or_unchanged<Rep>::type>;
|
|
rep val;
|
|
using seconds = std::chrono::duration<rep>;
|
|
seconds s;
|
|
using milliseconds = std::chrono::duration<rep, std::milli>;
|
|
bool negative;
|
|
|
|
using char_type = typename FormatContext::char_type;
|
|
using tm_writer_type = tm_writer<OutputIt, char_type>;
|
|
|
|
chrono_formatter(FormatContext& ctx, OutputIt o,
|
|
std::chrono::duration<Rep, Period> d)
|
|
: context(ctx),
|
|
out(o),
|
|
val(static_cast<rep>(d.count())),
|
|
negative(false) {
|
|
if (d.count() < 0) {
|
|
val = 0 - val;
|
|
negative = true;
|
|
}
|
|
|
|
// this may overflow and/or the result may not fit in the
|
|
// target type.
|
|
#if FMT_SAFE_DURATION_CAST
|
|
// might need checked conversion (rep!=Rep)
|
|
auto tmpval = std::chrono::duration<rep, Period>(val);
|
|
s = fmt_safe_duration_cast<seconds>(tmpval);
|
|
#else
|
|
s = std::chrono::duration_cast<seconds>(
|
|
std::chrono::duration<rep, Period>(val));
|
|
#endif
|
|
}
|
|
|
|
// returns true if nan or inf, writes to out.
|
|
bool handle_nan_inf() {
|
|
if (isfinite(val)) {
|
|
return false;
|
|
}
|
|
if (isnan(val)) {
|
|
write_nan();
|
|
return true;
|
|
}
|
|
// must be +-inf
|
|
if (val > 0) {
|
|
write_pinf();
|
|
} else {
|
|
write_ninf();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
Rep hour() const { return static_cast<Rep>(mod((s.count() / 3600), 24)); }
|
|
|
|
Rep hour12() const {
|
|
Rep hour = static_cast<Rep>(mod((s.count() / 3600), 12));
|
|
return hour <= 0 ? 12 : hour;
|
|
}
|
|
|
|
Rep minute() const { return static_cast<Rep>(mod((s.count() / 60), 60)); }
|
|
Rep second() const { return static_cast<Rep>(mod(s.count(), 60)); }
|
|
|
|
std::tm time() const {
|
|
auto time = std::tm();
|
|
time.tm_hour = to_nonnegative_int(hour(), 24);
|
|
time.tm_min = to_nonnegative_int(minute(), 60);
|
|
time.tm_sec = to_nonnegative_int(second(), 60);
|
|
return time;
|
|
}
|
|
|
|
void write_sign() {
|
|
if (negative) {
|
|
*out++ = '-';
|
|
negative = false;
|
|
}
|
|
}
|
|
|
|
void write(Rep value, int width, pad_type pad = pad_type::unspecified) {
|
|
write_sign();
|
|
if (isnan(value)) return write_nan();
|
|
uint32_or_64_or_128_t<int> n =
|
|
to_unsigned(to_nonnegative_int(value, max_value<int>()));
|
|
int num_digits = detail::count_digits(n);
|
|
if (width > num_digits) {
|
|
out = detail::write_padding(out, pad, width - num_digits);
|
|
}
|
|
out = format_decimal<char_type>(out, n, num_digits).end;
|
|
}
|
|
|
|
void write_nan() { std::copy_n("nan", 3, out); }
|
|
void write_pinf() { std::copy_n("inf", 3, out); }
|
|
void write_ninf() { std::copy_n("-inf", 4, out); }
|
|
|
|
template <typename Callback, typename... Args>
|
|
void format_tm(const tm& time, Callback cb, Args... args) {
|
|
if (isnan(val)) return write_nan();
|
|
get_locale loc(localized, context.locale());
|
|
auto w = tm_writer_type(loc, out, time);
|
|
(w.*cb)(args...);
|
|
out = w.out();
|
|
}
|
|
|
|
void on_text(const char_type* begin, const char_type* end) {
|
|
std::copy(begin, end, out);
|
|
}
|
|
|
|
// These are not implemented because durations don't have date information.
|
|
void on_abbr_weekday() {}
|
|
void on_full_weekday() {}
|
|
void on_dec0_weekday(numeric_system) {}
|
|
void on_dec1_weekday(numeric_system) {}
|
|
void on_abbr_month() {}
|
|
void on_full_month() {}
|
|
void on_datetime(numeric_system) {}
|
|
void on_loc_date(numeric_system) {}
|
|
void on_loc_time(numeric_system) {}
|
|
void on_us_date() {}
|
|
void on_iso_date() {}
|
|
void on_utc_offset(numeric_system) {}
|
|
void on_tz_name() {}
|
|
void on_year(numeric_system) {}
|
|
void on_short_year(numeric_system) {}
|
|
void on_offset_year() {}
|
|
void on_century(numeric_system) {}
|
|
void on_iso_week_based_year() {}
|
|
void on_iso_week_based_short_year() {}
|
|
void on_dec_month(numeric_system) {}
|
|
void on_dec0_week_of_year(numeric_system) {}
|
|
void on_dec1_week_of_year(numeric_system) {}
|
|
void on_iso_week_of_year(numeric_system) {}
|
|
void on_day_of_year() {}
|
|
void on_day_of_month(numeric_system) {}
|
|
void on_day_of_month_space(numeric_system) {}
|
|
|
|
void on_24_hour(numeric_system ns, pad_type pad) {
|
|
if (handle_nan_inf()) return;
|
|
|
|
if (ns == numeric_system::standard) return write(hour(), 2, pad);
|
|
auto time = tm();
|
|
time.tm_hour = to_nonnegative_int(hour(), 24);
|
|
format_tm(time, &tm_writer_type::on_24_hour, ns, pad);
|
|
}
|
|
|
|
void on_12_hour(numeric_system ns, pad_type pad) {
|
|
if (handle_nan_inf()) return;
|
|
|
|
if (ns == numeric_system::standard) return write(hour12(), 2, pad);
|
|
auto time = tm();
|
|
time.tm_hour = to_nonnegative_int(hour12(), 12);
|
|
format_tm(time, &tm_writer_type::on_12_hour, ns, pad);
|
|
}
|
|
|
|
void on_minute(numeric_system ns, pad_type pad) {
|
|
if (handle_nan_inf()) return;
|
|
|
|
if (ns == numeric_system::standard) return write(minute(), 2, pad);
|
|
auto time = tm();
|
|
time.tm_min = to_nonnegative_int(minute(), 60);
|
|
format_tm(time, &tm_writer_type::on_minute, ns, pad);
|
|
}
|
|
|
|
void on_second(numeric_system ns, pad_type pad) {
|
|
if (handle_nan_inf()) return;
|
|
|
|
if (ns == numeric_system::standard) {
|
|
if (std::is_floating_point<rep>::value) {
|
|
auto buf = memory_buffer();
|
|
write_floating_seconds(buf, std::chrono::duration<rep, Period>(val),
|
|
precision);
|
|
if (negative) *out++ = '-';
|
|
if (buf.size() < 2 || buf[1] == '.') {
|
|
out = detail::write_padding(out, pad);
|
|
}
|
|
out = std::copy(buf.begin(), buf.end(), out);
|
|
} else {
|
|
write(second(), 2, pad);
|
|
write_fractional_seconds<char_type>(
|
|
out, std::chrono::duration<rep, Period>(val), precision);
|
|
}
|
|
return;
|
|
}
|
|
auto time = tm();
|
|
time.tm_sec = to_nonnegative_int(second(), 60);
|
|
format_tm(time, &tm_writer_type::on_second, ns, pad);
|
|
}
|
|
|
|
void on_12_hour_time() {
|
|
if (handle_nan_inf()) return;
|
|
format_tm(time(), &tm_writer_type::on_12_hour_time);
|
|
}
|
|
|
|
void on_24_hour_time() {
|
|
if (handle_nan_inf()) {
|
|
*out++ = ':';
|
|
handle_nan_inf();
|
|
return;
|
|
}
|
|
|
|
write(hour(), 2);
|
|
*out++ = ':';
|
|
write(minute(), 2);
|
|
}
|
|
|
|
void on_iso_time() {
|
|
on_24_hour_time();
|
|
*out++ = ':';
|
|
if (handle_nan_inf()) return;
|
|
on_second(numeric_system::standard, pad_type::unspecified);
|
|
}
|
|
|
|
void on_am_pm() {
|
|
if (handle_nan_inf()) return;
|
|
format_tm(time(), &tm_writer_type::on_am_pm);
|
|
}
|
|
|
|
void on_duration_value() {
|
|
if (handle_nan_inf()) return;
|
|
write_sign();
|
|
out = format_duration_value<char_type>(out, val, precision);
|
|
}
|
|
|
|
void on_duration_unit() {
|
|
out = format_duration_unit<char_type, Period>(out);
|
|
}
|
|
};
|
|
|
|
} // namespace detail
|
|
|
|
#if defined(__cpp_lib_chrono) && __cpp_lib_chrono >= 201907
|
|
using weekday = std::chrono::weekday;
|
|
#else
|
|
// A fallback version of weekday.
|
|
class weekday {
|
|
private:
|
|
unsigned char value;
|
|
|
|
public:
|
|
weekday() = default;
|
|
explicit constexpr weekday(unsigned wd) noexcept
|
|
: value(static_cast<unsigned char>(wd != 7 ? wd : 0)) {}
|
|
constexpr unsigned c_encoding() const noexcept { return value; }
|
|
};
|
|
|
|
class year_month_day {};
|
|
#endif
|
|
|
|
// A rudimentary weekday formatter.
|
|
template <typename Char> struct formatter<weekday, Char> {
|
|
private:
|
|
bool localized = false;
|
|
|
|
public:
|
|
FMT_CONSTEXPR auto parse(basic_format_parse_context<Char>& ctx)
|
|
-> decltype(ctx.begin()) {
|
|
auto begin = ctx.begin(), end = ctx.end();
|
|
if (begin != end && *begin == 'L') {
|
|
++begin;
|
|
localized = true;
|
|
}
|
|
return begin;
|
|
}
|
|
|
|
template <typename FormatContext>
|
|
auto format(weekday wd, FormatContext& ctx) const -> decltype(ctx.out()) {
|
|
auto time = std::tm();
|
|
time.tm_wday = static_cast<int>(wd.c_encoding());
|
|
detail::get_locale loc(localized, ctx.locale());
|
|
auto w = detail::tm_writer<decltype(ctx.out()), Char>(loc, ctx.out(), time);
|
|
w.on_abbr_weekday();
|
|
return w.out();
|
|
}
|
|
};
|
|
|
|
template <typename Rep, typename Period, typename Char>
|
|
struct formatter<std::chrono::duration<Rep, Period>, Char> {
|
|
private:
|
|
format_specs<Char> specs_;
|
|
detail::arg_ref<Char> width_ref_;
|
|
detail::arg_ref<Char> precision_ref_;
|
|
bool localized_ = false;
|
|
basic_string_view<Char> format_str_;
|
|
|
|
public:
|
|
FMT_CONSTEXPR auto parse(basic_format_parse_context<Char>& ctx)
|
|
-> decltype(ctx.begin()) {
|
|
auto it = ctx.begin(), end = ctx.end();
|
|
if (it == end || *it == '}') return it;
|
|
|
|
it = detail::parse_align(it, end, specs_);
|
|
if (it == end) return it;
|
|
|
|
it = detail::parse_dynamic_spec(it, end, specs_.width, width_ref_, ctx);
|
|
if (it == end) return it;
|
|
|
|
auto checker = detail::chrono_format_checker();
|
|
if (*it == '.') {
|
|
checker.has_precision_integral = !std::is_floating_point<Rep>::value;
|
|
it = detail::parse_precision(it, end, specs_.precision, precision_ref_,
|
|
ctx);
|
|
}
|
|
if (it != end && *it == 'L') {
|
|
localized_ = true;
|
|
++it;
|
|
}
|
|
end = detail::parse_chrono_format(it, end, checker);
|
|
format_str_ = {it, detail::to_unsigned(end - it)};
|
|
return end;
|
|
}
|
|
|
|
template <typename FormatContext>
|
|
auto format(std::chrono::duration<Rep, Period> d, FormatContext& ctx) const
|
|
-> decltype(ctx.out()) {
|
|
auto specs = specs_;
|
|
auto precision = specs.precision;
|
|
specs.precision = -1;
|
|
auto begin = format_str_.begin(), end = format_str_.end();
|
|
// As a possible future optimization, we could avoid extra copying if width
|
|
// is not specified.
|
|
auto buf = basic_memory_buffer<Char>();
|
|
auto out = std::back_inserter(buf);
|
|
detail::handle_dynamic_spec<detail::width_checker>(specs.width, width_ref_,
|
|
ctx);
|
|
detail::handle_dynamic_spec<detail::precision_checker>(precision,
|
|
precision_ref_, ctx);
|
|
if (begin == end || *begin == '}') {
|
|
out = detail::format_duration_value<Char>(out, d.count(), precision);
|
|
detail::format_duration_unit<Char, Period>(out);
|
|
} else {
|
|
using chrono_formatter =
|
|
detail::chrono_formatter<FormatContext, decltype(out), Rep, Period>;
|
|
auto f = chrono_formatter(ctx, out, d);
|
|
f.precision = precision;
|
|
f.localized = localized_;
|
|
detail::parse_chrono_format(begin, end, f);
|
|
}
|
|
return detail::write(
|
|
ctx.out(), basic_string_view<Char>(buf.data(), buf.size()), specs);
|
|
}
|
|
};
|
|
|
|
template <typename Char, typename Duration>
|
|
struct formatter<std::chrono::time_point<std::chrono::system_clock, Duration>,
|
|
Char> : formatter<std::tm, Char> {
|
|
FMT_CONSTEXPR formatter() {
|
|
this->format_str_ = detail::string_literal<Char, '%', 'F', ' ', '%', 'T'>{};
|
|
}
|
|
|
|
template <typename FormatContext>
|
|
auto format(std::chrono::time_point<std::chrono::system_clock, Duration> val,
|
|
FormatContext& ctx) const -> decltype(ctx.out()) {
|
|
using period = typename Duration::period;
|
|
if (detail::const_check(
|
|
period::num != 1 || period::den != 1 ||
|
|
std::is_floating_point<typename Duration::rep>::value)) {
|
|
const auto epoch = val.time_since_epoch();
|
|
auto subsecs = std::chrono::duration_cast<Duration>(
|
|
epoch - std::chrono::duration_cast<std::chrono::seconds>(epoch));
|
|
|
|
if (subsecs.count() < 0) {
|
|
auto second =
|
|
std::chrono::duration_cast<Duration>(std::chrono::seconds(1));
|
|
if (epoch.count() < ((Duration::min)() + second).count())
|
|
FMT_THROW(format_error("duration is too small"));
|
|
subsecs += second;
|
|
val -= second;
|
|
}
|
|
|
|
return formatter<std::tm, Char>::do_format(
|
|
gmtime(std::chrono::time_point_cast<std::chrono::seconds>(val)), ctx,
|
|
&subsecs);
|
|
}
|
|
|
|
return formatter<std::tm, Char>::format(
|
|
gmtime(std::chrono::time_point_cast<std::chrono::seconds>(val)), ctx);
|
|
}
|
|
};
|
|
|
|
#if FMT_USE_LOCAL_TIME
|
|
template <typename Char, typename Duration>
|
|
struct formatter<std::chrono::local_time<Duration>, Char>
|
|
: formatter<std::tm, Char> {
|
|
FMT_CONSTEXPR formatter() {
|
|
this->format_str_ = detail::string_literal<Char, '%', 'F', ' ', '%', 'T'>{};
|
|
}
|
|
|
|
template <typename FormatContext>
|
|
auto format(std::chrono::local_time<Duration> val, FormatContext& ctx) const
|
|
-> decltype(ctx.out()) {
|
|
using period = typename Duration::period;
|
|
if (period::num != 1 || period::den != 1 ||
|
|
std::is_floating_point<typename Duration::rep>::value) {
|
|
const auto epoch = val.time_since_epoch();
|
|
const auto subsecs = std::chrono::duration_cast<Duration>(
|
|
epoch - std::chrono::duration_cast<std::chrono::seconds>(epoch));
|
|
|
|
return formatter<std::tm, Char>::do_format(
|
|
localtime(std::chrono::time_point_cast<std::chrono::seconds>(val)),
|
|
ctx, &subsecs);
|
|
}
|
|
|
|
return formatter<std::tm, Char>::format(
|
|
localtime(std::chrono::time_point_cast<std::chrono::seconds>(val)),
|
|
ctx);
|
|
}
|
|
};
|
|
#endif
|
|
|
|
#if FMT_USE_UTC_TIME
|
|
template <typename Char, typename Duration>
|
|
struct formatter<std::chrono::time_point<std::chrono::utc_clock, Duration>,
|
|
Char>
|
|
: formatter<std::chrono::time_point<std::chrono::system_clock, Duration>,
|
|
Char> {
|
|
template <typename FormatContext>
|
|
auto format(std::chrono::time_point<std::chrono::utc_clock, Duration> val,
|
|
FormatContext& ctx) const -> decltype(ctx.out()) {
|
|
return formatter<
|
|
std::chrono::time_point<std::chrono::system_clock, Duration>,
|
|
Char>::format(std::chrono::utc_clock::to_sys(val), ctx);
|
|
}
|
|
};
|
|
#endif
|
|
|
|
template <typename Char> struct formatter<std::tm, Char> {
|
|
private:
|
|
format_specs<Char> specs_;
|
|
detail::arg_ref<Char> width_ref_;
|
|
|
|
protected:
|
|
basic_string_view<Char> format_str_;
|
|
|
|
template <typename FormatContext, typename Duration>
|
|
auto do_format(const std::tm& tm, FormatContext& ctx,
|
|
const Duration* subsecs) const -> decltype(ctx.out()) {
|
|
auto specs = specs_;
|
|
auto buf = basic_memory_buffer<Char>();
|
|
auto out = std::back_inserter(buf);
|
|
detail::handle_dynamic_spec<detail::width_checker>(specs.width, width_ref_,
|
|
ctx);
|
|
|
|
auto loc_ref = ctx.locale();
|
|
detail::get_locale loc(static_cast<bool>(loc_ref), loc_ref);
|
|
auto w =
|
|
detail::tm_writer<decltype(out), Char, Duration>(loc, out, tm, subsecs);
|
|
detail::parse_chrono_format(format_str_.begin(), format_str_.end(), w);
|
|
return detail::write(
|
|
ctx.out(), basic_string_view<Char>(buf.data(), buf.size()), specs);
|
|
}
|
|
|
|
public:
|
|
FMT_CONSTEXPR auto parse(basic_format_parse_context<Char>& ctx)
|
|
-> decltype(ctx.begin()) {
|
|
auto it = ctx.begin(), end = ctx.end();
|
|
if (it == end || *it == '}') return it;
|
|
|
|
it = detail::parse_align(it, end, specs_);
|
|
if (it == end) return it;
|
|
|
|
it = detail::parse_dynamic_spec(it, end, specs_.width, width_ref_, ctx);
|
|
if (it == end) return it;
|
|
|
|
end = detail::parse_chrono_format(it, end, detail::tm_format_checker());
|
|
// Replace the default format_str only if the new spec is not empty.
|
|
if (end != it) format_str_ = {it, detail::to_unsigned(end - it)};
|
|
return end;
|
|
}
|
|
|
|
template <typename FormatContext>
|
|
auto format(const std::tm& tm, FormatContext& ctx) const
|
|
-> decltype(ctx.out()) {
|
|
return do_format<FormatContext, std::chrono::seconds>(tm, ctx, nullptr);
|
|
}
|
|
};
|
|
|
|
FMT_END_EXPORT
|
|
FMT_END_NAMESPACE
|
|
|
|
#endif // FMT_CHRONO_H_
|