Как записать/прочитать std::chrono::zoned_секунды в/из потока с помощью Chronic::parse?

Я пытаюсь записать объект chrono::zoned_seconds в виде текста в файл, а затем получить его и позже создать другой объект chrono::zoned_seconds. Как это можно сделать достаточно эффективным способом?

Я не думаю, что приведенный ниже фрагмент кода показывает правильный результат:

#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!!
    }
}

Как видно, я использовал std::chrono::parse, но результаты не совпадают:

2024-03-01 13:35:20 EST
2024-03-01 08:35:20 EST

Можете ли вы изменить свой пример и отобразить входные данные файла как std::istringstream и переместить код загрузки файла в функцию, принимающую std::istream&. Таким образом, мы сможем скомпилировать отладку и протестировать ваш код, а вы сможете удалить всю обработку файлов (проверку допустимого потока) из вашего примера. Преимущество для вас: вы немного научитесь писать тестируемый код (который не зависит от файловой системы).

Pepijn Kramer 01.03.2024 19:42

@PepijnKramer Я тестировал здесь.

digito_evo 01.03.2024 19:45

Следующий очевидный шаг — напечатать str, чтобы определить, связана ли проблема с записью или чтением файла. Обычно вы хотите протестировать круговые переключения, сериализацию и десериализацию как отдельные тесты. Вы показали задачу туда и обратно (A->B->A): посмотрите на два компонента. Надеюсь, это в B->A, как вы подозреваете.

Yakk - Adam Nevraumont 01.03.2024 19:48

@Yakk-AdamNevraumont Printing iss.str() показывает, что содержимое файла соответствует ожиданиям. Вот почему я думаю, что проблема в разборе iss.

digito_evo 01.03.2024 19:55

Я не знаю, чего вы ожидаете, поэтому «как и ожидалось» не имеет смысла. Я могу догадаться, чего вы ожидаете, но я также могу догадаться, что содержит файл. Я мог бы проверить, но опять же «как и ожидалось» не помогает. Настоящая строка поможет. Ага, у меня теперь есть теория.

Yakk - Adam Nevraumont 01.03.2024 20:03

@Yakk-AdamNevraumont Это означает, что текущее время в Нью-Йорке правильно записано в файле. Так что проблем с сериализацией, вероятно, нет.

digito_evo 01.03.2024 20:06
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
6
75
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

В вашем коде есть две ошибки. Чтобы показать им, я буду читать и писать с stringstream, чтобы включить демо-версии на https://wandbox.org (который не позволяет создавать файлы). Однако синтаксический анализ и потоковая передача в stringstream идентичны синтаксическому анализу и потоковой передаче в fstream, за исключением создания/открытия потока.

Вот ваш код практически не изменился, за исключением использования 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 );
}

И вывод на данный момент выглядит так:

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

Причина большого расхождения во времени заключается в том, что в вашем формате zoned_time отображается местное время, а не UTC. Но когда вы читаете это выше, вы анализируете, как если бы анализируемое время было UTC, а не локальным:

    std::chrono::sys_seconds tp;

Изменив приведенную выше строку на:

    std::chrono::local_seconds tp;

вы меняете семантику 2024-03-01 14:04:28 с UTC на местное время.

Выход:

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

Хотя это выглядит правильно, это все же слегка неправильно.

EST — часовой пояс IANA без правил летнего времени. Он имеет фиксированное смещение UTC -5h. America/New_York то же самое, что EST между sys_days{Sunday[1]/November/y} + 6h и sys_days{Sunday[2]/March/(y+1)} + 7h, в противном случае используется летнее время со смещением UTC -4h. Таким образом, приведенная выше программа будет корректной только в том случае, если летнее время не действует для America/New_York.

Чтобы увидеть это более четко, замените:

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

с:

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

Т.е. Посмотрим, как поведет себя эта программа летом.

Эта строка:

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

выдает исключение std::runtime_error, поскольку пытается найти часовой пояс с именем «EDT», но не может его найти. В IANA есть множество часовых поясов с аббревиатурой «EDT», но ни один из них не имеет такого названия.

gcc предоставляет полезное сообщение в сообщении runtime_error::what():

что(): tzdb: не удается найти зону: EDT


Кроме того: см. эту статью, в которой рассматриваются трудности, связанные с сопоставлением аббревиатуры часового пояса с названием часового пояса, а также методы, позволяющие сделать как можно больше.


