diff --git a/stl/inc/xloctime b/stl/inc/xloctime index f4e0c063e..da4f55a9a 100644 --- a/stl/inc/xloctime +++ b/stl/inc/xloctime @@ -20,6 +20,72 @@ _STL_DISABLE_CLANG_WARNINGS #undef new _STD_BEGIN +template +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 diff --git a/tests/std/tests/Dev11_0836436_get_time/test.cpp b/tests/std/tests/Dev11_0836436_get_time/test.cpp index ec73d6f69..5c1ca748a 100644 --- a/tests/std/tests/Dev11_0836436_get_time/test.cpp +++ b/tests/std/tests/Dev11_0836436_get_time/test.cpp @@ -18,6 +18,7 @@ using namespace std; // DevDiv-821672 ": visual studio.net 2013 time libraries buggy (%x %X) - time_get" // DevDiv-836436 ": get_time()'s AM/PM parsing is broken" // DevDiv-872926 ": time_get::get parsing format string gets tm::tm_hour wrong [libcxx]" +// VSO-1259138/GH-2618 ": 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 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>(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); +}