time_t 提高日期转换给出不正确的结果
time_t to boost date conversion giving incorrect result
我有一组 unix 时间戳,我正在转换为 boost (1.65.1) 日期,但是当它们在未来太远时,转换似乎会中断。 2040 年左右及以后的任何事情似乎都以某种方式回到 post 1900 年。
给定以下代码...
{
std::time_t t = 1558220400;
boost::gregorian::date date = boost::posix_time::from_time_t(t).date();
std::cout << "Date: " << date << std::endl;
}
{
std::time_t t = 2145500000;
boost::gregorian::date date = boost::posix_time::from_time_t(t).date();
std::cout << "Date: " << date << std::endl;
}
{
std::time_t t = 2500000000;
boost::gregorian::date date = boost::posix_time::from_time_t(t).date();
std::cout << "Date: " << date << std::endl;
}
...我得到以下输出...
Date: 2019-May-18
Date: 2037-Dec-27
Date: 1913-Feb-13
...但是我期待以下输出...
Expected output:
Date: 2019-May-18
Date: 2037-Dec-27
Date: 2049-Mar-22
我这里有什么地方做错了吗?
您似乎遇到了 2038 年问题。
32位有符号整数可表示的最大数为2'147'483'647。自 1970 年 1 月 1 日 00:00:00 UTC(UNIX 纪元)以来的 2'147'483'647 秒是 2038 年 1 月 19 日的 03:14:07 UTC。之后的任何 UNIX 时间都无法使用 32 位签名整数。
要么std::time_t
在系统上是32位的,要么是boost库里面转成32位的。从source that boost converts the input into long
using static_cast
(and still does in version 1.70)可以看出。 long
例如在 windows 上是 32 位,即使在 64 位架构上也是如此。它在许多其他系统上是 64 位的,例如 64 位 Linux.
正如 eerorika 所指出的,这是由整数溢出引起的,因为 boost::posix_time::from_time_t 将 64 位 time_t 值转换为 32 位长(在 Windows 上)。
如果你手头紧,发现自己处于相同的位置,那么你可以使用以下函数来执行转换:
boost::gregorian::datetimet_to_date(time_t t)
{
auto time = boost::posix_time::ptime(boost::gregorian::date(1970,1,1));
int64_t current_t = t;
long long_max = std::numeric_limits<long>::max();
while(current_t > 0)
{
long seconds_to_add = 0;
if(current_t >= long_max)
seconds_to_add = long_max;
else
seconds_to_add = static_cast<long>(current_t);
current_t -= seconds_to_add;
time += boost::posix_time::seconds(seconds_to_add);
}
return time.date();
}
在 C++20 中,这现在看起来像:
#include <chrono>
#include <iostream>
int
main()
{
using namespace std::chrono;
{
std::time_t t = 1558220400;
auto date = floor<days>(system_clock::from_time_t(t));
std::cout << "Date: " << date << '\n';
}
{
std::time_t t = 2145500000;
auto date = floor<days>(system_clock::from_time_t(t));
std::cout << "Date: " << date << '\n';
}
{
std::time_t t = 2500000000;
auto date = floor<days>(system_clock::from_time_t(t));
std::cout << "Date: " << date << '\n';
}
}
输出:
Date: 2019-05-18
Date: 2037-12-27
Date: 2049-03-22
如果您的 time_t
是 32 位,则以上方法不足以解决问题。在这种情况下,您必须完全避免使用 C API。这看起来像:
{
auto t = 1558220400;
auto date = floor<days>(sys_seconds{seconds{t}});
std::cout << "Date: " << date << '\n';
}
{
auto t = 2145500000;
auto date = floor<days>(sys_seconds{seconds{t}});
std::cout << "Date: " << date << '\n';
}
{
auto t = 2500000000;
auto date = floor<days>(sys_seconds{seconds{t}});
std::cout << "Date: " << date << '\n';
}
如果您的供应商尚未发布 C++20 的这一部分,a free, open-source preview that works with C++11/14/17 is available。1
只需添加:
#include "date/date.h"
...
using namespace date;
1 完全披露:我是这个库的主要作者。我不会从这项工作中寻求任何经济利益。但有时如果我不完全公开这些信息,人们会变得脾气暴躁。
我有一组 unix 时间戳,我正在转换为 boost (1.65.1) 日期,但是当它们在未来太远时,转换似乎会中断。 2040 年左右及以后的任何事情似乎都以某种方式回到 post 1900 年。
给定以下代码...
{
std::time_t t = 1558220400;
boost::gregorian::date date = boost::posix_time::from_time_t(t).date();
std::cout << "Date: " << date << std::endl;
}
{
std::time_t t = 2145500000;
boost::gregorian::date date = boost::posix_time::from_time_t(t).date();
std::cout << "Date: " << date << std::endl;
}
{
std::time_t t = 2500000000;
boost::gregorian::date date = boost::posix_time::from_time_t(t).date();
std::cout << "Date: " << date << std::endl;
}
...我得到以下输出...
Date: 2019-May-18
Date: 2037-Dec-27
Date: 1913-Feb-13
...但是我期待以下输出...
Expected output:
Date: 2019-May-18
Date: 2037-Dec-27
Date: 2049-Mar-22
我这里有什么地方做错了吗?
您似乎遇到了 2038 年问题。
32位有符号整数可表示的最大数为2'147'483'647。自 1970 年 1 月 1 日 00:00:00 UTC(UNIX 纪元)以来的 2'147'483'647 秒是 2038 年 1 月 19 日的 03:14:07 UTC。之后的任何 UNIX 时间都无法使用 32 位签名整数。
要么std::time_t
在系统上是32位的,要么是boost库里面转成32位的。从source that boost converts the input into long
using static_cast
(and still does in version 1.70)可以看出。 long
例如在 windows 上是 32 位,即使在 64 位架构上也是如此。它在许多其他系统上是 64 位的,例如 64 位 Linux.
正如 eerorika 所指出的,这是由整数溢出引起的,因为 boost::posix_time::from_time_t 将 64 位 time_t 值转换为 32 位长(在 Windows 上)。
如果你手头紧,发现自己处于相同的位置,那么你可以使用以下函数来执行转换:
boost::gregorian::datetimet_to_date(time_t t)
{
auto time = boost::posix_time::ptime(boost::gregorian::date(1970,1,1));
int64_t current_t = t;
long long_max = std::numeric_limits<long>::max();
while(current_t > 0)
{
long seconds_to_add = 0;
if(current_t >= long_max)
seconds_to_add = long_max;
else
seconds_to_add = static_cast<long>(current_t);
current_t -= seconds_to_add;
time += boost::posix_time::seconds(seconds_to_add);
}
return time.date();
}
在 C++20 中,这现在看起来像:
#include <chrono>
#include <iostream>
int
main()
{
using namespace std::chrono;
{
std::time_t t = 1558220400;
auto date = floor<days>(system_clock::from_time_t(t));
std::cout << "Date: " << date << '\n';
}
{
std::time_t t = 2145500000;
auto date = floor<days>(system_clock::from_time_t(t));
std::cout << "Date: " << date << '\n';
}
{
std::time_t t = 2500000000;
auto date = floor<days>(system_clock::from_time_t(t));
std::cout << "Date: " << date << '\n';
}
}
输出:
Date: 2019-05-18
Date: 2037-12-27
Date: 2049-03-22
如果您的 time_t
是 32 位,则以上方法不足以解决问题。在这种情况下,您必须完全避免使用 C API。这看起来像:
{
auto t = 1558220400;
auto date = floor<days>(sys_seconds{seconds{t}});
std::cout << "Date: " << date << '\n';
}
{
auto t = 2145500000;
auto date = floor<days>(sys_seconds{seconds{t}});
std::cout << "Date: " << date << '\n';
}
{
auto t = 2500000000;
auto date = floor<days>(sys_seconds{seconds{t}});
std::cout << "Date: " << date << '\n';
}
如果您的供应商尚未发布 C++20 的这一部分,a free, open-source preview that works with C++11/14/17 is available。1
只需添加:
#include "date/date.h"
...
using namespace date;
1 完全披露:我是这个库的主要作者。我不会从这项工作中寻求任何经济利益。但有时如果我不完全公开这些信息,人们会变得脾气暴躁。