两个UNIX时间戳之间的可读时间

我试图计算两个UNIX时间戳(即自1970年以来的秒)之间的差异。 我想说,例如“3年,4个月,6天等”,但我不知道如何计算闰年和不同时间的月份。 这当然是一个解决的问题..?

这与其他问题是不同的,这些问题需要在一个固定的/一致的持续时间(例如3小时或7周等)中expression一个大致的持续时间。 1月1日至1月2日的结果将为“1个月”,并且结果为1月2日至1月。即使天数不同,3月也将是“1月”。

我想精确地expression完整的持续时间,但以年/月/日/小时/分钟表示。 在C + +解决scheme将不胜感激!

对于较早的日期,使用Leap Year公式计算从Unix开始日期(1970年1月1日)到第一个时间戳的天数

(闰秒,不知道如果你需要得到这个精确的,希望会超出范围?)

闰年由约束日期计算到1600AD之后,格里历日历的算法来自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

如果一年是闰年,那么二月份有29天,其余28天

现在你知道第一个变量的月份,day_of_month,year

接下来,使用闰年公式计算第二个时间戳的另一组天数,直到您到达第二个时间戳。

 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/

使用这个免费的,开源的C ++ 11/14日期/时间库 ,这个问题可以用非常高级的语法很简单地解决。 它利用了C ++ 11 <chrono>库。

由于相对较少的人熟悉我的约会库的工作原理,所以我将逐一详细地讨论如何做到这一点。 最后,我把它们放在一个整齐的函数中。

为了演示它,我将假设一些有用的声明来减少冗长:

 #include "date.h" #include <iostream> int main() { using namespace date; using namespace std::chrono; 

而且它也有助于使用一些UNIX时间戳的例子。

 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时间戳的工作方式一致,并与std::chrono::system_clock::now()的操作一致。

接下来,以秒为单位精确地将这些时间戳“精炼”到精确度为几天的时间戳(自纪元以来的天数)是方便的。 这是用这个代码完成的:

 auto dp0 = floor<days>(t0); auto dp1 = floor<days>(t1); 

dp0dp1类型是std::chrono::time_point<std::chrono::system_clock, std::chrono::duration<int, std::ratio<86400>>> ,这是一个高高的满口! 不是很好 ! 有一个叫做date:: sys_daystypedef ,它是dp0dp1类型的一个方便的快捷方式,所以你不必输入丑陋的表单。

接下来将dp0dp1转换为{year, month, day}结构是很方便的。 有这样一个类型为date::year_month_day的结构,它将从sys_days隐式转换:

 year_month_day ymd0 = dp0; year_month_day ymd1 = dp1; 

这是一个非常简单的结构,具有year()month()day() getters。

对于每个UNIX时间戳,从午夜开始的时间也是很方便的。 这通过从秒分辨率时间点减去天分辨率时间点容易地获得:

 auto time0 = t0 - dp0; auto time1 = t1 - dp1; 

time0time1类型为std::chrono::seconds time0 std::chrono::seconds ,表示t0t1的开始日期。

为了验证我们的位置,输出到目前为止我们很方便:

 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时间戳分成他们可读的组件(至少年,月和日)。 上面的print语句中显示的函数make_time需要seconds ,并将其转换为{hours, minutes, seconds}结构。

好吧,但到目前为止,我们所做的只是获取UNIX时间戳并将其转换为字段类型。 现在的差异部分…

为了获得人类可读的差异,我们从大单位开始,并减去它们。 如果下一个最小的单位是不可减的(如果减法会产生一个负的结果),那么较大的单位减法就会大一。 留在我身边,代码+例子比人类语言更清晰:

 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 

首先我们将ymd1ymd0year字段之间的差异存储在类型为date::years的变量dy中。 然后我们将dy加回到ymd0并重新计算串行time_pointdp0t0 。 如果t0 > t1那么我们就增加了一个太多年(因为ymd0的月/日发生在一年后一年,比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'; 

这个例子的第一行值得特别注意,因为这里有很多事情要做。 我们需要以月为单位找出ymd1ymd0之间的差异。 只需从ymd0.month()减去ymd0.month() ,只有在ymd1.month() >= ymd0.month()时才有效。 但是ymd1.year()/ymd1.month()会创建一个date::year_month类型。 这些类型是“时间点”,但具有一个月的精度。 我们可以减去这些类型,并得到months的结果。

现在有相同的公式:把月份的差值加回到ymd0 ,重新计算dp0t0 ,并且发现你是否增加了一个月。 如果是这样,则增加一个月。 上面的代码输出:

 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的一个有趣的事情是,他们真的很擅长面向日常的算术。 因此,我们不用处理像year_month_dayyear_month这样的字段类型,而是使用这个级别的sys_days 。 我们只是减去dp1 - dp0来获得days的差异。 然后我们将其添加到dp0 ,并重新创建ymd0t0 。 检查是否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减去time0time1time0都有std::chrono::seconds time0 std::chrono::seconds类型,它们的差别是相同的。 如果事实证明time0 > time1 (如本例),我们需要添加day 。 然后我们可以加回差值,重新计算time0dp0ymd0来检查我们的工作。 我们现在应该得到和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 << xycount() << " years " << xmcount() << " months " << xdcount() << " days " << xhcount() << " hours " << x.min.count() << " minutes " << xscount() << " 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 

所有这些背后的算法都是公有领域,整齐地收集和解释在这里:

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 

与报告Wolfram Alpha输出内容的另一个答案中报告的输出相同。 作为一种奖励,这里的语法不会 – 也不能 – 受到端模糊(m / d / y vs d / m / y)的影响。 不可否认的是,这涉及到一点点运气,因为Wolfram的输出是使用“America / New_York”时区报告的,对于这两个时间戳,UTC偏移量是相同的(所以时区偏移量没有考虑)。

如果时区确实很重要,则需要在此之上增加一个软件层 。

回复:“ 肯定这是一个解决的问题?

如果Wolfram Alpha是正确的,这个问题似乎就可以解决了。 看来WA并没有公开提供他们的方法,他们的网站故意让屏幕抓取困难,但它一个方法,可以作为一个在线“黑匣子”。

要查看黑盒子,请转到Wolfram Alpha ,并输入两个日期,以“to”分隔,例如:

 7/12/1900 to 2/11/2000 

输出:

99年6个月30天

一天的时间也是:

 7/12/1900 3:24:07pm to 2/11/2000 9:21:06am 

输出:

99年6个月29天17小时56分59秒

请注意,Wolfram采用了默认的EST时区。 位置也可以输入,后面的例子中的数字范围相同,但是在不同的位置和时区中:

 7/12/1900 3:24:07pm in Boston to 2/11/2000 9:21:06am in Hong Kong 

输出与以前的答案有所不同:13小时:

99年6个月29天4小时56分59秒