Home > Software design >  strange behaviour of format flag in boost::posix_time
strange behaviour of format flag in boost::posix_time

Time:01-29

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 the FacetQuestionCode
  • 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.

  • Related