Чтобы исправить эту ошибку, "America/New_York" необходимо транслировать на file вместо "EST" или "EDT". Это можно исправить, изменив одну строку:

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

к:

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

Т.е. это выводит фактическое имя часового пояса, а не аббревиатуру часового пояса.

Выход:

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

Итак, теперь эта строка:

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

гарантированно будет использовать то же имя часового пояса, которое использовалось в этой строке:

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

Вот полный фиксированный пример для справки:

#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 );
}

Будет ли действовать изменение аббревиатуры на летнее время EDT?

Yakk - Adam Nevraumont 01.03.2024 20:27

Да. Например: wandbox.org/permlink/B29S3ImQM2QRFaNu

Howard Hinnant 01.03.2024 20:27

Тогда я пытаюсь увидеть ошибку правильности. Время — EDT или EST, и для него анализируется правильное смещение UTC. Просто потому, что мы можем не получить Америку/Нью-Йорк? Т.е. правильное (универсальное) время, неправильное место.

Yakk - Adam Nevraumont 01.03.2024 20:33

Итак, если я правильно понимаю, передача std::chrono::sys_seconds в parse заставляет его выполнять дополнительную работу по изменению его на UTC, потому что системное время было в формате UTC? В то время как передача std::chrono::local_seconds заставляет строку анализировать только такую, какая она есть, без каких-либо преобразований?

digito_evo 01.03.2024 20:38

Вот что произойдет в этом случае: wandbox.org/permlink/j8JjDKClTMkQkgWb. Оказывается, EDT не является названием часового пояса IANA. И вообще, аббревиатура (стандартное или летнее) может не быть названием часового пояса IANA. Кроме того, в целом аббревиатура часового пояса может использоваться в нескольких часовых поясах одновременно, и иногда для этих разных часовых поясов будут разные правила летнего времени и даже разные смещения UTC. Таким образом, вы просто не можете полагаться на сокращения для определения часового пояса.

Howard Hinnant 01.03.2024 20:39

См. github.com/HowardHinnant/date/wiki/… для получения дополнительной информации.

Howard Hinnant 01.03.2024 20:39

@digito_evo. Почти верно. sys_seconds — это UTC. А local_seconds — местное время. Система типов помогает вам разделить эти две разные идеи в вашем коде и даже перегружает конструктор zoned_seconds, чтобы поддерживать семантику каждой из них. В этом примере волшебство происходит в конструкторе zoned_seconds, а не внутри parse.

Howard Hinnant 01.03.2024 20:42

Дополнительно: каждый тип имеет простой читаемый оператор потоковой передачи. Так что свободно переходите к std::cout таким вещам, как now и tp, чтобы легко проверить значения и помочь в отладке и понимании этих примеров.

Howard Hinnant 01.03.2024 20:46

И есть ли способ избежать использования здесь stringstream? Любая библиотечная функция, которая анализирует str без необходимости использования <sstream>?

digito_evo 01.03.2024 21:51

Вы можете анализировать прямо из файла, используя тот же код. Просто измените тип file на fstream. Я изменил его на stringstream только для того, чтобы упростить работу с демо-версиями онлайн-компиляторов. Если вы имеете в виду из символьных буферов, нет. Однако буферы символов, управляемые std::string, можно эффективно перемещать в stringstream и из него в современном C++. Чтобы работать быстрее, чем синтаксический анализ на основе std-потока, вам нужно написать свой собственный. Эффективности можно добиться за счет уменьшения гибкости формата и уменьшения или исключения проверки ошибок.

Howard Hinnant 01.03.2024 21:58

@HowardHinnant Сейчас я не хочу писать свой собственный парсер. Думаю, я попробую переместить временную строку в iss, как вы упомянули.

digito_evo 02.03.2024 06:40

Вы даже можете: file >> std::chrono::parse("%F %T %Z", tp, zone_name); где file ваш оригинал std::ifstream.

Howard Hinnant 02.03.2024 14:32
    std::chrono::sys_seconds tp;
    std::string zone_name;
    iss >> std::chrono::parse("%F %T %Z", tp, zone_name);

это неправильно. Вы анализируете зональное время H:M:S в точку времени sys_секунды.

Полученный момент времени является ложью. Он находится в неправильном часовом поясе - системном часовом поясе.

Когда вы конвертируете неправильный часовой пояс в Нью-Йорк, значение часа корректно изменяется.

std::local_time — время без часового пояса. Вы хотите использовать его вместо sys_time там.

Другие вопросы по теме