Skip to content

Commit

Permalink
[libc++][chrono] Add hh_mm_ss formatter.
Browse files Browse the repository at this point in the history
Partially implements:
- P1361 Integration of chrono with text formatting
- P2372 Fixing locale handling in chrono formatters
- P1466 Miscellaneous minor fixes for chrono

Depends on D137022

Reviewed By: ldionne, #libc

Differential Revision: https://reviews.llvm.org/D139771
  • Loading branch information
mordante committed Feb 14, 2023
1 parent 5205c71 commit 7f5d130
Show file tree
Hide file tree
Showing 16 changed files with 924 additions and 9 deletions.
2 changes: 1 addition & 1 deletion libcxx/docs/Status/FormatPaper.csv
Expand Up @@ -23,7 +23,7 @@ Section,Description,Dependencies,Assignee,Status,First released version
`[time.syn] <https://wg21.link/time.syn>`_,"Formatter ``chrono::year_month_day_last``",,Mark de Wever,|Complete|, Clang 16
`[time.syn] <https://wg21.link/time.syn>`_,"Formatter ``chrono::year_month_weekday``",,Mark de Wever,|Complete|, Clang 16
`[time.syn] <https://wg21.link/time.syn>`_,"Formatter ``chrono::year_month_weekday_last``",,Mark de Wever,|Complete|, Clang 16
`[time.syn] <https://wg21.link/time.syn>`_,"Formatter ``chrono::hh_mm_ss<duration<Rep, Period>>``",,Mark de Wever,|In Progress|,
`[time.syn] <https://wg21.link/time.syn>`_,"Formatter ``chrono::hh_mm_ss<duration<Rep, Period>>``",,Mark de Wever,|Complete|, Clang 17
`[time.syn] <https://wg21.link/time.syn>`_,"Formatter ``chrono::sys_info``",A ``<chrono>`` implementation,Mark de Wever,,
`[time.syn] <https://wg21.link/time.syn>`_,"Formatter ``chrono::local_info``",A ``<chrono>`` implementation,Mark de Wever,,
`[time.syn] <https://wg21.link/time.syn>`_,"Formatter ``chrono::zoned_time<Duration, TimeZonePtr>``",A ``<chrono>`` implementation,Mark de Wever,,
Expand Down
1 change: 1 addition & 0 deletions libcxx/include/CMakeLists.txt
Expand Up @@ -216,6 +216,7 @@ set(files
__charconv/to_chars_base_10.h
__charconv/to_chars_result.h
__chrono/calendar.h
__chrono/concepts.h
__chrono/convert_to_timespec.h
__chrono/convert_to_tm.h
__chrono/day.h
Expand Down
32 changes: 32 additions & 0 deletions libcxx/include/__chrono/concepts.h
@@ -0,0 +1,32 @@
// -*- C++ -*-
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//

#ifndef _LIBCPP___CHRONO_CONCEPTS_H
#define _LIBCPP___CHRONO_CONCEPTS_H

#include <__chrono/hh_mm_ss.h>
#include <__config>
#include <__type_traits/is_specialization.h>

#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
# pragma GCC system_header
#endif

_LIBCPP_BEGIN_NAMESPACE_STD

#if _LIBCPP_STD_VER > 17

template <class _Tp>
concept __is_hh_mm_ss = __is_specialization_v<_Tp, chrono::hh_mm_ss>;

#endif // _LIBCPP_STD_VER > 17

_LIBCPP_END_NAMESPACE_STD

#endif // _LIBCPP___CHRONO_CONCEPTS_H
18 changes: 18 additions & 0 deletions libcxx/include/__chrono/convert_to_tm.h
Expand Up @@ -10,6 +10,7 @@
#ifndef _LIBCPP___CHRONO_CONVERT_TO_TM_H
#define _LIBCPP___CHRONO_CONVERT_TO_TM_H

#include <__chrono/concepts.h>
#include <__chrono/day.h>
#include <__chrono/duration.h>
#include <__chrono/hh_mm_ss.h>
Expand All @@ -26,14 +27,19 @@
#include <__chrono/year_month_weekday.h>
#include <__concepts/same_as.h>
#include <__config>
#include <__format/format_error.h>
#include <__memory/addressof.h>
#include <cstdint>
#include <ctime>
#include <limits>

#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
# pragma GCC system_header
#endif

_LIBCPP_PUSH_MACROS
#include <__undef_macros>

_LIBCPP_BEGIN_NAMESPACE_STD

#if _LIBCPP_STD_VER > 17
Expand Down Expand Up @@ -114,6 +120,16 @@ _LIBCPP_HIDE_FROM_ABI _Tm __convert_to_tm(const _ChronoT& __value) {
} else if constexpr (same_as<_ChronoT, chrono::year_month_weekday> ||
same_as<_ChronoT, chrono::year_month_weekday_last>) {
return std::__convert_to_tm<_Tm>(chrono::year_month_day{static_cast<chrono::sys_days>(__value)}, __value.weekday());
} else if constexpr (__is_hh_mm_ss<_ChronoT>) {
__result.tm_sec = __value.seconds().count();
__result.tm_min = __value.minutes().count();
// In libc++ hours is stored as a long. The type in std::tm is an int. So
// the overflow can only occur when hour uses more bits than an int
// provides.
if constexpr (sizeof(std::chrono::hours::rep) > sizeof(__result.tm_hour))
if (__value.hours().count() > std::numeric_limits<decltype(__result.tm_hour)>::max())
std::__throw_format_error("Formatting hh_mm_ss, encountered an hour overflow");
__result.tm_hour = __value.hours().count();
} else
static_assert(sizeof(_ChronoT) == 0, "Add the missing type specialization");

Expand All @@ -124,4 +140,6 @@ _LIBCPP_HIDE_FROM_ABI _Tm __convert_to_tm(const _ChronoT& __value) {

_LIBCPP_END_NAMESPACE_STD

_LIBCPP_POP_MACROS

#endif // _LIBCPP___CHRONO_CONVERT_TO_TM_H
74 changes: 68 additions & 6 deletions libcxx/include/__chrono/formatter.h
Expand Up @@ -11,6 +11,7 @@
#define _LIBCPP___CHRONO_FORMATTER_H

#include <__chrono/calendar.h>
#include <__chrono/concepts.h>
#include <__chrono/convert_to_tm.h>
#include <__chrono/day.h>
#include <__chrono/duration.h>
Expand Down Expand Up @@ -75,13 +76,15 @@ namespace __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) {
template <class _CharT, class _Rep, class _Period>
_LIBCPP_HIDE_FROM_ABI void
__format_sub_seconds(const chrono::duration<_Rep, _Period>& __value, basic_stringstream<_CharT>& __sstr) {
__sstr << std::use_facet<numpunct<_CharT>>(__sstr.getloc()).decimal_point();

using __duration = chrono::duration<_Rep, _Period>;

auto __fraction = __value - chrono::duration_cast<chrono::seconds>(__value);
if constexpr (chrono::treat_as_floating_point_v<typename _Tp::rep>)
if constexpr (chrono::treat_as_floating_point_v<_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
Expand All @@ -97,18 +100,36 @@ _LIBCPP_HIDE_FROM_ABI void __format_sub_seconds(const _Tp& __value, basic_string
std::format_to(std::ostreambuf_iterator<_CharT>{__sstr},
_LIBCPP_STATICALLY_WIDEN(_CharT, "{:0{}.0f}"),
__fraction.count(),
chrono::hh_mm_ss<_Tp>::fractional_width);
chrono::hh_mm_ss<__duration>::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);
chrono::hh_mm_ss<__duration>::fractional_width);
}

template <class _CharT, class _Duration>
_LIBCPP_HIDE_FROM_ABI void
__format_sub_seconds(const chrono::hh_mm_ss<_Duration>& __value, basic_stringstream<_CharT>& __sstr) {
__sstr << std::use_facet<numpunct<_CharT>>(__sstr.getloc()).decimal_point();
if constexpr (chrono::treat_as_floating_point_v<typename _Duration::rep>)
std::format_to(std::ostreambuf_iterator<_CharT>{__sstr},
_LIBCPP_STATICALLY_WIDEN(_CharT, "{:0{}.0f}"),
__value.subseconds().count(),
__value.fractional_width);
else
std::format_to(std::ostreambuf_iterator<_CharT>{__sstr},
_LIBCPP_STATICALLY_WIDEN(_CharT, "{:0{}}"),
__value.subseconds().count(),
__value.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 if constexpr (__is_hh_mm_ss<_Tp>)
return _Tp::fractional_width;
else
return false;
}
Expand Down Expand Up @@ -322,6 +343,8 @@ _LIBCPP_HIDE_FROM_ABI constexpr bool __weekday_ok(const _Tp& __value) {
return __value.weekday().ok();
else if constexpr (same_as<_Tp, chrono::year_month_weekday_last>)
return __value.weekday().ok();
else if constexpr (__is_hh_mm_ss<_Tp>)
return true;
else
static_assert(sizeof(_Tp) == 0, "Add the missing type specialization");
}
Expand Down Expand Up @@ -358,6 +381,8 @@ _LIBCPP_HIDE_FROM_ABI constexpr bool __weekday_name_ok(const _Tp& __value) {
return __value.weekday().ok();
else if constexpr (same_as<_Tp, chrono::year_month_weekday_last>)
return __value.weekday().ok();
else if constexpr (__is_hh_mm_ss<_Tp>)
return true;
else
static_assert(sizeof(_Tp) == 0, "Add the missing type specialization");
}
Expand Down Expand Up @@ -394,6 +419,8 @@ _LIBCPP_HIDE_FROM_ABI constexpr bool __date_ok(const _Tp& __value) {
return __value.ok();
else if constexpr (same_as<_Tp, chrono::year_month_weekday_last>)
return __value.ok();
else if constexpr (__is_hh_mm_ss<_Tp>)
return true;
else
static_assert(sizeof(_Tp) == 0, "Add the missing type specialization");
}
Expand Down Expand Up @@ -430,6 +457,8 @@ _LIBCPP_HIDE_FROM_ABI constexpr bool __month_name_ok(const _Tp& __value) {
return __value.month().ok();
else if constexpr (same_as<_Tp, chrono::year_month_weekday_last>)
return __value.month().ok();
else if constexpr (__is_hh_mm_ss<_Tp>)
return true;
else
static_assert(sizeof(_Tp) == 0, "Add the missing type specialization");
}
Expand Down Expand Up @@ -478,6 +507,29 @@ __format_chrono(const _Tp& __value,
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 (__is_hh_mm_ss<_Tp>) {
// Note this is a pedantic intepretation of the Standard. A hh_mm_ss
// is no longer a time_of_day and can store an arbitrary number of
// hours. A number of hours in a 12 or 24 hour clock can't represent
// 24 hours or more. The functions std::chrono::make12 and
// std::chrono::make24 reaffirm this view point.
//
// Interestingly this will be the only output stream function that
// throws.
//
// TODO FMT The wording probably needs to be adapted to
// - The displayed hours is hh_mm_ss.hours() % 24
// - It should probably allow %j in the same fashion as duration.
// - The stream formatter should change its output when hours >= 24
// - Write it as not valid,
// - or write the number of days.
if (__specs.__chrono_.__hour_ && __value.hours().count() > 23)
std::__throw_format_error("formatting a hour needs a valid value");

if (__value.is_negative())
__sstr << _CharT('-');
}

__formatter::__format_chrono_using_chrono_specs(__value, __sstr, __chrono_specs);
}
}
Expand Down Expand Up @@ -709,6 +761,16 @@ struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT formatter<chrono::year_m
}
};

