我从1970年1月1日00:00开始的秒数为纳秒的int64,我试图将其转换为月/日/年/星期.
迭代地执行此操作很容易,我有这个工作,但我想公式化.我正在寻找实际的数学.
How*_*ant 30
旧问题的新答案:
这个新答案的基本原理:现有的答案要么没有显示从纳秒到年/月/日的转换算法(例如,他们使用隐藏源的库),要么他们在他们显示的算法中使用迭代.
这个答案没有任何迭代.
算法在这里,并以极其详细的方式进行解释.它们还在+/-一百万年的时间内(超过您需要的方式)进行单元测试,以确保其正确性.
算法不计算闰秒.如果您需要,可以完成,但需要查表,并且该表随着时间的推移而增长.
日期算法仅处理天数,而不是纳秒.要将天数转换为纳秒,请乘以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;
}
Run Code Online (Sandbox Code Playgroud)
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);
}
Run Code Online (Sandbox Code Playgroud)
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);
}
Run Code Online (Sandbox Code Playgroud)
这些算法是为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';
}
Run Code Online (Sandbox Code Playgroud)
哪个输出:
1440201600000000000
2015-8-22 Sat
Run Code Online (Sandbox Code Playgroud)
算法属于公共领域.根据需要使用它们.如果需要,日期算法论文有几个更有用的日期算法(例如weekday_difference,非常简单且非常有用).
如果需要,这些算法包含在开源,跨平台,类型安全的日期库中.
更新:同一应用中的不同本地区域
更新:以这种方式进行日期计算时,忽略闰秒是否有任何陷阱?
这是以下评论中的一个很好的问题.
答:有一些陷阱.并且有一些好处.很高兴知道它们都是什么.
操作系统几乎每个时间源都基于Unix时间. Unix时间是自1970-01-01以来的时间计数,不包括闰秒.这包括C time(nullptr)和C++等功能std::chrono::system_clock::now(),以及POSIX gettimeofday和clock_gettime.这不是标准规定的事实(除非它由POSIX指定),但它是事实上的标准.
因此,如果您的秒数(纳秒,等等)忽略了闰秒,那么在转换为字段类型时忽略闰秒是完全正确的{year, month, day, hours, minutes, seconds, nanoseconds}.事实上,在这样的环境中考虑闰秒实际上会引入错误.
所以知道你的时间来源是很好的,尤其要知道它是否也忽略了Unix时间的闰秒.
如果你的时间来源没有忽略闰秒,你仍然可以得到正确的答案到第二个.您只需要知道已插入的闰秒集. 这是当前列表.
例如,如果你得到自1970-01-01 00:00:00 UTC以来的秒数,包括闰秒,你知道这代表"现在"(目前是2016-09-26),当前的飞跃数从现在到1970-01-01之间插入的秒数为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';
}
Run Code Online (Sandbox Code Playgroud)
哪个输出:
1474848026s
Run Code Online (Sandbox Code Playgroud)
忽略闰秒(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';
}
Run Code Online (Sandbox Code Playgroud)
哪个输出:
1474848000s
Run Code Online (Sandbox Code Playgroud)
对于差异26s.
即将到来的新年(2017-01-01)我们将插入第27 个闰秒.
在1958-01-01和1970-01-01之间插入了10个"闰秒",但是以小于一秒的单位,而不仅仅是在十二月或六月底.文件确切地插入了多少时间以及确切的时间是粗略的,我无法找到可靠的来源.
原子时间保持服务于1955年开始实验,第一个基于原子的国际时间标准TAI的时间为1958-01-01 00:00:00 GMT(现在是UTC).在此之前,我们最好的是基于石英的时钟,这些时钟不够精确,无法担心闰秒.
Ale*_*nze 10
Single Unix Specification为Epoch提供了Seconds的公式:
一个值,它近似于自Epoch以来经过的秒数.协调世界时名称(以秒(tm_sec),分钟(tm_min),小时(tm_hour),从1月1日开始的天数(tm_yday)和日历年减去1900(tm_year))与时间相关根据下面的表达式,表示自大纪元以来的秒数.
如果年份<1970年或值为负,则关系未定义.如果年份> = 1970且值为非负值,则该值与根据C语言表达式的协调世界时名称相关,其中tm_sec,tm_min,tm_hour,tm_yday和tm_year都是整数类型:
Run Code Online (Sandbox Code Playgroud)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以来的实际时间与秒的当前值之间的关系.
自Epoch以来对于与当前实际时间的期望关系对齐的秒值的变化是如何实现定义的.如自大纪元以来的秒数所示,每天应该恰好以86400秒计算.
注意:表达式的最后三个项在每年的一天中添加,这是从大纪元开始的第一个闰年开始的闰年之后.第一学期从1973年开始每4年增加一天,第二学期从2001年开始每100年减去一天,第三学期从2001年开始每400年增加一天.公式中的分数是整数除法; 也就是说,丢弃余数只留下整数商.
您需要将月份和日期转换为tm_yday才能使用此公式,并且考虑到闰年也应该这样做.公式中的其余部分是微不足道的.
试着从中了解如何从秒开始取回日期和时间.
编辑:
我在这个答案中实现了整数运算的转换器.