`<xloctime>`: apply two-digit year logic only if exactly two digits are read (#2666)

Co-authored-by: Stephan T. Lavavej <stl@microsoft.com>
This commit is contained in:
Matt Stephanson 2022-05-01 03:07:27 -07:00 коммит произвёл GitHub
Родитель b0b9a58b35
Коммит b965db4e56
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
2 изменённых файлов: 134 добавлений и 70 удалений

Просмотреть файл

@ -20,6 +20,72 @@ _STL_DISABLE_CLANG_WARNINGS
#undef new
_STD_BEGIN
template <class _InIt, class _Elem>
ios_base::iostate _Getint_v2(_InIt& _First, _InIt& _Last, int _Lo, int _Hi, int& _Val, int& _Digits_read,
const ctype<_Elem>& _Ctype_fac) { // get integer in range [_Lo, _Hi] from [_First, _Last)
_STL_INTERNAL_CHECK(0 <= _Hi && _Hi <= 9999);
const int _Hi_digits = (_Hi <= 9 ? 1 : _Hi <= 99 ? 2 : _Hi <= 999 ? 3 : 4);
char _Ac[_MAX_INT_DIG];
char* _Ep;
char* _Ptr = _Ac;
char _Ch;
_Digits_read = 0;
while (_First != _Last && _Digits_read < _Hi_digits && _Ctype_fac.is(ctype_base::space, *_First)) {
++_First;
++_Digits_read;
}
if (_First != _Last && _Digits_read < _Hi_digits) {
if ((_Ch = _Ctype_fac.narrow(*_First)) == '+') { // copy plus sign
*_Ptr++ = '+';
++_First;
} else if (_Ch == '-') { // copy minus sign
*_Ptr++ = '-';
++_First;
}
}
for (; _First != _Last && _Digits_read < _Hi_digits && _Ctype_fac.narrow(*_First) == '0'; ++_First) {
++_Digits_read; // strip leading zeros
}
if (_Digits_read > 0) {
*_Ptr++ = '0'; // replace one or more with single zero
}
for (char* const _Pe = &_Ac[_MAX_INT_DIG - 1];
_First != _Last && '0' <= (_Ch = _Ctype_fac.narrow(*_First)) && _Ch <= '9' && _Digits_read < _Hi_digits;
++_Digits_read, (void) ++_First) { // copy digits
*_Ptr = _Ch;
if (_Ptr < _Pe) {
++_Ptr; // drop trailing digits if already too large
}
}
if (_Digits_read == 0) {
_Ptr = _Ac;
}
*_Ptr = '\0';
int _Errno = 0;
const long _Ans = _CSTD _Stolx(_Ac, &_Ep, 10, &_Errno);
ios_base::iostate _State = ios_base::goodbit;
if (_First == _Last) {
_State |= ios_base::eofbit;
}
if (_Ep == _Ac || _Errno != 0 || _Ans < _Lo || _Hi < _Ans) {
_State |= ios_base::failbit; // bad conversion
} else {
_Val = _Ans; // store valid result
}
return _State;
}
struct _CRTIMP2_PURE_IMPORT time_base : locale::facet { // base class for time_get
enum dateorder { // constants for different orders of date components
no_order,
@ -343,17 +409,20 @@ protected:
ios_base::iostate& _State, tm* _Pt) const { // get year from [_First, _Last) into _Pt
const _Ctype& _Ctype_fac = _STD use_facet<_Ctype>(_Iosbase.getloc());
int _Ans = 0;
ios_base::iostate _Res = _Getint(_First, _Last, 0, 9999, _Ans, _Ctype_fac);
int _Ans = 0;
int _Digits_read;
ios_base::iostate _Res = _Getint_v2(_First, _Last, 0, 9999, _Ans, _Digits_read, _Ctype_fac);
_State |= _Res; // pass on eofbit and failbit
if (!(_Res & ios_base::failbit)) {
if (_Ans < 69) {
_Pt->tm_year = _Ans + 100; // [0, 68] parsed as [2000, 2068]
} else if (_Ans < 100) {
_Pt->tm_year = _Ans; // [69, 99] parsed as [1969, 1999]
if (_Digits_read <= 2) {
if (_Ans < 69) {
_Pt->tm_year = _Ans + 100; // [00, 68] parsed as [2000, 2068]
} else if (_Ans < 100) {
_Pt->tm_year = _Ans; // [69, 99] parsed as [1969, 1999]
}
} else {
_Pt->tm_year = _Ans - 1900; // [100, 9999] parsed literally
_Pt->tm_year = _Ans - 1900; // parsed literally
}
}
@ -485,7 +554,10 @@ protected:
break;
case 'Y':
_First = get_year(_First, _Last, _Iosbase, _State, _Pt);
_State |= _Getint(_First, _Last, 0, 9999, _Ans, _Ctype_fac);
if (!(_State & ios_base::failbit)) {
_Pt->tm_year = _Ans - 1900;
}
break;
default:
@ -529,68 +601,9 @@ protected:
private:
ios_base::iostate __CLRCALL_OR_CDECL _Getint(_InIt& _First, _InIt& _Last, int _Lo, int _Hi, int& _Val,
const _Ctype& _Ctype_fac) const { // get integer in range [_Lo, _Hi] from [_First, _Last)
_STL_INTERNAL_CHECK(0 <= _Hi && _Hi <= 9999);
const int _Hi_digits = (_Hi <= 9 ? 1 : _Hi <= 99 ? 2 : _Hi <= 999 ? 3 : 4);
char _Ac[_MAX_INT_DIG];
char* _Ep;
char* _Ptr = _Ac;
char _Ch;
int _Digits_seen = 0;
while (_First != _Last && _Digits_seen < _Hi_digits && _Ctype_fac.is(ctype_base::space, *_First)) {
++_First;
++_Digits_seen;
}
if (_First != _Last && _Digits_seen < _Hi_digits) {
if ((_Ch = _Ctype_fac.narrow(*_First)) == '+') { // copy plus sign
*_Ptr++ = '+';
++_First;
} else if (_Ch == '-') { // copy minus sign
*_Ptr++ = '-';
++_First;
}
}
for (; _First != _Last && _Digits_seen < _Hi_digits && _Ctype_fac.narrow(*_First) == '0';
++_First) { // strip leading zeros
++_Digits_seen;
}
if (_Digits_seen > 0) {
*_Ptr++ = '0'; // replace one or more with single zero
}
for (char* const _Pe = &_Ac[_MAX_INT_DIG - 1];
_First != _Last && '0' <= (_Ch = _Ctype_fac.narrow(*_First)) && _Ch <= '9' && _Digits_seen < _Hi_digits;
++_Digits_seen, (void) ++_First) { // copy digits
*_Ptr = _Ch;
if (_Ptr < _Pe) {
++_Ptr; // drop trailing digits if already too large
}
}
if (_Digits_seen == 0) {
_Ptr = _Ac;
}
*_Ptr = '\0';
int _Errno = 0;
const long _Ans = _CSTD _Stolx(_Ac, &_Ep, 10, &_Errno);
ios_base::iostate _State = ios_base::goodbit;
if (_First == _Last) {
_State |= ios_base::eofbit;
}
if (_Ep == _Ac || _Errno != 0 || _Ans < _Lo || _Hi < _Ans) {
_State |= ios_base::failbit; // bad conversion
} else {
_Val = _Ans; // store valid result
}
return _State;
// TRANSITION, ABI
int _Digits_read;
return _Getint_v2(_First, _Last, _Lo, _Hi, _Val, _Digits_read, _Ctype_fac);
}
void __CLR_OR_THIS_CALL _Tidy() noexcept { // free all storage

Просмотреть файл

@ -18,6 +18,7 @@ using namespace std;
// DevDiv-821672 "<locale>: visual studio.net 2013 time libraries buggy (%x %X) - time_get"
// DevDiv-836436 "<iomanip>: get_time()'s AM/PM parsing is broken"
// DevDiv-872926 "<locale>: time_get::get parsing format string gets tm::tm_hour wrong [libcxx]"
// VSO-1259138/GH-2618 "<xloctime>: get_time does not return correct year in tm.tm_year if year is 1"
tm helper(const char* const s, const char* const fmt) {
tm t{};
@ -107,6 +108,7 @@ void test_locale_german();
void test_locale_chinese();
void test_invalid_argument();
void test_buffer_resizing();
void test_gh_2618();
int main() {
assert(read_hour("12 AM") == 0);
@ -152,6 +154,7 @@ int main() {
test_locale_chinese();
test_invalid_argument();
test_buffer_resizing();
test_gh_2618();
}
typedef istreambuf_iterator<char> Iter;
@ -821,3 +824,51 @@ void test_buffer_resizing() {
assert(ss.rdstate() == ios_base::goodbit);
}
}
void test_gh_2618() {
auto TestTimeGetYear = [](const char* input, const int expected_y, const int expected_Y,
const int expected_get_year) {
{
tm time{};
istringstream iss{input};
iss >> get_time(&time, "%y");
assert(time.tm_year + 1900 == expected_y);
}
{
tm time{};
istringstream iss{input};
iss >> get_time(&time, "%Y");
assert(time.tm_year + 1900 == expected_Y);
}
{
tm time{};
ios_base::iostate state{};
istringstream iss{input};
use_facet<time_get<char>>(iss.getloc()).get_year({iss}, {}, iss, state, &time);
assert(time.tm_year + 1900 == expected_get_year);
}
};
// 4-digit strings: 'y' should only read the first two digits, 'Y' and `get_year` should agree
TestTimeGetYear("0001", 2000, 1, 1);
TestTimeGetYear("0080", 2000, 80, 80);
TestTimeGetYear("1995", 2019, 1995, 1995);
TestTimeGetYear("2022", 2020, 2022, 2022);
TestTimeGetYear("8522", 1985, 8522, 8522);
// 3-digit strings: same as 4-digit
TestTimeGetYear("001", 2000, 1, 1);
TestTimeGetYear("080", 2008, 80, 80);
TestTimeGetYear("995", 1999, 995, 995);
// 2-digit strings: 'Y' should parse literally, `get_year` should behave as 'y'
TestTimeGetYear("01", 2001, 1, 2001);
TestTimeGetYear("80", 1980, 80, 1980);
TestTimeGetYear("95", 1995, 95, 1995);
TestTimeGetYear("22", 2022, 22, 2022);
// 1-digit strings: same as 2-digit
TestTimeGetYear("1", 2001, 1, 2001);
}