如何序列化/反序列化std::chrono::zoned_time?

问题描述 投票:0回答:1

std::chrono::zoned_time
不是 POD 类型(因为它不是 trivial 类型),因此它不能作为原始数据序列写入文件或从文件读取。它有一个时间点成员(
std::chrono::time_point
)。它还有一个指向
std::chrono::time_zone
对象的指针成员。

问题在于指针成员无法序列化/反序列化。但我也想存储和检索

zoned_time
对象的时区(而不仅仅是时间点)。我想一种方法可能是使用
std::chrono::time_zone
 获取 
std::chrono::time_zone::name
的名称,并将该 string_view 及其大小保存在文件中。

我需要以某种方式获取成员对象并序列化它们。但如何呢?

#include <chrono>
#include <fstream>


std::ofstream& operator<<( std::ofstream& ofs, const std::chrono::zoned_seconds& time )
{
    // What should go here?
    return ofs;
}

std::ifstream& operator>>( std::ifstream& ifs, std::chrono::zoned_seconds& time )
{
    // What should go here?
    return ifs;
}
c++ serialization deserialization c++-chrono c++23
1个回答
0
投票

A

std::chrono::zoned_seconds
是一个非常简单的数据结构:
{std::chrono::time_zone const*, std::chrono::sys_seconds}

并且这些数据成员中的每一个都可以从现有的

zoned_seconds
中轻松检索,并且
zoned_seconds
可以根据这两条信息构造。

因此您可以将问题简化为两部分:

  1. 序列化/反序列化
    std::chrono::time_zone const*
  2. 序列化/反序列化
    std::chrono::sys_seconds

此外,大体而言,我强烈建议您不要使用

operator<<
/
operator>>
作为这些函数的名称。这将导致您遇到不方便的 ADL(参数依赖查找)问题。我建议您选择放入您自己的命名空间中的其他名称。我将任意选择这些名称来引用这些函数,但任何描述性名称都可以:

std::ostream&
put(std::ostream& os, std::chrono::zoned_seconds const& time);

std::istream&
get(std::istream& is, std::chrono::zoned_seconds& time);

另请注意,我选择使用更通用的

ostream
istream
,而不是文件版本
ofstream
ifstream
。无论哪种方式,编码都是相同的。对于更通用的版本,您可以使用
std::stringstream
轻松测试。

所以类似:

std::ostream&
put(std::ostream& os, std::chrono::zoned_seconds const& time)
{
    auto tz = time.get_time_zone();
    auto tp = time.get_sys_time();
    put(os, tz);
    put(os, tp);
    return os;
}

std::istream&
get(std::istream& is, std::chrono::zoned_seconds& time)
{
    auto tz = get_time_zone(is);
    auto tp = get_sys_seconds(is);
    time = std::chrono::zoned_seconds{tz, tp};
    return is;
}

put
函数以
time_zone const*
的精度提取
sys_time
seconds
,即
sys_seconds
,然后调用函数来序列化每个部分。

get
函数反序列化每一段数据,然后用这两条数据构造一个
zoned_seconds
并将其分配给
time

现在我们要看看如何实现这些较低级别的功能:

put
第一:

std::ostream&
put(std::ostream& os, std::chrono::time_zone const* tz)
{
    return os << tz->name() << ' ';
}

这通过提取名称并将其写出来来序列化

time_zone const*
time_zone
名称遵循 IANA 时区数据库 制定的规则。有效字符是 ASCII 字母数字以及一些其他详细信息。您需要在名称后面添加一个在 IANA 时区名称中不是有效字符的分隔符。
' '
是一个方便的分隔符。

std::chrono::time_zone const*
get_time_zone(std::istream& is)
{
    std::string tz_name;
    char delimiter;
    is >> tz_name >> delimiter;
    return std::chrono::locate_zone(tz_name);
}

要反序列化

time_zone const*
,只需读取名称和分隔符即可。如果您想对分隔符是否为
' '
进行错误检查,或进行任何其他错误检查,请在此处执行此操作。然后可以通过调用
string
time_zone const*
变成
locate_zone

接下来我们需要序列化

sys_seconds
。在引擎盖下,
sys_seconds
只持有一个
std::chrono::seconds
。并且
std::chrono::seconds
持有一个有符号整数类型,该类型 至少有 35 位(所以实际上是
int64_t
)。

std::ostream&
put(std::ostream& os, std::chrono::sys_seconds tp)
{
    put(os, tp.time_since_epoch().count());
    return os;
}

可以使用

.time_since_epoch().count()
提取内部整数。首先从
duration
seconds
中提取精度
time_point
的基础
sys_seconds
,然后从
seconds
duration
中提取积分值。

现在序列化整型。我不会详细介绍这一点,因为其他地方已经详细介绍了这一点。 例如这里。。如果需要的话,还有一个 boost 库。

std::chrono::sys_seconds get_sys_seconds(std::istream& is) { return std::chrono::sys_seconds{std::chrono::seconds{get_int64_t(is)}}; }
要反序列化 

sys_seconds

,请首先反序列化 
int64_t
,将其转换为 
seconds
,然后将其转换为 
sys_seconds

这些简单的步骤将为您提供数据库中最紧凑的表示。让它更紧凑的唯一方法是使用比

int64_t

 更小的整数类型,这当然是你的设计选择,而不是我。

如果您选择使用

int32_t

(例如),您的范围将仅限于大约 1902 年至 2038 年。而 2038 年很快就会到来。所以我不建议这样做。

如果您选择

uint32_t

,您的范围将是 1970 年到 2106 年左右。这意味着您将无法存储我的生日。 ;-)

您还可以选择序列化有符号的 6 字节整数,为每个条目节省 2 个字节。这将为您提供足够的范围(大约+/- 400万年)。我将把如何修改

此代码以序列化 6 个字节而不是 4 或 8 个字节作为练习。

© www.soinside.com 2019 - 2024. All rights reserved.