Home > Enterprise >  Boost datetime posix_time time_input_facet fails to parse single digit day
Boost datetime posix_time time_input_facet fails to parse single digit day

Time:10-25

When using time_input_facet boost datetime posix_time fails to parse single digit day of month. Unbeknownst to me at the time of initial posting, https://github.com/boostorg/date_time/issues/106 describes this issue, as pointed out in the accepted answer.

#include <boost/date_time/posix_time/posix_time.hpp>
#include <vector>

void date_from_string(const std::string& format, const std::string& date_str, boost::posix_time::ptime& date) {
    auto* facet = new boost::posix_time::time_input_facet;
    facet->format(format.c_str());
    std::stringstream ss(date_str);
    ss.imbue(std::locale(std::locale::classic(), facet));
    ss >> date;
    // delete facet; > "Your program has UB. delete facet; leads to double-free."
}

int main() {
    std::cout << "Boost Version: " << BOOST_VERSION << std::endl; // 107500
    // Boost Version: 107500

    for (auto& i : std::vector<std::pair<std::string,std::string>>{ 
            {"%a, %d %b %Y %H:%M:%S %ZP","Thu, 17 Oct 2021 15:00:00 GMT"},
// 2021-Oct-17 15:00:00 from str: Thu, 17 Oct 2021 15:00:00 GMT using: %a, %d %b %Y %H:%M:%S %ZP
            {"%a, %d %b %Y %H:%M:%S %ZP","Thu, 7 Oct 2021 15:00:00 GMT"},
// not-a-date-time from str: Thu, 7 Oct 2021 15:00:00 GMT using: %a, %d %b %Y %H:%M:%S %ZP
            {"%d", "7"},
            // not-a-date-time from str: 7 using: %d
            {"%d", "07"}
            // 1400-Jan-07 00:00:00 from str: 07 using: %d
        }) {
        boost::posix_time::ptime m_date;
        date_from_string(i.first, i.second,m_date); 
        std::cout << boost::posix_time::to_simple_string(m_date) << " from str: " << i.second << " using: " << i.first << std::endl;
    }
}

%d Day of the month as decimal 01 to 31. When used to parse input, the leading zero is optional. (emphasis mine)

https://www.boost.org/doc/libs/1_75_0/doc/html/date_time/date_time_io.html (format flags)

CodePudding user response:

Your program has UB. delete facet; leads to double-free.

Further more, you're only using a time input facet. I wondered whether the date input facet would be required in addition. So I extended the example to rule out some of these concerns:

Live On Compiler Explorer

#include <boost/date_time/local_time/local_time_io.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <iomanip>

using DateTime = boost::local_time::local_date_time;
using PTime    = boost::posix_time::ptime;
using Date     = boost::gregorian::date;
using boost::date_time::not_a_date_time;

void date_from_string( //
    std::string const& dformat, std::string const& dtformat,
    std::string const& date_str, DateTime& date)
{
    std::stringstream ss(date_str);
    ss.imbue(std::locale::classic());

    ss.imbue(std::locale( //
        ss.getloc(), new boost::gregorian::date_input_facet(dformat)));
    ss.imbue(std::locale(
        ss.getloc(), new boost::local_time::local_time_input_facet(dtformat)));
    ss >> date;
}

void date_from_string( //
    std::string const& dformat, std::string const& dtformat,
    std::string const& date_str, PTime& date)
{
    std::stringstream ss(date_str);
    ss.imbue(std::locale::classic());

    ss.imbue(std::locale(ss.getloc(),
                         new boost::gregorian::date_input_facet(dformat)));
    ss.imbue(std::locale(ss.getloc(),
                         new boost::posix_time::time_input_facet(dtformat)));
    ss >> date;
}

void date_from_string(std::string const& dformat, std::string const& date_str,
                      Date& date)
{
    std::stringstream ss(date_str);
    ss.imbue(std::locale::classic());

    ss.imbue(std::locale(ss.getloc(),
                         new boost::gregorian::date_input_facet(dformat)));
    ss >> date;
}

int main() {
    std::cout << "Boost Version: " << BOOST_VERSION << std::endl; // 107500

    std::string const fmt = "%a, %d %b %Y";

    struct {
        std::string dformat, dtformat, input;
    } tests[]{
        {fmt, fmt   " %H:%M:%S %ZP", "Thu, 17 Oct 2021 15:00:00 GMT"},
        {"%d", "%d", "07"},
        {fmt, fmt   " %H:%M:%S %ZP", "Thu, 7 Oct 2021 15:00:00 GMT"},
        {"%d", "%d", "7"},
    };
    for (auto& [df, dtf, input] : tests) {
        std::cout << " -- from str: " << std::quoted(input)
                  << " using: " << std::quoted(df) << "\n";
        Date     m_date;
        DateTime m_datetime(not_a_date_time);
        PTime    m_ptime(not_a_date_time);

        date_from_string(df, input, m_date);
        date_from_string(df, dtf, input, m_datetime);
        date_from_string(df, dtf, input, m_ptime);

        std::cout << " Date:  " << to_simple_string(m_date)            << "\n";
        std::cout << " Local: " << to_simple_string(m_datetime.date()) << "\n";
        std::cout << " Posix: " << to_simple_string(m_ptime.date())    << "\n";
    }
}

And the output confirms the bug:

Boost Version: 107700
 -- from str: "Thu, 17 Oct 2021 15:00:00 GMT" using: "%a, %d %b %Y"
 Date:  2021-Oct-17
 Local: 2021-Oct-17
 Posix: 2021-Oct-17
 -- from str: "07" using: "%d"
 Date:  1400-Jan-07
 Local: 1400-Jan-07
 Posix: 1400-Jan-07
 -- from str: "Thu, 7 Oct 2021 15:00:00 GMT" using: "%a, %d %b %Y"
 Date:  not-a-date-time
 Local: not-a-date-time
 Posix: not-a-date-time
 -- from str: "7" using: "%d"
 Date:  not-a-date-time
 Local: not-a-date-time
 Posix: not-a-date-time

What Next?

I'd lend support to this existing ticket: https://github.com/boostorg/date_time/issues/106

Also, I can point at this adaptive_parser I made in the past, which sidesteps the problem by not using IO streams parsing here C boost date_input_facet seems to parse dates unexpectedly with incorrect formats passed to the facet constructor

  • Related