I need to parse time from string (%Y-%M-%d %H:%m:%s
) according to some timezone.
My first idea was to try boost::date_time
, however it looks like its database is outdated and timezone detection algorithm is wrong in general. So I decided to try boost::locale
. It has ICU backend, so timezone support should be good. I use the following code:
namespace as = boost::locale::as;
void foo(std::string time, std::string timezone) {
auto glob = boost::locale::localization_backend_manager::global();
glob.select("icu"); // select icu backend
boost::locale::generator gen{glob};
auto loc = gen.generate(""); // generate locale with boost facets
auto cal = boost::locale::calendar{loc, timezone};
boost::locale::date_time dt{cal};
std::stringstream ss{time};
ss.imbue(loc);
std::cout.imbue(loc);
ss >> as::ftime("%Y-%m-%d %T") >> as::time_zone(timezone) >> dt;
std::cout << as::time_zone("UTC") << dt << std::endl;
std::cout << as::time_zone(timezone) << dt << std::endl;
}
This works well, however if I pass some invalid timezone name ("foo"), the library accepts it, no exception is thrown, the time is parsed as if it is UTC time. That's not good for me, I want to detect this case somehow, so that I can notify user that the result will not be what he/she expects.
My first idea was to check cal.get_time_zone()
, but it always returns the string that was passed to constructor ("foo" in my case), no matter if it's valid or not.
Next, I tried to extract calendar_facet
from the generated locale, like so:
const auto &icu_cal = std::use_facet<boost::locale::calendar_facet>(loc);
so that I can access an internal abstract_calendar
class. Unfortunately, this line doesn't compile. The reason is that boost/locale/generator.hpp
has a static constant with the same name (calendar_facet
) in the same boost::locale
namespace. The compiler reports that it can not instantiate std::use_facet
. Maybe I can move it to a separate compilation unit and avoid including generator.hpp
header there, but it looks like a hack for me. Is it a bug or I'm missing something here?
Is there a straightforward way how to validate timezone name with boost::locale
? Do you recommend it in general? Thanks for your help.
Edit: here is a minimal example of code that doesn't compile for me
#include <boost/locale.hpp>
int main() {
auto my = boost::locale::localization_backend_manager::global();
my.select("icu");
boost::locale::generator gen{my};
std::use_facet<boost::locale::calendar_facet>(gen.generate(""));
return 0;
}
I compile it like so (on ubuntu 16.04, gcc 5.4):
g++ -std=c++14 -L/usr/lib/x86_64-linux-gnu/ test.cpp -lboost_locale -lboost_date_time
Edit 2: With Sehe's help I managed to get calendar facet from locale and now can I check timezone like this:
int main(int argc, char **argv) {
auto my = boost::locale::localization_backend_manager::global();
my.select("icu");
boost::locale::generator gen{my};
auto ptr = std::unique_ptr<boost::locale::abstract_calendar>(std::use_facet<class boost::locale::calendar_facet>(gen.generate("")).create_calendar());
ptr->set_timezone(argv[1]);
// if ICU backend does not recognize timezone, it sets it to Etc/Unknown
if (ptr->get_timezone() != argv[1]) {
std::cout << "bad timezone " << ptr->get_timezone() << std::endl;
} else {
std::cout << "good timezone " << ptr->get_timezone() << std::endl;
}
return 0;
}
Update: while I managed to make boost locale do what I want on linux, I later faced some weird errors when I ported my code to OS X (it looks like mac doesn't have ICU backend by default...). So, I decided to switch to Howard Hinnant's date library instead. This library is of a high quality, works well on both linux and mac, author is helpful and responsive, so highly recommended.