自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,请删除constexpr
,noexcept
和static_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 Time。 Unix Time是自1970-01-01起的时间计数,不包括leap秒。这包括C
time(nullptr)
和C++ std::chrono::system_clock::now()
以及POSIX gettimeofday
和clock_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/