数学从1970年开始转换为日期,反之亦然 [英] Math to convert seconds since 1970 into date and vice versa

查看:183
本文介绍了数学从1970年开始转换为日期,反之亦然的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述



我从1970年1月1日00:00以秒为单位,以毫秒为单位,以int64为单位,并尝试将其转换为月/日/年/周/周。 p>这是很容易做到这一点迭代,我有这个工作,但我想做公式化。我正在寻找实际的数学。

解决方案

旧问题的新回答:



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



这个答案没有任何重复。



算法在这里,并以令人难以置信的细节解释。它们也是在+/- 100万年的范围内进行单位测试的正确性(比您需要的更多)。



算法不计算闰秒。如果需要,可以完成,但需要一个表查找,该表随时间增长。



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



有三种日期算法本文需要回答这个问题。



1。 days_from_civil

  //返回自1970-01-01以来的天数。负值表示在1970-01-01之前的
//天。
//前提条件:ymd表示Civil(Gregorian)日历中的日期
// m在[1,12]
// d在[1,last_day_of_month(y,m )]
// y在
//numeric_limits< Int> :: min()/ 366,numeric_limits< Int> :: max()/ 366]
/ /有效范围是:
// [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,
该算法尚未移植到16位无符号整数);
static_assert(std :: numeric_limits< Int> :: digits> = 20,
该算法尚未移植到16位有符号整数);
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。 code> civil_from_days :

  //返回民事日历中的年/月/日三重
//前提条件:z是自1970-01-01以来的天数,范围是:
// [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,
该算法尚未移植到16位无符号整数);
static_assert(std :: numeric_limits< Int> :: digits> = 20,
该算法尚未移植到16位有符号整数);
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。 code> weekday_from_days :

  //返回民用日历中的星期几[0, 6]→> [Sun,Sat] 
//前提条件:z是自1970-01-01以来的天数,在范围内:
// [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,请删除 constexpr noexcept static_assert s。



请注意,这三种算法中没有任何一种迭代。



可以这样使用:

  #include< iostream> 

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

哪些输出:

  1440201600000000000 
2015-8-22 Sat



日期算法论文有几个更有用的日期算法(例如 weekday_difference 非常简单,非常有用)。



这些算法包含在一个开源,跨平台,类型安全的日期库,如果需要的话。



如果是时区或需要闰秒支持,则在时区库 href =http://howardhinnant.github.io/date/date.html =noreferrer>日期库。



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



查看如何在不同时区之间进行转换



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



这是以下意见的一个很好的问题。



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



几乎每个来自操作系统的时间源都是基于 Unix时间 Unix时间是从1970-01-01 排除的时间数闰秒这包括诸如C time(nullptr)和C ++ std :: chrono :: system_clock :: now(),以及POSIX gettimeofday clock_gettime 。这不是标准指定的事实(除了它由POSIX指定),但它是事实上的标准。



所以如果你的秒数(纳秒,任何)忽略闰秒,转换为字段类型(例如 {年,月,日,小时,分钟,秒,纳秒)时忽略闰秒是完全正确的。事实上,在这样的背景下考虑到闰秒,实际上会引入错误。



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



如果您的时间源忽略闰秒,您可以仍然得到正确的答案到第二个。你只需要知道插入的一组闰秒。 以下是目前的列表



例如,如果您从1970-01-01 00:00:00 UTC(其中包含闰秒)得到秒数,并且您知道这代表now(目前为05-09-26 ),现在和1970-01-01之间插入的当前闰秒数是26.所以你可以从计数中减去26,然后按照这些算法,得到确切的结果。



这个图书馆可以自动化闰秒 - 为您计算。例如,要获得包括闰秒在UTC9-26 00:00:00 UTC和1970-01-01 00:00:00 UTC之间的秒数,您可以这样做: p>

  #includechrono_io.h
#includetz.h
#include< iostream>

int
main()
{
using namespace date;
auto now = to_utc_time(sys_days {2016_y / sep / 26});
auto then = to_utc_time(sys_days {1970_y / jan / 1});
std :: cout<<<现在 - 然后< '\\\
';
}

哪些输出:

  1474848026s 

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

  #includechrono_io.h
#includedate.h
#include< iostream>

int
main()
{
using namespace date;
使用命名空间std :: chrono_literals;
auto now = sys_days {2016_y / sep / 26} + 0s;
auto then = sys_days {1970_y / jan / 1};
std :: cout<<<现在 - 然后< '\\\
';
}

哪些输出:

  1474848000s 

对于 26s



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



在1958-01-01和1970-01-01之间插入10个闰秒,但以小于第二,而不仅仅是在十二月或六月底。记录准确插入了多少时间,以及什么时候是粗略的,我还没有找到一个可靠的来源。



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


I have seconds since Jan 1 1970 00:00 as an int64 in nanoseconds and I'm trying to convert it into month/day/year/day of week.

It's easy to do this iteratively, I have that working but I want to do it formulaically. I'm looking for the actual math.

解决方案

New answer for old question:

Rationale for this new answer: The existing answers either do not show the algorithms for the conversion from nanoseconds to year/month/day (e.g. they use libraries with the source hidden), or they use iteration in the algorithms they do show.

This answer has no iteration whatsoever.

The algorithms are here, and explained in excruciating detail. They are also unit tested for correctness over a span of +/- a million years (way more than you need).

The algorithms don't count leap seconds. If you need that, it can be done, but requires a table lookup, and that table grows with time.

The date algorithms deal only with units of days, and not nanoseconds. To convert days to nanoseconds, multiply by 86400*1000000000 (taking care to ensure you're using 64 bit arithmetic). To convert nanoseconds to days, divide by the same amount. Or better yet, use the C++11 <chrono> library.

There are three date algorithms from this paper that are needed to answer this question.

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);
}

These algorithms are written for C++14. If you have C++11, remove the constexpr. If you have C++98/03, remove the constexpr, the noexcept, and the static_asserts.

Note the lack of iteration in any of these three algorithms.

They can be used like this:

#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';
}

which outputs:

1440201600000000000
2015-8-22 Sat

The algorithms are in the public domain. Use them however you want. The date algorithms paper has several more useful date algorithms if needed (e.g. weekday_difference is both remarkably simple and remarkably useful).

These algorithms are wrapped up in an open source, cross platform, type-safe date library if needed.

If timezone or leap second support is needed, there exists a timezone library built on top of the date library.

Update: Different local zones in same app

See how to convert among different time zones.

Update: Are there any pitfalls to ignoring leap seconds when doing date calculations in this manner?

This is a good question from the comments below.

Answer: There are some pitfalls. And there are some benefits. It is good to know what they both are.

Almost every source of time from an OS is based on Unix Time. Unix Time is a count of time since 1970-01-01 excluding leap seconds. This includes functions like the C time(nullptr) and the C++ std::chrono::system_clock::now(), as well as the POSIX gettimeofday and clock_gettime. This is not a fact specified by the standard (except it is specified by POSIX), but it is the de facto standard.

So if your source of seconds (nanoseconds, whatever) neglects leap seconds, it is exactly correct to ignore leap seconds when converting to field types such as {year, month, day, hours, minutes, seconds, nanoseconds}. In fact to take leap seconds into account in such a context would actually introduce errors.

So it is good to know your source of time, and especially to know if it also neglects leap seconds as Unix Time does.

If your source of time does not neglect leap seconds, you can still get the correct answer down to the second. You just need to know the set of leap seconds that have been inserted. Here is the current list.

For example if you get a count of seconds since 1970-01-01 00:00:00 UTC which includes leap seconds and you know that this represents "now" (which is currently 2016-09-26), the current number of leap seconds inserted between now and 1970-01-01 is 26. So you could subtract 26 from your count, and then follow these algorithms, getting the exact result.

This library can automate leap-second-aware computations for you. For example to get the number of seconds between 2016-09-26 00:00:00 UTC and 1970-01-01 00:00:00 UTC including leap seconds, you could do this:

#include "chrono_io.h"
#include "tz.h"
#include <iostream>

int
main()
{
    using namespace date;
    auto now  = to_utc_time(sys_days{2016_y/sep/26});
    auto then = to_utc_time(sys_days{1970_y/jan/1});
    std::cout << now - then << '\n';
}

which outputs:

1474848026s

Neglecting leap seconds (Unix Time) looks like:

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

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

which outputs:

1474848000s

For a difference of 26s.

This upcoming New Years (2017-01-01) we will insert the 27th leap second.

Between 1958-01-01 and 1970-01-01 10 "leap seconds" were inserted, but in units smaller than a second, and not just at the end of Dec or Jun. Documentation on exactly how much time was inserted and exactly when is sketchy, and I have not been able to track down a reliable source.

Atomic time keeping services began experimentally in 1955, and the first atomic-based international time standard TAI has an epoch of 1958-01-01 00:00:00 GMT (what is now UTC). Prior to that the best we had was quartz-based clocks which were not accurate enough to worry about leap seconds.

这篇关于数学从1970年开始转换为日期,反之亦然的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