[libc++][chrono] Implements formatter duration.

Partially implements:
- P1361 Integration of chrono with text formatting
- P2372 Fixing locale handling in chrono formatters
- LWG3270 Parsing and formatting %j with durations

Completes:
- P1650R0 std::chrono::days with 'd' suffix
- LWG3262 Formatting of negative durations is not specified
- LWG3314 Is stream insertion behavior locale dependent when Period::type is micro?

Reviewed By: ldionne, #libc

Differential Revision: https://reviews.llvm.org/D134742
This commit is contained in:
Mark de Wever
2022-03-20 13:40:02 +01:00
parent 594fa1474f
commit 719c3dc6f2
11 changed files with 1586 additions and 44 deletions

View File

@@ -213,10 +213,10 @@
"`3254 <https://wg21.link/LWG3254>`__","Strike ``stop_token``\ 's ``operator!=``\ ","Prague","",""
"`3255 <https://wg21.link/LWG3255>`__","``span``\ 's ``array``\ constructor is too strict","Prague","|Complete|",""
"`3260 <https://wg21.link/LWG3260>`__","``year_month*``\ arithmetic rejects durations convertible to years","Prague","","","|chrono|"
"`3262 <https://wg21.link/LWG3262>`__","Formatting of negative durations is not specified","Prague","","","|chrono| |format|"
"`3262 <https://wg21.link/LWG3262>`__","Formatting of negative durations is not specified","Prague","|Complete|","16.0","|chrono| |format|"
"`3264 <https://wg21.link/LWG3264>`__","``sized_range``\ and ``ranges::size``\ redundantly use ``disable_sized_range``\ ","Prague","|Complete|","15.0","|ranges|"
"`3269 <https://wg21.link/LWG3269>`__","Parse manipulators do not specify the result of the extraction from stream","Prague","","","|chrono|"
"`3270 <https://wg21.link/LWG3270>`__","Parsing and formatting ``%j``\ with ``duration``\ s","Prague","","","|chrono| |format|"
"`3270 <https://wg21.link/LWG3270>`__","Parsing and formatting ``%j``\ with ``duration``\ s","Prague","|Partial|","","|chrono| |format|"
"`3280 <https://wg21.link/LWG3280>`__","View converting constructors can cause constraint recursion and are unneeded","Prague","|Complete|","15.0","|ranges|"
"`3281 <https://wg21.link/LWG3281>`__","Conversion from ``*pair-like*``\ types to ``subrange``\ is a silent semantic promotion","Prague","|Complete|","15.0","|ranges|"
"`3282 <https://wg21.link/LWG3282>`__","``subrange``\ converting constructor should disallow derived to base conversions","Prague","|Complete|","15.0","|ranges|"
@@ -236,7 +236,7 @@
"`3307 <https://wg21.link/LWG3307>`__","``std::allocator<void>().allocate(n)``\ ","Prague","",""
"`3310 <https://wg21.link/LWG3310>`__","Replace ``SIZE_MAX``\ with ``numeric_limits<size_t>::max()``\ ","Prague","",""
"`3313 <https://wg21.link/LWG3313>`__","``join_view::iterator::operator--``\ is incorrectly constrained","Prague","|Complete|","14.0","|ranges|"
"`3314 <https://wg21.link/LWG3314>`__","Is stream insertion behavior locale dependent when ``Period::type``\ is ``micro``\ ?","Prague","","","|chrono|"
"`3314 <https://wg21.link/LWG3314>`__","Is stream insertion behavior locale dependent when ``Period::type``\ is ``micro``\ ?","Prague","|Complete|","16.0","|chrono|"
"`3315 <https://wg21.link/LWG3315>`__","Correct Allocator Default Behavior","Prague","",""
"`3316 <https://wg21.link/LWG3316>`__","Correctly define epoch for ``utc_clock``\ / ``utc_timepoint``\ ","Prague","","","|chrono|"
"`3317 <https://wg21.link/LWG3317>`__","Incorrect ``operator<<``\ for floating-point durations","Prague","","","|chrono|"
1 Issue # Issue Name Meeting Status First released version Labels
213 `3254 <https://wg21.link/LWG3254>`__ Strike ``stop_token``\ 's ``operator!=``\ Prague
214 `3255 <https://wg21.link/LWG3255>`__ ``span``\ 's ``array``\ constructor is too strict Prague |Complete|
215 `3260 <https://wg21.link/LWG3260>`__ ``year_month*``\ arithmetic rejects durations convertible to years Prague |chrono|
216 `3262 <https://wg21.link/LWG3262>`__ Formatting of negative durations is not specified Prague |Complete| 16.0 |chrono| |format|
217 `3264 <https://wg21.link/LWG3264>`__ ``sized_range``\ and ``ranges::size``\ redundantly use ``disable_sized_range``\ Prague |Complete| 15.0 |ranges|
218 `3269 <https://wg21.link/LWG3269>`__ Parse manipulators do not specify the result of the extraction from stream Prague |chrono|
219 `3270 <https://wg21.link/LWG3270>`__ Parsing and formatting ``%j``\ with ``duration``\ s Prague |Partial| |chrono| |format|
220 `3280 <https://wg21.link/LWG3280>`__ View converting constructors can cause constraint recursion and are unneeded Prague |Complete| 15.0 |ranges|
221 `3281 <https://wg21.link/LWG3281>`__ Conversion from ``*pair-like*``\ types to ``subrange``\ is a silent semantic promotion Prague |Complete| 15.0 |ranges|
222 `3282 <https://wg21.link/LWG3282>`__ ``subrange``\ converting constructor should disallow derived to base conversions Prague |Complete| 15.0 |ranges|
236 `3307 <https://wg21.link/LWG3307>`__ ``std::allocator<void>().allocate(n)``\ Prague
237 `3310 <https://wg21.link/LWG3310>`__ Replace ``SIZE_MAX``\ with ``numeric_limits<size_t>::max()``\ Prague
238 `3313 <https://wg21.link/LWG3313>`__ ``join_view::iterator::operator--``\ is incorrectly constrained Prague |Complete| 14.0 |ranges|
239 `3314 <https://wg21.link/LWG3314>`__ Is stream insertion behavior locale dependent when ``Period::type``\ is ``micro``\ ? Prague |Complete| 16.0 |chrono|
240 `3315 <https://wg21.link/LWG3315>`__ Correct Allocator Default Behavior Prague
241 `3316 <https://wg21.link/LWG3316>`__ Correctly define epoch for ``utc_clock``\ / ``utc_timepoint``\ Prague |chrono|
242 `3317 <https://wg21.link/LWG3317>`__ Incorrect ``operator<<``\ for floating-point durations Prague |chrono|

