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

Math to convert seconds since 1970 into date and vice versa

本文关键字:转换 日期 反之亦然 1970年      更新时间:2023-10-16

自1970年1月1日00:00以来,我有秒数作为int64(以纳秒为单位(,我正试图将其转换为一周中的月/日/年/日。

迭代地做这件事很容易,我已经做到了,但我想公式化地做。我在找真正的数学。

旧问题的新答案:

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

这个答案没有任何重复。

算法就在这里,并进行了极其详细的解释。它们还经过了+/-一百万年的正确性单元测试(远远超过您的需求(。

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

日期算法只处理天的单位,而不是纳秒。要将天数转换为纳秒,请乘以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 s。

请注意,这三种算法中的任何一种都缺乏迭代。

它们可以这样使用:

#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

这些算法属于公共领域。可以随心所欲地使用它们。如果需要的话,日期算法论文有几个更有用的日期算法(例如weekday_difference既非常简单又非常有用(。

如果需要,这些算法被封装在一个开源、跨平台、类型安全的日期库中。

如果需要时区或闰秒支持,则会在日期库的基础上构建一个时区库。

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

了解如何在不同时区之间转换。

更新:以这种方式进行日期计算时忽略闰秒有什么陷阱吗?

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

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

操作系统中几乎所有的时间来源都是基于Unix时间的。Unix时间是自1970-01-01以来的时间计数,不包括闰秒。这包括像C time(nullptr)和C++std::chrono::system_clock::now()以及POSIX gettimeofdayclock_gettime这样的功能。这不是标准规定的事实(POSIX规定的除外(,但它是事实上的标准。

因此,如果秒的来源(纳秒,不管怎样(忽略了闰秒,那么在转换为字段类型(如{year, month, day, hours, minutes, seconds, nanoseconds}(时忽略闰秒是完全正确的。事实上,在这种情况下考虑闰秒实际上会引入错误。

因此,了解你的时间来源是件好事,尤其是知道它是否也像Unix时间那样忽略了闰秒。

如果你的时间来源没有忽略闰秒,你仍然可以得到第二个正确答案。您只需要知道已插入的闰秒集。这是当前的列表。

例如,如果您获得自1970-01-01 00:00:00 UTC以来的秒数,包括闰秒,并且您知道这表示"现在"(当前为2016-09-26(,则从现在到1970-01-0之间插入的当前闰秒数为26。所以你可以从你的计数中减去26,然后遵循这些算法,得到确切的结果。

这个库可以为您自动化闰秒计算。例如,要获取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时间(看起来像:

#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

对于CCD_ 19。

在即将到来的新年(2017-01-01(,我们将插入第27个闰秒。

在1958-01-01和1970-01-01之间,插入了10个"闰秒",但单位小于一秒,而不仅仅是在12月底或6月底。关于插入的确切时间和确切时间的文件很粗略,我一直无法找到可靠的来源。

原子计时服务始于1955年,第一个基于原子的国际时间标准TAI的历元为1958-01-01 00:00:00 GMT(现在的UTC(。在此之前,我们拥有的最好的时钟是石英钟,它不够准确,不足以担心闰秒。

单一Unix规范给出了自大纪元以来的秒数公式:

一个近似于经过的秒数的值自大纪元以来。协调世界时名称(用术语指定秒(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未指定。

自大纪元以来对秒值的任何更改是如何进行的与当前实际时间的期望关系对齐实现定义。如自大纪元以来以秒表示的,每一天都应精确计算86400秒。

注:表达式的最后三个术语为自纪元从1973年开始,第一个学期每4年增加一天从2001年开始,第二天每100年减去一天,以及第三次是从2001年开始,每400年增加一天。这个公式中的除法是整数除法;即剩余部分被丢弃,只留下整数商。

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

试着从中找出如何从秒中恢复日期和时间。

编辑

我在这个答案中实现了一个整数算术转换器。

查看ideone上的测试运行。

取决于您想要gmtime或localtime的时间,然后只读取struct_tm

此代码有效。。。

用法:uint32_t getSecsSinceEpoch(1970,月,日,年_日期_时间,小时,分钟,秒(;

示例:timestamp=getSecsSinceEpoch(1970,6,12,(2014-1970(,15,29,0(

退货:1402586940

您可以在www.epochconverter.com.上进行验证

写这篇文章花了大约20分钟,大部分时间都花在了和一个朋友争论我是否应该包括闰秒、纳秒等。

玩得开心。。。

Dr。Bryan Wilcutt

#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;
}
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到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:指示夏令时是否有效的标志在所描述的时间。如果夏令时为正数时间有效,如果无效则为零,如果信息不可用。

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

这取决于你的时区(夏令时规则、闰年、闰秒(,但我想说的是,首先用整数除以86400得到天数。然后通过模除以86400,找出剩余的部分。现在,你可以通过第一个整数将天数除以365,然后从剩余的天数中减去闰日(通过天数除以365的模计算(来计算出已经过去了多少年。您还需要从剩余秒数(已计算(中减去闰秒数。如果这个减法将这些数字降到零以下,那么就从下一个最大面额中减去。然后,您可以使用日历的显式逻辑来计算一个月中的哪一天。如果您在夏令时着陆,请确保增加一个小时(或夏令时偏移量(。

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

之前

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

稍后:

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

更正后,代码对我有效。

我需要在没有硬件乘法器的低端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;
  }
*/