Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix handling of locale in chrono formatters #1892

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 42 additions & 27 deletions stl/inc/chrono
Original file line number Diff line number Diff line change
Expand Up @@ -5265,24 +5265,25 @@ struct _Chrono_spec {
};

template <class _CharT>
struct _Chrono_format_specs {
struct _Chrono_format_specs2 {
int _Width = 0;
int _Precision = -1;
int _Dynamic_width_index = -1;
int _Dynamic_precision_index = -1;
_Fmt_align _Alignment = _Fmt_align::_None;
uint8_t _Fill_length = 1;
bool _Localized = false;
// At most one codepoint (so one char32_t or four utf-8 char8_t)
_CharT _Fill[4 / sizeof(_CharT)] = {_CharT{' '}};
// recursive definition in grammar, so could have any number of these
vector<_Chrono_spec<_CharT>> _Chrono_specs_list;
};

// Model of _Chrono_parse_spec_callbacks that fills a _Chrono_format_specs with the parsed data
// Model of _Chrono_parse_spec_callbacks that fills a _Chrono_format_specs2 with the parsed data
template <class _CharT, class _ParseContext>
class _Chrono_specs_setter {
public:
constexpr explicit _Chrono_specs_setter(_Chrono_format_specs<_CharT>& _Specs_, _ParseContext& _Parse_ctx_)
constexpr explicit _Chrono_specs_setter(_Chrono_format_specs2<_CharT>& _Specs_, _ParseContext& _Parse_ctx_)
: _Specs(_Specs_), _Parse_ctx(_Parse_ctx_) {}

// same as _Specs_setter
Expand Down Expand Up @@ -5327,6 +5328,10 @@ public:
_Specs._Dynamic_precision_index = _Verify_dynamic_arg_index_in_range(_Parse_ctx.next_arg_id());
}

constexpr void _On_localized() {
_Specs._Localized = true;
}

constexpr void _On_conversion_spec(char _Modifier, _CharT _Type) {
// NOTE: same performance note from _Basic_format_specs also applies here
if (_Modifier != '\0' && _Modifier != 'E' && _Modifier != 'O') {
Expand All @@ -5347,7 +5352,7 @@ public:
}

private:
_Chrono_format_specs<_CharT>& _Specs;
_Chrono_format_specs2<_CharT>& _Specs;
_ParseContext& _Parse_ctx;

_NODISCARD static constexpr int _Verify_dynamic_arg_index_in_range(const size_t _Idx) {
Expand Down Expand Up @@ -5408,6 +5413,14 @@ _NODISCARD constexpr const _CharT* _Parse_chrono_format_specs(
}
}

if (*_Begin == 'L') {
_Callbacks._On_localized();
++_Begin;
if (_Begin == _End) {
return _Begin;
}
}

if (*_Begin != '}' && *_Begin != '%') {
_THROW(format_error("Invalid format string - chrono-specs must begin with conversion-spec"));
}
Expand Down Expand Up @@ -5615,7 +5628,7 @@ namespace chrono {

template <class _CharT, class _Traits>
basic_ostream<_CharT, _Traits>& operator<<(basic_ostream<_CharT, _Traits>& _Os, const month& _Val) {
return _Os << (_Val.ok() ? _STD format(_Os.getloc(), _STATICALLY_WIDEN(_CharT, "{:%b}"), _Val)
return _Os << (_Val.ok() ? _STD format(_Os.getloc(), _STATICALLY_WIDEN(_CharT, "{:L%b}"), _Val)
: _STD format(_Os.getloc(), _STATICALLY_WIDEN(_CharT, "{} is not a valid month"),
static_cast<unsigned int>(_Val)));
}
Expand All @@ -5628,7 +5641,7 @@ namespace chrono {

template <class _CharT, class _Traits>
basic_ostream<_CharT, _Traits>& operator<<(basic_ostream<_CharT, _Traits>& _Os, const weekday& _Val) {
return _Os << (_Val.ok() ? _STD format(_Os.getloc(), _STATICALLY_WIDEN(_CharT, "{:%a}"), _Val)
return _Os << (_Val.ok() ? _STD format(_Os.getloc(), _STATICALLY_WIDEN(_CharT, "{:L%a}"), _Val)
: _STD format(_Os.getloc(), _STATICALLY_WIDEN(_CharT, "{} is not a valid weekday"),
_Val.c_encoding()));
}
Expand All @@ -5637,40 +5650,41 @@ namespace chrono {
basic_ostream<_CharT, _Traits>& operator<<(basic_ostream<_CharT, _Traits>& _Os, const weekday_indexed& _Val) {
const auto _Idx = _Val.index();
return _Os << (_Idx >= 1 && _Idx <= 5
? _STD format(_Os.getloc(), _STATICALLY_WIDEN(_CharT, "{}[{}]"), _Val.weekday(), _Idx)
: _STD format(_Os.getloc(), _STATICALLY_WIDEN(_CharT, "{}[{} is not a valid index]"),
? _STD format(_Os.getloc(), _STATICALLY_WIDEN(_CharT, "{:L}[{}]"), _Val.weekday(), _Idx)
: _STD format(_Os.getloc(), _STATICALLY_WIDEN(_CharT, "{:L}[{} is not a valid index]"),
_Val.weekday(), _Idx));
}

template <class _CharT, class _Traits>
basic_ostream<_CharT, _Traits>& operator<<(basic_ostream<_CharT, _Traits>& _Os, const weekday_last& _Val) {
return _Os << _STD format(_Os.getloc(), _STATICALLY_WIDEN(_CharT, "{}[last]"), _Val.weekday());
return _Os << _STD format(_Os.getloc(), _STATICALLY_WIDEN(_CharT, "{:L}[last]"), _Val.weekday());
}

template <class _CharT, class _Traits>
basic_ostream<_CharT, _Traits>& operator<<(basic_ostream<_CharT, _Traits>& _Os, const month_day& _Val) {
return _Os << _STD format(_Os.getloc(), _STATICALLY_WIDEN(_CharT, "{}/{}"), _Val.month(), _Val.day());
return _Os << _STD format(_Os.getloc(), _STATICALLY_WIDEN(_CharT, "{:L}/{:L}"), _Val.month(), _Val.day());
}

template <class _CharT, class _Traits>
basic_ostream<_CharT, _Traits>& operator<<(basic_ostream<_CharT, _Traits>& _Os, const month_day_last& _Val) {
return _Os << _STD format(_Os.getloc(), _STATICALLY_WIDEN(_CharT, "{}/last"), _Val.month());
return _Os << _STD format(_Os.getloc(), _STATICALLY_WIDEN(_CharT, "{:L}/last"), _Val.month());
}

template <class _CharT, class _Traits>
basic_ostream<_CharT, _Traits>& operator<<(basic_ostream<_CharT, _Traits>& _Os, const month_weekday& _Val) {
return _Os << _STD format(
_Os.getloc(), _STATICALLY_WIDEN(_CharT, "{}/{}"), _Val.month(), _Val.weekday_indexed());
_Os.getloc(), _STATICALLY_WIDEN(_CharT, "{:L}/{:L}"), _Val.month(), _Val.weekday_indexed());
}

template <class _CharT, class _Traits>
basic_ostream<_CharT, _Traits>& operator<<(basic_ostream<_CharT, _Traits>& _Os, const month_weekday_last& _Val) {
return _Os << _STD format(_Os.getloc(), _STATICALLY_WIDEN(_CharT, "{}/{}"), _Val.month(), _Val.weekday_last());
return _Os << _STD format(
_Os.getloc(), _STATICALLY_WIDEN(_CharT, "{:L}/{:L}"), _Val.month(), _Val.weekday_last());
}

template <class _CharT, class _Traits>
basic_ostream<_CharT, _Traits>& operator<<(basic_ostream<_CharT, _Traits>& _Os, const year_month& _Val) {
return _Os << _STD format(_Os.getloc(), _STATICALLY_WIDEN(_CharT, "{}/{}"), _Val.year(), _Val.month());
return _Os << _STD format(_Os.getloc(), _STATICALLY_WIDEN(_CharT, "{:L}/{:L}"), _Val.year(), _Val.month());
}

template <class _CharT, class _Traits>
Expand All @@ -5681,25 +5695,26 @@ namespace chrono {

template <class _CharT, class _Traits>
basic_ostream<_CharT, _Traits>& operator<<(basic_ostream<_CharT, _Traits>& _Os, const year_month_day_last& _Val) {
return _Os << _STD format(_Os.getloc(), _STATICALLY_WIDEN(_CharT, "{}/{}"), _Val.year(), _Val.month_day_last());
return _Os << _STD format(
_Os.getloc(), _STATICALLY_WIDEN(_CharT, "{:L}/{:L}"), _Val.year(), _Val.month_day_last());
}

template <class _CharT, class _Traits>
basic_ostream<_CharT, _Traits>& operator<<(basic_ostream<_CharT, _Traits>& _Os, const year_month_weekday& _Val) {
return _Os << _STD format(_Os.getloc(), _STATICALLY_WIDEN(_CharT, "{}/{}/{}"), _Val.year(), _Val.month(),
return _Os << _STD format(_Os.getloc(), _STATICALLY_WIDEN(_CharT, "{:L}/{:L}/{:L}"), _Val.year(), _Val.month(),
_Val.weekday_indexed());
}

template <class _CharT, class _Traits>
basic_ostream<_CharT, _Traits>& operator<<(
basic_ostream<_CharT, _Traits>& _Os, const year_month_weekday_last& _Val) {
return _Os << _STD format(
_Os.getloc(), _STATICALLY_WIDEN(_CharT, "{}/{}/{}"), _Val.year(), _Val.month(), _Val.weekday_last());
return _Os << _STD format(_Os.getloc(), _STATICALLY_WIDEN(_CharT, "{:L}/{:L}/{:L}"), _Val.year(), _Val.month(),
_Val.weekday_last());
}

template <class _CharT, class _Traits, class _Duration>
basic_ostream<_CharT, _Traits>& operator<<(basic_ostream<_CharT, _Traits>& _Os, const hh_mm_ss<_Duration>& _Val) {
return _Os << _STD format(_Os.getloc(), _STATICALLY_WIDEN(_CharT, "{:%T}"), _Val);
return _Os << _STD format(_Os.getloc(), _STATICALLY_WIDEN(_CharT, "{:L%T}"), _Val);
}

#pragma warning(push)
Expand Down Expand Up @@ -5750,7 +5765,7 @@ namespace chrono {
// clang-format on
const auto _Dp = _CHRONO floor<days>(_Val);
return _Os << _STD format(
_Os.getloc(), _STATICALLY_WIDEN(_CharT, "{} {}"), year_month_day{_Dp}, hh_mm_ss{_Val - _Dp});
_Os.getloc(), _STATICALLY_WIDEN(_CharT, "{:L} {:L}"), year_month_day{_Dp}, hh_mm_ss{_Val - _Dp});
}

template <class _CharT, class _Traits>
Expand All @@ -5760,22 +5775,22 @@ namespace chrono {

template <class _CharT, class _Traits, class _Duration>
basic_ostream<_CharT, _Traits>& operator<<(basic_ostream<_CharT, _Traits>& _Os, const utc_time<_Duration>& _Val) {
return _Os << _STD format(_STATICALLY_WIDEN(_CharT, "{:%F %T}"), _Val);
return _Os << _STD format(_Os.getloc(), _STATICALLY_WIDEN(_CharT, "{:L%F %T}"), _Val);
}

template <class _CharT, class _Traits, class _Duration>
basic_ostream<_CharT, _Traits>& operator<<(basic_ostream<_CharT, _Traits>& _Os, const tai_time<_Duration>& _Val) {
return _Os << _STD format(_STATICALLY_WIDEN(_CharT, "{:%F %T}"), _Val);
return _Os << _STD format(_Os.getloc(), _STATICALLY_WIDEN(_CharT, "{:L%F %T}"), _Val);
}

template <class _CharT, class _Traits, class _Duration>
basic_ostream<_CharT, _Traits>& operator<<(basic_ostream<_CharT, _Traits>& _Os, const gps_time<_Duration>& _Val) {
return _Os << _STD format(_STATICALLY_WIDEN(_CharT, "{:%F %T}"), _Val);
return _Os << _STD format(_Os.getloc(), _STATICALLY_WIDEN(_CharT, "{:L%F %T}"), _Val);
}

template <class _CharT, class _Traits, class _Duration>
basic_ostream<_CharT, _Traits>& operator<<(basic_ostream<_CharT, _Traits>& _Os, const file_time<_Duration>& _Val) {
return _Os << _STD format(_STATICALLY_WIDEN(_CharT, "{:%F %T}"), _Val);
return _Os << _STD format(_Os.getloc(), _STATICALLY_WIDEN(_CharT, "{:L%F %T}"), _Val);
}

template <class _CharT, class _Traits, class _Duration>
Expand All @@ -5788,7 +5803,7 @@ namespace chrono {
basic_ostream<_CharT, _Traits>& _Os, const _Local_time_format_t<_Duration>& _Val) {
// Doesn't appear in the Standard, but allowed by N4885 [global.functions]/2.
// Implements N4885 [time.zone.zonedtime.nonmembers]/2 for zoned_time.
return _Os << _STD format(_Os.getloc(), _STATICALLY_WIDEN(_CharT, "{:%F %T %Z}"), _Val);
return _Os << _STD format(_Os.getloc(), _STATICALLY_WIDEN(_CharT, "{:L%F %T %Z}"), _Val);
}

template <class _CharT, class _Traits, class _Duration, class _TimeZonePtr>
Expand Down Expand Up @@ -5932,10 +5947,10 @@ namespace chrono {
_NODISCARD auto _Write(_FormatContext& _FormatCtx, const _Ty& _Val, const tm& _Time) {
basic_ostringstream<_CharT> _Stream;

_Stream.imbue(_Specs._Localized ? _FormatCtx.locale() : locale::classic());
if (_Specs._Chrono_specs_list.empty()) {
_Stream << _Val; // N4885 [time.format]/6
} else {
_Stream.imbue(_FormatCtx.locale());
if constexpr (_Is_specialization_v<_Ty, hh_mm_ss>) {
if (_Val.is_negative()) {
_Stream << _CharT{'-'};
Expand Down Expand Up @@ -6294,7 +6309,7 @@ namespace chrono {
return _Fmt_str;
}

_Chrono_format_specs<_CharT> _Specs{};
_Chrono_format_specs2<_CharT> _Specs{};
basic_string_view<_CharT> _Time_zone_abbreviation{};
};
} // namespace chrono
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ struct testing_callbacks {
int expected_precision = -1;
size_t expected_dynamic_precision = static_cast<size_t>(-1);
bool expected_auto_dynamic_precision = false;
bool expected_localized = false;
vector<_Chrono_spec<CharT>>& expected_chrono_specs;
size_t curr_index = 0;

Expand Down Expand Up @@ -66,6 +67,9 @@ struct testing_callbacks {
void _On_dynamic_precision(_Auto_id_tag) {
assert(expected_auto_dynamic_precision);
}
void _On_localized() {
assert(expected_localized);
}
void _On_conversion_spec(char mod, CharT type) {
assert(mod == expected_chrono_specs[curr_index]._Modifier);
assert(static_cast<char>(type) == expected_chrono_specs[curr_index]._Type);
Expand Down Expand Up @@ -884,6 +888,34 @@ void test_zoned_time_formatter() {
assert(format(STR("{:%g %G %U %V %W}"), zt) == STR("21 2021 16 16 16"));
}

template <typename CharT>
void test_localized() {
locale loc("de-DE");

assert(format(loc, STR("{:%S}"), 42ms) == STR("00.042"));
assert(format(loc, STR("{:L%S}"), 42ms) == STR("00,042"));

auto stream = [=](auto value) {
std::basic_ostringstream<CharT> os;
os.imbue(loc);
os << value;
return os.str();
};
assert(stream(month{May}) == STR("Mai"));
assert(stream(weekday{Tuesday}) == STR("Di"));
assert(stream(weekday_indexed{Tuesday[3]}) == STR("Di[3]"));
assert(stream(weekday_indexed{Tuesday[42]}) == STR("Di[42 is not a valid index]"));
assert(stream(weekday_last{Tuesday}) == STR("Di[last]"));
assert(stream(month_day{May, day{4}}) == STR("Mai/04"));
assert(stream(month_day_last{May}) == STR("Mai/last"));
assert(stream(month_weekday{May / Tuesday[4]}) == STR("Mai/Di[4]"));
assert(stream(month_weekday_last{May / Tuesday[last]}) == STR("Mai/Di[last]"));
assert(stream(year_month{2021y / May}) == STR("2021/Mai"));
assert(stream(year_month_day_last{2021y / May / last}) == STR("2021/Mai/last"));
assert(stream(year_month_weekday{2021y / May / Tuesday[4]}) == STR("2021/Mai/Di[4]"));
assert(stream(year_month_weekday_last{2021y / May / Tuesday[last]}) == STR("2021/Mai/Di[last]"));
}

void test() {
test_parse_conversion_spec<char>();
test_parse_conversion_spec<wchar_t>();
Expand Down Expand Up @@ -955,6 +987,9 @@ void test() {

test_zoned_time_formatter<char>();
test_zoned_time_formatter<wchar_t>();

test_localized<char>();
test_localized<wchar_t>();
}

int main() {
Expand Down