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

时间:2019-10-29 08:12:00

标签: c++ timestamp c++14 chrono formatdatetime

我只能使用C ++标准库(C ++ 14)将时间戳转换为给定的格式date-time。我是C ++的新手,我知道C ++通过Java之类的库对我们的支持并不多。 在给定的日期和时间 2011-03-10 11:23:56在中欧时区(CET)中, 将产生标准格式的输出:“ 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 

2 个答案:

答案 0 :(得分:1)

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

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

  2. 重要的是要认识到中欧时区(CET)被定义为time zone with a fixed UTC offset of 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指令很方便地将UDL h纳入范围,以及此函数中<chrono>所需的其他所有内容:

using namespace std::chrono;

B。得到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年1月1日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>>;

C。要从tp_d获取年,月和日字段,可以方便地使用public domain algorithms for calendrical operations之一。这不是第三方图书馆。它是用于编写自己的日历库的算法(这就是我正在解释的内容)。我已经定制了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被截断为毫秒精度,并使用time_zonezoned_time配对。然后使用显示的格式字符串对此zoned_time进行格式化(以毫秒为单位)。

存在一个C ++ 20规范的开源(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规范中,其优点是时区缩写(在您的问题中未使用)将正确报告“ CET”而不是“ +01”。

找到有关自定义时区的更多文档here

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

#include <iostream>

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

典型的输出如下所示:

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

答案 1 :(得分:0)

import { Injectable } from '@angular/core';
import { DefaultDataService, HttpUrlGenerator, QueryParams } from '@ngrx/data';
import { Group } from 'src/app/models/group.model';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { map, tap } from 'rxjs/operators';


@Injectable()
export class GroupDataService extends DefaultDataService<Group> {
    constructor(http: HttpClient, httpUrl: HttpUrlGenerator) {
        super('Group', http, httpUrl);
    }
    getWithQuery(params: QueryParams): Observable<any> {
        return this.http.get<Group[]>('http://localhost:3000/api/groups?' + params)
            .pipe(
                map(data => {
                    const obj = {};
                    data.map(g => {
                        obj[g._id] = {
                            id: g._id,
                            name: g.name,
                            swearingAllowed: g.swearingAllowed,
                            creator: g.admin,
                            topic: g.topic
                        }
                    });
                    return Object.values(obj);
                })
            );
    }
};