更新了更好的算法,链接到算法的详细描述,并完全转换为 std::tm
。
我想要打印或提取年/月/日值。是否有一种简单的方法将时间点转换为 tm(最好不使用 boost)?
首先要注意的是,std::chrono::time_point
不仅以 duration
为模板参数,还以时钟为模板参数。时钟意味着一个纪元。不同的时钟可以有不同的纪元。
例如,在我的系统上,std::chrono::high_resolution_clock
和 std::chrono::steady_clock
的纪元是:计算机启动时的时间。如果您不知道计算机启动的时间,就无法将该 time_point
转换为任何日历系统。
话虽如此,您可能只是在谈论 std::chrono::system_clock::time_point
,因为只有这个 time_point
,而且只有这个 time_point
,才需要与民用(公历)日历具有确定的关系。
事实证明,我所知道的每个 std::chrono::system_clock
实现都使用 Unix 时间。忽略闰秒,这有一个 1970 年元旦的纪元。
这并不是标准保证的。但是,如果您想要利用这一点,则可以使用以下公式在:
Chrono-Compatible Low-Level Date Algorithms
首先,警告,我使用最新的 C++1y 草案,其中包括很棒的新 constexpr
工具。如果您需要为您的编译器取消一些 constexpr
属性,请这样做。
通过上面链接中找到的算法,您可以将 std::chrono::time_point<std::chrono::system_clock, Duration>
转换为 std::tm
,而无需使用 time_t
,使用下面的函数即可:
template <class Duration>
std::tm
make_utc_tm(std::chrono::time_point<std::chrono::system_clock, Duration> tp)
{
using namespace std;
using namespace std::chrono;
typedef duration<int, ratio_multiply<hours::period, ratio<24>>> days;
Duration t = tp.time_since_epoch();
days d = round_down<days>(t);
t -= d;
int year;
unsigned month;
unsigned day;
std::tie(year, month, day) = civil_from_days(d.count());
std::tm tm = {0};
tm.tm_year = year - 1900;
tm.tm_mon = month - 1;
tm.tm_mday = day;
tm.tm_wday = weekday_from_days(d.count());
tm.tm_yday = d.count() - days_from_civil(year, 1, 1);
tm.tm_hour = duration_cast<hours>(t).count();
t -= hours(tm.tm_hour);
tm.tm_min = duration_cast<minutes>(t).count();
t -= minutes(tm.tm_min);
tm.tm_sec = duration_cast<seconds>(t).count();
return tm;
}
请注意,所有现有实现中的
std::chrono::system_clock::time_point
都是UTC(忽略闰秒)时区中的持续时间。如果您想使用另一个时区转换
time_point
,则需要在将其转换为
days
的精度之前向
std::chrono::system_clock::time_point
添加/减去时区的持续时间偏移量。如果您还想考虑闰秒,则在截断到
days
之前根据
此表格调整适当数量的秒,并且要知道unix时间与UTC
当前对齐。
可以通过以下方式验证此函数:
void
print_tm(const std::tm& tm)
{
using namespace std;
cout << tm.tm_year+1900;
char fill = cout.fill();
cout << setfill('0');
cout << '-' << setw(2) << tm.tm_mon+1;
cout << '-' << setw(2) << tm.tm_mday;
cout << ' ';
switch (tm.tm_wday)
{
case 0:
cout << "Sun";
break;
case 1:
cout << "Mon";
break;
case 2:
cout << "Tue";
break;
case 3:
cout << "Wed";
break;
case 4:
cout << "Thu";
break;
case 5:
cout << "Fri";
break;
case 6:
cout << "Sat";
break;
}
cout << ' ';
cout << ' ' << setw(2) << tm.tm_hour;
cout << ':' << setw(2) << tm.tm_min;
cout << ':' << setw(2) << tm.tm_sec << " UTC.";
cout << setfill(fill);
cout << " This is " << tm.tm_yday << " days since Jan 1\n";
}
int
main()
{
print_tm(make_utc_tm(std::chrono::system_clock::now()));
}
目前对我来说,输出如下:
2013年9月15日星期日UTC 18:16:50。这距离1月1日已经过去257天
如果chrono-Compatible Low-Level Date Algorithms 离线或者被移动了,这里是make_utc_tm
中使用的算法。在上述链接中有关于这些算法的深入解释。它们经过了充分的测试,并且拥有非常广阔的有效范围。
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);
const unsigned doy = (153*(m + (m > 2 ? -3 : 9)) + 2)/5 + d-1;
const unsigned doe = yoe * 365 + yoe/4 - yoe/100 + doy;
return era * 146097 + static_cast<Int>(doe) - 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);
const unsigned yoe = (doe - doe/1460 + doe/36524 - doe/146096) / 365;
const Int y = static_cast<Int>(yoe) + era * 400;
const unsigned doy = doe - (365*yoe + yoe/4 - yoe/100);
const unsigned mp = (5*doy + 2)/153;
const unsigned d = doy - (153*mp+2)/5 + 1;
const unsigned m = mp + (mp < 10 ? 3 : -9);
return std::tuple<Int, unsigned, unsigned>(y + (m <= 2), m, d);
}
template <class Int>
constexpr
unsigned
weekday_from_days(Int z) noexcept
{
return static_cast<unsigned>(z >= -4 ? (z+4) % 7 : (z+5) % 7 + 6);
}
template <class To, class Rep, class Period>
To
round_down(const std::chrono::duration<Rep, Period>& d)
{
To t = std::chrono::duration_cast<To>(d);
if (t > d)
--t;
return t;
}
更新
最近,我将上述算法封装为一个免费可用的日期/时间库,并在这里进行了文档记录和发布。该库使得从std::system_clock::time_point
中提取年/月/日,甚至是时:分:秒:毫秒等信息变得非常容易,而且无需经过time_t
。
下面是一个简单的程序,使用上述头文件库将当前日期和时间以UTC时区的形式打印出来,精确到system_clock::time_point
提供的精度(在本例中为微秒):
#include "date.h"
#include <iostream>
int
main()
{
using namespace date;
using namespace std;
using namespace std::chrono;
auto const now = system_clock::now();
auto const dp = time_point_cast<days>(now);
auto const date = year_month_day(dp);
auto const time = make_time(now-dp);
cout << date << ' ' << time << " UTC\n";
}
它只是为我输出:
2015-05-19 15:03:47.754002 UTC
这个库有效地将std::chrono::system_clock::time_point
转换为易于使用的日期时间类型。
time_t t = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
- bobobobo