Skip to content

<iomanip>: get_time handles leading zeros incorrectly #1606

@StephanTLavavej

Description

@StephanTLavavej

Reported by @randydu in #944 (comment) (thanks @statementreply for looking into this).

While reviewing #1280, we noticed and fixed an issue with leading zeros, but the fix was incomplete. Here's a slightly enhanced version of @randydu's test case:

C:\Temp>type meow.cpp
#include <cassert>
#include <cstdio>
#include <ctime>
#include <iomanip>
#include <sstream>
using namespace std;

int main() {
  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);
}
C:\Temp>cl /EHsc /nologo /W4 /MTd meow.cpp
meow.cpp

C:\Temp>meow
Expected hour 0, min 0, sec 6
     Got hour 0, min 6, sec 0
Assertion failed: t.tm_min == 0, file meow.cpp, line 21

We start by calculating the maximum number of digits we can consume (currently always within [1, 4]):

const int _Hi_digits = (_Hi <= 9 ? 1 : _Hi <= 99 ? 2 : _Hi <= 999 ? 3 : 4);

But we'll consume an unlimited number of leading zeros, updating _Digits_seen accordingly:

STL/stl/inc/xloctime

Lines 561 to 563 in 6779b61

for (; _First != _Last && _Ctype_fac.narrow(*_First) == '0'; ++_First) { // strip leading zeros
++_Digits_seen;
}

When we reach the point of consuming digits after the leading zeros, we check _Digits_seen < _Hi_digits:

STL/stl/inc/xloctime

Lines 569 to 571 in 6779b61

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

So in this test case, the hour field %H consumes 00000 (after the T), then notices it's consumed way more than the limit of 2 digits for an hour. It succeeds, and t.tm_hour == 0, but we should have consumed only 00 to get that result.

Then, the minute field %M consumes the 6 at the end of the input.

We end up returning with only eofbit set, and never parse the %S. (Separately, I am surprised that we don't failbit when we were unable to parse all fields, but I am not an iostreams expert - this part may be intentional.)

STL/stl/inc/xloctime

Lines 134 to 139 in 6779b61

_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;
}

I believe that the leading zeros loop needs to be limited with _Digits_seen < _Hi_digits. I'm unsure whether other code changes will be necessary.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingfixedSomething works now, yay!

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions