使用 C++将时间戳转换为格式化的日期时间



我只能使用C++标准库(C++14)将时间戳转换为给定格式的日期时间。我是C++新手,我知道C++通过像Java这样的库对我们的支持并不多。 在中欧时区 (CET) 的给定日期和时间 2011-03-10 11:23:56,以下 将生成标准格式输出:">2011-03-10T11:23:56.123+0100"。

std::string format = "yyyy-MM-dd'T'HH:mm:ss'.'SSSZ"; //default format
auto duration = std::chrono::system_clock::now().time_since_epoch();
auto timestamp = std::chrono::duration_cast<std::chrono::milliseconds>(duration).count();

我的格式字符串语法是

G : Era 
yy,yyyy : year (two digits/four digits) 
M,MM : month in number (without zero/ with zero) - (e.g.,1/01) 
MMM,MMMM : month in text (shortname/fullname)- (e.g.,jan/january) 
d,dd : day in month (without zero/with zero)- (e.g.,1/01) 
D : day in year
F : day of week of month
E, EEEE : day of week 
h,hh : hours(1-12) (without zero/with zero)- (e.g.,1/01) 
H,HH : hours(0-23) (without zero/with zero)- (e.g.,1/01) 
m,mm : minutes (without zero/with zero)- (e.g.,1/01) 
s,ss : seconds (without zero/with zero)- (e.g.,1/01) 
S,SS,SSS : milliseconds 
w,W : Week in year (without zero/with zero)- (e.g.,1/01) 
a : AM/PM 
z,zzzz : timezone name 

这是一个有点棘手的问题,因为:

  1. 它没有明确说明输入是什么。 但是从示例代码中,我将假设std::chrono::system_clock::time_point.

  2. 重要的是要认识到中欧时区 (CET)被定义为具有固定 UTC 偏移量为 1h 的时区。 一些地理区域全年都遵循此时区规则,有些则不然。 没有人总是遵循它。 无论如何,这部分问题允许我们对所涉及的 UTC 偏移量进行硬编码:1h。 无需进行夏令时调整。

在 C++14 中,有两种方法可以在不涉及受版权保护(甚至是开源)的第三方软件的情况下做到这一点:

  1. 使用 C API。

  2. 自己滚。

1 的问题在于它容易出错。 它不直接处理毫秒精度。 它不直接处理特定时区,例如 CET。 C API 只知道 UTC 和计算机本地设置的时区。 但这些问题是可以克服的。

2 的问题在于它涉及非直观的算术来从std::chrono::system_clock::time_point中提取年、月和日字段。

尽管 2 存在问题,但这是我更喜欢的解决方案,我将在下面介绍。 我还将展示 C++20 将如何使这变得更容易。

在所有解决方案中,我将通过实现以下形式的函数来形式化输入和输出:

std::string format_CET(std::chrono::system_clock::time_point tp);

自己卷 (C++14)

有六个独立的步骤。 它将需要这些标头,而不需要其他标头:

#include <chrono>
#include <string>
#include <iomanip>
#include <iostream>
#include <limits>
#include <sstream>

A. 将输入偏移 +1 小时 UTC 偏移量。

// shift time_point to CET
tp += 1h;

函数本地 using 指令可以方便地将 UDLh以及此函数中<chrono>所需的所有其他内容都纳入范围:

using namespace std::chrono;

二. 获取time_point tp的两种变体:一种具有毫秒精度,另一种具有日精度:

// Get time_points with both millisecond and day precision
auto tp_ms = time_point_cast<milliseconds>(tp);
auto tp_d = time_point_cast<days>(tp_ms);

重要的是要了解这两个转换四舍五入到零,并且对于负时间点会给出不正确的结果。system_clock给出其纪元 1970-01-01 00:00:00 UTC 之前的负时间点。 C++17 介绍了解决此问题的floor<millliseconds>(tp)

日精度time_point将用于提取年、月和日字段,毫秒精度time_point将用于提取小时、分钟、秒和毫秒字段。 上面使用的duration days要到 C++20 才会添加,但您可以使用:

using days = std::chrono::duration<int, std::ratio<86400>>;

三. 要从tp_d获取年、月和日字段,使用一种公共域算法进行历法运算非常方便。 这不是第三方库。 它是用于编写自己的日历库的算法(这是我正在解释的内容)。 我已经自定义了civil_from_days算法以准确解决format_CET的需求:

// Get {y, m, d} from tp_d
auto z = tp_d.time_since_epoch().count();
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]
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]
y += (m <= 2);

在上面链接的网站上,对于那些想知道它如何工作的人来说,这个算法有一个详尽的详细推导。

此时,积分变量{y, m, d}包含年、月、日三元组。

