【发布时间】:2017-09-08 11:06:16
【问题描述】:
我需要根据某个时区从字符串 (%Y-%M-%d %H:%m:%s) 中解析时间。
我的第一个想法是尝试boost::date_time,但是看起来它的数据库已经过时并且时区检测算法通常是错误的。所以我决定试试boost::locale。它有ICU backend,所以时区支持应该很好。我使用以下代码:
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;
}
这很好用,但是如果我传递了一些无效的时区名称(“foo”),库会接受它,不会抛出异常,时间会被解析为 UTC 时间。这对我不好,我想以某种方式检测这种情况,以便我可以通知用户结果不会是他/她所期望的。
我的第一个想法是检查cal.get_time_zone(),但它总是返回传递给构造函数的字符串(在我的例子中是“foo”),无论它是否有效。
接下来,我尝试从生成的语言环境中提取calendar_facet,如下所示:
const auto &icu_cal = std::use_facet<boost::locale::calendar_facet>(loc);
这样我就可以访问内部abstract_calendar 类。不幸的是,这一行无法编译。原因是boost/locale/generator.hpp 在同一个boost::locale 命名空间中有一个同名的静态常量(calendar_facet)。编译器报告它无法实例化std::use_facet。也许我可以将它移动到一个单独的编译单元并避免在其中包含 generator.hpp 标头,但它对我来说似乎是一个 hack。是错误还是我在这里遗漏了什么?
有没有一种简单的方法可以使用boost::locale 验证时区名称?你一般推荐吗?感谢您的帮助。
编辑:这是一个不能为我编译的代码的最小示例
#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;
}
我是这样编译的(在 ubuntu 16.04,gcc 5.4 上):
g++ -std=c++14 -L/usr/lib/x86_64-linux-gnu/ test.cpp -lboost_locale -lboost_date_time
编辑 2:在 Sehe 的帮助下,我设法从语言环境中获取日历方面,现在我可以像这样检查时区:
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;
}
更新:虽然我设法让 boost 语言环境在 linux 上做我想做的事,但后来我将代码移植到 OS X 时遇到了一些奇怪的错误(看起来 mac 没有 ICU默认情况下后端...)。所以,我决定改用Howard Hinnant's date library。这个库质量很高,在 linux 和 mac 上都运行良好,作者乐于助人,反应迅速,强烈推荐。
【问题讨论】:
-
没有样品?没有独立的代码?
-
我不知道 Boost Locale,但 ICU 本身也有类似的怪癖:
createTimeZone()从不返回 null,但它确实返回与getUnknown()相同的值,因此您需要与之比较每次看你是否有一个“坏”的时区。在此处查看createTimeZone():icu-project.org/apiref/icu4c/… -
@sehe 我添加了一个无法为我编译的代码示例,可以吗?
-
您对某个特定技巧的问题是无法编译,还是您想要可靠的 TZ 解析?
-
@JohnZwinck 是的,我读过它,但至少在 icu 中可以与
getUnknown()进行比较。所以我想也许类似的东西可以通过 boost..