两个 UNIX 时间戳之间的人类可读持续时间
Human-readable duration between two UNIX timestamps
我正在尝试计算两个 UNIX 时间戳之间的差异(即自 1970 年以来的秒数)。我想说例如“3 年、4 个月、6 天等”,但我不知道如何计算不同持续时间的闰年和月份。这肯定是一个已解决的问题..?
这与其他问题不同,其他问题想要在具有 fixed/homogeneous 持续时间(例如 3 小时或 7 周等)的一个单位中表达大致持续时间。 1 月 1 日到 2 月 1 日的结果将是“1 个月”,2 月 1 日到 3 月 1 日的结果也将是“1 个月”,即使天数不同。
我想精确地表达完整的持续时间,但在 years/months/days/hours/minutes 中。 C++ 中的解决方案将不胜感激!
回复:“确定这是一个已解决的问题吗?”
如果 Wolfram Alpha 正确,此问题似乎已解决。 WA 似乎没有公开提供他们的方法,他们的网站故意让屏幕抓取变得困难,但它 有 一种方法,可以在线使用 "black box"。
要查看运行中的黑框,请转至 Wolfram Alpha,然后输入两个日期,以 "to" 分隔,例如:
7/12/1900 to 2/11/2000
输出:
99 years 6 months 30 days
一天中的时间还有:
7/12/1900 3:24:07pm to 2/11/2000 9:21:06am
输出:
99 years 6 months 29 days 17 hours 56 minutes 59 seconds
请注意,Wolfram 假定默认的 EST 时区。也可以输入位置,这里是后一个示例的相同数字范围,但在不同的位置和时区:
7/12/1900 3:24:07pm in Boston to 2/11/2000 9:21:06am in Hong Kong
输出与之前的答案相差 13 小时:
99 years 6 months 29 days 4 hours 56 minutes 59 seconds
对于较旧的日期,使用闰年公式开始计算从 Unix 开始日期(1970 年 1 月 1 日)到您的第一个时间戳的天数
(对于闰秒,不知道您是否需要如此精确,希望会超出范围?)
通过将日期限制在公元 1600 年之后计算的闰年和
公历的算法来自:
http://en.wikipedia.org/wiki/Leap_year 个
if year modulo 400 is 0
then is_leap_year
else if year modulo 100 is 0
then not_leap_year
else if year modulo 4 is 0
then is_leap_year
else
not_leap_year
如果年份是闰年,那么2月有29天,否则28天
现在您知道第一个变量的月份,day_of_month,年份
接下来,使用闰年公式计算第二个时间戳的另一组天数。
typedef struct {
int year;
int month;
int dayOfMonth;
} date_struct;
static int days_in_month[2][13] = {
{0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
{0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
};
int isLeapYear(int year) {
int value;
value = year;
//if year modulo 400 is 0
// then is_leap_year
value = year % 400;
if(value == 0) return 1;
//else if year modulo 100 is 0
// then not_leap_year
value = year % 100;
if(value == 0) return 0;
//else if year modulo 4 is 0
// then is_leap_year
value = year % 4;
if(value == 0) return 1;
//else
// not_leap_year
return 0;
}
date_struct addOneDay(date_struct ds, int isLeapYear){
int daysInMonth;
ds.dayOfMonth++;
//If the month is February test for leap year and adjust daysInMonth
if(ds.month == 2) {
daysInMonth = days_in_month[isLeapYear][ds.month];
} else {
daysInMonth = days_in_month[0][ds.month];
}
if(ds.dayOfMonth > daysInMonth) {
ds.month++;
ds.dayOfMonth = 1;
if(ds.month > 12) {
ds.year += 1;
ds.month = 1;
}
}
return ds;
}
long daysBetween(date_struct date1, date_struct date2){
long result = 0l;
date_struct minDate = min(date1, date2);
date_struct maxDate = max(date1, date2);
date_struct countingDate;
countingDate.year = minDate.year;
countingDate.month = minDate.month;
countingDate.dayOfMonth = minDate.dayOfMonth;
int leapYear = isLeapYear(countingDate.year);
int countingYear = countingDate.year;
while(isLeftDateSmaller(countingDate,maxDate)) {
countingDate = addOneDay(countingDate,leapYear);
//if the year changes while counting, check to see if
//it is a new year
if(countingYear != countingDate.year) {
countingYear = countingDate.year;
leapYear = isLeapYear(countingDate.year);
}
result++;
}
return result;
}
(我在 C/C++ 中写了一个开源程序,它给出了两个日历日期之间的差异。它的源代码,我在上面提出的一些,可能会帮助你启发自己的解决方案,或者你也可以改编其中的一些 http://mrflash818.geophile.net/software/timediff/ )
使用这个free, open-source C++11/14 date/time library,这个问题可以用非常高级的语法非常简单地解决。它利用了 C++11 <chrono>
库。
由于相对较少的人熟悉我的 date library 是如何工作的,我将逐一详细介绍如何做到这一点。然后最后我把它们打包成一个简洁的函数。
为了演示它,我将假设一些有用的使用声明来减少冗长:
#include "date.h"
#include <iostream>
int
main()
{
using namespace date;
using namespace std::chrono;
还有一些示例 UNIX timestamps 可以使用。
auto t0 = sys_days{1970_y/7/28} + 8h + 0min + 0s;
auto t1 = sys_days{2016_y/4/2} + 2h + 34min + 43s;
std::cout << t0.time_since_epoch().count() << '\n';
std::cout << t1.time_since_epoch().count() << '\n';
这将打印出:
18000000
1459564483
这表示 1970-07-28 08:00:00 UTC 是纪元后 18000000 秒,2016-04-02 02:34:43 UTC 是纪元后 1459564483 秒。这都忽略了闰秒。即与UNIX timestamps的工作方式一致,也与std::chrono::system_clock::now()
.
的操作一致
接下来,方便的是 "coarsen" 这些精度为秒的时间戳到精度为天的时间戳(自纪元以来的天数)。这是通过以下代码完成的:
auto dp0 = floor<days>(t0);
auto dp1 = floor<days>(t1);
dp0
和 dp1
的类型是 std::chrono::time_point<std::chrono::system_clock, std::chrono::duration<int, std::ratio<86400>>>
,真是满口!是不是auto
不错!有一个名为 date:: sys_days
的 typedef
,它是 dp0
和 dp1
类型的便捷快捷方式,因此您永远不必输入丑陋的形式。
接下来可以方便地将dp0
和dp1
转换为{year, month, day}
结构。有这样一个类型为 date::year_month_day
的结构,它将从 sys_days
:
隐式转换
year_month_day ymd0 = dp0;
year_month_day ymd1 = dp1;
这是一个非常简单的结构,包含 year()
、month()
和 day()
吸气剂。
对于这些UNIX timestamps,从午夜开始的时间也很方便。这很容易通过从秒分辨率 time_point
:
中减去天分辨率 time_point
得到
auto time0 = t0 - dp0;
auto time1 = t1 - dp1;
time0
和 time1
的类型为 std::chrono::seconds
,代表 t0
和 t1
.[=119= 当天开始后的秒数]
为了验证我们在哪里,输出我们到目前为止的内容很方便:
std::cout << ymd0 << ' ' << make_time(time0) << '\n';
std::cout << ymd1 << ' ' << make_time(time1) << '\n';
输出:
1970-07-28 08:00:00
2016-04-02 02:34:43
到目前为止一切顺利。我们有两个 UNIX timestamps 分为人类可读的部分(至少年、月和日)。上面 print 语句中显示的函数 make_time
接受 seconds
并将其转换为 {hours, minutes, seconds}
结构。
好的,但到目前为止我们所做的只是采用 UNIX timestamps 并将它们转换为字段类型。现在是差异部分...
为了获得人类可读的差异,我们从大单位开始并减去它们。然后,如果下一个最小单位不可减去(如果减法会产生负结果),则更大单位的减法太大了。跟着我,代码+例子比人类语言更清晰:
auto dy = ymd1.year() - ymd0.year();
ymd0 += dy;
dp0 = ymd0;
t0 = dp0 + time0;
if (t0 > t1)
{
--dy;
ymd0 -= years{1};
dp0 = ymd0;
t0 = dp0 + time0;
}
std::cout << dy.count() << " years\n";
std::cout << ymd0 << ' ' << make_time(time0) << '\n';
std::cout << ymd1 << ' ' << make_time(time1) << '\n';
这输出:
45 years
2015-07-28 08:00:00
2016-04-02 02:34:43
首先,我们取 ymd1
和 ymd0
的 year
字段之间的差异,并将其存储在类型为 date::years
的变量 dy
中。然后我们将 dy
添加回 ymd0
并重新计算序列 time_point
s dp0
和 t0
。如果结果是 t0 > t1
那么我们加了太多年(因为 ymd0
的 month/day 出现在比 ymd1
更晚的年份。所以我们减去一年并重新计算。
现在我们有了年差,我们已经将问题简化为找到 {months, days, hours, minutes, seconds}
的差值,并且这个差值保证小于 1 年。
这就是整道题的基本公式!现在我们只需要冲洗并用较小的单元重复:
auto dm = ymd1.year()/ymd1.month() - ymd0.year()/ymd0.month();
ymd0 += dm;
dp0 = ymd0;
t0 = dp0 + time0;
if (t0 > t1)
{
--dm;
ymd0 -= months{1};
dp0 = ymd0;
t0 = dp0 + time0;
}
std::cout << dm.count() << " months\n";
std::cout << ymd0 << ' ' << make_time(time0) << '\n';
std::cout << ymd1 << ' ' << make_time(time1) << '\n';
这个例子的第一行值得特别注意,因为这里发生了很多事情。我们需要以月为单位找出 ymd1
和 ymd0
之间的差异。从 ymd1.month()
中减去 ymd0.month()
只有在 ymd1.month() >= ymd0.month()
时才有效。但是 ymd1.year()/ymd1.month()
创建了一个 date::year_month
类型。这些类型是 "time points",但精度为一个月。可以减去这些类型并得到 months
作为结果。
现在遵循相同的公式:将月份差加回到 ymd0
,重新计算 dp0
和 t0
,然后发现您添加的月份是否过多。如果是这样,请少加一个月。以上代码输出:
8 months
2016-03-28 08:00:00
2016-04-02 02:34:43
现在我们要找出两个日期之间 {days, hours, minutes, seconds}
的差异。
auto dd = dp1 - dp0;
dp0 += dd;
ymd0 = dp0;
t0 = dp0 + time0;
if (t0 > t1)
{
--dd;
dp0 -= days{1};
ymd0 = dp0;
t0 = dp0 + time0;
}
std::cout << dd.count() << " days\n";
std::cout << ymd0 << ' ' << make_time(time0) << '\n';
std::cout << ymd1 << ' ' << make_time(time1) << '\n';
现在关于 sys_days
s 的有趣之处在于他们非常擅长面向天的算术。因此,我们在此级别处理 sys_days
,而不是处理 year_month_day
或 year_month
等字段类型。我们只需减去 dp1 - dp0
即可得到 days
的差值。然后我们将其添加到 dp0
,并重新创建 ymd0
和 t0
。检查 t0 > t1
是否存在,如果是,我们添加的 days
太多了,所以我们推迟了一天。此代码输出:
4 days
2016-04-01 08:00:00
2016-04-02 02:34:43
现在我们要找出两个时间戳之间 {hours, minutes, seconds}
的区别。这真的很简单,<chrono>
的亮点。
auto delta_time = time1 - time0;
if (time0 > time1)
delta_time += days{1};
auto dt = make_time(delta_time);
std::cout << dt.hours().count() << " hours\n";
std::cout << dt.minutes().count() << " minutes\n";
std::cout << dt.seconds().count() << " seconds\n";
t0 += delta_time;
dp0 = floor<days>(t0);
ymd0 = dp0;
time0 = t0 - dp0;
std::cout << ymd0 << ' ' << make_time(time0) << '\n';
std::cout << ymd1 << ' ' << make_time(time1) << '\n';
我们可以从 time1
中减去 time0
。 time1
和 time0
都具有类型 std::chrono::seconds
并且它们的差异具有相同的类型。如果结果是time0 > time1
(如本例),我们需要添加一个day
。然后我们可以将差异加回去并重新计算 time0
、dp0
和 ymd0
以检查我们的工作。我们现在应该得到与 t1
相同的时间戳。此代码输出:
18 hours
34 minutes
43 seconds
2016-04-02 02:34:43
2016-04-02 02:34:43
这是对这段代码的冗长解释:
#include "date.h"
#include <iostream>
struct ymdhms
{
date::years y;
date::months m;
date::days d;
std::chrono::hours h;
std::chrono::minutes min;
std::chrono::seconds s;
};
std::ostream&
operator<<(std::ostream& os, const ymdhms& x)
{
os << x.y.count() << " years "
<< x.m.count() << " months "
<< x.d.count() << " days "
<< x.h.count() << " hours "
<< x.min.count() << " minutes "
<< x.s.count() << " seconds";
return os;
}
using second_point =
std::chrono::time_point<std::chrono::system_clock, std::chrono::seconds>;
ymdhms
human_readable_difference(second_point t1, second_point t0)
{
using namespace date;
auto dp0 = floor<days>(t0);
auto dp1 = floor<days>(t1);
year_month_day ymd0 = dp0;
year_month_day ymd1 = dp1;
auto time0 = t0 - dp0;
auto time1 = t1 - dp1;
auto dy = ymd1.year() - ymd0.year();
ymd0 += dy;
dp0 = ymd0;
t0 = dp0 + time0;
if (t0 > t1)
{
--dy;
ymd0 -= years{1};
dp0 = ymd0;
t0 = dp0 + time0;
}
auto dm = ymd1.year()/ymd1.month() - ymd0.year()/ymd0.month();
ymd0 += dm;
dp0 = ymd0;
t0 = dp0 + time0;
if (t0 > t1)
{
--dm;
ymd0 -= months{1};
dp0 = ymd0;
t0 = dp0 + time0;
}
auto dd = dp1 - dp0;
dp0 += dd;
t0 = dp0 + time0;
if (t0 > t1)
{
--dd;
dp0 -= days{1};
t0 = dp0 + time0;
}
auto delta_time = time1 - time0;
if (time0 > time1)
delta_time += days{1};
auto dt = make_time(delta_time);
return {dy, dm, dd, dt.hours(), dt.minutes(), dt.seconds()};
}
可以这样练习:
int
main()
{
std::cout << human_readable_difference(second_point{1459564483s},
second_point{18000000s}) << '\n';
}
并输出:
45 years 8 months 4 days 18 hours 34 minutes 43 seconds
所有这些背后的算法都是 public 域,并在此处整齐地收集和解释:
http://howardhinnant.github.io/date_algorithms.html
我想强调一下,这道题是一道很好但复杂的题,因为年、月等单位不统一。处理此类问题的最有效方法是拥有能够使用高级语法抽象出复杂的低级算术的低级工具。
作为补充验证,这:
std::cout << human_readable_difference(sys_days{feb/11/2000} + 9h + 21min + 6s,
sys_days{jul/12/1900} + 15h + 24min + 7s) << '\n';
输出:
99 years 6 months 29 days 17 hours 56 minutes 59 seconds
与 that reports what Wolfram Alpha outputs. As a bonus, the syntax here does not -- and can not -- suffer from endian ambiguity (m/d/y vs d/m/y). Admittedly this involved a little luck in that Wolfram's 中报告的输出相同,使用 "America/New_York" 时区报告输出,并且对于这两个时间戳,UTC 偏移量相同(因此时区偏移量不影响).
如果时区确实很重要,则需要在此之上添加 additional software layer。
我正在尝试计算两个 UNIX 时间戳之间的差异(即自 1970 年以来的秒数)。我想说例如“3 年、4 个月、6 天等”,但我不知道如何计算不同持续时间的闰年和月份。这肯定是一个已解决的问题..?
这与其他问题不同,其他问题想要在具有 fixed/homogeneous 持续时间(例如 3 小时或 7 周等)的一个单位中表达大致持续时间。 1 月 1 日到 2 月 1 日的结果将是“1 个月”,2 月 1 日到 3 月 1 日的结果也将是“1 个月”,即使天数不同。
我想精确地表达完整的持续时间,但在 years/months/days/hours/minutes 中。 C++ 中的解决方案将不胜感激!
回复:“确定这是一个已解决的问题吗?”
如果 Wolfram Alpha 正确,此问题似乎已解决。 WA 似乎没有公开提供他们的方法,他们的网站故意让屏幕抓取变得困难,但它 有 一种方法,可以在线使用 "black box"。
要查看运行中的黑框,请转至 Wolfram Alpha,然后输入两个日期,以 "to" 分隔,例如:
7/12/1900 to 2/11/2000
输出:
99 years 6 months 30 days
一天中的时间还有:
7/12/1900 3:24:07pm to 2/11/2000 9:21:06am
输出:
99 years 6 months 29 days 17 hours 56 minutes 59 seconds
请注意,Wolfram 假定默认的 EST 时区。也可以输入位置,这里是后一个示例的相同数字范围,但在不同的位置和时区:
7/12/1900 3:24:07pm in Boston to 2/11/2000 9:21:06am in Hong Kong
输出与之前的答案相差 13 小时:
99 years 6 months 29 days 4 hours 56 minutes 59 seconds
对于较旧的日期,使用闰年公式开始计算从 Unix 开始日期(1970 年 1 月 1 日)到您的第一个时间戳的天数
(对于闰秒,不知道您是否需要如此精确,希望会超出范围?)
通过将日期限制在公元 1600 年之后计算的闰年和 公历的算法来自: http://en.wikipedia.org/wiki/Leap_year 个
if year modulo 400 is 0
then is_leap_year
else if year modulo 100 is 0
then not_leap_year
else if year modulo 4 is 0
then is_leap_year
else
not_leap_year
如果年份是闰年,那么2月有29天,否则28天
现在您知道第一个变量的月份,day_of_month,年份
接下来,使用闰年公式计算第二个时间戳的另一组天数。
typedef struct {
int year;
int month;
int dayOfMonth;
} date_struct;
static int days_in_month[2][13] = {
{0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
{0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
};
int isLeapYear(int year) {
int value;
value = year;
//if year modulo 400 is 0
// then is_leap_year
value = year % 400;
if(value == 0) return 1;
//else if year modulo 100 is 0
// then not_leap_year
value = year % 100;
if(value == 0) return 0;
//else if year modulo 4 is 0
// then is_leap_year
value = year % 4;
if(value == 0) return 1;
//else
// not_leap_year
return 0;
}
date_struct addOneDay(date_struct ds, int isLeapYear){
int daysInMonth;
ds.dayOfMonth++;
//If the month is February test for leap year and adjust daysInMonth
if(ds.month == 2) {
daysInMonth = days_in_month[isLeapYear][ds.month];
} else {
daysInMonth = days_in_month[0][ds.month];
}
if(ds.dayOfMonth > daysInMonth) {
ds.month++;
ds.dayOfMonth = 1;
if(ds.month > 12) {
ds.year += 1;
ds.month = 1;
}
}
return ds;
}
long daysBetween(date_struct date1, date_struct date2){
long result = 0l;
date_struct minDate = min(date1, date2);
date_struct maxDate = max(date1, date2);
date_struct countingDate;
countingDate.year = minDate.year;
countingDate.month = minDate.month;
countingDate.dayOfMonth = minDate.dayOfMonth;
int leapYear = isLeapYear(countingDate.year);
int countingYear = countingDate.year;
while(isLeftDateSmaller(countingDate,maxDate)) {
countingDate = addOneDay(countingDate,leapYear);
//if the year changes while counting, check to see if
//it is a new year
if(countingYear != countingDate.year) {
countingYear = countingDate.year;
leapYear = isLeapYear(countingDate.year);
}
result++;
}
return result;
}
(我在 C/C++ 中写了一个开源程序,它给出了两个日历日期之间的差异。它的源代码,我在上面提出的一些,可能会帮助你启发自己的解决方案,或者你也可以改编其中的一些 http://mrflash818.geophile.net/software/timediff/ )
使用这个free, open-source C++11/14 date/time library,这个问题可以用非常高级的语法非常简单地解决。它利用了 C++11 <chrono>
库。
由于相对较少的人熟悉我的 date library 是如何工作的,我将逐一详细介绍如何做到这一点。然后最后我把它们打包成一个简洁的函数。
为了演示它,我将假设一些有用的使用声明来减少冗长:
#include "date.h"
#include <iostream>
int
main()
{
using namespace date;
using namespace std::chrono;
还有一些示例 UNIX timestamps 可以使用。
auto t0 = sys_days{1970_y/7/28} + 8h + 0min + 0s;
auto t1 = sys_days{2016_y/4/2} + 2h + 34min + 43s;
std::cout << t0.time_since_epoch().count() << '\n';
std::cout << t1.time_since_epoch().count() << '\n';
这将打印出:
18000000
1459564483
这表示 1970-07-28 08:00:00 UTC 是纪元后 18000000 秒,2016-04-02 02:34:43 UTC 是纪元后 1459564483 秒。这都忽略了闰秒。即与UNIX timestamps的工作方式一致,也与std::chrono::system_clock::now()
.
接下来,方便的是 "coarsen" 这些精度为秒的时间戳到精度为天的时间戳(自纪元以来的天数)。这是通过以下代码完成的:
auto dp0 = floor<days>(t0);
auto dp1 = floor<days>(t1);
dp0
和 dp1
的类型是 std::chrono::time_point<std::chrono::system_clock, std::chrono::duration<int, std::ratio<86400>>>
,真是满口!是不是auto
不错!有一个名为 date:: sys_days
的 typedef
,它是 dp0
和 dp1
类型的便捷快捷方式,因此您永远不必输入丑陋的形式。
接下来可以方便地将dp0
和dp1
转换为{year, month, day}
结构。有这样一个类型为 date::year_month_day
的结构,它将从 sys_days
:
year_month_day ymd0 = dp0;
year_month_day ymd1 = dp1;
这是一个非常简单的结构,包含 year()
、month()
和 day()
吸气剂。
对于这些UNIX timestamps,从午夜开始的时间也很方便。这很容易通过从秒分辨率 time_point
:
time_point
得到
auto time0 = t0 - dp0;
auto time1 = t1 - dp1;
time0
和 time1
的类型为 std::chrono::seconds
,代表 t0
和 t1
.[=119= 当天开始后的秒数]
为了验证我们在哪里,输出我们到目前为止的内容很方便:
std::cout << ymd0 << ' ' << make_time(time0) << '\n';
std::cout << ymd1 << ' ' << make_time(time1) << '\n';
输出:
1970-07-28 08:00:00
2016-04-02 02:34:43
到目前为止一切顺利。我们有两个 UNIX timestamps 分为人类可读的部分(至少年、月和日)。上面 print 语句中显示的函数 make_time
接受 seconds
并将其转换为 {hours, minutes, seconds}
结构。
好的,但到目前为止我们所做的只是采用 UNIX timestamps 并将它们转换为字段类型。现在是差异部分...
为了获得人类可读的差异,我们从大单位开始并减去它们。然后,如果下一个最小单位不可减去(如果减法会产生负结果),则更大单位的减法太大了。跟着我,代码+例子比人类语言更清晰:
auto dy = ymd1.year() - ymd0.year();
ymd0 += dy;
dp0 = ymd0;
t0 = dp0 + time0;
if (t0 > t1)
{
--dy;
ymd0 -= years{1};
dp0 = ymd0;
t0 = dp0 + time0;
}
std::cout << dy.count() << " years\n";
std::cout << ymd0 << ' ' << make_time(time0) << '\n';
std::cout << ymd1 << ' ' << make_time(time1) << '\n';
这输出:
45 years
2015-07-28 08:00:00
2016-04-02 02:34:43
首先,我们取 ymd1
和 ymd0
的 year
字段之间的差异,并将其存储在类型为 date::years
的变量 dy
中。然后我们将 dy
添加回 ymd0
并重新计算序列 time_point
s dp0
和 t0
。如果结果是 t0 > t1
那么我们加了太多年(因为 ymd0
的 month/day 出现在比 ymd1
更晚的年份。所以我们减去一年并重新计算。
现在我们有了年差,我们已经将问题简化为找到 {months, days, hours, minutes, seconds}
的差值,并且这个差值保证小于 1 年。
这就是整道题的基本公式!现在我们只需要冲洗并用较小的单元重复:
auto dm = ymd1.year()/ymd1.month() - ymd0.year()/ymd0.month();
ymd0 += dm;
dp0 = ymd0;
t0 = dp0 + time0;
if (t0 > t1)
{
--dm;
ymd0 -= months{1};
dp0 = ymd0;
t0 = dp0 + time0;
}
std::cout << dm.count() << " months\n";
std::cout << ymd0 << ' ' << make_time(time0) << '\n';
std::cout << ymd1 << ' ' << make_time(time1) << '\n';
这个例子的第一行值得特别注意,因为这里发生了很多事情。我们需要以月为单位找出 ymd1
和 ymd0
之间的差异。从 ymd1.month()
中减去 ymd0.month()
只有在 ymd1.month() >= ymd0.month()
时才有效。但是 ymd1.year()/ymd1.month()
创建了一个 date::year_month
类型。这些类型是 "time points",但精度为一个月。可以减去这些类型并得到 months
作为结果。
现在遵循相同的公式:将月份差加回到 ymd0
,重新计算 dp0
和 t0
,然后发现您添加的月份是否过多。如果是这样,请少加一个月。以上代码输出:
8 months
2016-03-28 08:00:00
2016-04-02 02:34:43
现在我们要找出两个日期之间 {days, hours, minutes, seconds}
的差异。
auto dd = dp1 - dp0;
dp0 += dd;
ymd0 = dp0;
t0 = dp0 + time0;
if (t0 > t1)
{
--dd;
dp0 -= days{1};
ymd0 = dp0;
t0 = dp0 + time0;
}
std::cout << dd.count() << " days\n";
std::cout << ymd0 << ' ' << make_time(time0) << '\n';
std::cout << ymd1 << ' ' << make_time(time1) << '\n';
现在关于 sys_days
s 的有趣之处在于他们非常擅长面向天的算术。因此,我们在此级别处理 sys_days
,而不是处理 year_month_day
或 year_month
等字段类型。我们只需减去 dp1 - dp0
即可得到 days
的差值。然后我们将其添加到 dp0
,并重新创建 ymd0
和 t0
。检查 t0 > t1
是否存在,如果是,我们添加的 days
太多了,所以我们推迟了一天。此代码输出:
4 days
2016-04-01 08:00:00
2016-04-02 02:34:43
现在我们要找出两个时间戳之间 {hours, minutes, seconds}
的区别。这真的很简单,<chrono>
的亮点。
auto delta_time = time1 - time0;
if (time0 > time1)
delta_time += days{1};
auto dt = make_time(delta_time);
std::cout << dt.hours().count() << " hours\n";
std::cout << dt.minutes().count() << " minutes\n";
std::cout << dt.seconds().count() << " seconds\n";
t0 += delta_time;
dp0 = floor<days>(t0);
ymd0 = dp0;
time0 = t0 - dp0;
std::cout << ymd0 << ' ' << make_time(time0) << '\n';
std::cout << ymd1 << ' ' << make_time(time1) << '\n';
我们可以从 time1
中减去 time0
。 time1
和 time0
都具有类型 std::chrono::seconds
并且它们的差异具有相同的类型。如果结果是time0 > time1
(如本例),我们需要添加一个day
。然后我们可以将差异加回去并重新计算 time0
、dp0
和 ymd0
以检查我们的工作。我们现在应该得到与 t1
相同的时间戳。此代码输出:
18 hours
34 minutes
43 seconds
2016-04-02 02:34:43
2016-04-02 02:34:43
这是对这段代码的冗长解释:
#include "date.h"
#include <iostream>
struct ymdhms
{
date::years y;
date::months m;
date::days d;
std::chrono::hours h;
std::chrono::minutes min;
std::chrono::seconds s;
};
std::ostream&
operator<<(std::ostream& os, const ymdhms& x)
{
os << x.y.count() << " years "
<< x.m.count() << " months "
<< x.d.count() << " days "
<< x.h.count() << " hours "
<< x.min.count() << " minutes "
<< x.s.count() << " seconds";
return os;
}
using second_point =
std::chrono::time_point<std::chrono::system_clock, std::chrono::seconds>;
ymdhms
human_readable_difference(second_point t1, second_point t0)
{
using namespace date;
auto dp0 = floor<days>(t0);
auto dp1 = floor<days>(t1);
year_month_day ymd0 = dp0;
year_month_day ymd1 = dp1;
auto time0 = t0 - dp0;
auto time1 = t1 - dp1;
auto dy = ymd1.year() - ymd0.year();
ymd0 += dy;
dp0 = ymd0;
t0 = dp0 + time0;
if (t0 > t1)
{
--dy;
ymd0 -= years{1};
dp0 = ymd0;
t0 = dp0 + time0;
}
auto dm = ymd1.year()/ymd1.month() - ymd0.year()/ymd0.month();
ymd0 += dm;
dp0 = ymd0;
t0 = dp0 + time0;
if (t0 > t1)
{
--dm;
ymd0 -= months{1};
dp0 = ymd0;
t0 = dp0 + time0;
}
auto dd = dp1 - dp0;
dp0 += dd;
t0 = dp0 + time0;
if (t0 > t1)
{
--dd;
dp0 -= days{1};
t0 = dp0 + time0;
}
auto delta_time = time1 - time0;
if (time0 > time1)
delta_time += days{1};
auto dt = make_time(delta_time);
return {dy, dm, dd, dt.hours(), dt.minutes(), dt.seconds()};
}
可以这样练习:
int
main()
{
std::cout << human_readable_difference(second_point{1459564483s},
second_point{18000000s}) << '\n';
}
并输出:
45 years 8 months 4 days 18 hours 34 minutes 43 seconds
所有这些背后的算法都是 public 域,并在此处整齐地收集和解释:
http://howardhinnant.github.io/date_algorithms.html
我想强调一下,这道题是一道很好但复杂的题,因为年、月等单位不统一。处理此类问题的最有效方法是拥有能够使用高级语法抽象出复杂的低级算术的低级工具。
作为补充验证,这:
std::cout << human_readable_difference(sys_days{feb/11/2000} + 9h + 21min + 6s,
sys_days{jul/12/1900} + 15h + 24min + 7s) << '\n';
输出:
99 years 6 months 29 days 17 hours 56 minutes 59 seconds
与
如果时区确实很重要,则需要在此之上添加 additional software layer。