一个月的最后一天?

时间:2018-12-25 07:13:39

标签: c++ date

此示例是一种使用date库来计算一个月的最后一天的方法。 为此目标有更简单的解决方案吗?

这个想法行不通: lastDay = firstDay + months {1}-days {1};

#include <date/date.h>
#include <iostream>

using namespace std;
using namespace date;

int main() {
sys_days firstDay, lastDay;
string d1;

d1 = "2018-12-01";
istringstream in1{d1};
in1 >> parse ("%F", firstDay);

sys_days d2 = firstDay;
auto ymd = year_month_day{d2};
unsigned j = unsigned (ymd.month());
unsigned i = j;
if (i == 12)
    i = 0;
while (true) {
    d2 = d2 + days{1};
    ymd = year_month_day{d2};
    j = unsigned (ymd.month());
    if (j == i + 1)
        break;
}
lastDay = d2 - days{1};

cout << lastDay << '\n'; // 2018-12-31
return 0;
}

3 个答案:

答案 0 :(得分:3)

只需创建您自己的last_day_of_month方法:

#include <chrono>

template <class Int>
constexpr
bool
is_leap(Int y) noexcept
{
    return  y % 4 == 0 && (y % 100 != 0 || y % 400 == 0);
}

constexpr
unsigned
last_day_of_month_common_year(unsigned m) noexcept
{
    constexpr unsigned char a[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
    return a[m - 1];
}

template <class Int>
constexpr
unsigned
last_day_of_month(Int y, unsigned m) noexcept
{
    return m != 2 || !is_leap(y) ? last_day_of_month_common_year(m) : 29u;
}

int main()
{
    auto dayofmonth = last_day_of_month(2018, 6);
}

答案 1 :(得分:1)

这是一种更简单的方法:

#include "date/date.h"
#include <iostream>

date::year_month_day
lastDay(date::year_month_day ymd)
{
    return ymd.year()/ymd.month()/date::last;
}

int
main()
{
    using namespace date;
    std::cout << lastDay(2018_y/12/01) << '\n';  // 2018-12-31
}

last可以在任何year/month/day表达式中用作“日期说明符”,以表示“ year/month对的最后一天。这将创建类型year_month_day_last,该类型可隐式转换为year_month_day,也有一个day()吸气剂:

https://howardhinnant.github.io/date/date.html#year_month_day_last

答案 2 :(得分:0)

对于那些对更高性能非常感兴趣的人的答案。理想情况下,这个小问题将由广泛使用的库(例如,C/C++/Java/Python 标准库)实现,我们将简单地调用更高级别的 API。我希望这篇文章能激励库作者实现我想出的代码:

unsigned char last_day_of_month(int year, unsigned char month) {
  return month != 2 ? ((month ^ (month >> 3))) | 30 :
    is_leap_year(year) ? 29 : 28;
}

众所周知,二月是特殊的,我们需要检查 year 是否为闰年。我假设我们已经有一个非常有效的 is_leap_year 实现(这是另一个令人惊讶的 whole story)。对于 month == 2,代码应该很清楚。

让我们关注在 ((month ^ (month >> 3))) | 30 时计算的神秘部分 month != 2。在这种情况下,结果是 3031,或者换句话说,b + 30,其中 b01 .我们进一步将推理分为两个子案例,具体取决于是否 month < 8

  1. 如果 month 在 {1, 3, 4, 5, 6, 7} 中;

此集合中有 31 天 (b == 1) 的月份是 1(一月)、3(三月)、5(五月)和 { {1}}(六月),即奇数 个数字。相反,甚至个月都有 7 天 (30)。

  1. 如果 b == 0 在 {8, 9, 10, 11, 12} 中;

现在,情况正好相反:这组中具有 month 天 (31) 的月份是 b == 1(八月)、8(十月)和 {{ 1}}(十二月)因此偶数,而奇数个月有10天(12)。

我们得出结论,对于任何 30(但 b == 0),其长度取决于两件事:它的奇偶性以及它是否为 month。这些属性由 2 的两位决定:奇偶校验的第 0 位和 >= 8 的第 3 位。我们只需要检查这两个位是否不同(并获得 month)或不不同(并获得 >= 8)。这可以通过这些位之间的 XOR (b == 1) 来实现,即 b == 0 简化为 ^。由于 b = ((month >> 0) & 1) ^ ((month >> 3) & 1) 甚至我们有 b = (month ^ (month >> 3)) & 1。 (在 30b + 30 = b | 30 之间进行性能明智的选择似乎并不重要。)

表达式 + 可以进一步简化。 (感谢 Matthias Kretz 博士给我以下建议。)确实,二进制中的 |((month ^ (month >> 3)) & 1) | 30,并且假设 30,对于 0b11110,依赖于 month <= 12x = (month ^ (month >> 3)) 是最右边的,因此 x | 30 意味着可以删除 x。这产生了上面的实现。

benchmarked 以上针对使用查找数组的实现。我的似乎快了大约 10%。这看起来并不多,但请注意我的实现没有内存占用(一切都应该在寄存器中完成),因此不会与程序的其他部分竞争 L1 缓存行(减少缓存抖动)。这可能没有反映在“基准实验室条件”中,其中紧密循环所做的唯一事情就是计算该月的最后一天。生产条件可能会有所不同,性能提升会更大。

注意:

正如一些人所提到的,有一些历史怪癖使 1582 年之前的年份在世界任何地方都变得毫无意义。更糟糕的是,不同的国家在不同的时间采用了公历,因此它们几个世纪以来都不同步。因此,从历史的角度来看,大多数计算机实现(包括上述)都是错误的。然而,从实用的角度来看,他们中的大多数只是简单地推断出当前形式的公历,就好像它一直存在一样,从而产生所谓的 proleptic Gregorian calendar。我强调:预兆公历在历史上并不准确,但它在大多数应用程序中都能很好地服务