我从1970年1月1日00:00开始以秒为单位,以纳秒为单位,我试图将其转换为月/日/年/星期。
迭代地执行此操作很容易,我有这个工作,但我想要公式化。我正在寻找实际的数学。
答案 0 :(得分:26)
旧问题的新答案:
这个新答案的基本原理:现有答案要么没有显示从纳秒到年/月/日的转换算法(例如,他们使用隐藏源的库),要么他们在他们显示的算法中使用迭代
这个答案没有任何迭代。
The algorithms are here,并以极其详细的方式解释。他们还在+/-一百万年的时间内(比您需要的方式更多)进行单元测试。
算法不计算闰秒。如果您需要,可以完成,但需要查表,并且该表随着时间的推移而增长。
日期算法仅处理天数,而不是纳秒。要将天数转换为纳秒,请乘以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
算法属于公共领域。根据需要使用它们。如果需要,date algorithms paper还有几个有用的日期算法(例如weekday_difference
非常简单且非常有用)。
如果需要,这些算法将包含在open source, cross platform, type-safe date library中。
如果需要时区或闰秒支持,则timezone library上会建立date library。
更新:同一应用中的不同本地区域
了解如何convert among different time zones。
更新:以这种方式进行日期计算时,忽略闰秒是否有任何陷阱?
以下评论中这是一个很好的问题。
答案:有一些陷阱。并且有一些好处。很高兴知道它们都是。
操作系统几乎每个时间源都基于Unix Time。 Unix Time是自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 Time所做的闰秒。
如果你的时间没有忽略闰秒,你可以仍然得到正确答案到第二个。您只需要知道已插入的闰秒集。 Here is the current list
例如,如果您获得自1970-01-01 00:00:00 UTC以来的秒数,包含闰秒,并且您知道这代表“现在”(目前是2016年 - 09-26),从现在到1970-01-01之间插入的当前闰秒数是26.所以你可以从你的计数中减去26,然后然后遵循这些算法,得到确切的结果。
This library可以为您自动执行闰秒感知计算。例如,要获得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';
}
输出:
1474848026s
忽略闰秒(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 th 闰秒。
在1958-01-01和1970-01-01之间插入了10个“闰秒”,但是以小于一秒的单位插入,而不是仅在12月或6月底。文件中插入了多少时间确切地说是粗略的时候,我无法找到可靠的来源。
原子时间保持服务于1955年开始实验,第一个基于原子的国际时间标准TAI的时间为1958-01-01 00:00:00 GMT(现在是UTC)。在此之前,我们最好的是基于石英的时钟,这些时钟不够准确,无法担心闰秒。
答案 1 :(得分:10)
单Unix规范给出了Seconds since the Epoch的公式:
一个近似于已经过的秒数的值 从大纪元开始。协调世界时名称(以术语表示 秒(tm_sec),分钟(tm_min),小时(tm_hour),天数 1月1日(tm_yday),日历年减1900 (tm_year))与自秒以来表示为秒的时间有关 Epoch,根据下面的表达。
如果年份<1970年或价值为负,则关系为 未定义。如果年份是> = 1970并且该值是非负的,那么 值与根据协调世界时名称相关 C语言表达式,其中tm_sec,tm_min,tm_hour,tm_yday和 tm_year都是整数类型:
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才能使用此公式,并且考虑到闰年也应该这样做。公式中的其余部分是微不足道的。
尝试从中了解如何从秒开始取回日期和时间。
修改强>:
我在this answer中实现了整数运算的转换器。
答案 2 :(得分:2)
bool FloatToTime(float seconds_since_epoch, bool local_time, struct tm *timest)
{
struct tm *ret;
time_t t=(time_t) seconds_since_epoch;
if (local_time) ret=localtime(&t);
else ret=gmtime(&t);
if(ret==NULL) return false;
memcpy(timest, ret, sizeof(struct tm));
return true;
}
将秒作为第一个参数传递给它。第二个参数对于本地时间应为true,对于GMT应为false。第三个参数是指向保存响应的结构的指针。
返回结构(来自手册页):
tm_sec:分钟后的秒数,通常在0到0的范围内 59,但可以达到60以允许闰秒。
tm_min:小时后的分钟数,范围为0到59.
tm_hour:午夜过后的小时数,范围为0到23。
tm_mday:该月中的某一天,范围为1到31。
tm_mon:自1月以来的月数,范围为0到11。
tm_year:自1900年以来的年数。
tm_wday:自星期日以来的天数,范围为0到6。
tm_yday:自1月1日以来的天数,范围为0到365.
tm_isdst:一个标志,指示夏令时是否有效 在描述的时间。如果夏令时,该值为正 时间有效,如果不是则为零,如果是,则为负 信息不可用。
答案 3 :(得分:2)
答案 4 :(得分:1)
有很多功能可以执行此操作,请参阅http://www.cplusplus.com/reference/clibrary/ctime/,即strftime
。
答案 5 :(得分:1)
此代码有效......
使用方法: uint32_t getSecsSinceEpoch(1970,month,day,years_since_epoch,hour,minute,second);
实施例: timestamp = getSecsSinceEpoch(1970,6,12,(2014 - 1970),15,29,0)
返回:1402586940
您可以访问www.epochconverter.com进行验证。
花了大约20分钟来写它,其中大部分用于与朋友争论我是否应该包括闰秒,纳秒等等.Blech。
玩得开心......
博士。布莱恩威尔切特
#define DAYSPERWEEK (7)
#define DAYSPERNORMYEAR (365U)
#define DAYSPERLEAPYEAR (366U)
#define SECSPERDAY (86400UL) /* == ( 24 * 60 * 60) */
#define SECSPERHOUR (3600UL) /* == ( 60 * 60) */
#define SECSPERMIN (60UL) /* == ( 60) */
#define LEAPYEAR(year) (!((year) % 4) && (((year) % 100) || !((year) % 400)))
const int _ytab[2][12] = {
{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
{31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
};
/****************************************************
* Class:Function : getSecsSomceEpoch
* Input : uint16_t epoch date (ie, 1970)
* Input : uint8 ptr to returned month
* Input : uint8 ptr to returned day
* Input : uint8 ptr to returned years since Epoch
* Input : uint8 ptr to returned hour
* Input : uint8 ptr to returned minute
* Input : uint8 ptr to returned seconds
* Output : uint32_t Seconds between Epoch year and timestamp
* Behavior :
*
* Converts MM/DD/YY HH:MM:SS to actual seconds since epoch.
* Epoch year is assumed at Jan 1, 00:00:01am.
****************************************************/
uint32_t getSecsSinceEpoch(uint16_t epoch, uint8_t month, uint8_t day, uint8_t years, uint8_t hour, uint8_t minute, uint8_t second)
{
unsigned long secs = 0;
int countleap = 0;
int i;
int dayspermonth;
secs = years * (SECSPERDAY * 365);
for (i = 0; i < (years - 1); i++)
{
if (LEAPYEAR((epoch + i)))
countleap++;
}
secs += (countleap * SECSPERDAY);
secs += second;
secs += (hour * SECSPERHOUR);
secs += (minute * SECSPERMIN);
secs += ((day - 1) * SECSPERDAY);
if (month > 1)
{
dayspermonth = 0;
if (LEAPYEAR((epoch + years))) // Only counts when we're on leap day or past it
{
if (month > 2)
{
dayspermonth = 1;
} else if (month == 2 && day >= 29) {
dayspermonth = 1;
}
}
for (i = 0; i < month - 1; i++)
{
secs += (_ytab[dayspermonth][i] * SECSPERDAY);
}
}
return secs;
}
答案 6 :(得分:0)
首先,不要将秒数存储为浮点数。如果您需要微/纳秒,请单独存储它们。你将需要整数来进行这些计算。
这取决于你的时区(DST规则,闰年,闰秒),但我会说首先得到整数除以86400的天数。然后通过模数除以86400找出剩余的天数。现在你可以计算出第一个整数将天数除以365,然后减去剩余天数的闰天数(通过以天数除以365的模数计算)。您还需要从剩余秒数(已计算)中减去闰秒数。如果该减法驱动那些低于零的数字,则从下一个最大面额中减去。然后,您可以使用日历的显式逻辑计算月中的日期。如果你降落在夏令时,请务必添加一小时(或任何DST偏移)。
就个人而言,我只会使用Boost.Date_Time,因为它会完成所有这些以及更多(可能错误比你或我在前几次迭代中所做的更少),但我想我会拍摄一下你的问题......
答案 7 :(得分:0)
在
for (i = 0; i < (years - 1); i++)
{
if (LEAPYEAR((epoch + i)))
countleap++;
}
后来:
for (i = 0; i < years; i++)
{
if (LEAPYEAR((epoch + i)))
countleap++;
}
修正后代码对我有用。
答案 8 :(得分:0)
我需要在没有硬件乘法器的低端8位MCU上实现到Unix时间的转换。下面是C#代码,只需要一般的8位乘法和除以常数4和100。都在32位(长)操作数上。 C#代码可以轻松移植到最终框架。它给出的结果与.NET中的 DateTimeOffset.ToUnixTimeSeconds()相同。
static long UnixTime ( int sec, int min, int hour, int day, int month, int year )
{
// Cumulative days for each previous month of the year
int[] mdays = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 };
// Year is to be relative to the epoch start
year -= 1970;
// Compensation of the non-leap years
int minusYear = 0;
// Detect potential lead day (February 29th) in this year?
if ( month >= 3 )
{
// Then add this year into "sum of leap days" computation
year++;
// Compute one year less in the non-leap years sum
minusYear = 1;
}
return
// + Seconds from computed minutes
60 * (
// + Minutes from computed hours
60 * (
// + Hours from computed days
24 * (
// + Day (zero index)
day - 1
// + days in previous months (leap day not included)
+ mdays[month - 1]
// + days for each year divisible by 4 (starting from 1973)
+ ( ( year + 1 ) / 4 )
// - days for each year divisible by 100 (starting from 2001)
- ( ( year + 69 ) / 100 )
// + days for each year divisible by 400 (starting from 2001)
+ ( ( year + 369 ) / 100 / 4 )
// + days for each year (as all are non-leap years) from 1970 (minus this year if potential leap day taken into account)
+ ( 5 * 73 /*=365*/ ) * ( year - minusYear )
// + Hours
) + hour
// + Minutes
) + min
// + Seconds
) + sec;
}
希望有帮助。
已编辑:
下面是针对8位PIC MCU和CC5X编译器的优化代码。
uns32 unixTime;
...
// Test data returning 0xFfFfFfFf UnixTime
uns8 year = 2106 - 1970;
uns8 month = 2;
uns8 day = 7;
uns8 hour = 6;
uns8 min = 28;
uns8 sec = 15;
// See original C# code below
//### Compute days
// ( 5 * 73 /*=365*/ ) * year
unixTime = year;
mulUnixTime( 5 );
mulUnixTime( 73 );
// if ( month >= 3 ) year++;
if ( month > 3 )
year++;
// if ( year > 130 ) => minus 1 total days ( year-=4 makes a result of the next division by 4 less by 1)
if ( year > 130 )
year -= 4;
// + ( ( year + 1 ) / 4 )
addUnixTime( ( year + 1 ) / 4 );
// + mdays[month - 1]
addUnixTime( daysInMonths( month ) );
// + day - 1
addUnixTime( day - 1 );
//### Compute hours
// Hours from computed days
mulUnixTime( 24 );
// + Hours
addUnixTime( hour );
//### Compute minutes
// Minutes from computed hours
mulUnixTime( 60 );
// + Minutes
addUnixTime( min );
//### Compute seconds
// Seconds from computed minutes
mulUnixTime( 60 );
// + Seconds
addUnixTime( sec );
...
void mulUnixTime( uns8 mul )
{
unixTime *= mul;
}
void addUnixTime( uns8 add )
{
unixTime += add;
}
uns8 daysInMonths( uns8 month @ W )
{
skip( month );
#pragma computedGoto 1
return 0xFF;// Dummy value for month 0
return 0; // January
return 31; // February
return 59; // ...
return 90;
return 120;
return 151;
return 181;
return 212;
return 243;
return 273;
return 304; // ...
return 334; // December
#pragma computedGoto 0
}
/*
static long UnixTime ( int sec, int min, int hour, int day, int month, int year )
{
// Cumulative days for each previous month of the year
int[] mdays = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 };
// Year is to be relative to the epoch start
year -= 1970;
// Compensation of the non-leap years
int minusYear = 0;
// Detect potential lead day (February 29th) in this year?
if ( month >= 3 )
{
// Then add this year into "sum of leap days" computation
year++;
// Compute one year less in the non-leap years sum
minusYear = 1;
}
return
// + Seconds from computed minutes
60 * (
// + Minutes from computed hours
60 * (
// + Hours from computed days
24L * (
// + Day (zero index)
day - 1
// + days in previous months (leap day not included)
+ mdays[month - 1]
// + days for each year divisible by 4 (starting from 1973)
+ ( ( year + 1 ) / 4 )
// - days after year 2000
- ( ( year > 130 ) ? 1 : 0 )
// + days for each year (as all are non-leap years) from 1970 (minus this year if potential leap day taken into account)
+ ( 5 * 73 ) * ( year - minusYear )
// + Hours
) + hour
// + Minutes
) + min
// + Seconds
) + sec;
}
*/