数学将自1970年以来的秒数转换为日期,反之亦然

时间:2011-10-31 22:05:45

标签: c++ date math

我从1970年1月1日00:00开始以秒为单位,以纳秒为单位,我试图将其转换为月/日/年/星期。

迭代地执行此操作很容易,我有这个工作,但我想要公式化。我正在寻找实际的数学。

9 个答案:

答案 0 :(得分:26)

旧问题的新答案:

这个新答案的基本原理:现有答案要么没有显示从纳秒到年/月/日的转换算法(例如,他们使用隐藏源的库),要么他们在他们显示的算法中使用迭代

这个答案没有任何迭代。

The algorithms are here,并以极其详细的方式解释。他们还在+/-一百万年的时间内(比您需要的方式更多)进行单元测试。

算法不计算闰秒。如果您需要,可以完成,但需要查表,并且该表随着时间的推移而增长。

日期算法仅处理天数,而不是纳秒。要将天数转换为纳秒,请乘以86400*1000000000(注意确保使用64位算术)。要将纳秒转换为天数,除以相同的数量。或者更好的是,使用C ++ 11 <chrono>库。

本文中有三种日期算法可以回答这个问题。

1. days_from_civil

// Returns number of days since civil 1970-01-01.  Negative values indicate
//    days prior to 1970-01-01.
// Preconditions:  y-m-d represents a date in the civil (Gregorian) calendar
//                 m is in [1, 12]
//                 d is in [1, last_day_of_month(y, m)]
//                 y is "approximately" in
//                   [numeric_limits<Int>::min()/366, numeric_limits<Int>::max()/366]
//                 Exact range of validity is:
//                 [civil_from_days(numeric_limits<Int>::min()),
//                  civil_from_days(numeric_limits<Int>::max()-719468)]
template <class Int>
constexpr
Int
days_from_civil(Int y, unsigned m, unsigned d) noexcept
{
    static_assert(std::numeric_limits<unsigned>::digits >= 18,
             "This algorithm has not been ported to a 16 bit unsigned integer");
    static_assert(std::numeric_limits<Int>::digits >= 20,
             "This algorithm has not been ported to a 16 bit signed integer");
    y -= m <= 2;
    const Int era = (y >= 0 ? y : y-399) / 400;
    const unsigned yoe = static_cast<unsigned>(y - era * 400);      // [0, 399]
    const unsigned doy = (153*(m + (m > 2 ? -3 : 9)) + 2)/5 + d-1;  // [0, 365]
    const unsigned doe = yoe * 365 + yoe/4 - yoe/100 + doy;         // [0, 146096]
    return era * 146097 + static_cast<Int>(doe) - 719468;
}

2. civil_from_days

// Returns year/month/day triple in civil calendar
// Preconditions:  z is number of days since 1970-01-01 and is in the range:
//                   [numeric_limits<Int>::min(), numeric_limits<Int>::max()-719468].
template <class Int>
constexpr
std::tuple<Int, unsigned, unsigned>
civil_from_days(Int z) noexcept
{
    static_assert(std::numeric_limits<unsigned>::digits >= 18,
             "This algorithm has not been ported to a 16 bit unsigned integer");
    static_assert(std::numeric_limits<Int>::digits >= 20,
             "This algorithm has not been ported to a 16 bit signed integer");
    z += 719468;
    const Int era = (z >= 0 ? z : z - 146096) / 146097;
    const unsigned doe = static_cast<unsigned>(z - era * 146097);          // [0, 146096]
    const unsigned yoe = (doe - doe/1460 + doe/36524 - doe/146096) / 365;  // [0, 399]
    const Int y = static_cast<Int>(yoe) + era * 400;
    const unsigned doy = doe - (365*yoe + yoe/4 - yoe/100);                // [0, 365]
    const unsigned mp = (5*doy + 2)/153;                                   // [0, 11]
    const unsigned d = doy - (153*mp+2)/5 + 1;                             // [1, 31]
    const unsigned m = mp + (mp < 10 ? 3 : -9);                            // [1, 12]
    return std::tuple<Int, unsigned, unsigned>(y + (m <= 2), m, d);
}

3. weekday_from_days

