boost :: locale :: date_time的陷阱

时间:2014-04-02 13:53:03

标签: c++ datetime boost locale

您是否可以提供在使用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::period::first_day_of_week(int v)

2 个答案:

答案 0 :(得分:3)

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

我说的是:

chrono-Compatible Low-Level Date Algorithms

这只是一篇文章,概述了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. 将序列日期转换为星期几。
  3. 这是通过以下方式完成的:

    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
    

    同样,您可以轻松地将所有这些包装在一个简洁的功能中。实际上,the paper甚至在其中具有这样的示例功能。

    如果这些低级日期算法对您或任何其他读者有帮助,那么很好,否则没关系。 : - )

答案 1 :(得分:2)

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

查找源代码,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()基本相同。

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

查看ICU文档而不是Boost.locale文档是有帮助的,因为Boost.locale实际上是ICU之上的薄层。您将找到Boost.locale here中使用的ICU日历类的文档。

您的示例1导致ICU文档引用的内容&#34;信息不一致&#34;。您指定了不一致的日期和星期几,并且ICU不会尝试使用每周提供的日期更正日期。它只是认为它不一致,因此显示你提到的奇怪行为。您需要指定一个月而不是一天来避免这种不一致。

你的例子2更有趣。当你首先指定星期几时,你就把它弄好了,当你每周设置一天的时候就错了。我猜逻辑是这样的:

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

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

您可以考虑切换到Howard Hinnant's Date library,它有更好的API。