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

标签 c++ date math

自1970年1月1日00:00以来,我有一个秒,以int64表示(以纳秒为单位),我正尝试将其转换为月/日/年/日。

迭代地执行此操作很容易,我可以正常工作,但我想按惯例进行。我正在寻找实际的数学。

最佳答案

旧问题的新答案:

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

这个答案没有任何迭代。

The algorithms are here,并进行了详细说明。还对它们进行了+/-一百万年的跨度(远远超出您的需要)的单元测试。

该算法不计算leap秒。如果需要,可以完成,但需要查找表,并且该表会随着时间增长。

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

该算法在公共(public) Realm 。根据需要使用它们。如果需要,date algorithms paper有一些更有用的日期算法(例如weekday_difference既非常简单又非常有用)。

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

如果需要时区或leap秒支持,则在timezone library之上构建一个date library

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

了解如何convert among different time zones

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

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

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

操作系统中几乎每个时间源都基于Unix TimeUnix Time是自1970-01-01起的时间计数,不包括leap秒。这包括C time(nullptr)和C++ std::chrono::system_clock::now()以及POSIX gettimeofdayclock_gettime之类的函数。这不是标准指定的事实(除了POSIX指定的事实),而是事实标准。

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

因此,最好知道您的时间来源,尤其是要知道它是否也像Unix Time一样忽略leap秒。

如果您的时间来源没有忽略leap秒,那么您仍然可以将正确答案降到秒。您只需要知道已插入的of秒设置即可。 Here is the current list

例如,如果您获得自1970-01-01 00:00:00 UTC以来的秒数,其中包括leap秒,并且您知道这表示“现在”(当前为2016-09-26),则表示当前的leap数从现在到1970-01-01之间插入的秒数是26。因此您可以从计数中减去26,然后按照以下算法进行操作,即可获得准确的结果。

This library可以为您自动化leap秒感知的计算。例如,要获取2016年9月26日00:00:00 UTC和1970-01-01 00:00:00 UTC之间的秒数(包括seconds秒),可以执行以下操作:
#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

忽略leap秒(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个leap秒。

在1958-01-01和1970-01-01之间插入了10个“le秒”,但单位小于1秒,而不仅仅是在12月或6月末。有关确切插入多少时间以及何时插入的文档是粗略的,我无法找到可靠的消息来源。

原子计时服务于1955年开始实验,第一个基于原子的国际时间标准TAI的纪元是格林尼治标准时间1958-01-01 00:00:00(现在是UTC)。在此之前,我们拥有的最好的是 quartz 钟,其精度不足以担心worry秒。

关于c++ - 数学将1970年以来的秒转换为日期,反之亦然,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7960318/

相关文章:

c++ - 在 gdb 中打印有关文件流对象的详细信息 - C++

java - 从日期中删除日期名称并将其设置为仅 'yyyy-MM-dd HH:mm:ss'

javascript - 使用正则表达式验证 mm/dd/yyyy

javascript - 需要帮助来计算欧拉常数的近似值

c - 在 C 中使用 exp101 和 log101 函数

c++ - "error: taking address of temporary array"是什么意思?

c++ - std::vector - 游戏循环期间的优化

c++ - 从另一个容器创建容器,在 C++ 中应用每个元素一些函数

javascript - 比较JS中的两个日期

java - 限制子集总和到指定范围内