数学将 1970 年以来的秒数转换为日期,反之亦然
自 1970 年 1 月 1 日 00:00 以来,我将秒数作为 int64 以纳秒为单位,我正在尝试将其转换为月/日/年/星期几.
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.
推荐答案
旧问题的新答案:
这个新答案的基本原理:现有的答案要么没有显示从纳秒到年/月/日的转换算法(例如,他们使用隐藏源的库),或者他们在他们显示的算法中使用迭代.
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.
日期算法只处理以天为单位,而不是纳秒.要将天数转换为纳秒,请乘以 86400*1000000000
(注意确保您使用的是 64 位算法).要将纳秒转换为天,请除以相同的数量.或者更好的是,使用 C++11
库.
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);
}
这些算法是为 C++14 编写的.如果您有 C++11,请删除 constexpr
.如果您有 C++98/03,请删除 constexpr
、noexcept
和 static_assert
s.
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_assert
s.
注意这三种算法中的任何一种都缺乏迭代.
Note the lack of iteration in any of these three algorithms.
它们可以这样使用:
#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;
unsigned m, d;
std::tie(y, m, d) = civil_from_days(ns/86400/1000000000);
std::cout << y << '-' << m << '-' << d << ' ' << weekdays[wd] << '
';
}
输出:
1440201600000000000
2015-8-22 Sat
算法属于公共领域.随心所欲地使用它们.如果需要,日期算法论文有几个更有用的日期算法(例如 weekday_difference
非常简单而且非常有用).
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: 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.
几乎所有来自操作系统的时间来源都基于 Unix 时间.Unix 时间 是自 1970 年 1 月 1 日以来的时间计数不包括闰秒.这包括 C time(nullptr)
和 C++ std::chrono::system_clock::now()
等函数,以及 POSIX gettimeofday
和 clock_gettime
.这不是标准规定的事实(POSIX规定的除外),但它是事实上的标准.
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.
因此,如果您的秒源(纳秒,无论如何)忽略了闰秒,那么在转换为诸如 {year, month, day, hours, minute, seconds, nanoseconds 之类的字段类型时忽略闰秒是完全正确的}
.事实上,在这种情况下考虑闰秒实际上会引入错误.
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.
所以最好知道你的时间来源,尤其是知道它是否也忽略了闰秒 Unix 时间 确实如此.
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.
例如,如果您获得自 1970-01-01 00:00:00 UTC 以来的秒数,其中包括闰秒,并且您知道这代表现在"(目前是 2016-09-26),从现在到 1970-01-01 之间插入的当前闰秒数是 26.所以你可以从你的计数中减去 26,然后然后按照这些算法,得到确切的结果.
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.
这个库可以为您自动进行闰秒感知计算.例如,要获取 2016-09-26 00:00:00 UTC 和 1970-01-01 00:00:00 UTC 包括闰秒之间的秒数,您可以这样做:>
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 "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 << '
';
}
输出:
1474848026s
忽略闰秒(Unix 时间)看起来像:
Neglecting leap seconds (Unix Time) looks like:
#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 << '
';
}
输出:
1474848000s
相差26s
.
即将到来的新年 (2017-01-01) 我们将插入第 27 个闰秒.
This upcoming New Years (2017-01-01) we will insert the 27th leap second.
在 1958-01-01 和 1970-01-01 之间插入了 10 个闰秒",但单位小于 1 秒,而不仅仅是在 12 月底或 6 月.有关插入的确切时间的文档确切的时间是粗略的,我一直无法找到可靠的消息来源.
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.
原子计时服务于 1955 年开始进行实验,第一个基于原子的国际时间标准 TAI 的纪元是 1958-01-01 00:00:00 GMT(现在是 UTC).在此之前,我们拥有的最好的是基于石英的时钟,它不够准确,无需担心闰秒.
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.
相关文章