【问题标题】:How to check that timezone exists with boost locale如何使用 boost locale 检查时区是否存在
【发布时间】: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..

标签: c++ datetime boost locale


【解决方案1】:

非编译示例的修复:

Live On Coliru

#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<class boost::locale::calendar_facet>(gen.generate(""));
}

【讨论】:

  • 请检查我的第二次更新。我从日历方面提取一个抽象日历(它在 ICU 后端实现)并从中获取时区名称。你觉得怎么样,可以吗?
  • @PavelDavydov 看起来很有希望。我会确保您比较的时区类型是std::string,以避免在审查中出现任何问题(另外,请检查argc :))。而所有这些东西只有通过详尽的测试才能通过我的审查。但是 - 至少你找到了 TZ 部分的前进方向
  • 感谢您的帮助!当然,我会在使用此代码之前解决问题并添加测试。
【解决方案2】:

这是一个alternative timezone library,可能更容易使用:

#include "tz.h"
#include <iostream>
#include <sstream>

int
main(int argc, char **argv)
{
    try
    {
        auto tz = date::locate_zone(argv[1]);
        std::cout << "good timezone " << tz->name() << std::endl;
        date::local_seconds tp;
        std::istringstream in{"2017-09-08 11:30:15"};
        in >> date::parse("%Y-%m-%d %H:%M:%S", tp);
        auto zt = date::make_zoned(tz, tp);
        std::cout << date::format("%Y-%m-%d %T %Z which is ", zt);
        std::cout << date::format("%Y-%m-%d %T %Z\n", zt.get_sys_time());
    }
    catch (std::exception const& e)
    {
        std::cout << "bad timezone " << e.what() << std::endl;
    }
}

示例输出 1:

good timezone America/New_York
2017-09-08 11:30:15 EDT which is 2017-09-08 15:30:15 UTC

示例输出 2:

bad timezone America/New_Yor not found in timezone database

【讨论】:

  • 事实上我想过,阅读文档,我喜欢这个 api。感谢您在 chrono 和 date 库上所做的工作。唯一阻止我在项目中使用日期的是我不确定它现在有多稳定?在我看来,它是未来标准化的原型,不是吗?您是否建议已经在生产环境中使用它?
  • 上一次出现重大不兼容 API 更改是 2016 年 5 月 21 日。我将 day_point 的名称更改为 sys_days。我并没有轻易做到这一点,因为当时我有很多客户。但这是必要的,我尽我所能记录下来。一个简单的搜索/替换解决了这个问题。几乎所有其他更改都向后兼容。如果我确实需要更改某些内容,我会很吵,并提供有关如何移植的说明。如果需要尝试更改以进行标准化,我会将它们浮动到std 分支上,然后合并到master
  • 是的,人们在生产中使用它,我对此很重视。随着标准化的发展,可能会有进一步的变化。但是这个实现和 API 不会很快消失。如果标准化,我将允许从这个库到标准库的多年迁移时间。最快的标准化可能是 2020 年。
  • 我尝试了日期库并决定从 boost 语言环境切换到它。日期库更干净,更易于使用。所以,我会将此标记为答案。谢谢。
猜你喜欢
  • 2011-11-25
  • 1970-01-01
  • 2023-03-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-11
  • 1970-01-01
  • 2011-02-11
相关资源
最近更新 更多