Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is std::get_time broken in g++ and clang++?

I was working with some time functions today and noticed that the standard conversion using %r (or %p) does not seem to work for input via std::get_time() on g++ or clang++. See this live code version for g++ and clang++. It does seem to work as expected under Windows with VC++ (see this closely related question). Also note that the effect is the same whether or not the imbue line is included. The locale on my Linux machine is set to "en_US.UTF-8" if it matters.

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

int main(){
    std::tm t{};
    std::stringstream ss{"1:23:45 PM"};
    ss.imbue(std::locale(std::cin.getloc(), 
             new std::time_get_byname<char>("en_US")));
    ss >> std::get_time(&t, "%r");
    if (ss.fail()) {
        std::cout << "Conversion failed\n" << std::put_time(&t, "%r") << '\n';
    } else {
        std::cout << std::put_time(&t, "%r") << '\n';
    }
}
like image 210
Edward Avatar asked May 31 '16 18:05

Edward


2 Answers

As the commenters have pointed out, this is actually a bug (an omission) in libstdc++. A bug report has been submitted.

like image 108
Edward Avatar answered Nov 16 '22 16:11

Edward


The bad news is that get_time() is more broken than the OP and the commenters thought. It definitely does not work with the "%F" format specifier (dates in ISO-8601 format, e.g. "2019-01-13") either. I modified the OP's code slightly to demonstrate this. Compiled with g++ version 7.3.0, under Ubuntu 18.04.1:

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

int main(){
    std::tm tdate{0,0,0,0,0,0,0,0,-1}; // was t{};
    std::istringstream ins{"2019-01-13"};
    ss.imbue(std::locale(std::cin.getloc(), 
             new std::time_get_byname<char>("en_US")));
    ss >> std::get_time(&tdate, "%F");
    if (ss.fail()) {
        std::cout << "Conversion failed\n" << std::put_time(&tdate, "%F") << '\n';
    } else {
        std::cout << std::put_time(&tdate, "%F") << '\n';
    }

This is the output:

Conversion failed
1900-01-00

A minor remark: it is highly advisable to zero out the std::tm object before any processing is attempted.

Update: here is a workaround which relies on the UNIX function strptime (note that this is not a C or C++ standard function!). I show the code example with the ISO8601 date parsing.

First, let's detect the GNU stdlibc++ library and pull in the <time.h> header. (On some platforms the header may be <sys/time.h>):

#include <cstddef>
#if defined(__GLIBCXX__) || defined(__GLIBCPP__)
#define GET_TIME_WORKAROUND
#include <time.h>
#endif

Then parse:

#ifdef GET_TIME_WORKAROUND
char *result = strptime(datestr.c_str(), "%F", &_cal);
if (result == nullptr) {
    throw std::invalid_argument{ "Date(" + datestr + "): ISO-8601 date expected" };
}
#else
// proper C++11
// ........
like image 44
Laryx Decidua Avatar answered Nov 16 '22 14:11

Laryx Decidua