Skip to content

Commit b965db4

Browse files
<xloctime>: apply two-digit year logic only if exactly two digits are read (#2666)
Co-authored-by: Stephan T. Lavavej <[email protected]>
1 parent b0b9a58 commit b965db4

File tree

2 files changed

+134
-70
lines changed

2 files changed

+134
-70
lines changed

stl/inc/xloctime

Lines changed: 83 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,72 @@ _STL_DISABLE_CLANG_WARNINGS
2020
#undef new
2121

2222
_STD_BEGIN
23+
template <class _InIt, class _Elem>
24+
ios_base::iostate _Getint_v2(_InIt& _First, _InIt& _Last, int _Lo, int _Hi, int& _Val, int& _Digits_read,
25+
const ctype<_Elem>& _Ctype_fac) { // get integer in range [_Lo, _Hi] from [_First, _Last)
26+
_STL_INTERNAL_CHECK(0 <= _Hi && _Hi <= 9999);
27+
const int _Hi_digits = (_Hi <= 9 ? 1 : _Hi <= 99 ? 2 : _Hi <= 999 ? 3 : 4);
28+
char _Ac[_MAX_INT_DIG];
29+
char* _Ep;
30+
char* _Ptr = _Ac;
31+
char _Ch;
32+
33+
_Digits_read = 0;
34+
35+
while (_First != _Last && _Digits_read < _Hi_digits && _Ctype_fac.is(ctype_base::space, *_First)) {
36+
++_First;
37+
++_Digits_read;
38+
}
39+
40+
if (_First != _Last && _Digits_read < _Hi_digits) {
41+
if ((_Ch = _Ctype_fac.narrow(*_First)) == '+') { // copy plus sign
42+
*_Ptr++ = '+';
43+
++_First;
44+
} else if (_Ch == '-') { // copy minus sign
45+
*_Ptr++ = '-';
46+
++_First;
47+
}
48+
}
49+
50+
for (; _First != _Last && _Digits_read < _Hi_digits && _Ctype_fac.narrow(*_First) == '0'; ++_First) {
51+
++_Digits_read; // strip leading zeros
52+
}
53+
54+
if (_Digits_read > 0) {
55+
*_Ptr++ = '0'; // replace one or more with single zero
56+
}
57+
58+
for (char* const _Pe = &_Ac[_MAX_INT_DIG - 1];
59+
_First != _Last && '0' <= (_Ch = _Ctype_fac.narrow(*_First)) && _Ch <= '9' && _Digits_read < _Hi_digits;
60+
++_Digits_read, (void) ++_First) { // copy digits
61+
*_Ptr = _Ch;
62+
if (_Ptr < _Pe) {
63+
++_Ptr; // drop trailing digits if already too large
64+
}
65+
}
66+
67+
if (_Digits_read == 0) {
68+
_Ptr = _Ac;
69+
}
70+
71+
*_Ptr = '\0';
72+
int _Errno = 0;
73+
const long _Ans = _CSTD _Stolx(_Ac, &_Ep, 10, &_Errno);
74+
ios_base::iostate _State = ios_base::goodbit;
75+
76+
if (_First == _Last) {
77+
_State |= ios_base::eofbit;
78+
}
79+
80+
if (_Ep == _Ac || _Errno != 0 || _Ans < _Lo || _Hi < _Ans) {
81+
_State |= ios_base::failbit; // bad conversion
82+
} else {
83+
_Val = _Ans; // store valid result
84+
}
85+
86+
return _State;
87+
}
88+
2389
struct _CRTIMP2_PURE_IMPORT time_base : locale::facet { // base class for time_get
2490
enum dateorder { // constants for different orders of date components
2591
no_order,
@@ -343,17 +409,20 @@ protected:
343409
ios_base::iostate& _State, tm* _Pt) const { // get year from [_First, _Last) into _Pt
344410
const _Ctype& _Ctype_fac = _STD use_facet<_Ctype>(_Iosbase.getloc());
345411

346-
int _Ans = 0;
347-
ios_base::iostate _Res = _Getint(_First, _Last, 0, 9999, _Ans, _Ctype_fac);
412+
int _Ans = 0;
413+
int _Digits_read;
414+
ios_base::iostate _Res = _Getint_v2(_First, _Last, 0, 9999, _Ans, _Digits_read, _Ctype_fac);
348415

349416
_State |= _Res; // pass on eofbit and failbit
350417
if (!(_Res & ios_base::failbit)) {
351-
if (_Ans < 69) {
352-
_Pt->tm_year = _Ans + 100; // [0, 68] parsed as [2000, 2068]
353-
} else if (_Ans < 100) {
354-
_Pt->tm_year = _Ans; // [69, 99] parsed as [1969, 1999]
418+
if (_Digits_read <= 2) {
419+
if (_Ans < 69) {
420+
_Pt->tm_year = _Ans + 100; // [00, 68] parsed as [2000, 2068]
421+
} else if (_Ans < 100) {
422+
_Pt->tm_year = _Ans; // [69, 99] parsed as [1969, 1999]
423+
}
355424
} else {
356-
_Pt->tm_year = _Ans - 1900; // [100, 9999] parsed literally
425+
_Pt->tm_year = _Ans - 1900; // parsed literally
357426
}
358427
}
359428

@@ -485,7 +554,10 @@ protected:
485554
break;
486555

487556
case 'Y':
488-
_First = get_year(_First, _Last, _Iosbase, _State, _Pt);
557+
_State |= _Getint(_First, _Last, 0, 9999, _Ans, _Ctype_fac);
558+
if (!(_State & ios_base::failbit)) {
559+
_Pt->tm_year = _Ans - 1900;
560+
}
489561
break;
490562

491563
default:
@@ -529,68 +601,9 @@ protected:
529601
private:
530602
ios_base::iostate __CLRCALL_OR_CDECL _Getint(_InIt& _First, _InIt& _Last, int _Lo, int _Hi, int& _Val,
531603
const _Ctype& _Ctype_fac) const { // get integer in range [_Lo, _Hi] from [_First, _Last)
532-
_STL_INTERNAL_CHECK(0 <= _Hi && _Hi <= 9999);
533-
const int _Hi_digits = (_Hi <= 9 ? 1 : _Hi <= 99 ? 2 : _Hi <= 999 ? 3 : 4);
534-
char _Ac[_MAX_INT_DIG];
535-
char* _Ep;
536-
char* _Ptr = _Ac;
537-
char _Ch;
538-
539-
int _Digits_seen = 0;
540-
541-
while (_First != _Last && _Digits_seen < _Hi_digits && _Ctype_fac.is(ctype_base::space, *_First)) {
542-
++_First;
543-
++_Digits_seen;
544-
}
545-
546-
if (_First != _Last && _Digits_seen < _Hi_digits) {
547-
if ((_Ch = _Ctype_fac.narrow(*_First)) == '+') { // copy plus sign
548-
*_Ptr++ = '+';
549-
++_First;
550-
} else if (_Ch == '-') { // copy minus sign
551-
*_Ptr++ = '-';
552-
++_First;
553-
}
554-
}
555-
556-
for (; _First != _Last && _Digits_seen < _Hi_digits && _Ctype_fac.narrow(*_First) == '0';
557-
++_First) { // strip leading zeros
558-
++_Digits_seen;
559-
}
560-
561-
if (_Digits_seen > 0) {
562-
*_Ptr++ = '0'; // replace one or more with single zero
563-
}
564-
565-
for (char* const _Pe = &_Ac[_MAX_INT_DIG - 1];
566-
_First != _Last && '0' <= (_Ch = _Ctype_fac.narrow(*_First)) && _Ch <= '9' && _Digits_seen < _Hi_digits;
567-
++_Digits_seen, (void) ++_First) { // copy digits
568-
*_Ptr = _Ch;
569-
if (_Ptr < _Pe) {
570-
++_Ptr; // drop trailing digits if already too large
571-
}
572-
}
573-
574-
if (_Digits_seen == 0) {
575-
_Ptr = _Ac;
576-
}
577-
578-
*_Ptr = '\0';
579-
int _Errno = 0;
580-
const long _Ans = _CSTD _Stolx(_Ac, &_Ep, 10, &_Errno);
581-
ios_base::iostate _State = ios_base::goodbit;
582-
583-
if (_First == _Last) {
584-
_State |= ios_base::eofbit;
585-
}
586-
587-
if (_Ep == _Ac || _Errno != 0 || _Ans < _Lo || _Hi < _Ans) {
588-
_State |= ios_base::failbit; // bad conversion
589-
} else {
590-
_Val = _Ans; // store valid result
591-
}
592-
593-
return _State;
604+
// TRANSITION, ABI
605+
int _Digits_read;
606+
return _Getint_v2(_First, _Last, _Lo, _Hi, _Val, _Digits_read, _Ctype_fac);
594607
}
595608

596609
void __CLR_OR_THIS_CALL _Tidy() noexcept { // free all storage

tests/std/tests/Dev11_0836436_get_time/test.cpp

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ using namespace std;
1818
// DevDiv-821672 "<locale>: visual studio.net 2013 time libraries buggy (%x %X) - time_get"
1919
// DevDiv-836436 "<iomanip>: get_time()'s AM/PM parsing is broken"
2020
// DevDiv-872926 "<locale>: time_get::get parsing format string gets tm::tm_hour wrong [libcxx]"
21+
// VSO-1259138/GH-2618 "<xloctime>: get_time does not return correct year in tm.tm_year if year is 1"
2122

2223
tm helper(const char* const s, const char* const fmt) {
2324
tm t{};
@@ -107,6 +108,7 @@ void test_locale_german();
107108
void test_locale_chinese();
108109
void test_invalid_argument();
109110
void test_buffer_resizing();
111+
void test_gh_2618();
110112

111113
int main() {
112114
assert(read_hour("12 AM") == 0);
@@ -152,6 +154,7 @@ int main() {
152154
test_locale_chinese();
153155
test_invalid_argument();
154156
test_buffer_resizing();
157+
test_gh_2618();
155158
}
156159

157160
typedef istreambuf_iterator<char> Iter;
@@ -821,3 +824,51 @@ void test_buffer_resizing() {
821824
assert(ss.rdstate() == ios_base::goodbit);
822825
}
823826
}
827+
828+
void test_gh_2618() {
829+
auto TestTimeGetYear = [](const char* input, const int expected_y, const int expected_Y,
830+
const int expected_get_year) {
831+
{
832+
tm time{};
833+
istringstream iss{input};
834+
iss >> get_time(&time, "%y");
835+
assert(time.tm_year + 1900 == expected_y);
836+
}
837+
838+
{
839+
tm time{};
840+
istringstream iss{input};
841+
iss >> get_time(&time, "%Y");
842+
assert(time.tm_year + 1900 == expected_Y);
843+
}
844+
845+
{
846+
tm time{};
847+
ios_base::iostate state{};
848+
istringstream iss{input};
849+
use_facet<time_get<char>>(iss.getloc()).get_year({iss}, {}, iss, state, &time);
850+
assert(time.tm_year + 1900 == expected_get_year);
851+
}
852+
};
853+
854+
// 4-digit strings: 'y' should only read the first two digits, 'Y' and `get_year` should agree
855+
TestTimeGetYear("0001", 2000, 1, 1);
856+
TestTimeGetYear("0080", 2000, 80, 80);
857+
TestTimeGetYear("1995", 2019, 1995, 1995);
858+
TestTimeGetYear("2022", 2020, 2022, 2022);
859+
TestTimeGetYear("8522", 1985, 8522, 8522);
860+
861+
// 3-digit strings: same as 4-digit
862+
TestTimeGetYear("001", 2000, 1, 1);
863+
TestTimeGetYear("080", 2008, 80, 80);
864+
TestTimeGetYear("995", 1999, 995, 995);
865+
866+
// 2-digit strings: 'Y' should parse literally, `get_year` should behave as 'y'
867+
TestTimeGetYear("01", 2001, 1, 2001);
868+
TestTimeGetYear("80", 1980, 80, 1980);
869+
TestTimeGetYear("95", 1995, 95, 1995);
870+
TestTimeGetYear("22", 2022, 22, 2022);
871+
872+
// 1-digit strings: same as 2-digit
873+
TestTimeGetYear("1", 2001, 1, 2001);
874+
}

0 commit comments

Comments
 (0)