diff --git a/stl/inc/xloctime b/stl/inc/xloctime index d812c1b5743..b29d17822bc 100644 --- a/stl/inc/xloctime +++ b/stl/inc/xloctime @@ -93,37 +93,38 @@ public: _State = ios_base::goodbit; for (; _Fmtfirst != _Fmtlast; ++_Fmtfirst) { - if (_Ctype_fac.narrow(*_Fmtfirst) != '%') { // match literal element + if (_State != ios_base::goodbit) { + // N4878 [locale.time.get.members]/8.2 + // _State is fail, eof, or bad. Do not proceed to the next fields. Return with current _State. + break; + } else if (_First == _Last) { + // N4878 [locale.time.get.members]/8.3 + _State = ios_base::eofbit | ios_base::failbit; + break; + } else if (_Ctype_fac.narrow(*_Fmtfirst) != '%') { // match literal element if (_Ctype_fac.is(_Ctype::space, *_Fmtfirst)) { while (_First != _Last && _Ctype_fac.is(_Ctype::space, *_First)) { ++_First; } - } else if (*_First != *_Fmtfirst) { // bad literal match + } else if (_Ctype_fac.tolower(*_First) != _Ctype_fac.tolower(*_Fmtfirst)) { // bad literal match _State |= ios_base::failbit; break; } else { ++_First; } - } else if (++_Fmtfirst == _Fmtlast) { // treat trailing % as literal match - if (*_First != _Fmtfirst[-1]) { - _State |= ios_base::failbit; - } else { - ++_First; - } + } else if (++_Fmtfirst == _Fmtlast) { + // N4878 [locale.time.get.members]/8.4: "If the number of elements in the range [fmt, fmtend) is not + // sufficient to unambiguously determine whether the conversion specification is complete and valid, + // the function evaluates err = ios_base::failbit." + _State = ios_base::failbit; break; } else { // get specifier after % char _Specifier = _Ctype_fac.narrow(*_Fmtfirst); char _Modifier = '\0'; - _Elem _Percent = _Fmtfirst[-1]; if (_Specifier == 'E' || _Specifier == 'O' || _Specifier == 'Q' || _Specifier == '#') { - if (++_Fmtfirst == _Fmtlast) { // no specifier, treat %[E0Q#] as literal match - if (*_First != _Percent || ++_First == _Last || _Ctype_fac.narrow(*_First) != _Specifier) { - _State |= ios_base::failbit; - } else { - ++_First; - } - + if (++_Fmtfirst == _Fmtlast) { // no specifier + _State = ios_base::failbit; break; } else { // save both qualifier and specifier _Modifier = _Specifier; @@ -132,18 +133,9 @@ public: } _First = do_get(_First, _Last, _Iosbase, _State, _Pt, _Specifier, _Modifier); // convert a single field - - if (_State != ios_base::goodbit) { - // _State is fail, eof, or bad. Do not proceed to the next fields. Return with current _State. - break; - } } } - if (_First == _Last) { - _State |= ios_base::eofbit; - } - return _First; } @@ -223,7 +215,7 @@ protected: if (_State != ios_base::goodbit || _Ctype_fac.narrow(*_First) != ':') { _State |= ios_base::failbit; // min field is bad } else { - _State |= _Getint(++_First, _Last, 0, 59, _Pt->tm_sec, _Ctype_fac); + _State |= _Getint(++_First, _Last, 0, 60, _Pt->tm_sec, _Ctype_fac); } return _First; @@ -546,7 +538,14 @@ private: char* _Ptr = _Ac; char _Ch; - if (_First != _Last) { + 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; @@ -556,9 +555,8 @@ private: } } - int _Digits_seen = 0; - - for (; _First != _Last && _Ctype_fac.narrow(*_First) == '0'; ++_First) { // strip leading zeros + for (; _First != _Last && _Digits_seen < _Hi_digits && _Ctype_fac.narrow(*_First) == '0'; + ++_First) { // strip leading zeros ++_Digits_seen; } diff --git a/tests/std/tests/Dev11_0836436_get_time/test.cpp b/tests/std/tests/Dev11_0836436_get_time/test.cpp index 2d704e204dd..d802f7db9ec 100644 --- a/tests/std/tests/Dev11_0836436_get_time/test.cpp +++ b/tests/std/tests/Dev11_0836436_get_time/test.cpp @@ -494,6 +494,64 @@ void test_990695() { assert(t.tm_min == 8); assert(t.tm_sec == 0); } + + { + // Should fail if EOF while not parsing specifier (N4878 [locale.time.get.members]/8.4). + tm t{}; + istringstream iss("4"); + iss >> get_time(&t, "42"); + assert(iss.rdstate() == (ios_base::eofbit | ios_base::failbit)); + } + + { + // Trailing % should not be treated as a literal (N4878 [locale.time.get.members]/8.4 again). + tm t{}; + istringstream iss("%"); + iss >> get_time(&t, "%"); + assert(iss.fail()); + } + + { + // % with modifier but no specifier is also incomplete. + tm t{}; + istringstream iss("%E"); + iss >> get_time(&t, "%E"); + assert(iss.fail()); + } + + { + // Literal match is case-insensitive. + tm t{}; + istringstream iss("aBc"); + iss >> get_time(&t, "AbC"); + assert(iss); + } + + { + // GH-1606: reads too many leading zeros + istringstream iss("19700405T000006"); + tm t{}; + iss >> get_time(&t, "%Y%m%dT%H%M%S"); + assert(iss); + + printf("Expected hour 0, min 0, sec 6\n"); + printf(" Got hour %d, min %d, sec %d\n", t.tm_hour, t.tm_min, t.tm_sec); + + assert(t.tm_year == 70); + assert(t.tm_mon == 3); + assert(t.tm_mday == 5); + assert(t.tm_hour == 0); + assert(t.tm_min == 0); + assert(t.tm_sec == 6); + } + + { + // strptime specification: "leading zeros are permitted but not required" + tm t{}; + istringstream{" 7 4"} >> get_time(&t, "%m%d"); + assert(t.tm_mon == 6); + assert(t.tm_mday == 4); + } } }