What is the correct behavior of std::get_time() for "short" input

Viewed 208

I'm trying to understand what should be the correct behavior of C++11 std::get_time() when the input data is "shorter" than expected by the format string. For example, what the following program should print:

#include <ctime>
#include <iomanip>
#include <sstream>
#include <iostream>

int main (int argc, char* argv[])
{
  using namespace std;

  tm t {};
  istringstream is ("2016");
  is >> get_time (&t, "%Y %d");

  cout << "eof:  " << is.eof ()  << endl
       << "fail: " << is.fail () << endl;
}

Note that get_time() behavior is described in terms of std::time_get<CharT,InputIt>::get(). Based on the latter (see 1c paragraph) I would expect both eofbit and failbit to be set and so the program to print:

eof:  1
fail: 1

However, for all the major Standard C++ Library implementations (libstdc++ 10.2.1, libc++ 11.0.0, and MSVC 16.8) it prints:

eof:  1
fail: 0

Interestingly, that for MSVC before 16.8 it prints:

eof:  1
fail: 1

But the commit "std::get_time should not fail when format is longer than the stream" suggests that this was fixed deliberately.

Could someone clarify if (and why) the mentioned standard libraries behave correctly and, if that's the case, how it is supposed to detect that the format string was not fully used.

2 Answers

I cannot explain with 100% accuracy, but I can try to explain why the function behaves as observed.

I suspect that the eofbit is not set in your case, which is 1c, because case 1b takes precedence:

b) There was a parsing error (err != std::ios_base::goodbit)

According to The eofbit part of https://en.cppreference.com/w/cpp/io/ios_base/iostate , one of the situations when the eof bit is set is when

The std::get_time I/O manipulator and any of the std::time_get parsing functions: time_get::get, time_get::get_time, time_get::get_date etc., if the end of the stream is reached before the last character needed to parse the expected date/time value was processed.

The same source for the failbit says:

The time input manipulator std::get_time (technically, time_get::get it calls), if the input cannot be unambiguously parsed as a time value according to the given format string.

So my guess is that when the input is 2000, get tries to read it in using operator>>(std::string&), hits the eof condition and sets the eofbit. This satisfies condition 1b, so condition 1c cannot be applied.

If the function expects a year and the input is shorter than 4 digit, e.g. 200, or of it contains a space after the year, 2000 , or contains more than 4 digits, 20001, the function returns failbit. However, if the input is a 4-digit number starting with 0's, e.g. 0005, the function returns eofbit == 1, failbit == 0. This is in accordance with the specification of %Y format specifier:

parses full year as a 4 digit decimal number, leading zeroes permitted but not required

So I hope this explains why sometimes condition 1c is not taken into account. We can detect that the format string has not been fully used in a usual way, by testing the good() member function. I believe telling the difference between the function returning failbit == 1 or 0 is of very little practical importance. I also believe the standard is imprecise here, but if we assume that the user is interested in the value of good(), this lack of precision is of no practical relevance.

It is also possible that the value of failbit in the case you consider is implementation-defined: an implementation could try and read exactly 4 characters to satisfy the %Y format specifier, in which case the eofbit would not be set. But this is only my guess.

EDIT Look at this modification of your program:

int main (int argc, char* argv[])
{
  using namespace std;

  tm t {};
  istringstream is ("2016");
//  is >> get_time (&t, "%Y %d");
  std::string s;
  is >> s;

  cout << "eof:  " << is.eof ()  << endl
       << "fail: " << is.fail () << endl;
}

I replaced get_time with std::string, but the behavior did not change! The string has been read in to its end, so the stream state cannot be set to fail; however, it hit the end-of-file, so the eofbit has been set!

eof: 1
fail: 0

What I'm saying is that a similar phenomenon can take place inside get_time and then the stream's state is propagated up to the result of get_time.

Ok, it seems that all the mentioned implementations behave according to the C++11 standard.

Here is my understanding of what happens in the above program.

std::get_time() does all the preparations and calls std::time_get<CharT,InputIt>::get().

Since the first format string character is '%', the get() function calls do_get() at the first iteration of the parsing loop.

do_get() reads "2016" while processing the %Y specifier and fills the respective field in the time object. Besides that, it sets eofbit according to the standard, since "the end of the input stream is reached after reading a character". This makes get() function to bail out from the loop after the do_get() call due to 1b condition (see get() for details), with only eofbit set for the stream. Note that the format part that follows %Y is fully ignored.

But if we, for example, change the input stream from "2016" to "2016 " (append the space character), then do_get() doesn't set eofbit, get() reads/matches the spaces in the stream and format after the do_get() call, and then bails out due to 1c condition with both eofbit and failbit set.

Generally reading with std::get_time() seems to succeed (failbit is not set) when either format string is fully matched against the stream (which may still have some data in it) or if the end of the stream is reached after a conversion specifier was successfully applied (with the rest of the format string ignored).

Related