I have a string containing two time stamps. I am trying to convert both the time stamps into boost::posix_time::ptime.
the string format looks like:
"ASTRO20220923.1435 0000-20220923.1440 0000"
the format I use in boost::posix_time::time_input_facet
for the second time stamp is ".* A%Y%m%d.%H%M"
.
I get correct output but i don’t understand how i get it. On searching the format flags i cannot find any flag as ".*"
or " A"
. Can someone please help explain why this behaviour is happening?
Benchmark code: Coliru
#include <iostream>
#include <boost/date_time/posix_time/posix_time.hpp>
namespace FacetQuestionCode {
// code from the question, already removing output and one unnecessary allocation (`timeStr` is now a
// string view)
//
// - still has unnecessary facet instantiation/destruction, locale creation/destruction, spurious double
// stream insertion and no error detection
auto parsePeriod(std::string_view timeStr) {
std::stringstream ss;
boost::posix_time::ptime pTimeStart;
ss.imbue(std::locale(ss.getloc(), new boost::posix_time::time_input_facet("ASTRO%Y%m%d.%H%M.*")));
ss << timeStr;
ss >> pTimeStart;
// std::cout << "START: " << boost::posix_time::to_simple_string(pTimeStart) << std::endl;
boost::posix_time::ptime pTimeEnd;
ss.imbue(std::locale(ss.getloc(), new boost::posix_time::time_input_facet(".* A%Y%m%d.%H%M")));
ss << timeStr;
ss >> pTimeEnd;
// std::cout << "END : " << boost::posix_time::to_simple_string(pTimeEnd) << std::endl;
return std::pair(pTimeStart, pTimeEnd);
}
}
namespace FacetNaive {
// undocumented behaviour, error-prone because not-specifc
auto parsePeriod(std::string_view input) {
std::stringstream ss{std::string(input)};
auto* facet = new boost::posix_time::time_input_facet();
ss.exceptions(std::ios::failbit | std::ios::badbit);
ss.imbue(std::locale(ss.getloc(), facet));
boost::posix_time::ptime s, e;
facet->format("?????%Y%m%d.%H%M??"); ss >> s;
facet->format("????%Y%m%d.%H%M?????"); ss >> e;
return std::pair(s, e);
}
}
namespace FacetClever {
// undocumented behaviour, error-prone because not-specifc
// very dirty optimization tricks
auto parsePeriod(std::string_view input) {
thread_local std::stringstream ss;
thread_local boost::posix_time::time_input_facet* facet = [&] {
auto tmp = new boost::posix_time::time_input_facet();
ss.exceptions(std::ios::failbit | std::ios::badbit);
ss.imbue(std::locale(ss.getloc(), tmp));
return tmp;
}();
ss.clear();
ss.str(std::string(input));
boost::posix_time::ptime s, e;
facet->format("?????%Y%m%d.%H%M??"); ss >> s;
facet->format("????%Y%m%d.%H%M?????"); ss >> e;
return std::pair(s, e);
}
}
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/fusion/adapted/std_tuple.hpp>
#include <boost/spirit/home/x3.hpp>
namespace SpiritX3 {
namespace Parser {
namespace x3 = boost::spirit::x3;
using Part = unsigned short;
using Parts = std::tuple<Part, Part, Part, Part, Part>;
static auto const dig4 = x3::uint_parser<Part, 10, 4, 4>{};
static auto const dig2 = x3::uint_parser<Part, 10, 2, 2>{};
static auto const timeformat
= x3::rule<struct partsformat_, Parts>{"timeformat"}
= dig4 >> dig2 >> dig2 >> '.' >> dig2 >> dig2 >> " 0000";
static auto const period //
= "ASTRO" >> Parser::timeformat >> "-" >> Parser::timeformat >> x3::eoi;
} // namespace Parser
boost::posix_time::ptime to_ptime(Parser::Parts const& p) {
auto [yyyy, mm, dd, HH, MM] = p;
return {{yyyy, mm, dd}, boost::posix_time::hours(HH) boost::posix_time::minutes(MM)};
}
struct Period {
boost::posix_time::ptime start, end;
};
Period parsePeriod(std::string_view input) {
Parser::Parts s, e;
auto both = std::tie(s, e);
if (parse(begin(input), end(input), Parser::period, both))
return {to_ptime(s), to_ptime(e)};
throw std::runtime_error("parsePeriod");
}
}
#define NONIUS_RUNNER
#include <nonius/benchmark.h >
#include <nonius/main.h >
constexpr auto input = "ASTRO20220923.1435 0000-20220923.1440 0000";
NONIUS_BENCHMARK("FacetQuestionCode", []{ FacetQuestionCode::parsePeriod(input); })
NONIUS_BENCHMARK("FacetNaive", []{ FacetNaive::parsePeriod(input); })
NONIUS_BENCHMARK("FacetClever", []{ FacetClever::parsePeriod(input); })
NONIUS_BENCHMARK("SpiritX3", []{ SpiritX3::parsePeriod(input); })
Note how:
- the
FacetNaive
is already 2x faster than theFacetQuestionCode
- the Too Clever By Far(TM) optimized version using facets is still 20x slower than Spirit
- all the facet versions suffer from lack of specificity and rely on undocumented behavior
- The Spirit version is effectively >11,000x faster than the original code, also with the smalles relative deviations
The choice really doesn't seem too hard.