View File

@@ -127,7 +127,7 @@
"`P1638R1 <https://wg21.link/P1638R1>`__","LWG","basic_istream_view::iterator should not be copyable","Cologne","|Complete|","16.0"
"`P1643R1 <https://wg21.link/P1643R1>`__","LWG","Add wait/notify to atomic_ref","Cologne","",""
"`P1644R0 <https://wg21.link/P1644R0>`__","LWG","Add wait/notify to atomic<shared_ptr>","Cologne","",""
"`P1650R0 <https://wg21.link/P1650R0>`__","LWG","Output std::chrono::days with 'd' suffix","Cologne","",""
"`P1650R0 <https://wg21.link/P1650R0>`__","LWG","Output std::chrono::days with 'd' suffix","Cologne","","|Complete|","16.0"
"`P1651R0 <https://wg21.link/P1651R0>`__","LWG","bind_front should not unwrap reference_wrapper","Cologne","|Complete|","13.0"
"`P1652R1 <https://wg21.link/P1652R1>`__","LWG","Printf corner cases in std::format","Cologne","|Complete|","14.0"
"`P1661R1 <https://wg21.link/P1661R1>`__","LWG","Remove dedicated precalculated hash lookup interface","Cologne","|Nothing To Do|",""
@@ -201,7 +201,7 @@
"`P2328R1 <https://wg21.link/P2328R1>`__","LWG",join_view should join all views of ranges,"June 2021","",""
"`P2367R0 <https://wg21.link/P2367R0>`__","LWG",Remove misuses of list-initialization from Clause 24,"June 2021","",""
"","","","","",""
"`P2372R3 <https://wg21.link/P2372R3>`__","LWG","Fixing locale handling in chrono formatters","October 2021","",""
"`P2372R3 <https://wg21.link/P2372R3>`__","LWG","Fixing locale handling in chrono formatters","October 2021","|In Progress|",""
"`P2415R2 <https://wg21.link/P2415R2>`__","LWG","What is a ``view``","October 2021","|Complete|","14.0"
"`P2418R2 <https://wg21.link/P2418R2>`__","LWG","Add support for ``std::generator``-like types to ``std::format``","October 2021","|Complete|","15.0"
"`P2432R1 <https://wg21.link/P2432R1>`__","LWG","Fix ``istream_view``","October 2021","|Complete|","16.0"
1 Paper # Group Paper Name Meeting Status First released version
127 `P1638R1 <https://wg21.link/P1638R1>`__ LWG basic_istream_view::iterator should not be copyable Cologne |Complete| 16.0
128 `P1643R1 <https://wg21.link/P1643R1>`__ LWG Add wait/notify to atomic_ref Cologne
129 `P1644R0 <https://wg21.link/P1644R0>`__ LWG Add wait/notify to atomic<shared_ptr> Cologne
130 `P1650R0 <https://wg21.link/P1650R0>`__ LWG Output std::chrono::days with 'd' suffix Cologne |Complete|
131 `P1651R0 <https://wg21.link/P1651R0>`__ LWG bind_front should not unwrap reference_wrapper Cologne |Complete| 13.0
132 `P1652R1 <https://wg21.link/P1652R1>`__ LWG Printf corner cases in std::format Cologne |Complete| 14.0
133 `P1661R1 <https://wg21.link/P1661R1>`__ LWG Remove dedicated precalculated hash lookup interface Cologne |Nothing To Do|
201 `P2328R1 <https://wg21.link/P2328R1>`__ LWG join_view should join all views of ranges June 2021
202 `P2367R0 <https://wg21.link/P2367R0>`__ LWG Remove misuses of list-initialization from Clause 24 June 2021
203
204 `P2372R3 <https://wg21.link/P2372R3>`__ LWG Fixing locale handling in chrono formatters October 2021 |In Progress|
205 `P2415R2 <https://wg21.link/P2415R2>`__ LWG What is a ``view`` October 2021 |Complete| 14.0
206 `P2418R2 <https://wg21.link/P2418R2>`__ LWG Add support for ``std::generator``-like types to ``std::format`` October 2021 |Complete| 15.0
207 `P2432R1 <https://wg21.link/P2432R1>`__ LWG Fix ``istream_view`` October 2021 |Complete| 16.0

View File