// Returns day of week in civil calendar [0, 6] -> [Sun, Sat]
// Preconditions:  z is number of days since 1970-01-01 and is in the range:
//                   [numeric_limits<Int>::min(), numeric_limits<Int>::max()-4].
template <class Int>
constexpr
unsigned
weekday_from_days(Int z) noexcept
{
    return static_cast<unsigned>(z >= -4 ? (z+4) % 7 : (z+5) % 7 + 6);
}

这些算法是为C ++ 14编写的。如果您有C ++ 11,请删除constexpr。如果你有C ++ 98/03,请删除constexprnoexceptstatic_assert

注意这三种算法中没有迭代。

它们可以像这样使用:

#include <iostream>

int
main()
{
    int64_t z = days_from_civil(2015LL, 8, 22);
    int64_t ns = z*86400*1000000000;
    std::cout << ns << '\n';
    const char* weekdays[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
    unsigned wd = weekday_from_days(z);
    int64_t y;
    unsigned m, d;
    std::tie(y, m, d) = civil_from_days(ns/86400/1000000000);
    std::cout << y << '-' << m << '-' << d << ' ' << weekdays[wd] << '\n';
}

输出:

1440201600000000000
2015-8-22 Sat

算法属于公共领域。根据需要使用它们。如果需要,date algorithms paper还有几个有用的日期算法(例如weekday_difference非常简单且非常有用)。

如果需要,这些算法将包含在open source, cross platform, type-safe date library中。

如果需要时区或闰秒支持,则timezone library上会建立date library

更新:同一应用中的不同本地区域

了解如何convert among different time zones

更新:以这种方式进行日期计算时,忽略闰秒是否有任何陷阱?

以下评论中这是一个很好的问题。

答案:有一些陷阱。并且有一些好处。很高兴知道它们都是。

操作系统几乎每个时间源都基于Unix TimeUnix Time是自1970-01-01 排除闰秒以来的时间计数。这包括C time(nullptr)和C ++ std::chrono::system_clock::now()等功能,以及POSIX gettimeofdayclock_gettime。这不是标准规定的事实(除非由POSIX指定),但它是事实上的标准。

因此,如果你的秒数(纳秒,无论什么)忽略了闰秒,那么在转换为{year, month, day, hours, minutes, seconds, nanoseconds}等字段类型时忽略闰秒是完全正确的。实际上,在这样的上下文中考虑闰秒实际上会引入错误。

所以知道你的时间来源是很好的,特别是知道它是否也忽略了Unix Time所做的闰秒。

如果你的时间没有忽略闰秒,你可以仍然得到正确答案到第二个。您只需要知道已插入的闰秒集。 Here is the current list

例如,如果您获得自1970-01-01 00:00:00 UTC以来的秒数,包含闰秒,并且您知道这代表“现在”(目前是2016年 - 09-26),从现在到1970-01-01之间插入的当前闰秒数是26.所以你可以从你的计数中减去26,然后然后遵循这些算法,得到确切的结果。

This library可以为您自动执行闰秒感知计算。例如,要获得2016-09-26 00:00:00 UTC和1970-01-01 00:00:00 UTC 包括闰秒之间的秒数,您可以这样做:

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

int
main()
{
    using namespace date;
    auto now  = clock_cast<utc_clock>(sys_days{2016_y/September/26});
    auto then = clock_cast<utc_clock>(sys_days{1970_y/January/1});
    std::cout << now - then << '\n';
}

输出:

1474848026s

忽略闰秒(Unix Time)看起来像:

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

int
main()
{
    using namespace date;
    using namespace std::chrono_literals;
    auto now  = sys_days{2016_y/September/26} + 0s;
    auto then = sys_days{1970_y/January/1};
    std::cout << now - then << '\n';
}

输出:

1474848000s

差异为26s

即将到来的新年(2017-01-01)我们将插入27 th 闰秒。

在1958-01-01和1970-01-01之间插入了10个“闰秒”,但是以小于一秒的单位插入,而不是仅在12月或6月底。文件中插入了多少时间确切地说是粗略的时候,我无法找到可靠的来源。

原子时间保持服务于1955年开始实验,第一个基于原子的国际时间标准TAI的时间为1958-01-01 00:00:00 GMT(现在是UTC)。在此之前,我们最好的是基于石英的时钟,这些时钟不够准确,无法担心闰秒。

答案 1 :(得分:10)

单Unix规范给出了Seconds since the Epoch的公式:

  

一个近似于已经过的秒数的值   从大纪元开始。协调世界时名称(以术语表示   秒(tm_sec),分钟(tm_min),小时(tm_hour),天数   1月1日(tm_yday),日历年减1900   (tm_year))与自秒以来表示为秒的时间有关   Epoch,根据下面的表达。

     

如果年份<1970年或价值为负,则关系为   未定义。如果年份是> = 1970并且该值是非负的,那么   值与根据协调世界时名称相关   C语言表达式,其中tm_sec,tm_min,tm_hour,tm_yday和   tm_year都是整数类型:

tm_sec + tm_min*60 + tm_hour*3600 + tm_yday*86400 +
    (tm_year-70)*31536000 + ((tm_year-69)/4)*86400 -
    ((tm_year-1)/100)*86400 + ((tm_year+299)/400)*86400
     

实际时间与当前值之间的关系   因为Epoch未指定,所以秒数。

     

如何更改自Epoch以来秒数的值   与当前实际时间对齐到期望的关系   实现定义。从大纪元以来的秒数表示,   每一天都要计算86400秒。

     

注意:       表达式的最后三个术语每年增加一天,从闰年开始,从第一个闰年开始   时代。从1973年开始,第一学期每4年增加一天   第二,从2001年开始,每100年减去一天,和   从2001年开始,第三年每400年增加一天   公式中的除法是整数除法;也就是余下的   被丢弃,只留下整数商。

您需要将月份和日期转换为tm_yday才能使用此公式,并且考虑到闰年也应该这样做。公式中的其余部分是微不足道的。

尝试从中了解如何从秒开始取回日期和时间。

修改

我在this answer中实现了整数运算的转换器。

See a test run at ideone

答案 2 :(得分:2)

bool FloatToTime(float seconds_since_epoch, bool local_time, struct tm *timest)
{
   struct tm *ret;
   time_t t=(time_t) seconds_since_epoch;
   if (local_time) ret=localtime(&t);
      else ret=gmtime(&t);
   if(ret==NULL) return false;
   memcpy(timest, ret, sizeof(struct tm));
   return true;
}

将秒作为第一个参数传递给它。第二个参数对于本地时间应为true,对于GMT应为false。第三个参数是指向保存响应的结构的指针。

返回结构(来自手册页):

  

tm_sec:分钟后的秒数,通常在0到0的范围内                  59,但可以达到60以允许闰秒。

     

tm_min:小时后的分钟数,范围为0到59.

     

tm_hour:午夜过后的小时数,范围为0到23。

     

tm_mday:该月中的某一天,范围为1到31。

     

tm_mon:自1月以来的月数,范围为0到11。

     

tm_year:自1900年以来的年数。

     

tm_wday:自星期日以来的天数,范围为0到6。

     

tm_yday:自1月1日以来的天数,范围为0到365.

     

tm_isdst:一个标志,指示夏令时是否有效                  在描述的时间。如果夏令时,该值为正                  时间有效,如果不是则为零,如果是,则为负                  信息不可用。

答案 3 :(得分:2)

取决于您想要gmtimelocaltime的时间,然后阅读struct_tm

答案 4 :(得分:1)

有很多功能可以执行此操作,请参阅http://www.cplusplus.com/reference/clibrary/ctime/,即strftime

答案 5 :(得分:1)

此代码有效......

使用方法: uint32_t getSecsSinceEpoch(1970,month,day,years_since_epoch,hour,minute,second);

实施例: timestamp = getSecsSinceEpoch(1970,6,12,(2014 - 1970),15,29,0)

返回:1402586940

您可以访问www.epochconverter.com进行验证。

花了大约20分钟来写它,其中大部分用于与朋友争论我是否应该包括闰秒,纳秒等等.Blech。

玩得开心......

博士。布莱恩威尔切特

#define DAYSPERWEEK (7)
#define DAYSPERNORMYEAR (365U)
#define DAYSPERLEAPYEAR (366U)

#define SECSPERDAY (86400UL) /* == ( 24 * 60 * 60) */
#define SECSPERHOUR (3600UL) /* == ( 60 * 60) */
#define SECSPERMIN (60UL) /* == ( 60) */

#define LEAPYEAR(year)          (!((year) % 4) && (((year) % 100) || !((year) % 400)))

const int _ytab[2][12] = {
{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
{31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
};

/****************************************************
* Class:Function    : getSecsSomceEpoch
* Input     : uint16_t epoch date (ie, 1970)
* Input     : uint8 ptr to returned month
* Input     : uint8 ptr to returned day
* Input     : uint8 ptr to returned years since Epoch
* Input     : uint8 ptr to returned hour
* Input     : uint8 ptr to returned minute
* Input     : uint8 ptr to returned seconds
* Output        : uint32_t Seconds between Epoch year and timestamp
* Behavior      :
*
* Converts MM/DD/YY HH:MM:SS to actual seconds since epoch.
* Epoch year is assumed at Jan 1, 00:00:01am.
****************************************************/
uint32_t getSecsSinceEpoch(uint16_t epoch, uint8_t month, uint8_t day, uint8_t years, uint8_t hour, uint8_t minute, uint8_t second)
{
unsigned long secs = 0;
int countleap = 0;
int i;
int dayspermonth;

secs = years * (SECSPERDAY * 365);
for (i = 0; i < (years - 1); i++)
{   
    if (LEAPYEAR((epoch + i)))
      countleap++;
}
secs += (countleap * SECSPERDAY);

secs += second;
secs += (hour * SECSPERHOUR);
secs += (minute * SECSPERMIN);
secs += ((day - 1) * SECSPERDAY);

if (month > 1)
{
    dayspermonth = 0;

    if (LEAPYEAR((epoch + years))) // Only counts when we're on leap day or past it
    {
        if (month > 2)
        {
            dayspermonth = 1;
        } else if (month == 2 && day >= 29) {
            dayspermonth = 1;
        }
    }

    for (i = 0; i < month - 1; i++)
    {   
        secs += (_ytab[dayspermonth][i] * SECSPERDAY);
    }
}

return secs;
}

答案 6 :(得分:0)

首先,不要将秒数存储为浮点数。如果您需要微/纳秒,请单独存储它们。你将需要整数来进行这些计算。

这取决于你的时区(DST规则,闰年,闰秒),但我会说首先得到整数除以86400的天数。然后通过模数除以86400找出剩余的天数。现在你可以计算出第一个整数将天数除以365,然后减去剩余天数的闰天数(通过以天数除以365的模数计算)。您还需要从剩余秒数(已计算)中减去闰秒数。如果该减法驱动那些低于零的数字,则从下一个最大面额中减去。然后,您可以使用日历的显式逻辑计算月中的日期。如果你降落在夏令时,请务必添加一小时(或任何DST偏移)。

就个人而言,我只会使用Boost.Date_Time,因为它会完成所有这些以及更多(可能错误比你或我在前几次迭代中所做的更少),但我想我会拍摄一下你的问题......

答案 7 :(得分:0)

    for (i = 0; i < (years - 1); i++)
    {   
        if (LEAPYEAR((epoch + i)))
        countleap++;
    }

后来:

    for (i = 0; i < years; i++)
 {   
   if (LEAPYEAR((epoch + i)))
    countleap++;
 }

修正后代码对我有用。

答案 8 :(得分:0)

我需要在没有硬件乘法器的低端8位MCU上实现到Unix时间的转换。下面是C#代码,只需要一般的8位乘法和除以常数4和100。都在32位(长)操作数上。 C#代码可以轻松移植到最终框架。它给出的结果与.NET中的 DateTimeOffset.ToUnixTimeSeconds()相同。

static long UnixTime ( int sec, int min, int hour, int day, int month, int year )
{
  // Cumulative days for each previous month of the year
  int[] mdays = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 };
  // Year is to be relative to the epoch start
  year -= 1970;
  // Compensation of the non-leap years
  int minusYear = 0;
  // Detect potential lead day (February 29th) in this year?
  if ( month >= 3 )
  {
    // Then add this year into "sum of leap days" computation
    year++;
    // Compute one year less in the non-leap years sum
    minusYear = 1;
  }

  return 
    // + Seconds from computed minutes
    60 * (
      // + Minutes from computed hours
      60 * (
        // + Hours from computed days
        24 * (
          // + Day (zero index)
          day - 1
          // + days in previous months (leap day not included)
          + mdays[month - 1]
          // + days for each year divisible by 4 (starting from 1973)
          + ( ( year + 1 ) / 4 )
          // - days for each year divisible by 100 (starting from 2001)
          - ( ( year + 69 ) / 100 )
          // + days for each year divisible by 400 (starting from 2001)
          + ( ( year + 369 ) / 100 / 4 )
          // + days for each year (as all are non-leap years) from 1970 (minus this year if potential leap day taken into account)
          + ( 5 * 73 /*=365*/ ) * ( year - minusYear )
          // + Hours
        ) + hour
        // + Minutes
      ) + min 
      // + Seconds
    ) + sec;
}

希望有帮助。

已编辑:

下面是针对8位PIC MCU和CC5X编译器的优化代码。

uns32 unixTime;

...
  // Test data returning 0xFfFfFfFf UnixTime
  uns8 year = 2106 - 1970;
  uns8 month = 2;
  uns8 day = 7;
  uns8 hour = 6;
  uns8 min = 28;
  uns8 sec = 15;

  // See original C# code below

  //### Compute days
  // ( 5 * 73 /*=365*/ ) * year
  unixTime = year;
  mulUnixTime( 5 );
  mulUnixTime( 73 );

  // if ( month >= 3 ) year++;
  if ( month > 3 )
    year++;

  // if ( year > 130 ) => minus 1 total days ( year-=4 makes a result of the next division by 4 less by 1)
  if ( year > 130 )
    year -= 4;
  // + ( ( year + 1 ) / 4 )
  addUnixTime( ( year + 1 ) / 4 );
  // + mdays[month - 1]
  addUnixTime( daysInMonths( month ) );
  // + day - 1
  addUnixTime( day - 1 );
  //### Compute hours
  // Hours from computed days
  mulUnixTime( 24 );
  // + Hours
  addUnixTime( hour );
  //### Compute minutes
  // Minutes from computed hours 
  mulUnixTime( 60 );
  // + Minutes
  addUnixTime( min );
  //### Compute seconds
  // Seconds from computed minutes
  mulUnixTime( 60 );
  // + Seconds
  addUnixTime( sec );
...

void mulUnixTime( uns8 mul )
{
  unixTime *= mul;
}

void addUnixTime( uns8 add )
{
  unixTime += add;
}

uns8 daysInMonths( uns8 month @ W )
{
  skip( month );
#pragma computedGoto 1
  return 0xFF;// Dummy value for month 0
  return   0; // January
  return  31; // February
  return  59; // ...
  return  90;
  return 120;
  return 151;
  return 181;
  return 212;
  return 243;
  return 273;
  return 304; // ...
  return 334; // December
#pragma computedGoto 0
}


/*
 static long UnixTime ( int sec, int min, int hour, int day, int month, int year )
  {
    // Cumulative days for each previous month of the year
    int[] mdays = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 };
    // Year is to be relative to the epoch start
    year -= 1970;
    // Compensation of the non-leap years
    int minusYear = 0;
    // Detect potential lead day (February 29th) in this year?
    if ( month >= 3 )
    {
      // Then add this year into "sum of leap days" computation
      year++;
      // Compute one year less in the non-leap years sum
      minusYear = 1;
    }

    return
      // + Seconds from computed minutes
      60 * (
        // + Minutes from computed hours
        60 * (
          // + Hours from computed days
          24L * (
            // + Day (zero index)
            day - 1
            // + days in previous months (leap day not included)
            + mdays[month - 1]
            // + days for each year divisible by 4 (starting from 1973)
            + ( ( year + 1 ) / 4 )
            // - days after year 2000
            - ( ( year > 130 ) ? 1 : 0 )
            // + days for each year (as all are non-leap years) from 1970 (minus this year if potential leap day taken into account)
            + ( 5 * 73 ) * ( year - minusYear )
          // + Hours
          ) + hour
        // + Minutes
        ) + min
      // + Seconds
      ) + sec;
  }
*/