Pitfalls of boost::locale::date_time

Pitfalls of boost::locale::date_time

本文关键字:time date locale boost Pitfalls of      更新时间:2023-10-16

您能否提供您在 boost 1.55 中使用 boost::locale::date_time 时遇到的陷阱,或者告诉我我在以下示例中犯了什么错误?我误解boost::locale::date_time API 吗?

day_of_week 示例 1:将日期更改为星期日

#include <iostream>
#include <boost/locale.hpp>
int main() {
  using namespace boost::locale;
  generator gen;
  std::locale locale = gen("en_US.UTF-8");
  std::locale::global(locale);
  std::cout.imbue(locale);
  date_time_period_set s;
  s.add(period::year(2013));
  s.add(period::month(2));
  s.add(period::day(5));
  s.add(period::hour(9));
  s.add(period::minute(0));
  s.add(period::second(0));
  // 2013-03-05 is a Tuesday, let's shift it to a Sunday
  s.add(period::day_of_week(1));
  date_time now(s);// why it's not 2013-03-02 or 2013-03-10?
  std::cout << now << std::endl;
}

输出:Mar 30, 2014, 9:00:00 AM,为什么不2013-03-02, 9:00:00 AM2013-03-10, 9:00:00 AM?甚至不是星期天。

day_of_week 示例 2:2013 年 3 月的最后一个星期六

#include <iostream>
#include <boost/locale.hpp>
int main() {
  using namespace boost::locale;
  generator gen;
  std::locale locale = gen("en_US.UTF-8");
  std::locale::global(locale);
  std::cout.imbue(locale);
  date_time_period_set s;
  s.add(period::year(2013));
  s.add(period::month(2));
  s.add(period::hour(9));
  s.add(period::minute(0));
  s.add(period::second(0));

  // swapping the following 2 lines rusults in a wrong date
  s.add(period::day_of_week(7));
  s.add(period::day_of_week_in_month(-1));

  date_time now(s);// last Saturday of March 2013
  std::cout << now << std::endl;
}

它输出:Mar 30, 2013, 9:00:00 AM

但是,如果您更改源代码中标记的 2 行,您可能会看到:Apr 5, 2014, 9:00:00 AM

在哪里以及如何使用date_time_period boost::locale::period::first_day_of_week(int v)

请参阅date_time_period boost::locale::p eriod::first_day_of_week(int v)。

这不是这个问题的答案。 我不精通boost::locale::date_time. 但是,无论如何我都会用可能的替代方案做出回应。 这个替代方案不像boost::date_time那样打包得很好,也没有那么完整,特别是在语言环境方面。 但是,它对您来说可能是一个可行的选择,因为您的需求看起来很简单,而且这个替代方案非常简单。

我说的是:

与时间兼容的低级日期算法

这只不过是一篇论文,概述了用于日期(不是时间)计算的 10 个非常低级的构建块,例如您问题中的那些。 上面的论文没有提供您可以下载的库。 相反,它只是列出并解释了 10 种算法。 每种算法的代码都很短,复制/粘贴到您自己的代码中并根据您的特定需求进行修改是非常实用的。 该代码属于公共领域,因此不必担心版权问题。

本文使用10种算法中的5种,结合以下用于输出日期的样板(我用std::tuple来保存日期,使用任何你喜欢的),我为您的简单问题提出了非常简单的解决方案:

// here is my copy of the 10 algorithms
#include "../date_performance/date_algorithms"
#include <string>
#include <iostream>
// I'm being lazy in defining my date class
using date = std::tuple<int, unsigned, unsigned>;
// I'm being lazy in defining I/O for my date class
std::string
to_string(date const& d)
{
    int year;
    unsigned month;
    unsigned day;
    std::tie(year, month, day) = d;
    std::string r = std::to_string(year) + '-';
    if (month < 10)
        r += '0';
    r += std::to_string(month) + '-';
    if (day < 10)
        r += '0';
    r += std::to_string(day);
    return r;
}

day_of_week 示例 1:将日期更改为星期日

以下是我如何构建日期,例如 2013 年 3 月 5 日:

date d(2013, 3, 5);

为了找到这个日期当天或之前的星期日,我首先需要找出这个日期在一周中的哪一天。 这是一个使用以下低级构建块的两步过程:

  1. 将此日期转换为序列日期:自某个纪元以来的天数。
  2. 将序列日期转换为星期几。

这是通过以下方式完成的:

