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:
#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