@@ -1,6 +1,6 @@
Section,Description,Dependencies,Assignee,Status,First released version
`P1361 <https://wg21.link/P1361>`__ `P2372 <https://wg21.link/P2372>`__,"Formatting chrono"
`[time.syn] <https://wg21.link/time.syn>`_,"Formatter ``chrono::duration<Rep, Period>``",,Mark de Wever,|In Progress|,
`[time.syn] <https://wg21.link/time.syn>`_,"Formatter ``chrono::duration<Rep, Period>``",,Mark de Wever,|Complete|, Clang 16
`[time.syn] <https://wg21.link/time.syn>`_,"Formatter ``chrono::sys_time<Duration>``",,Mark de Wever,|In Progress|,
`[time.syn] <https://wg21.link/time.syn>`_,"Formatter ``chrono::utc_time<Duration>``",A ``<chrono>`` implementation,Not assigned,,,
`[time.syn] <https://wg21.link/time.syn>`_,"Formatter ``chrono::tai_time<Duration>``",A ``<chrono>`` implementation,Not assigned,,,
1 Section Description Dependencies Assignee Status First released version
2 `P1361 <https://wg21.link/P1361>`__ `P2372 <https://wg21.link/P2372>`__ Formatting chrono
3 `[time.syn] <https://wg21.link/time.syn>`_ Formatter ``chrono::duration<Rep, Period>`` Mark de Wever |In Progress| |Complete| Clang 16
4 `[time.syn] <https://wg21.link/time.syn>`_ Formatter ``chrono::sys_time<Duration>`` Mark de Wever |In Progress|
5 `[time.syn] <https://wg21.link/time.syn>`_ Formatter ``chrono::utc_time<Duration>`` A ``<chrono>`` implementation Not assigned
6 `[time.syn] <https://wg21.link/time.syn>`_ Formatter ``chrono::tai_time<Duration>`` A ``<chrono>`` implementation Not assigned

View File