D. 获取自当地午夜以来的持续时间。 这将用于提取当地时间:

// Get milliseconds since the local midnight
auto ms = tp_ms - tp_d;

E. 获取小时、分钟、秒和毫秒字段:

// Get {h, M, s, ms} from milliseconds since midnight
auto h = duration_cast<hours>(ms);
ms -= h;
auto M = duration_cast<minutes>(ms);
ms -= M;
auto s = duration_cast<seconds>(ms);
ms -= s;

此时,chrono::duration变量{h, M, s, ms}保存所需的值。

F. 现在我们准备格式化:

// Format {y, m, d, h, M, s, ms} as yyyy-MM-dd'T'HH:mm:ss'.'SSS+0100
std::ostringstream os;
os.fill('0');
os << std::setw(4) << y << '-' << std::setw(2) << m << '-' << std::setw(2)
<< d << 'T' << std::setw(2) << h.count() << ':'
<< std::setw(2) << M.count() << ':' << std::setw(2) << s.count()
<< '.' << std::setw(3) << ms.count() << "+0100";
return os.str();

使用操纵器setw的组合来设置每个字段的宽度,填充字符为0,可以获得所需的前导零。

C++20解决方案

这在 C++20 规范中要容易得多

std::string
format_CET(std::chrono::system_clock::time_point tp)
{
using namespace std::chrono;
static auto const CET = locate_zone("Etc/GMT-1");
return std::format("{:%FT%T%z}", zoned_time{CET, floor<milliseconds>(tp)});
}

"Etc/GMT-1"相当于中欧时区 (CET)。time_zone const*位于变量CET中并存储在 中。time_point tp被截断为毫秒精度,并使用zoned_timetime_zone配对。 然后使用所示的格式字符串格式化此zoned_time(精度为毫秒级)。

存在 C++20 规范的开源(MIT 许可证)预览,此处的语法差异非常小。

#include "date/tz.h"
std::string
format_CET(std::chrono::system_clock::time_point tp)
{
using namespace date;
using namespace std::chrono;
static auto const CET = locate_zone("Etc/GMT-1");
return format("%FT%T%z", zoned_time<milliseconds>{CET, floor<milliseconds>(tp)});
}

Windows 需要一些安装。

此预览版适用于 C++14。 在 C++17 及更高版本中,zoned_time<milliseconds>可以简化为zoned_time.

自定义时区支持

还有一种方法可以使用预览库,这样就不需要安装。 它成为仅标头库。 这是通过创建一个仅对 CET 进行建模的自定义时区来完成的,然后将其安装在zoned_time中。 自定义时区如下所示:

#include "date/tz.h"
class CET
{
public:
template <class Duration>
auto
to_local(date::sys_time<Duration> tp) const
{
using namespace date;
using namespace std::chrono;
return local_time<Duration>{(tp + 1h).time_since_epoch()};
}
template <class Duration>
auto
to_sys(date::local_time<Duration> tp) const
{
using namespace date;
using namespace std::chrono;
return sys_time<Duration>{(tp - 1h).time_since_epoch()};
}
template <class Duration>
date::sys_info
get_info(date::sys_time<Duration>) const
{
using namespace date;
using namespace std::chrono;
return {ceil<seconds>(sys_time<milliseconds>::min()),
floor<seconds>(sys_time<milliseconds>::max()),
1h, 0min, "CET"};
}
const CET* operator->() const {return this;}
};

CET现在满足足够的时区要求,可以在zoned_time中使用并像以前一样格式化。 在 C++14 中,语法很复杂,因为必须显式指定模板参数zoned_time

std::string
format_CET(std::chrono::system_clock::time_point tp)
{
using namespace date;
using namespace std::chrono;
using ZT = zoned_time<milliseconds, CET>;
return format("%FT%T%z", ZT{CET{}, floor<milliseconds>(tp)});
}

此选项也在 C++20 规范中,其优点是时区缩写(在您的问题中未使用)将正确报告"CET"而不是"+01"。

有关自定义时区的更多文档,请参阅此处。

使用这些解决方案中的任何一个,现在可以像这样执行该功能:

#include <iostream>
int
main()
{
std::cout << format_CET(std::chrono::system_clock::now()) << 'n';
}

典型的输出如下所示:

2019-10-29T16:37:51.217+0100
#include <ctime>
#include <iostream>
#include <iomanip>
int main()
{
auto t = std::time(nullptr);
auto tm = *std::localtime(&t);
std::cout << std::put_time(&tm, "%Y-%m-%dT%H:%M:%S.%z%Z") << "n";
}
----
2019-10-29T05:05:14.-0700PDT

相关内容

  • 没有找到相关文章

最新更新