Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to write/read std::chrono::zoned_seconds to/from a stream using chrono::parse?

I am trying to write a chrono::zoned_seconds object as text to a file and then retrieve it and construct another chrono::zoned_seconds object later. How can this be done in a fairly efficient way?

I don't think the code snippet below shows the correct result:

#include <fstream>
#include <print>
#include <chrono>
#include <format>
#include <string>
#include <sstream>


int main()
{
    {
        std::ofstream file("data.txt");
        if (!file) {
            std::println( "Unable to open file.\n" );
            return 1;
        }

        auto now = std::chrono::system_clock::now();
        const std::chrono::zoned_seconds zs { "America/New_York", std::chrono::time_point_cast<std::chrono::seconds>( now ) };

        std::format_to(std::ostreambuf_iterator<char>(file), "{:%F %T %Z}", zs);
        std::println( "{:%F %T %Z}", zs ); // correct time
    }

    {
        std::ifstream file("data.txt");
        if (!file) {
            std::println( "Unable to open file.\n" );
            return 1;
        }

        std::string str;
        std::getline(file, str);
        std::istringstream iss { str };

        std::chrono::sys_seconds tp;
        std::string zone_name;
        iss >> std::chrono::parse("%F %T %Z", tp, zone_name);
        std::chrono::zoned_seconds zs { zone_name, tp };
        std::println( "{:%F %T %Z}", zs );  // incorrect time!!
    }
}

As can be seen, I used std::chrono::parse but the outputs don't match:

2024-03-01 13:35:20 EST
2024-03-01 08:35:20 EST
like image 379
digito_evo Avatar asked Dec 06 '25 07:12

digito_evo


1 Answers

There are two bugs in your code. To show them I'm going to read and write from a stringstream to enable demos at https://wandbox.org (which does not allow file creation). However parsing and streaming to stringstream is identical to parsing and streaming to fstream except for construction/opening of the stream.

Here is your code virtually unchanged, except using stringstream:

#include <sstream>
#include <print>
#include <chrono>
#include <format>
#include <string>
#include <sstream>


int main()
{
    std::stringstream file;
    auto now = std::chrono::floor<std::chrono::seconds>(std::chrono::system_clock::now());
    const std::chrono::zoned_seconds zs { "America/New_York", now };

    file << std::format( "{:%F %T %Z}", zs);
    std::println( "{:%F %T %Z}", zs ); // correct time

    std::chrono::sys_seconds tp;
    std::string zone_name;
    file >> std::chrono::parse("%F %T %Z", tp, zone_name);
    std::chrono::zoned_seconds zs2 { zone_name, tp };
    std::println( "{:%F %T %Z}", zs2 );
}

And the output currently looks like:

2024-03-01 14:04:28 EST
2024-03-01 09:04:28 EST

The reason for the large discrepancy in time is that when your format a zoned_time it displays the local time, not UTC. But when you read it in above, you are parsing as if the time parsed is UTC, not local:

    std::chrono::sys_seconds tp;

By changing the above line to:

    std::chrono::local_seconds tp;

you change the semantics of 2024-03-01 14:04:28 from UTC to local time.

Output:

2024-03-01 14:08:06 EST
2024-03-01 14:08:06 EST

Although this looks right, it is still subtly wrong.

EST is a IANA time zone with no DST rules. It has a fixed UTC offset of -5h. America/New_York is the same as EST between sys_days{Sunday[1]/November/y} + 6h and sys_days{Sunday[2]/March/(y+1)} + 7h, and otherwise is on daylight saving with a UTC offset of -4h. So the program above will only be correct when daylight saving is not in effect for America/New_York.

To see this more clearly, replace:

auto now = std::chrono::floor<std::chrono::seconds>(std::chrono::system_clock::now());

with:

auto now = sys_days{July/4/2024} + 12h + 0s;

I.e. Let's see how this program behaves in the Summer time.

This line:

std::chrono::zoned_seconds zs2 { zone_name, tp };

throws a std::runtime_error exception because it is trying to locate a time zone named "EDT" and is failing to find it. IANA has lots of time zones with an abbreviation of "EDT", but none that have that name.

gcc provides a helpful message in the runtime_error::what() message:

what(): tzdb: cannot locate zone: EDT


Aside: See this article which addresses the difficulties associated with mapping a time zone abbreviation to a time zone name, and techniques for doing as much as possible.


To correct this bug, "America/New_York" must be streamed to file instead of "EST" or "EDT". This can be fixed by changing one line:

file << std::format( "{:%F %T %Z}", zs);

to:

file << std::format( "{:%F %T }", zs) << zs.get_time_zone()->name();

I.e. this outputs the actual time zone name as opposed to the time zone abbreviation.

Output:

2024-03-01 14:13:30 EST
2024-03-01 14:13:30 EST

So now this line:

std::chrono::zoned_seconds zs2 { zone_name, tp };

is guaranteed to use the same time zone name as was used in this line:

const std::chrono::zoned_seconds zs { "America/New_York", now };

Here is the complete fixed example for reference:

#include <sstream>
#include <print>
#include <chrono>
#include <format>
#include <string>
#include <sstream>


int main()
{
    std::stringstream file;
    auto now = std::chrono::floor<std::chrono::seconds>(std::chrono::system_clock::now());
    const std::chrono::zoned_seconds zs { "America/New_York", now };

    file << std::format( "{:%F %T }", zs) << zs.get_time_zone()->name();
    std::println( "{:%F %T %Z}", zs ); // correct time

    std::chrono::local_seconds tp;
    std::string zone_name;
    file >> std::chrono::parse("%F %T %Z", tp, zone_name);
    std::chrono::zoned_seconds zs2 { zone_name, tp };
    std::println( "{:%F %T %Z}", zs2 );
}
like image 152
Howard Hinnant Avatar answered Dec 11 '25 01:12

Howard Hinnant



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!