template <class _Duration, __fmt_char_type _CharT>
struct formatter<chrono::hh_mm_ss<_Duration>, _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()) {
return _Base::__parse(__parse_ctx, __format_spec::__fields_chrono, __format_spec::__flags::__time);
}
};
#endif // if _LIBCPP_STD_VER > 17 && !defined(_LIBCPP_HAS_NO_INCOMPLETE_FORMAT)

_LIBCPP_END_NAMESPACE_STD
Expand Down
1 change: 1 addition & 0 deletions libcxx/include/__chrono/hh_mm_ss.h
Expand Up @@ -85,6 +85,7 @@ class hh_mm_ss
chrono::seconds __s_;
precision __f_;
};
_LIBCPP_CTAD_SUPPORTED_FOR_TYPE(hh_mm_ss);

_LIBCPP_HIDE_FROM_ABI constexpr bool is_am(const hours& __h) noexcept { return __h >= hours( 0) && __h < hours(12); }
_LIBCPP_HIDE_FROM_ABI constexpr bool is_pm(const hours& __h) noexcept { return __h >= hours(12) && __h < hours(24); }
Expand Down
7 changes: 7 additions & 0 deletions libcxx/include/__chrono/ostream.h
Expand Up @@ -12,6 +12,7 @@

#include <__chrono/day.h>
#include <__chrono/duration.h>
#include <__chrono/hh_mm_ss.h>
#include <__chrono/month.h>
#include <__chrono/month_weekday.h>
#include <__chrono/monthday.h>
Expand Down Expand Up @@ -229,6 +230,12 @@ operator<<(basic_ostream<_CharT, _Traits>& __os, const year_month_weekday_last&
__ymwdl.weekday_last());
}

