此示例是一种使用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;
}
答案 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
。在这种情况下,结果是 30
或 31
,或者换句话说,b + 30
,其中 b
是 0
或 1
.我们进一步将推理分为两个子案例,具体取决于是否 month < 8
:
month
在 {1, 3, 4, 5, 6, 7} 中;此集合中有 31
天 (b == 1
) 的月份是 1
(一月)、3
(三月)、5
(五月)和 { {1}}(六月),即奇数 个数字。相反,甚至个月都有 7
天 (30
)。
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
。 (在 30
或 b + 30 = b | 30
之间进行性能明智的选择似乎并不重要。)
表达式 +
可以进一步简化。 (感谢 Matthias Kretz 博士给我以下建议。)确实,二进制中的 |
是 ((month ^ (month >> 3)) & 1) | 30
,并且假设 30
,对于 0b11110
,依赖于 month <= 12
的 x = (month ^ (month >> 3))
是最右边的,因此 x | 30
意味着可以删除 x
。这产生了上面的实现。
我 benchmarked 以上针对使用查找数组的实现。我的似乎快了大约 10%。这看起来并不多,但请注意我的实现没有内存占用(一切都应该在寄存器中完成),因此不会与程序的其他部分竞争 L1 缓存行(减少缓存抖动)。这可能没有反映在“基准实验室条件”中,其中紧密循环所做的唯一事情就是计算该月的最后一天。生产条件可能会有所不同,性能提升会更大。
注意:
正如一些人所提到的,有一些历史怪癖使 1582 年之前的年份在世界任何地方都变得毫无意义。更糟糕的是,不同的国家在不同的时间采用了公历,因此它们几个世纪以来都不同步。因此,从历史的角度来看,大多数计算机实现(包括上述)都是错误的。然而,从实用的角度来看,他们中的大多数只是简单地推断出当前形式的公历,就好像它一直存在一样,从而产生所谓的 proleptic Gregorian calendar。我强调:预兆公历在历史上并不准确,但它在大多数应用程序中都能很好地服务。