The std::chrono::zoned_time is not a POD type (since it's not a trivial type) and so it cannot be written/read to/from a file as a sequence of raw data. It has a time point member (std::chrono::time_point). It also has a pointer member that points at a std::chrono::time_zone object.
The problem is that the pointer member cannot be serialized/deserialized. But I want to store and retrieve the time zone of the zoned_time objects as well (and not just the time point). I guess one approach could be to get the name of the std::chrono::time_zone using std::chrono::time_zone::name and save that string_view and its size in the file.
I need to somehow fetch the member objects and serialize them. But how?
#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;
}
A
std::chrono::zoned_secondsis a very simple data structure under the hood:{std::chrono::time_zone const*, std::chrono::sys_seconds}.And each of these data members are both easily retrievable from an existing
zoned_seconds, and azoned_secondsis constructible from these two pieces of information.So you can reduce your problem to two parts:
std::chrono::time_zone const*.std::chrono::sys_seconds.Also, big picture, I strongly recommend that you not use
operator<</operator>>for the names of these functions. This will lead you to inconvenient ADL (Argument Dependent Lookup) issues. I recommend you choose other names that are put into your own namespace. I'll arbitrarily choose these names to refer to these functions, but any descriptive names will do:Also note that I chose to use the more generic
ostreamandistreamas opposed to the file versionsofstreamandifstream. It is going to be the same coding either way. And with the more generic versions you can easily test withstd::stringstream.So something like:
The
putfunction extracts thetime_zone const*and thesys_time, at the precision ofseconds, sosys_seconds, and then calls functions to serialize each of those pieces.The
getfunctions deserializes each piece, and then constructs azoned_secondswith the two pieces of data and assigns that totime.Now we have to look at how to implement these lower level functions:
putfirst:This serializes the
time_zone const*by extracting its name, and writing that out. Thetime_zonenames follow the rules laid down by the IANA time zone database. Valid characters are ASCII alphanumeric, along with a few other details. You will need to follow the name with a delimiter that is not a valid character in an IANA time zone name.' 'is a convenient delimiter.To deserialize the
time_zone const*, just read in the name and the delimiter. If you would like to error check that the delimiter is' ', or any other error checking, do that here. Then thestringcan be turned into atime_zone const*by callinglocate_zone.Note: The above function is modified from my original answer. It now reads
delimiterwith the unformatted functionis.get(). I previously readdelimiterwith the formatted stream operator. Formatted stream functions skip over whitespace prior to beginning the parse. It skipped over the character I was attempting to read intodelimiter.Next we need to serialize the
sys_seconds. Under the hood,sys_secondsjust holds astd::chrono::seconds. And astd::chrono::secondsholds a signed integral type that has at least 35 bits (so in practice anint64_t).One can extract the internal integer with
.time_since_epoch().count(). This first extracts the underlyingdurationof precisionsecondsfrom thetime_pointsys_seconds, and then extracts the integral value from thesecondsduration.Now serialize the integral type. I won't go into details about that as that is covered in good detail elsewhere. For example here.. There is also a boost library for this if desired.
To deserialize the
sys_seconds, first deserialize theint64_t, convert that toseconds, and then convert that tosys_seconds.These simple steps will give you the most compact representation in your database possible. The only way to get it more compact is to use a smaller integral type than
int64_t, which of course is a design choice for you, not me.If you choose to use
int32_t(for example), your range will be limited to approximately the years 1902 to 2038. And 2038 is coming up quickly. So I don't recommend that.If you choose
uint32_tyour range will be the years 1970 to about 2106. This means you won't be able to store my birthday. ;-)You might also choose to serizalize a signed 6 byte integer, saving 2 bytes per entry. This would give you plenty of range (about +/- 4 million years). I will leave it as an exercise how to modify this code to serialize 6 bytes instead of 4 or 8.