使用C ++将时间戳转换为格式化的日期时间 [英] Convert timestamp to the formatted date time using C++

查看:311
本文介绍了使用C ++将时间戳转换为格式化的日期时间的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I can only use C++ standard library (C++14) to convert timestamp to the given format date-time. I new to C++ and I know that C++ doesn't support us a lot via a library like Java. At a given date and time of 2011-03-10 11:23:56 in the Central European Time zone (CET), the following standard-format output will be produced: "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 


推荐答案

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

This is a bit of tricky question because:


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

重要的是要认识到,中欧时区(CET)被定义为时区,UTC偏移固定为1h 。有些地理区域全年遵循该时区规则,有些则没有。而且没有人一直跟随它。无论如何,问题的这一部分使我们能够对所涉及的UTC偏移进行硬编码:1h。没有进行夏令时的调整。

It is important to recognize that Central European Time zone (CET) is defined as a time zone with a fixed UTC offset of 1h. Some geographical regions follow this time zone rule year round, some do not. And none have always followed it. In any event, this part of the problem allows us to hard-code the UTC offset involved: 1h. There is no daylight saving adjustment to make.

在C ++ 14中,有两种方法可以完成此操作而不涉及受版权保护(甚至是开源)的第三方软件:

In C++14 there are two ways to do this without involving copyrighted (even open-source) 3rd party software:


  1. 使用C API。

  1. Use the C API.

滚动自己的文件。

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

The problem with 1 is that it is error prone. It doesn't directly handle millisecond precision. It doesn't directly handle specific time zones such as CET. The C API only knows about UTC, and the computer's locally set time zone. But these problems are surmountable.

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

The problem with 2 is that it involves non-intuitive arithmetic to extract the year, month and day fields from a std::chrono::system_clock::time_point.

尽管有2的问题,但这是我更喜欢的解决方案以及我将要介绍的内容下面。我还将展示C ++ 20如何使此很多更加容易。

Despite the problems with 2, that is the solution I prefer and what I will present below. I'll also show how C++20 will make this much easier.

在所有解决方案中,我都会将输入和输出形式化通过实现以下形式的功能:

In all solutions, I'll formalize the input and output by implementing a function of this form:

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



滚动自己的语言(C ++ 14)



有六个离散步骤。它将需要这些标头,而无需其他标头:

Roll your own (C++14)

There are six discrete steps. It will require these headers and no others:

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

A。

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

使用函数本地使用的指令可以方便地将UDL h 纳入范围,以及此函数中< chrono> 所需的所有其他内容:

A function-local using directive is convenient to bring the UDL h into scope, and everything else that will be needed from <chrono> within this function:

using namespace std::chrono;

B。获得 time_point tp 的两种变体:一种具有毫秒精度,另一种具有日精度:

B. Get two variations of the time_point tp: One with millisecond precision and one with day precision:

// 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年1月1日00:00:00 UTC之前的负时间点。 C ++ 17引入了 floor< millliseconds>(tp)来解决此问题。

It is important to understand that these two casts round towards zero, and will give incorrect results for negative time points. system_clock gives negative time points prior to its epoch of 1970-01-01 00:00:00 UTC. C++17 introduces floor<millliseconds>(tp) which fixes this problem.

日精度 time_point 将用于提取年,月和日字段,毫秒精度 time_point 将用于提取小时,分钟,秒和毫秒字段。在C ++ 20之前,不会添加上面使用的持续时间 c,但是您可以使用以下方法做到这一点:

The day-precision time_point will be used to extract the year, month and day fields, and the millisecond-precision time_point will be used to extract the hour, minute, second and millisecond fields. The duration days used above won't be added until C++20, but you can do it with:

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

C。要从 tp_d 获取年,月和日字段,使用用于日历操作的公共领域算法。这不是第三方图书馆。它是用于编写自己的日历库的算法(这就是我正在解释的内容)。我已经自定义了 civil_from_days 算法以完全满足 format_CET 的需求:

C. To get the year, month and day fields from tp_d it is convenient to use one of the public domain algorithms for calendrical operations. This is not a 3rd party library. It is algorithms for writing your own calendrical libraries (which is what I'm in the middle of explaining). I have customized the civil_from_days algorithm to exactly fix the needs of 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);

在上面链接的站点上,对于那些想知道如何

There is an exhaustingly detailed derivation of this algorithm at the site linked above for those who want to know how it works.

这时,积分变量 {y,m,d} 包含年,月,第三天。

At this point, the integral variables {y, m, d} contain the year, month, day triple.

D。获取自当地午夜以来的持续时间。

D. Get the time duration since the local midnight. This will be used to extract the local time of day:

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

E。获取小时,分钟,秒和毫秒字段:

E. Get the hour, minute, second and millisecond fields:

// 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} 保持所需的值。

At this point, the chrono::duration variables {h, M, s, ms} hold the desired values.

F。现在我们可以格式化:

F. Now we're ready to format:

// 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 填充字符,一个将获得所需的前导零。

Using a combination of the manipulator setw to set the width of each field, with a fill character of 0, one gets the desired leading zeroes.

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

This is much easier in the C++20 spec:

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_time与 time_zone 配对。然后使用显示的格式字符串将此 zoned_time 格式化(以毫秒为单位)。

"Etc/GMT-1" is the IANA equivalent of Central European Time zone (CET). This time_zone const* is located and stored in the variable CET. The time_point tp is truncated to millisecond-precision, and paired with the time_zone using a zoned_time. This zoned_time is then formatted (to millisecond precision) using the format string shown.

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

There exists an open-source (MIT license) preview of the C++20 spec with very minor syntax differences 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)});
}

Windows需要某些安装。

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

This preview does work with C++14. In C++17 and later zoned_time<milliseconds> can be simplified to just zoned_time.

还有一种使用预览库的方式,无需安装。它成为 header-only 库。这是通过创建仅模拟CET的自定义时区,然后将其安装在 zoned_time 中来完成的。自定义时区如下所示:

There is also a way to use the preview library such that no installation is required. It becomes a header-only library. This is done by creating a custom time zone that models just CET, and then install that in the zoned_time. Here is what the custom time zone could look like:

#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 模板参数:

CET now meets enough of the time zone requirements that it can be used within zoned_time and formatted as before. In C++14, the syntax is complicated by having to explicitly specify the zoned_time template arguments:

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。

This option is also in the C++20 spec, and its advantage is that the time zone abbreviation (which is unused in your problem) will correctly report "CET" instead of "+01".

找到了更多有关自定义时区的文档< a href = https://howardhinnant.github.io/date/tz.html#Examples rel = nofollow noreferrer>此处。

More documentation on custom time zones is found here.

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

With any of these solutions, the function can now be exercised like this:

#include <iostream>

int
main()
{
    std::cout << format_CET(std::chrono::system_clock::now()) << '\n';
}

典型输出如下:

2019-10-29T16:37:51.217+0100

这篇关于使用C ++将时间戳转换为格式化的日期时间的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