template <class _CharT, class _Traits, class _Duration>
_LIBCPP_HIDE_FROM_ABI _LIBCPP_AVAILABILITY_FORMAT basic_ostream<_CharT, _Traits>&
operator<<(basic_ostream<_CharT, _Traits>& __os, const hh_mm_ss<_Duration> __hms) {
return __os << std::format(__os.getloc(), _LIBCPP_STATICALLY_WIDEN(_CharT, "{:L%T}"), __hms);
}

} // namespace chrono

#endif //if _LIBCPP_STD_VER > 17 && !defined(_LIBCPP_HAS_NO_INCOMPLETE_FORMAT)
Expand Down
4 changes: 4 additions & 0 deletions libcxx/include/__chrono/parser_std_format_spec.h
Expand Up @@ -214,13 +214,15 @@ class _LIBCPP_TEMPLATE_VIS __parser_chrono {
case _CharT('p'): // TODO FMT does the formater require an hour or a time?
case _CharT('H'):
case _CharT('I'):
__parser_.__hour_ = true;
__validate_hour(__flags);
break;

case _CharT('r'):
case _CharT('R'):
case _CharT('T'):
case _CharT('X'):
__parser_.__hour_ = true;
__format_spec::__validate_time(__flags);
break;

Expand Down Expand Up @@ -313,6 +315,7 @@ class _LIBCPP_TEMPLATE_VIS __parser_chrono {

switch (*__begin) {
case _CharT('X'):
__parser_.__hour_ = true;
__format_spec::__validate_time(__flags);
break;

Expand Down Expand Up @@ -361,6 +364,7 @@ class _LIBCPP_TEMPLATE_VIS __parser_chrono {

case _CharT('I'):
case _CharT('H'):
__parser_.__hour_ = true;
__format_spec::__validate_hour(__flags);
break;

Expand Down
6 changes: 5 additions & 1 deletion libcxx/include/__format/parser_std_format_spec.h
Expand Up @@ -199,6 +199,7 @@ struct __std {
struct __chrono {
__alignment __alignment_ : 3;
bool __locale_specific_form_ : 1;
bool __hour_ : 1;
bool __weekday_name_ : 1;
bool __weekday_ : 1;
bool __day_of_year_ : 1;
Expand Down Expand Up @@ -329,6 +330,7 @@ class _LIBCPP_TEMPLATE_VIS __parser {
.__chrono_ =
__chrono{.__alignment_ = __alignment_,
.__locale_specific_form_ = __locale_specific_form_,
.__hour_ = __hour_,
.__weekday_name_ = __weekday_name_,
.__weekday_ = __weekday_,
.__day_of_year_ = __day_of_year_,
Expand All @@ -348,6 +350,8 @@ class _LIBCPP_TEMPLATE_VIS __parser {

// These flags are only used for formatting chrono. Since the struct has
// padding space left it's added to this structure.
bool __hour_ : 1 {false};

bool __weekday_name_ : 1 {false};
bool __weekday_ : 1 {false};

Expand All @@ -356,7 +360,7 @@ class _LIBCPP_TEMPLATE_VIS __parser {

bool __month_name_ : 1 {false};

uint8_t __reserved_1_ : 3 {0};
uint8_t __reserved_1_ : 2 {0};
uint8_t __reserved_2_ : 6 {0};
// These two flags are only used internally and not part of the
// __parsed_specifications. Therefore put them at the end.
Expand Down
6 changes: 6 additions & 0 deletions libcxx/include/chrono
Expand Up @@ -656,6 +656,10 @@ public:
constexpr precision to_duration() const noexcept;
};
template<class charT, class traits, class Duration>
basic_ostream<charT, traits>&
operator<<(basic_ostream<charT, traits>& os, const hh_mm_ss<Duration>& hms); // C++20
// 26.10, 12/24 hour functions
constexpr bool is_am(hours const& h) noexcept;
constexpr bool is_pm(hours const& h) noexcept;
Expand Down Expand Up @@ -691,6 +695,8 @@ namespace std {
template<class charT> struct formatter<chrono::year_month_day_last, charT>; // C++20
template<class charT> struct formatter<chrono::year_month_weekday, charT>; // C++20
template<class charT> struct formatter<chrono::year_month_weekday_last, charT>; // C++20
template<class Rep, class Period, class charT>
struct formatter<chrono::hh_mm_ss<duration<Rep, Period>>, charT>; // C++20
} // namespace std
namespace chrono {
Expand Down
1 change: 1 addition & 0 deletions libcxx/include/module.modulemap.in
Expand Up @@ -674,6 +674,7 @@ module std [system] {

module __chrono {
module calendar { private header "__chrono/calendar.h" }
module concepts { private header "__chrono/concepts.h" }
module convert_to_timespec { private header "__chrono/convert_to_timespec.h" }
module convert_to_tm { private header "__chrono/convert_to_tm.h" }
module day { private header "__chrono/day.h" }
Expand Down
1 change: 1 addition & 0 deletions libcxx/test/libcxx/private_headers.verify.cpp
Expand Up @@ -250,6 +250,7 @@ END-SCRIPT
#include <__charconv/to_chars_base_10.h> // expected-error@*:* {{use of private header from outside its module: '__charconv/to_chars_base_10.h'}}
#include <__charconv/to_chars_result.h> // expected-error@*:* {{use of private header from outside its module: '__charconv/to_chars_result.h'}}
#include <__chrono/calendar.h> // expected-error@*:* {{use of private header from outside its module: '__chrono/calendar.h'}}
#include <__chrono/concepts.h> // expected-error@*:* {{use of private header from outside its module: '__chrono/concepts.h'}}
#include <__chrono/convert_to_timespec.h> // expected-error@*:* {{use of private header from outside its module: '__chrono/convert_to_timespec.h'}}
#include <__chrono/convert_to_tm.h> // expected-error@*:* {{use of private header from outside its module: '__chrono/convert_to_tm.h'}}
#include <__chrono/day.h> // expected-error@*:* {{use of private header from outside its module: '__chrono/day.h'}}
Expand Down

0 comments on commit 7f5d130

Please sign in to comment.