两个UNIX时间戳之间的人类可读持续时间

时间:2016-01-21 06:26:12

标签: c++ date unix unix-timestamp duration

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

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

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

3 个答案:

答案 0 :(得分:4)

使用此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()的操作一致。

接下来很方便&#34;粗化&#34;这些时间戳以秒精度下降到时间戳,精确度为天(自纪元以来的天数)。这是通过以下代码完成的:

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>>>,这是一个惊人的口!不是auto 很好!有一个typedef名为date:: sys_days,是dp0dp1类型的便捷快捷方式,因此您无需键入丑陋的表单

接下来,将dp0dp1转换为{year, month, day}结构非常方便。有一种date::year_month_day类型的结构将隐式转换为sys_days

year_month_day ymd0 = dp0;
year_month_day ymd1 = dp1;

这是一个包含year()month()day() getter的非常简单的结构。

从午夜开始,每个UNIX timestamps都有时间。通过从秒分辨率time_point中减去天数分辨率time_point可以轻松获得:

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

time0time1的类型为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 timestamps划分为人类可读的组件(至少年,月和日)。上面打印语句中显示的函数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

首先,我们对yearymd1的{​​{1}}字段进行区分,然后将其存储在类型为ymd0的变量dy中。然后我们将date::years添加回dy并重新计算序列ymd0 s time_pointdp0。如果结果是t0,那么我们已经添加了太多年(因为t0 > t1的月/日发生在一年的晚些时候而不是ymd0)。所以我们减去一年并重新计算。

现在我们有多年的差异,我们已经减少了问题,找到了ymd1的差异,这个delta保证不到1年。

这是整个问题的基本公式!现在我们只需要用较小的单位进行冲洗和重复:

{months, days, hours, minutes, seconds}

这个例子的第一行值得特别注意,因为这里有很多。我们需要以月为单位找出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中删除ymd0.month()仅在ymd1.month()时有效。但ymd1.month() >= ymd0.month()会创建ymd1.year()/ymd1.month()类型。这些类型是&#34;时间点&#34;,但精确度为一个月。可以减去这些类型并得到date::year_month

现在遵循相同的公式:将月份差异添加回months,重新计算ymd0dp0,并发现您是否添加了太多月份。如果是这样,请减少一个月。上面的代码输出:

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_daysyear_month_day等字段类型,而是在此级别使用year_month。我们只需减去sys_days即可获得dp1 - dp0的差异。然后我们将其添加到days,然后重新创建dp0ymd0。检查是否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'; 中减去time0time1time1都有类型time0,它们的区别具有相同的类型。如果结果是std::chrono::seconds(如本例所示),我们需要添加time0 > time1。然后我们可以添加差异并重新计算daytime0dp0以查看我们的工作。我们现在应该获得与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';
}

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

http://howardhinnant.github.io/date_algorithms.html

我想强调的是,这个问题是一个很好但很复杂的问题,因为单位和月份等单位的不均匀性。处理这样一个问题的最有效方法是使用能够用更高级语法抽象出复杂的低级算术的低级工具。

作为补充验证,这是:

45 years 8 months 4 days 18 hours 34 minutes 43 seconds

输出:

std::cout << human_readable_difference(sys_days{feb/11/2000} + 9h + 21min + 6s,
                                       sys_days{jul/12/1900} + 15h + 24min + 7s) << '\n';

与报告another answer输出的Wolfram Alpha中报告的输出相同。作为奖励,这里的语法不是 - 而不能 - 受到endian歧义(m / d / y vs d / m / y)的影响。不可否认,这有点运气,因为Wolfram's输出是使用&#34; America / New_York&#34;时区,对于这两个时间戳,UTC偏移量是相同的(因此时区偏移量不会计入)。

如果时区确实很重要,则需要additional software layer

答案 1 :(得分:1)

对于旧日期,请使用闰年公式开始计算从Unix开始日期1970年1月1日到第一个时间戳的天数

(对于闰秒,dunno如果你需要那么准确,希望会超出范围?)

闰年通过将日期约束到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

如果一年是闰年,则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/

答案 2 :(得分:-1)

Re:“肯定这是一个已解决的问题?

如果 Wolfram Alpha 正确,此问题似乎已得到解决。似乎西澳大利亚州没有公开提供他们的方法,他们的网站故意使得屏幕抓取困难,但它一种方法,可以作为在线“黑匣子”。

要查看该黑匣子,请转到Wolfram Alpha,然后输入两个日期,以“至”分隔,例如:

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秒