Skip to content
Open
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
78 changes: 57 additions & 21 deletions stl/inc/xloctime
Original file line number Diff line number Diff line change
Expand Up @@ -888,24 +888,36 @@ protected:

virtual _OutIt __CLR_OR_THIS_CALL do_put(_OutIt _Dest, ios_base& _Iosbase, _Elem, const tm* _Pt, char _Specifier,
char _Modifier = '\0') const { // put formatted time from _Pt to _Dest for [_Fmtfirst, _Fmtlast)
char _Fmt[5] = "!%x\0"; // '!' for nonzero count, null for modifier
const char* _Fmt_ptr = nullptr;
char _Fmt[5] = "!%x\0"; // '!' for nonzero count, null for modifier
size_t _Count;
size_t _Num;
string _Str;

if (_Modifier == '\0') {
_Fmt[2] = _Specifier;
} else { // add both modifier and specifier
_Fmt[2] = _Modifier;
_Fmt[3] = _Specifier;
if (_Iosbase.getloc() == locale::classic()) {
if (_Specifier == 'c') {
_Fmt_ptr = "!%a %b %e %T %Y";
} else if (_Specifier == 'r') {
_Fmt_ptr = "!%I:%M:%S %p";
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The C Standard (N3685 7.31.3.6/7) and the informative "Application Usage" part of the POSIX spec both say that
"In the C [or POSIX] locale, the E and O modifiers are ignored". The UCRT is actually doing the opposite of this PR; it unconditionally replaces %r in the C locale but only replaces non-alternative %c (I don't have a link for this but you can find it in the UCRT expand_time source).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the catch. I've made some changes, now in C locale, %r, %Er and %Or are treated in the same way.

}

if (!_Fmt_ptr) {
if (_Modifier == '\0') {
_Fmt[2] = _Specifier;
} else { // add both modifier and specifier
_Fmt[2] = _Modifier;
_Fmt[3] = _Specifier;
}
_Fmt_ptr = _Fmt;
}

int& _Errno_ref = errno; // Nonzero cost, pay it once
const int _Old_errno = _Errno_ref;

for (_Num = 16;; _Num *= 2) { // convert into ever larger string buffer until success
_Str.append(_Num, '\0');
_Count = _Strftime(&_Str[0], _Str.size(), _Fmt, _Pt, _Tnames._Getptr());
_Count = _Strftime(&_Str[0], _Str.size(), _Fmt_ptr, _Pt, _Tnames._Getptr());
if (0 < _Count) {
break;
} else if (_Errno_ref == EINVAL) {
Expand Down Expand Up @@ -1034,24 +1046,36 @@ protected:

virtual _OutIt __CLR_OR_THIS_CALL do_put(_OutIt _Dest, ios_base& _Iosbase, _Elem, const tm* _Pt, char _Specifier,
char _Modifier = '\0') const { // put formatted time from _Pt to _Dest for [_Fmtfirst, _Fmtlast)
wchar_t _Fmt[5] = L"!%x\0"; // ! for nonzero count, null for modifier
const wchar_t* _Fmt_ptr = nullptr;
wchar_t _Fmt[5] = L"!%x\0"; // ! for nonzero count, null for modifier
size_t _Count;
size_t _Num;
wstring _Str;

if (_Modifier == '\0') {
_Fmt[2] = static_cast<_Elem>(_Specifier); // conversion rule unspecified
} else { // add both modifier and specifier
_Fmt[2] = static_cast<_Elem>(_Modifier);
_Fmt[3] = static_cast<_Elem>(_Specifier);
if (_Iosbase.getloc() == locale::classic()) {
if (_Specifier == 'c') {
_Fmt_ptr = L"!%a %b %e %T %Y";
} else if (_Specifier == 'r') {
_Fmt_ptr = L"!%I:%M:%S %p";
}
}

if (!_Fmt_ptr) {
if (_Modifier == '\0') {
_Fmt[2] = static_cast<_Elem>(_Specifier);
} else {
_Fmt[2] = static_cast<_Elem>(_Modifier);
_Fmt[3] = static_cast<_Elem>(_Specifier);
}
_Fmt_ptr = _Fmt;
}

int& _Errno_ref = errno; // Nonzero cost, pay it once
const int _Old_errno = _Errno_ref;

for (_Num = 16;; _Num *= 2) { // convert into ever larger string buffer until success
_Str.append(_Num, '\0');
_Count = _Wcsftime(&_Str[0], _Str.size(), _Fmt, _Pt, _Tnames._Getptr());
_Count = _Wcsftime(&_Str[0], _Str.size(), _Fmt_ptr, _Pt, _Tnames._Getptr());
if (0 < _Count) {
break;
} else if (_Errno_ref == EINVAL) {
Expand Down Expand Up @@ -1183,24 +1207,36 @@ protected:

virtual _OutIt __CLR_OR_THIS_CALL do_put(_OutIt _Dest, ios_base& _Iosbase, _Elem, const tm* _Pt, char _Specifier,
char _Modifier = '\0') const { // put formatted time from _Pt to _Dest for [_Fmtfirst, _Fmtlast)
wchar_t _Fmt[5] = L"!%x\0"; // ! for nonzero count, null for modifier
const wchar_t* _Fmt_ptr = nullptr;
wchar_t _Fmt[5] = L"!%x\0"; // ! for nonzero count, null for modifier
size_t _Count;
size_t _Num;
wstring _Str;

if (_Modifier == '\0') {
_Fmt[2] = static_cast<_Elem>(_Specifier); // conversion rule unspecified
} else { // add both modifier and specifier
_Fmt[2] = static_cast<_Elem>(_Modifier);
_Fmt[3] = static_cast<_Elem>(_Specifier);
if (_Iosbase.getloc() == locale::classic()) {
if (_Specifier == 'c') {
_Fmt_ptr = L"!%a %b %e %T %Y";
} else if (_Specifier == 'r') {
_Fmt_ptr = L"!%I:%M:%S %p";
}
}

if (!_Fmt_ptr) {
if (_Modifier == '\0') {
_Fmt[2] = static_cast<_Elem>(_Specifier);
} else {
_Fmt[2] = static_cast<_Elem>(_Modifier);
_Fmt[3] = static_cast<_Elem>(_Specifier);
}
_Fmt_ptr = _Fmt;
}

int& _Errno_ref = errno; // Nonzero cost, pay it once
const int _Old_errno = _Errno_ref;

for (_Num = 16;; _Num *= 2) { // convert into ever larger string buffer until success
_Str.append(_Num, '\0');
_Count = _Wcsftime(&_Str[0], _Str.size(), _Fmt, _Pt, _Tnames._Getptr());
_Count = _Wcsftime(&_Str[0], _Str.size(), _Fmt_ptr, _Pt, _Tnames._Getptr());
if (0 < _Count) {
break;
} else if (_Errno_ref == EINVAL) {
Expand Down
3 changes: 0 additions & 3 deletions tests/libcxx/expected_results.txt
Original file line number Diff line number Diff line change
Expand Up @@ -566,9 +566,6 @@ std/localization/locale.categories/category.time/locale.time.get/locale.time.get
std/localization/locale.categories/category.time/locale.time.get/locale.time.get.members/get_weekday.pass.cpp FAIL
std/localization/locale.categories/category.time/locale.time.get/locale.time.get.members/get_weekday_wide.pass.cpp FAIL

# GH-6134 <xloctime>: time_put::do_put does not match strftime for the %c and %r specifiers in the "C" locale
std/localization/locale.categories/category.time/locale.time.put/locale.time.put.members/put2.pass.cpp FAIL

# GH-6135 <xloctime>: time_put_byname doesn't encode the output in UTF-8 even if the locale name contains ".UTF-8"
std/localization/locale.categories/category.time/locale.time.put.byname/put1.pass.cpp FAIL

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1012,7 +1012,7 @@ void test_local_time_format_formatter() {
// Implements N4885 [time.zone.zonedtime.nonmembers]/2 for zoned_time.
empty_braces_helper(ltf, STR("2021-04-19 01:02:03 Meow"));

assert(format(STR("{:%c, %x, %X}"), ltf) == STR("04/19/21 01:02:03, 04/19/21, 01:02:03"));
assert(format(STR("{:%c, %x, %X}"), ltf) == STR("Mon Apr 19 01:02:03 2021, 04/19/21, 01:02:03"));
assert(format(STR("{:%D %F, %Y %C %y, %b %B %h %m, %d %e, %a %A %u %w}"), ltf)
== STR("04/19/21 2021-04-19, 2021 20 21, Apr April Apr 04, 19 19, Mon Monday 1 1"));
assert(format(STR("{:%H %I %M %S, %r, %R %T %p}"), ltf) == STR("01 01 02 03, 01:02:03 AM, 01:02 01:02:03 AM"));
Expand All @@ -1027,7 +1027,7 @@ void test_zoned_time_formatter() {

empty_braces_helper(zt, STR("2021-04-19 08:16:17 PDT"), STR("2021-04-19 08:16:17 GMT-7"));

assert(format(STR("{:%c, %x, %X}"), zt) == STR("04/19/21 08:16:17, 04/19/21, 08:16:17"));
assert(format(STR("{:%c, %x, %X}"), zt) == STR("Mon Apr 19 08:16:17 2021, 04/19/21, 08:16:17"));
assert(format(STR("{:%D %F, %Y %C %y, %b %B %h %m, %d %e, %a %A %u %w}"), zt)
== STR("04/19/21 2021-04-19, 2021 20 21, Apr April Apr 04, 19 19, Mon Monday 1 1"));
assert(format(STR("{:%H %I %M %S, %r, %R %T %p}"), zt) == STR("08 08 16 17, 08:16:17 AM, 08:16 08:16:17 AM"));
Expand Down