@@ -11,10 +11,12 @@
#define _LIBCPP___CHRONO_CONVERT_TO_TM_H
#include <__chrono/day.h>
#include <__chrono/duration.h>
#include <__chrono/month.h>
#include <__chrono/year.h>
#include <__concepts/same_as.h>
#include <__config>
#include <cstdint>
#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
# pragma GCC system_header
@@ -24,23 +26,34 @@ _LIBCPP_BEGIN_NAMESPACE_STD
#if _LIBCPP_STD_VER > 17
// Convert a chrono calendar time point to the given tm type,
// Convert a chrono (calendar) time point, or dururation to the given _Tm type,
// which must have the same properties as std::tm.
template <class _Tm, class _ChronoCalendarTimePoint>
_LIBCPP_HIDE_FROM_ABI _Tm __convert_to_tm(const _ChronoCalendarTimePoint& __value) {
template <class _Tm, class _ChronoT>
_LIBCPP_HIDE_FROM_ABI _Tm __convert_to_tm(const _ChronoT& __value) {
_Tm __result = {};
# ifdef __GLIBC__
__result.tm_zone = "UTC";
# endif
if constexpr (same_as<_ChronoCalendarTimePoint, chrono::day>)
if constexpr (chrono::__is_duration<_ChronoT>::value) {
// [time.format]/6
// ... However, if a flag refers to a "time of day" (e.g. %H, %I, %p,
// etc.), then a specialization of duration is interpreted as the time of
// day elapsed since midnight.
uint64_t __sec = chrono::duration_cast<chrono::seconds>(__value).count();
__sec %= 24 * 3600;
__result.tm_hour = __sec / 3600;
__sec %= 3600;
__result.tm_min = __sec / 60;
__result.tm_sec = __sec % 60;
} else if constexpr (same_as<_ChronoT, chrono::day>)
__result.tm_mday = static_cast<unsigned>(__value);
else if constexpr (same_as<_ChronoCalendarTimePoint, chrono::month>)
else if constexpr (same_as<_ChronoT, chrono::month>)
__result.tm_mon = static_cast<unsigned>(__value) - 1;
else if constexpr (same_as<_ChronoCalendarTimePoint, chrono::year>)
else if constexpr (same_as<_ChronoT, chrono::year>)
__result.tm_year = static_cast<int>(__value) - 1900;
else
static_assert(sizeof(_ChronoCalendarTimePoint) == 0, "Add the missing type specialization");
static_assert(sizeof(_ChronoT) == 0, "Add the missing type specialization");
return __result;
}

View File

@@ -12,10 +12,15 @@
#include <__chrono/convert_to_tm.h>
#include <__chrono/day.h>
#include <__chrono/duration.h>
#include <__chrono/hh_mm_ss.h>
#include <__chrono/month.h>
#include <__chrono/ostream.h>
#include <__chrono/parser_std_format_spec.h>
#include <__chrono/statically_widen.h>
#include <__chrono/time_point.h>
#include <__chrono/year.h>
#include <__concepts/arithmetic.h>
#include <__concepts/same_as.h>
#include <__config>
#include <__format/concepts.h>
@@ -60,6 +65,47 @@ namespace __formatter {
///
/// When no chrono-specs are provided it uses the stream formatter.
// For tiny ratios it's not possible to convert a duration to a hh_mm_ss. This
// fails compile-time due to the limited precision of the ratio (64-bit is too
// small). Therefore a duration uses its own conversion.
template <class _CharT, class _Tp>
requires(chrono::__is_duration<_Tp>::value)
_LIBCPP_HIDE_FROM_ABI void __format_sub_seconds(const _Tp& __value, basic_stringstream<_CharT>& __sstr) {
__sstr << std::use_facet<numpunct<_CharT>>(__sstr.getloc()).decimal_point();
auto __fraction = __value - chrono::duration_cast<chrono::seconds>(__value);
if constexpr (chrono::treat_as_floating_point_v<typename _Tp::rep>)
// When the floating-point value has digits itself they are ignored based
// on the wording in [tab:time.format.spec]
// If the precision of the input cannot be exactly represented with
// seconds, then the format is a decimal floating-point number with a
// fixed format and a precision matching that of the precision of the
// input (or to a microseconds precision if the conversion to
// floating-point decimal seconds cannot be made within 18 fractional
// digits).
//
// This matches the behaviour of MSVC STL, fmtlib interprets this
// differently and uses 3 decimals.
// https://godbolt.org/z/6dsbnW8ba
std::format_to(std::ostreambuf_iterator<_CharT>{__sstr},
_LIBCPP_STATICALLY_WIDEN(_CharT, "{:0{}.0f}"),
__fraction.count(),
chrono::hh_mm_ss<_Tp>::fractional_width);
else
std::format_to(std::ostreambuf_iterator<_CharT>{__sstr},
_LIBCPP_STATICALLY_WIDEN(_CharT, "{:0{}}"),
__fraction.count(),
chrono::hh_mm_ss<_Tp>::fractional_width);
}
template <class _Tp>
consteval bool __use_fraction() {
if constexpr (chrono::__is_duration<_Tp>::value)
return chrono::hh_mm_ss<_Tp>::fractional_width;
else
return false;
}
template <class _CharT>
_LIBCPP_HIDE_FROM_ABI void __format_year(int __year, basic_stringstream<_CharT>& __sstr) {
if (__year < 0) {
@@ -119,32 +165,73 @@ _LIBCPP_HIDE_FROM_ABI void __format_chrono_using_chrono_specs(
__facet.put({__sstr}, __sstr, _CharT(' '), std::addressof(__t), __s, __it + 1);
} break;
// Unlike time_put and strftime the formatting library requires %Y
//
// [tab:time.format.spec]
// The year as a decimal number. If the result is less than four digits
// it is left-padded with 0 to four digits.
//
// This means years in the range (-1000, 1000) need manual formatting.
// It's unclear whether %EY needs the same treatment. For example the
// Japanese EY contains the era name and year. This is zero-padded to 2
// digits in time_put (note that older glibc versions didn't do
// padding.) However most eras won't reach 100 years, let alone 1000.
// So padding to 4 digits seems unwanted for Japanese.
//
// The same applies to %Ex since that too depends on the era.
//
// %x the locale's date representation is currently doesn't handle the
// zero-padding too.
//
// The 4 digits can be implemented better at a later time. On POSIX
// systems the required information can be extracted by nl_langinfo
// https://man7.org/linux/man-pages/man3/nl_langinfo.3.html
//
// Note since year < -1000 is expected to be rare it uses the more
// expensive year routine.
//
// TODO FMT evaluate the comment above.
case _CharT('j'):
if constexpr (chrono::__is_duration<_Tp>::value)
// Converting a duration where the period has a small ratio to days
// may fail to compile. This due to loss of precision in the
// conversion. In order to avoid that issue convert to seconds as
// an intemediate step.
__sstr << chrono::duration_cast<chrono::days>(chrono::duration_cast<chrono::seconds>(__value)).count();
else
__facet.put({__sstr}, __sstr, _CharT(' '), std::addressof(__t), __s, __it + 1);
break;
case _CharT('q'):
if constexpr (chrono::__is_duration<_Tp>::value) {
__sstr << chrono::__units_suffix<_CharT, typename _Tp::period>();
break;
}
__builtin_unreachable();
case _CharT('Q'):
// TODO FMT Determine the proper ideas
// - Should it honour the precision?
// - Shoult it honour the locale setting for the separators?
// The wording for Q doesn't use the word locale and the effect of
// precision is unspecified.
//
// MSVC STL ignores precision but uses separator
// FMT honours precision and has a bug for separator
// https://godbolt.org/z/78b7sMxns
if constexpr (chrono::__is_duration<_Tp>::value) {
__sstr << format(_LIBCPP_STATICALLY_WIDEN(_CharT, "{}"), __value.count());
break;
}
__builtin_unreachable();
case _CharT('S'):
case _CharT('T'):
__facet.put({__sstr}, __sstr, _CharT(' '), std::addressof(__t), __s, __it + 1);
if constexpr (__use_fraction<_Tp>())
__formatter::__format_sub_seconds(__value, __sstr);
break;
// Unlike time_put and strftime the formatting library requires %Y
//
// [tab:time.format.spec]
// The year as a decimal number. If the result is less than four digits
// it is left-padded with 0 to four digits.
//
// This means years in the range (-1000, 1000) need manual formatting.
// It's unclear whether %EY needs the same treatment. For example the
// Japanese EY contains the era name and year. This is zero-padded to 2
// digits in time_put (note that older glibc versions didn't do
// padding.) However most eras won't reach 100 years, let alone 1000.
// So padding to 4 digits seems unwanted for Japanese.
//
// The same applies to %Ex since that too depends on the era.
//
// %x the locale's date representation is currently doesn't handle the
// zero-padding too.
//
// The 4 digits can be implemented better at a later time. On POSIX
// systems the required information can be extracted by nl_langinfo
// https://man7.org/linux/man-pages/man3/nl_langinfo.3.html
//
// Note since year < -1000 is expected to be rare it uses the more
// expensive year routine.
//
// TODO FMT evaluate the comment above.
# if defined(__GLIBC__) || defined(_AIX)
case _CharT('y'):
@@ -162,6 +249,18 @@ _LIBCPP_HIDE_FROM_ABI void __format_chrono_using_chrono_specs(
} break;
case _CharT('O'):
if constexpr (__use_fraction<_Tp>()) {
// Handle OS using the normal representation for the non-fractional
// part. There seems to be no locale information regarding how the
// fractional part should be formatted.
if (*(__it + 1) == 'S') {
++__it;
__facet.put({__sstr}, __sstr, _CharT(' '), std::addressof(__t), __s, __it + 1);
__formatter::__format_sub_seconds(__value, __sstr);
break;
}
}
[[fallthrough]];
case _CharT('E'):
++__it;
[[fallthrough]];
@@ -207,10 +306,19 @@ __format_chrono(const _Tp& __value,
if (__chrono_specs.empty())
__sstr << __value;
else {
if (__specs.__chrono_.__month_name_ && !__formatter::__month_name_ok(__value))
std::__throw_format_error("formatting a month name from an invalid month number");
if constexpr (chrono::__is_duration<_Tp>::value) {
if (__value < __value.zero())
__sstr << _CharT('-');
__formatter::__format_chrono_using_chrono_specs(chrono::abs(__value), __sstr, __chrono_specs);
// TODO FMT When keeping the precision it will truncate the string.
// Note that the behaviour what the precision does isn't specified.
__specs.__precision_ = -1;
} else {
if (__specs.__chrono_.__month_name_ && !__formatter::__month_name_ok(__value))
std::__throw_format_error("formatting a month name from an invalid month number");
__formatter::__format_chrono_using_chrono_specs(__value, __sstr, __chrono_specs);
__formatter::__format_chrono_using_chrono_specs(__value, __sstr, __chrono_specs);
}
}
// TODO FMT Use the stringstream's view after P0408R7 has been implemented.
@@ -238,6 +346,28 @@ public:
__format_spec::__parser_chrono<_CharT> __parser_;
};
template <class _Rep, class _Period, __fmt_char_type _CharT>
struct formatter<chrono::duration<_Rep, _Period>, _CharT> : public __formatter_chrono<_CharT> {
public:
using _Base = __formatter_chrono<_CharT>;
_LIBCPP_HIDE_FROM_ABI constexpr auto parse(basic_format_parse_context<_CharT>& __parse_ctx)
-> decltype(__parse_ctx.begin()) {
// [time.format]/1
// Giving a precision specification in the chrono-format-spec is valid only
// for std::chrono::duration types where the representation type Rep is a
// floating-point type. For all other Rep types, an exception of type
// format_error is thrown if the chrono-format-spec contains a precision
// specification.
//
// Note this doesn't refer to chrono::treat_as_floating_point_v<_Rep>.
if constexpr (std::floating_point<_Rep>)
return _Base::__parse(__parse_ctx, __format_spec::__fields_chrono_fractional, __format_spec::__flags::__duration);
else
return _Base::__parse(__parse_ctx, __format_spec::__fields_chrono, __format_spec::__flags::__duration);
}
};
template <__fmt_char_type _CharT>
struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT formatter<chrono::day, _CharT>
: public __formatter_chrono<_CharT> {

View File

@@ -11,12 +11,15 @@
#define _LIBCPP___CHRONO_OSTREAM_H
#include <__chrono/day.h>
#include <__chrono/duration.h>
#include <__chrono/month.h>
#include <__chrono/statically_widen.h>
#include <__chrono/year.h>
#include <__concepts/same_as.h>
#include <__config>
#include <__format/format_functions.h>
#include <ostream>
#include <ratio>
#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
# pragma GCC system_header
@@ -28,6 +31,71 @@ _LIBCPP_BEGIN_NAMESPACE_STD
namespace chrono {
// Depending on the type the return is a const _CharT* or a basic_string<_CharT>
template <class _CharT, class _Period>
_LIBCPP_HIDE_FROM_ABI auto __units_suffix() {
// TODO FMT LWG issue the suffixes are always char and not STATICALLY-WIDEN'ed.
if constexpr (same_as<typename _Period::type, atto>)
return _LIBCPP_STATICALLY_WIDEN(_CharT, "as");
else if constexpr (same_as<typename _Period::type, femto>)
return _LIBCPP_STATICALLY_WIDEN(_CharT, "fs");
else if constexpr (same_as<typename _Period::type, pico>)
return _LIBCPP_STATICALLY_WIDEN(_CharT, "ps");
else if constexpr (same_as<typename _Period::type, nano>)
return _LIBCPP_STATICALLY_WIDEN(_CharT, "ns");
else if constexpr (same_as<typename _Period::type, micro>)
# ifndef _LIBCPP_HAS_NO_UNICODE
return _LIBCPP_STATICALLY_WIDEN(_CharT, "\u00b5s");
# else
return _LIBCPP_STATICALLY_WIDEN(_CharT, "us");
# endif
else if constexpr (same_as<typename _Period::type, milli>)
return _LIBCPP_STATICALLY_WIDEN(_CharT, "ms");
else if constexpr (same_as<typename _Period::type, centi>)
return _LIBCPP_STATICALLY_WIDEN(_CharT, "cs");
else if constexpr (same_as<typename _Period::type, deci>)
return _LIBCPP_STATICALLY_WIDEN(_CharT, "ds");
else if constexpr (same_as<typename _Period::type, ratio<1>>)
return _LIBCPP_STATICALLY_WIDEN(_CharT, "s");
else if constexpr (same_as<typename _Period::type, deca>)
return _LIBCPP_STATICALLY_WIDEN(_CharT, "das");
else if constexpr (same_as<typename _Period::type, hecto>)
return _LIBCPP_STATICALLY_WIDEN(_CharT, "hs");
else if constexpr (same_as<typename _Period::type, kilo>)
return _LIBCPP_STATICALLY_WIDEN(_CharT, "ks");
else if constexpr (same_as<typename _Period::type, mega>)
return _LIBCPP_STATICALLY_WIDEN(_CharT, "Ms");
else if constexpr (same_as<typename _Period::type, giga>)
return _LIBCPP_STATICALLY_WIDEN(_CharT, "Gs");
else if constexpr (same_as<typename _Period::type, tera>)
return _LIBCPP_STATICALLY_WIDEN(_CharT, "Ts");
else if constexpr (same_as<typename _Period::type, peta>)
return _LIBCPP_STATICALLY_WIDEN(_CharT, "Ps");
else if constexpr (same_as<typename _Period::type, exa>)
return _LIBCPP_STATICALLY_WIDEN(_CharT, "Es");
else if constexpr (same_as<typename _Period::type, ratio<60>>)
return _LIBCPP_STATICALLY_WIDEN(_CharT, "min");
else if constexpr (same_as<typename _Period::type, ratio<3600>>)
return _LIBCPP_STATICALLY_WIDEN(_CharT, "h");
else if constexpr (same_as<typename _Period::type, ratio<86400>>)
return _LIBCPP_STATICALLY_WIDEN(_CharT, "d");
else if constexpr (_Period::den == 1)
return std::format(_LIBCPP_STATICALLY_WIDEN(_CharT, "[{}]s"), _Period::num);
else
return std::format(_LIBCPP_STATICALLY_WIDEN(_CharT, "[{}/{}]s"), _Period::num, _Period::den);
}
template <class _CharT, class _Traits, class _Rep, class _Period>
_LIBCPP_HIDE_FROM_ABI _LIBCPP_AVAILABILITY_FORMAT basic_ostream<_CharT, _Traits>&
operator<<(basic_ostream<_CharT, _Traits>& __os, const duration<_Rep, _Period>& __d) {
basic_ostringstream<_CharT, _Traits> __s;
__s.flags(__os.flags());
__s.imbue(__os.getloc());
__s.precision(__os.precision());
__s << __d.count() << chrono::__units_suffix<_CharT, _Period>();
return __os << __s.str();
}
template <class _CharT, class _Traits>
_LIBCPP_HIDE_FROM_ABI _LIBCPP_AVAILABILITY_FORMAT basic_ostream<_CharT, _Traits>&
operator<<(basic_ostream<_CharT, _Traits>& __os, const day& __d) {

View File

@@ -29,6 +29,8 @@ _LIBCPP_BEGIN_NAMESPACE_STD
namespace __format_spec {
// By not placing this constant in the formatter class it's not duplicated for char and wchar_t
inline constexpr __fields __fields_chrono_fractional{
.__precision_ = true, .__locale_specific_form_ = true, .__type_ = false};
inline constexpr __fields __fields_chrono{.__locale_specific_form_ = true, .__type_ = false};
/// Flags available or required in a chrono type.

View File

@@ -207,7 +207,11 @@ template <class ToDuration, class Rep, class Period>
template <class ToDuration, class Rep, class Period>
constexpr ToDuration round(const duration<Rep, Period>& d); // C++17
// duration I/O is elsewhere
// duration I/O
template<class charT, class traits, class Rep, class Period> // C++20
basic_ostream<charT, traits>&
operator<<(basic_ostream<charT, traits>& os,
const duration<Rep, Period>& d);
// time_point arithmetic (all constexpr in C++14)
template <class Clock, class Duration1, class Rep2, class Period2>
@@ -625,6 +629,8 @@ bool operator>=(const time_zone& x, const time_zone& y) noexcept;
} // chrono
namespace std {
template<class Rep, class Period, class charT>
struct formatter<chrono::duration<Rep, Period>, charT>; // C++20
template<class charT> struct formatter<chrono::day, charT>; // C++20
template<class charT> struct formatter<chrono::month, charT>; // C++20
template<class charT> struct formatter<chrono::year, charT>; // C++20

View File

@@ -0,0 +1,232 @@
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
// UNSUPPORTED: c++03, c++11, c++14, c++17
// UNSUPPORTED: no-localization
// UNSUPPORTED: libcpp-has-no-incomplete-format
// TODO FMT Investigate Windows issues.
// UNSUPPORTED: msvc, target={{.+}}-windows-gnu
// REQUIRES: locale.fr_FR.UTF-8
// REQUIRES: locale.ja_JP.UTF-8
// <chrono>
// template<class Rep, class Period = ratio<1>> class duration;
// template<class charT, class traits, class Rep, class Period>
// basic_ostream<charT, traits>&
// operator<<(basic_ostream<charT, traits>& os,
// const duration<Rep, Period>& d);
#include <chrono>
#include <cassert>
#include <concepts>
#include <sstream>
#include "make_string.h"
#include "platform_support.h" // locale name macros
#include "test_macros.h"
#define SV(S) MAKE_STRING_VIEW(CharT, S)
template <class CharT, class Rep, class Period>
static std::basic_string<CharT> stream_c_locale(std::chrono::duration<Rep, Period> duration) {
std::basic_stringstream<CharT> sstr;
sstr.precision(4);
sstr << std::fixed << duration;
return sstr.str();
}
template <class CharT, class Rep, class Period>
static std::basic_string<CharT> stream_fr_FR_locale(std::chrono::duration<Rep, Period> duration) {
std::basic_stringstream<CharT> sstr;
const std::locale locale(LOCALE_fr_FR_UTF_8);
sstr.imbue(locale);
sstr.precision(4);
sstr << std::fixed << duration;
return sstr.str();
}
template <class CharT, class Rep, class Period>
static std::basic_string<CharT> stream_ja_JP_locale(std::chrono::duration<Rep, Period> duration) {
std::basic_stringstream<CharT> sstr;
const std::locale locale(LOCALE_ja_JP_UTF_8);
sstr.imbue(locale);
sstr.precision(4);
sstr << std::fixed << duration;
return sstr.str();
}
template <class CharT>
static void test_values() {
using namespace std::literals::chrono_literals;
assert(stream_c_locale<CharT>(-1'000'000s) == SV("-1000000s"));
assert(stream_c_locale<CharT>(1'000'000s) == SV("1000000s"));
assert(stream_c_locale<CharT>(-1'000.123456s) == SV("-1000.1235s"));
assert(stream_c_locale<CharT>(1'000.123456s) == SV("1000.1235s"));
if constexpr (std::same_as<CharT, char>) {
#if defined(__APPLE__)
assert(stream_fr_FR_locale<CharT>(-1'000'000s) == SV("-1000000s"));
assert(stream_fr_FR_locale<CharT>(1'000'000s) == SV("1000000s"));
assert(stream_fr_FR_locale<CharT>(-1'000.123456s) == SV("-1000,1235s"));
assert(stream_fr_FR_locale<CharT>(1'000.123456s) == SV("1000,1235s"));
#else
assert(stream_fr_FR_locale<CharT>(-1'000'000s) == SV("-1 000 000s"));
assert(stream_fr_FR_locale<CharT>(1'000'000s) == SV("1 000 000s"));
assert(stream_fr_FR_locale<CharT>(-1'000.123456s) == SV("-1 000,1235s"));
assert(stream_fr_FR_locale<CharT>(1'000.123456s) == SV("1 000,1235s"));
#endif
} else {
#ifdef _WIN32
assert(stream_fr_FR_locale<CharT>(-1'000'000s) == SV("-1\u00A0000\u00A0000s"));
assert(stream_fr_FR_locale<CharT>(1'000'000s) == SV("1\u00A0000\u00A0000s"));
assert(stream_fr_FR_locale<CharT>(-1'000.123456s) == SV("-1\u00A0000,1235s"));
assert(stream_fr_FR_locale<CharT>(1'000.123456s) == SV("1\u00A0000,1235s"));
#elif defined(__APPLE__)
assert(stream_fr_FR_locale<CharT>(-1'000'000s) == SV("-1000000s"));
assert(stream_fr_FR_locale<CharT>(1'000'000s) == SV("1000000s"));
assert(stream_fr_FR_locale<CharT>(-1'000.123456s) == SV("-1000,1235s"));
assert(stream_fr_FR_locale<CharT>(1'000.123456s) == SV("1000,1235s"));
#else
assert(stream_fr_FR_locale<CharT>(-1'000'000s) == SV("-1\u202f000\u202f000s"));
assert(stream_fr_FR_locale<CharT>(1'000'000s) == SV("1\u202f000\u202f000s"));
assert(stream_fr_FR_locale<CharT>(-1'000.123456s) == SV("-1\u202f000,1235s"));
assert(stream_fr_FR_locale<CharT>(1'000.123456s) == SV("1\u202f000,1235s"));
#endif
}
assert(stream_ja_JP_locale<CharT>(-1'000'000s) == SV("-1,000,000s"));
assert(stream_ja_JP_locale<CharT>(1'000'000s) == SV("1,000,000s"));
assert(stream_ja_JP_locale<CharT>(-1'000.123456s) == SV("-1,000.1235s"));
assert(stream_ja_JP_locale<CharT>(1'000.123456s) == SV("1,000.1235s"));
}
template <class CharT>
static void test_units() {
using namespace std::literals::chrono_literals;
// C locale
assert(stream_c_locale<CharT>(std::chrono::duration<int, std::atto>(0)) == SV("0as"));
assert(stream_c_locale<CharT>(std::chrono::duration<int, std::femto>(0)) == SV("0fs"));
assert(stream_c_locale<CharT>(std::chrono::duration<int, std::pico>(0)) == SV("0ps"));
assert(stream_c_locale<CharT>(0ns) == SV("0ns"));
#ifndef TEST_HAS_NO_UNICODE
assert(stream_c_locale<CharT>(0us) == SV("0\u00b5s"));
#else
assert(stream_c_locale<CharT>(0us) == SV("0us"));
#endif
assert(stream_c_locale<CharT>(0ms) == SV("0ms"));
assert(stream_c_locale<CharT>(std::chrono::duration<int, std::centi>(0)) == SV("0cs"));
assert(stream_c_locale<CharT>(std::chrono::duration<int, std::deci>(0)) == SV("0ds"));
assert(stream_c_locale<CharT>(0s) == SV("0s"));
assert(stream_c_locale<CharT>(std::chrono::duration<int, std::deca>(0)) == SV("0das"));
assert(stream_c_locale<CharT>(std::chrono::duration<int, std::hecto>(0)) == SV("0hs"));
assert(stream_c_locale<CharT>(std::chrono::duration<int, std::kilo>(0)) == SV("0ks"));
assert(stream_c_locale<CharT>(std::chrono::duration<int, std::mega>(0)) == SV("0Ms"));
assert(stream_c_locale<CharT>(std::chrono::duration<int, std::giga>(0)) == SV("0Gs"));
assert(stream_c_locale<CharT>(std::chrono::duration<int, std::tera>(0)) == SV("0Ts"));
assert(stream_c_locale<CharT>(std::chrono::duration<int, std::peta>(0)) == SV("0Ps"));
assert(stream_c_locale<CharT>(std::chrono::duration<int, std::exa>(0)) == SV("0Es"));
assert(stream_c_locale<CharT>(0min) == SV("0min"));
assert(stream_c_locale<CharT>(0h) == SV("0h"));
assert(stream_c_locale<CharT>(std::chrono::duration<int, std::ratio<86400>>(0)) == SV("0d"));
assert(stream_c_locale<CharT>(std::chrono::duration<int, std::ratio<42>>(0)) == SV("0[42]s"));
assert(stream_c_locale<CharT>(std::chrono::duration<int, std::ratio<33, 3>>(0)) == SV("0[11]s"));
assert(stream_c_locale<CharT>(std::chrono::duration<int, std::ratio<11, 9>>(0)) == SV("0[11/9]s"));
// fr_FR locale
assert(stream_fr_FR_locale<CharT>(std::chrono::duration<int, std::atto>(0)) == SV("0as"));
assert(stream_fr_FR_locale<CharT>(std::chrono::duration<int, std::femto>(0)) == SV("0fs"));
assert(stream_fr_FR_locale<CharT>(std::chrono::duration<int, std::pico>(0)) == SV("0ps"));
assert(stream_fr_FR_locale<CharT>(0ns) == SV("0ns"));
#ifndef TEST_HAS_NO_UNICODE
assert(stream_fr_FR_locale<CharT>(0us) == SV("0\u00b5s"));
#else
assert(stream_fr_FR_locale<CharT>(0us) == SV("0us"));
#endif
assert(stream_fr_FR_locale<CharT>(0ms) == SV("0ms"));
assert(stream_fr_FR_locale<CharT>(std::chrono::duration<int, std::centi>(0)) == SV("0cs"));
assert(stream_fr_FR_locale<CharT>(std::chrono::duration<int, std::deci>(0)) == SV("0ds"));
assert(stream_fr_FR_locale<CharT>(0s) == SV("0s"));
assert(stream_fr_FR_locale<CharT>(std::chrono::duration<int, std::deca>(0)) == SV("0das"));
assert(stream_fr_FR_locale<CharT>(std::chrono::duration<int, std::hecto>(0)) == SV("0hs"));
assert(stream_fr_FR_locale<CharT>(std::chrono::duration<int, std::kilo>(0)) == SV("0ks"));
assert(stream_fr_FR_locale<CharT>(std::chrono::duration<int, std::mega>(0)) == SV("0Ms"));
assert(stream_fr_FR_locale<CharT>(std::chrono::duration<int, std::giga>(0)) == SV("0Gs"));
assert(stream_fr_FR_locale<CharT>(std::chrono::duration<int, std::tera>(0)) == SV("0Ts"));
assert(stream_fr_FR_locale<CharT>(std::chrono::duration<int, std::peta>(0)) == SV("0Ps"));
assert(stream_fr_FR_locale<CharT>(std::chrono::duration<int, std::exa>(0)) == SV("0Es"));
assert(stream_fr_FR_locale<CharT>(0min) == SV("0min"));
assert(stream_fr_FR_locale<CharT>(0h) == SV("0h"));
assert(stream_fr_FR_locale<CharT>(std::chrono::duration<int, std::ratio<86400>>(0)) == SV("0d"));
assert(stream_fr_FR_locale<CharT>(std::chrono::duration<int, std::ratio<42>>(0)) == SV("0[42]s"));
assert(stream_fr_FR_locale<CharT>(std::chrono::duration<int, std::ratio<33, 3>>(0)) == SV("0[11]s"));
assert(stream_fr_FR_locale<CharT>(std::chrono::duration<int, std::ratio<11, 9>>(0)) == SV("0[11/9]s"));
// ja_JP locale
assert(stream_ja_JP_locale<CharT>(std::chrono::duration<int, std::atto>(0)) == SV("0as"));
assert(stream_ja_JP_locale<CharT>(std::chrono::duration<int, std::femto>(0)) == SV("0fs"));
assert(stream_ja_JP_locale<CharT>(std::chrono::duration<int, std::pico>(0)) == SV("0ps"));
assert(stream_ja_JP_locale<CharT>(0ns) == SV("0ns"));
#ifndef TEST_HAS_NO_UNICODE
assert(stream_ja_JP_locale<CharT>(0us) == SV("0\u00b5s"));
#else
assert(stream_ja_JP_locale<CharT>(0us) == SV("0us"));
#endif
assert(stream_ja_JP_locale<CharT>(0ms) == SV("0ms"));
assert(stream_ja_JP_locale<CharT>(std::chrono::duration<int, std::centi>(0)) == SV("0cs"));
assert(stream_ja_JP_locale<CharT>(std::chrono::duration<int, std::deci>(0)) == SV("0ds"));
assert(stream_ja_JP_locale<CharT>(0s) == SV("0s"));
assert(stream_ja_JP_locale<CharT>(std::chrono::duration<int, std::deca>(0)) == SV("0das"));
assert(stream_ja_JP_locale<CharT>(std::chrono::duration<int, std::hecto>(0)) == SV("0hs"));
assert(stream_ja_JP_locale<CharT>(std::chrono::duration<int, std::kilo>(0)) == SV("0ks"));
assert(stream_ja_JP_locale<CharT>(std::chrono::duration<int, std::mega>(0)) == SV("0Ms"));
assert(stream_ja_JP_locale<CharT>(std::chrono::duration<int, std::giga>(0)) == SV("0Gs"));
assert(stream_ja_JP_locale<CharT>(std::chrono::duration<int, std::tera>(0)) == SV("0Ts"));
assert(stream_ja_JP_locale<CharT>(std::chrono::duration<int, std::peta>(0)) == SV("0Ps"));
assert(stream_ja_JP_locale<CharT>(std::chrono::duration<int, std::exa>(0)) == SV("0Es"));
assert(stream_ja_JP_locale<CharT>(0min) == SV("0min"));
assert(stream_ja_JP_locale<CharT>(0h) == SV("0h"));
assert(stream_ja_JP_locale<CharT>(std::chrono::duration<int, std::ratio<86400>>(0)) == SV("0d"));
assert(stream_ja_JP_locale<CharT>(std::chrono::duration<int, std::ratio<42>>(0)) == SV("0[42]s"));
assert(stream_ja_JP_locale<CharT>(std::chrono::duration<int, std::ratio<33, 3>>(0)) == SV("0[11]s"));
assert(stream_ja_JP_locale<CharT>(std::chrono::duration<int, std::ratio<11, 9>>(0)) == SV("0[11/9]s"));
}
template <class CharT>
static void test() {
test_values<CharT>();
test_units<CharT>();
}
int main(int, char**) {
test<char>();
#ifndef TEST_HAS_NO_WIDE_CHARACTERS
test<wchar_t>();
#endif
return 0;
}

File diff suppressed because it is too large Load Diff

View File

@@ -130,7 +130,7 @@ void test_P1361() {
// In libc++ std:::ostringstream requires localization support.
#ifndef TEST_HAS_NO_LOCALIZATION
assert_is_not_formattable<std::chrono::microseconds, CharT>();
assert_is_formattable<std::chrono::microseconds, CharT>();
assert_is_not_formattable<std::chrono::sys_time<std::chrono::microseconds>, CharT>();
//assert_is_formattable<std::chrono::utc_time<std::chrono::microseconds>, CharT>();