Я пытаюсь записать объект 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
@PepijnKramer Я тестировал здесь.
Следующий очевидный шаг — напечатать str
, чтобы определить, связана ли проблема с записью или чтением файла. Обычно вы хотите протестировать круговые переключения, сериализацию и десериализацию как отдельные тесты. Вы показали задачу туда и обратно (A->B->A): посмотрите на два компонента. Надеюсь, это в B->A, как вы подозреваете.
@Yakk-AdamNevraumont Printing iss.str()
показывает, что содержимое файла соответствует ожиданиям. Вот почему я думаю, что проблема в разборе iss
.
Я не знаю, чего вы ожидаете, поэтому «как и ожидалось» не имеет смысла. Я могу догадаться, чего вы ожидаете, но я также могу догадаться, что содержит файл. Я мог бы проверить, но опять же «как и ожидалось» не помогает. Настоящая строка поможет. Ага, у меня теперь есть теория.
@Yakk-AdamNevraumont Это означает, что текущее время в Нью-Йорке правильно записано в файле. Так что проблем с сериализацией, вероятно, нет.
В вашем коде есть две ошибки. Чтобы показать им, я буду читать и писать с 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?
Да. Например: wandbox.org/permlink/B29S3ImQM2QRFaNu
Тогда я пытаюсь увидеть ошибку правильности. Время — EDT или EST, и для него анализируется правильное смещение UTC. Просто потому, что мы можем не получить Америку/Нью-Йорк? Т.е. правильное (универсальное) время, неправильное место.
Итак, если я правильно понимаю, передача std::chrono::sys_seconds
в parse
заставляет его выполнять дополнительную работу по изменению его на UTC, потому что системное время было в формате UTC? В то время как передача std::chrono::local_seconds
заставляет строку анализировать только такую, какая она есть, без каких-либо преобразований?
Вот что произойдет в этом случае: wandbox.org/permlink/j8JjDKClTMkQkgWb. Оказывается, EDT
не является названием часового пояса IANA. И вообще, аббревиатура (стандартное или летнее) может не быть названием часового пояса IANA. Кроме того, в целом аббревиатура часового пояса может использоваться в нескольких часовых поясах одновременно, и иногда для этих разных часовых поясов будут разные правила летнего времени и даже разные смещения UTC. Таким образом, вы просто не можете полагаться на сокращения для определения часового пояса.
См. github.com/HowardHinnant/date/wiki/… для получения дополнительной информации.
@digito_evo. Почти верно. sys_seconds
— это UTC. А local_seconds
— местное время. Система типов помогает вам разделить эти две разные идеи в вашем коде и даже перегружает конструктор zoned_seconds
, чтобы поддерживать семантику каждой из них. В этом примере волшебство происходит в конструкторе zoned_seconds
, а не внутри parse
.
Дополнительно: каждый тип имеет простой читаемый оператор потоковой передачи. Так что свободно переходите к std::cout
таким вещам, как now
и tp
, чтобы легко проверить значения и помочь в отладке и понимании этих примеров.
И есть ли способ избежать использования здесь stringstream? Любая библиотечная функция, которая анализирует str
без необходимости использования <sstream>?
Вы можете анализировать прямо из файла, используя тот же код. Просто измените тип file
на fstream
. Я изменил его на stringstream
только для того, чтобы упростить работу с демо-версиями онлайн-компиляторов. Если вы имеете в виду из символьных буферов, нет. Однако буферы символов, управляемые std::string
, можно эффективно перемещать в stringstream
и из него в современном C++. Чтобы работать быстрее, чем синтаксический анализ на основе std-потока, вам нужно написать свой собственный. Эффективности можно добиться за счет уменьшения гибкости формата и уменьшения или исключения проверки ошибок.
@HowardHinnant Сейчас я не хочу писать свой собственный парсер. Думаю, я попробую переместить временную строку в iss
, как вы упомянули.
Вы даже можете: file >> std::chrono::parse("%F %T %Z", tp, zone_name);
где file
ваш оригинал std::ifstream
.
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
там.
Можете ли вы изменить свой пример и отобразить входные данные файла как
std::istringstream
и переместить код загрузки файла в функцию, принимающуюstd::istream&
. Таким образом, мы сможем скомпилировать отладку и протестировать ваш код, а вы сможете удалить всю обработку файлов (проверку допустимого потока) из вашего примера. Преимущество для вас: вы немного научитесь писать тестируемый код (который не зависит от файловой системы).