使用 C++ 将时间戳转换为格式化的日期时间
Convert timestamp to the formatted date time using C++
我只能使用 C++ 标准库 (C++14) 将时间戳转换为给定格式的日期时间。我是 C++ 的新手,我知道 C++ 不会通过像 Java 这样的库为我们提供很多支持。
在欧洲中部时区 (CET)2011-03-1011: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
#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
这个问题有点棘手,因为:
没有明确说明输入是什么。但是根据示例代码,我将假设 std::chrono::system_clock::time_point
.
重要的是要认识到 欧洲中部时区 (CET) 被定义为 time zone with a fixed UTC offset of 1h。有些地理区域全年都遵循这个时区规则,有些则不然。而none一直都在关注它。无论如何,这部分问题允许我们hard-code涉及的UTC偏移量:1h。没有夏令时调整。
在 C++14 中,有两种方法可以在不涉及受版权保护(甚至 open-source)的第 3 方软件的情况下做到这一点:
使用C API.
自己动手。
1 的问题在于它容易出错。它不直接处理毫秒精度。它不直接处理特定时区,例如 CET。 C API 只知道 UTC,以及计算机本地设置的时区。但这些问题都是可以克服的。
2的问题在于它涉及non-intuitive算法从std::chrono::system_clock::time_point
中提取年月日字段。
尽管 2 存在问题,但这是我更喜欢的解决方案,我将在下面介绍。我还将展示 C++20 如何使这个 变得更容易。
在所有解决方案中,我将通过实现以下形式的函数来形式化输入和输出:
std::string format_CET(std::chrono::system_clock::time_point tp);
自己动手 (C++14)
有六个独立的步骤。它将需要这些 headers 而不是其他的:
#include <chrono>
#include <string>
#include <iomanip>
#include <iostream>
#include <limits>
#include <sstream>
一个。将输入偏移 +1 小时 UTC 偏移量。
// shift time_point to CET
tp += 1h;
一个 function-local using 指令可以方便地将 UDL h
带入范围,以及此函数中 <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)
解决了这个问题。
day-precisiontime_point
将用于提取年月日字段,millisecond-precisiontime_point
将用于提取时分,秒和毫秒字段。上面使用的 duration days
直到 C++20 才会被添加,但是你可以这样做:
using days = std::chrono::duration<int, std::ratio<86400>>;
C。要从 tp_d
中获取年、月和日字段,使用 public domain algorithms for calendrical operations 之一很方便。这不是第 3 方库。它是用于编写您自己的日历库的算法(这就是我正在解释的内容)。我已经定制了 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) 的 IANA 等价物。此 time_zone const*
位于并存储在变量 CET
中。 time_point tp
被截断为 millisecond-precision,并使用 zoned_time
与 time_zone
配对。 zoned_time
然后使用显示的格式字符串格式化(精确到毫秒)。
存在 C++20 规范的 open-source(MIT 许可)预览,语法差异非常小 here。
#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)});
}
Some installation is required for Windows.
此预览适用于 C++14。在 C++17 及更高版本中 zoned_time<milliseconds>
可以简化为 zoned_time
.
自定义时区支持
还有一种无需安装即可使用预览库的方法。它变成了一个 header-only 库。这是通过创建一个仅模拟 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规范中也有,它的优点是时区缩写(whch 在您的问题中未使用)将正确报告 "CET" 而不是“+01”。
找到更多关于自定义时区的文档here。
使用这些解决方案中的任何一个,现在都可以像这样执行函数:
#include <iostream>
int
main()
{
std::cout << format_CET(std::chrono::system_clock::now()) << '\n';
}
典型的输出如下所示:
2019-10-29T16:37:51.217+0100
我只能使用 C++ 标准库 (C++14) 将时间戳转换为给定格式的日期时间。我是 C++ 的新手,我知道 C++ 不会通过像 Java 这样的库为我们提供很多支持。 在欧洲中部时区 (CET)2011-03-1011: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
#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
这个问题有点棘手,因为:
没有明确说明输入是什么。但是根据示例代码,我将假设
std::chrono::system_clock::time_point
.重要的是要认识到 欧洲中部时区 (CET) 被定义为 time zone with a fixed UTC offset of 1h。有些地理区域全年都遵循这个时区规则,有些则不然。而none一直都在关注它。无论如何,这部分问题允许我们hard-code涉及的UTC偏移量:1h。没有夏令时调整。
在 C++14 中,有两种方法可以在不涉及受版权保护(甚至 open-source)的第 3 方软件的情况下做到这一点:
使用C API.
自己动手。
1 的问题在于它容易出错。它不直接处理毫秒精度。它不直接处理特定时区,例如 CET。 C API 只知道 UTC,以及计算机本地设置的时区。但这些问题都是可以克服的。
2的问题在于它涉及non-intuitive算法从std::chrono::system_clock::time_point
中提取年月日字段。
尽管 2 存在问题,但这是我更喜欢的解决方案,我将在下面介绍。我还将展示 C++20 如何使这个 变得更容易。
在所有解决方案中,我将通过实现以下形式的函数来形式化输入和输出:
std::string format_CET(std::chrono::system_clock::time_point tp);
自己动手 (C++14)
有六个独立的步骤。它将需要这些 headers 而不是其他的:
#include <chrono>
#include <string>
#include <iomanip>
#include <iostream>
#include <limits>
#include <sstream>
一个。将输入偏移 +1 小时 UTC 偏移量。
// shift time_point to CET
tp += 1h;
一个 function-local using 指令可以方便地将 UDL h
带入范围,以及此函数中 <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)
解决了这个问题。
day-precisiontime_point
将用于提取年月日字段,millisecond-precisiontime_point
将用于提取时分,秒和毫秒字段。上面使用的 duration days
直到 C++20 才会被添加,但是你可以这样做:
using days = std::chrono::duration<int, std::ratio<86400>>;
C。要从 tp_d
中获取年、月和日字段,使用 public domain algorithms for calendrical operations 之一很方便。这不是第 3 方库。它是用于编写您自己的日历库的算法(这就是我正在解释的内容)。我已经定制了 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) 的 IANA 等价物。此 time_zone const*
位于并存储在变量 CET
中。 time_point tp
被截断为 millisecond-precision,并使用 zoned_time
与 time_zone
配对。 zoned_time
然后使用显示的格式字符串格式化(精确到毫秒)。
存在 C++20 规范的 open-source(MIT 许可)预览,语法差异非常小 here。
#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)});
}
Some installation is required for Windows.
此预览适用于 C++14。在 C++17 及更高版本中 zoned_time<milliseconds>
可以简化为 zoned_time
.
自定义时区支持
还有一种无需安装即可使用预览库的方法。它变成了一个 header-only 库。这是通过创建一个仅模拟 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规范中也有,它的优点是时区缩写(whch 在您的问题中未使用)将正确报告 "CET" 而不是“+01”。
找到更多关于自定义时区的文档here。
使用这些解决方案中的任何一个,现在都可以像这样执行函数:
#include <iostream>
int
main()
{
std::cout << format_CET(std::chrono::system_clock::now()) << '\n';
}
典型的输出如下所示:
2019-10-29T16:37:51.217+0100