Search code examples
c++boostboost-program-optionsboost-date-time

How can you get boost::program_options working with boost::posix_time::ptime?


I've made several attemps at getting this working to no avail. The program compiles but every timestamp format I can think of for supplying to the program is invalid.

#include <boost/program_options.hpp>
#include <boost/optional.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <iostream>
#include <string>
using namespace boost::program_options;
using namespace std;

int main(int argc, const char** argv) {
    boost::posix_time::ptime forceDate;
    boost::optional<boost::posix_time::ptime> forceDateOptional;

    options_description descr("Options");
    descr.add_options()
    ("force-date,a", value(&forceDate)->value_name("<date>"), "...");

    try {
        variables_map args;
        store(command_line_parser(argc, argv).options(descr).run(), args);
        notify(args);
        cout << "Done." << endl;
        return 0;
    } catch (std::exception& e) {
        cerr << e.what() << endl;
        return 1;
    }
}

Compiled with g++ -I /usr/local/include -L /usr/local/lib -lboost_program_options -lboost_system x.cpp and run with ./a.out --force-date 2012-01-03.

Here is the error from the exception thrown:

the argument ('2012-01-03') for option '--force-date' is invalid

Solution

  • Let's first try to simplify your example by removing program_options and simply trying to parse posix_time::ptime from a stream.

    boost::posix_time::ptime forceDate;
    std::istringstream ss("2012-01-03");
    if(ss >> forceDate) {
        std::cout << forceDate << "\n";
    }
    

    The above example prints nothing, so clearly something's going wrong with the parsing. If you look up the documentation for operator>> it becomes evident that the expected format for the month is an abbreviated name instead of a numeric value.

    If you change the above code to

    boost::posix_time::ptime forceDate;
    std::istringstream ss("2012-Jan-03");
    if(ss >> forceDate) {
        std::cout << forceDate << "\n";
    }
    

    it produces the desired output.

    More digging through the documentation shows the way to control the input format is using boost::posix_time::time_input_facet. The set_iso_extended_format sets the format to the numeric format you want to use.

    boost::posix_time::ptime forceDate;
    std::istringstream ss("2012-01-03");
    
    auto facet = new boost::posix_time::time_input_facet();
    facet->set_iso_extended_format();
    
    ss.imbue(std::locale(ss.getloc(), facet));
    if(ss >> forceDate) {
        std::cout << forceDate << "\n";
    }
    

    Live demo


    Back to program_options now. It doesn't look like the library offers locale support directly, so the only way I can get the above solution to work is by messing with the global locale. If you add the following lines to your example, it behaves as you want it to.

    auto facet = new boost::posix_time::time_input_facet();
    facet->set_iso_extended_format();
    std::locale::global(std::locale(std::locale(), facet));
    

    Live demo