int s = days_from_civil(std::get<0>(d), std::get<1>(d), std::get<2>(d));
unsigned wd = weekday_from_days(s);

或者,如果您愿意:

int s = days_from_civil(2013, 3, 5);
unsigned wd = weekday_from_days(s);

接下来,最好为星期几命名常量。 此代码遵循 C 和 C++ 约定,例如:

constexpr unsigned sun = 0;

现在,这一天前一天是星期日,是连续日期,减去此工作日已过星期日的天数(如果这是星期日,则可能是 0,如果此工作日是星期六,则可能高达 6):

date d1 = civil_from_days(s - weekday_difference(wd, sun));

当我打印出来时:

std::cout << "The Sunday before was " << to_string(d1) << 'n';

我得到:

The Sunday before was 2013-03-03

这是解决您的第一个问题的很少的代码。 如果你想要2013-03-05之后的星期日,那也很简单:

date d2 = civil_from_days(s + weekday_difference(sun, wd));
std::cout << "The Sunday after was " << to_string(d2) << 'n';

其中输出:

The Sunday after was 2013-03-10

您可以轻松地使用这些低级算法来编写自己的高级算法来计算您想要的内容。

day_of_week 示例 2:2013 年 3 月的最后一个星期六

您需要做的第一件事是找到今年本月的最后一天:

unsigned last = last_day_of_month(2013, 3);

在这个例子中,我们都知道这与

unsigned last = 31;

但是,如果年份和月份是运行时值,则last_day_of_month派上用场。 现在我们需要每月最后一天的工作日。 如故:

unsigned wd_last = weekday_from_days(days_from_civil(2013, 3, last));

现在我们只需要从last中减去最后一个工作日已经过了星期六的天数。 假设我们已经设置了一个信息常数(sat == 6),这看起来像:

unsigned day = last - weekday_difference(wd_last, sat);

day是2013年3月最后一个星期六的某一天。 我们可以用以下方法打印出来:

std::cout << "The last Saturday of March 2013 was " << to_string(date(2013, 3, day)) << 'n';

并获得:

The last Saturday of March 2013 was 2013-03-30

再说一次,你很容易将所有这些包装在一个简洁的功能中。 事实上,这篇论文甚至有这样一个示例功能。

如果这些低级日期算法对您或任何其他读者有帮助,那就太好了,否则没关系。

这是一个相当古老的问题,但我把我的发现留在这里记录在案。

查找源代码,date_time类中唯一实际使用first_day_of_week的地方是date_time::get()函数。你不能用它来建造date_time,或其他花哨的东西。唯一允许您使用它的地方是当您执行类似 mydt.get(period::first_day_of_week) 的操作时,它会返回范围为 [1,7] 的 int。它本质上与calendar::first_day_of_week()相同。

请注意,boost::locale::period::first_day_of_week(int v)中的int v将被有效忽略。 int v设置date_time_period的数量,但没有 API 会尊重 first_day_of_week 的数量值。我猜它只是自动生成的 API。

查看 ICU 文档而不是 Boost.locale 文档很有帮助,因为 Boost.locale 实际上是 ICU 之上的一层薄薄的一层。您可以在此处找到在 Boost.locale 中使用的 ICU 日历类的文档。

您的示例 1 导致 ICU 文档所指的"信息不一致"。您指定的日期和星期不一致,ICU 不会尝试使用提供的日期更正日期。它只是认为它不一致,因此显示出您提到的奇怪行为。您需要指定每月而不是天以避免这种不一致。

您的示例 2 更有趣。当您首先指定星期几时,您做对了,当您先设置星期几时,您做错了。我想逻辑是这样的:

首次将星期几设置为-1时,ICU 的信息不足。因此,与在文档中一样,它尝试将剩余字段(星期几)填充为默认值,即 1。日历设置为现在Mar 31, 2013,即2013年3月的最后一个星期日。然后您将星期几设置为 7,因此它会传输到该周的星期六,即 4 月...6?我不知道你到底为什么会Apr 5, 2014, 9:00:00 AM,但我相信逻辑过程与此类似。始终先设置星期几以避免这种奇怪的行为。

我认为ICU日历有点不直观。在SE 7之前,Java也使用与ICU日历非常相似的DateTime API,Java程序员认为标准的DateTime库是一场噩梦。(在SE 8发布时,他们用更好的API替换了类似ICU的API。

你可以考虑切换到Howard Hinnant的Date库,它有更好的API。