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.