Std::chrono::time_point немного отключен

Если я сделаю следующее:

auto time = std::chrono::system_clock::duration::zero();
std::cout << time.count() << std::endl;
time += std::chrono::years(2024 - 1970);
std::cout << time.count() << std::endl;

Оказывается, это понедельник, 1 января 2024 года, 2:16:48, а не 12:00. Я хочу представлять исторические календарные даты. year_month_day и hh_mm_ss доступны для использования, но мне нравится иметь возможность получить time_point::time_since_epoch(). Мне это нужно, потому что это удобный способ хранения данных о дате и времени в базе данных в виде одного файла int64. Есть ли способ получить лучшее из обоих миров?

На мой взгляд, мои единственные варианты — самому управлять таймингами и продолжать использовать std::chrono, но это противоречит цели использования std::chrono, или использовать chrono::year_month_day и chrono::hh_mm_ss и обращаться с базой данных по-другому.

«Оказывается, понедельник, 1 января 2024 г., 2:16:48» — но time — это duration, а time.count() — целочисленное значение. Я не понимаю, как ваш опубликованный код может напечатать такую ​​дату. Пожалуйста, опубликуйте минимально воспроизводимый пример.

wohlstad 18.06.2024 14:47

«год равен 365,2425 дням (средняя продолжительность григорианского года)». en.cppreference.com/w/cpp/chrono/duration. Вы не можете использовать std::chrono::years для фактических дат.

Tomasz Kalisiak 18.06.2024 14:51

Вы хотите сдвинуть время на количество секунд или количество лет? Сейчас вы делаете первое, потому что std::chrono::years не подсчитывает истинное количество секунд между двумя датами 1 января, а выполняет только 365*24*3600*n конверсию. Сделать последнее в эпоху эпохи не так уж и тривиально.

Quimby 18.06.2024 14:52

@wohlstad Я имел в виду, что указанное интегральное значение эквивалентно 2:16:48 утра. Это весь код. Я скопировал и вставил его из своей основной функции.

Eshy 18.06.2024 15:13

@Куимби, я хотел сдвинуться на несколько лет. Полагаю, я просто буду использовать год_месяц_день, если сложно выполнить эту работу во времени эпохи.

Eshy 18.06.2024 15:14

Вы делаете хронологические вычисления. Вам нужны календарные вычисления. См. этот ответ для описания каждого из них и того, как их делать. В связанном ответе в качестве мотивирующего примера используются месяцы, но годы работают таким же образом.

Howard Hinnant 18.06.2024 15:16

Примечание: ничто в этом коде не требует дополнительных вещей, которые делает std::endl. Используйте '\n', чтобы завершить строку, если у вас нет веской причины не делать этого.

Pete Becker 18.06.2024 16:23

@Eshy: «Мне нравится иметь возможность получать time_point::time_since_epoch()». Перестаньте этим наслаждаться. Храните данные в базе данных в виде текста. Если вам нужно работать с датами, вам нужно работать с датами, а не «временем с эпох». Весь смысл chrono в том, что вы не должны работать с голым подсчетом эпох. Чем больше информации высокого уровня вы храните, тем с меньшим количеством «неожиданных» результатов вы столкнетесь.

Nicol Bolas 18.06.2024 16:57
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
8
88
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Твой

time += std::chrono::years(2024 - 1970);

предполагает, что вы хотите работать с «календарным временем», а не с метками времени.

В C++20 добавлена ​​поддержка календарей и часовых поясов (которые необходимы — например, в день перехода на летнее время день может иметь 23 или 25 часов вместо обычных 24). Хотя я ими еще не пользовался.

Посмотрите обзор https://en.cppreference.com/w/cpp/chrono

Однако «традиционных» календарных звонков часто бывает достаточно. Например, если у вас есть std::chrono::system_clock::time_point, вы можете использовать std::chrono::system_clock::to_time_t(), чтобы получить за него time_t. Затем вы можете использовать std::localtime, чтобы разбить это time_t на местные секунды/минуты/часы/день/месяц/год. Также есть std::mktime(), делающий противоположное, и std::chrono::system_block::from_time_t, чтобы вернуться к time_point.

Интересная особенность std::mktime заключается в том, что он допускает «недопустимые» значения в качестве входных данных и «исправляет» их. В качестве примера предположим, что сейчас «28 февраля», и вы хотите перейти на следующий день. Вы можете просто добавить 1 к дню, сделав его «29 февраля» — если в этом конкретном году нет «29 февраля», вместо этого он будет рассматриваться как «1 марта».

Аналогично, если вы перейдете на переключатель летнего времени, добавление +1 к дню может дать разные результаты по сравнению с добавлением +24 к часу. Итак, забудьте о том, что «в сутках 24 часа» или «в году 365 дней», и занимайтесь арифметикой именно с теми полями, которые вам действительно нужны.

Конечно, «исторические даты»… на самом деле не работают ни с чем из этого. Базы данных часовых поясов содержат историческую информацию, но это только до сих пор... и помимо того, что time_t является всего лишь 32-битным значением в некоторых системах, нет реальной причины, по которой база данных часовых поясов содержит древнюю информацию.

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

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

См. этот ответ для более подробного описания хронологических вычислений по сравнению с календарными вычислениями.

Но вкратце это:

auto time = std::chrono::system_clock::duration::zero();
std::cout << time.count() << std::endl;
time += std::chrono::years(2024 - 1970);
std::cout << time.count() << std::endl;

представляет собой хронологический расчет. Я характеризую ее так, потому что она добавляет регулярную единицу к числу обычных единиц. Один std::chrono::years — это не что иное, как 31'556'952 seconds, который представляет собой точную продолжительность среднего года в гражданском календаре (в других календарях годы будут иметь другую среднюю продолжительность).

И, по правде говоря, я бы даже не посчитал приведенный выше код обязательно правильно сформулированным, поскольку он явно предполагает, что time будет моментом времени, и тем не менее он объявлен как duration. Лучшим способом выполнить это хронологическое вычисление было бы:

Демо:

using namespace std::literals;
std::chrono::system_clock::time_point time{};  // zero-initialize
std::cout << time << '\n';   // 1970-01-01 00:00:00.000000000
time += 2024y - 1970y;
std::cout << time << '\n';   // 2024-01-01 02:16:48.000000000

Теперь time — это time_point. При желании из него все равно можно получить внутренний счетчик:

std::cout << time.time_since_epoch().count() << '\n';

Для выполнения календарных вычислений необходимо перейти в календарную систему (их несколько), выполнить вычисления в календарной системе, а затем (при желании) перейти обратно в хронологическую систему.

Например:

Демо:

std::chrono::system_clock::time_point time{};  // zero-initialize
std::cout << time << '\n';   // 1970-01-01 00:00:00.000000000
std::chrono::year_month_day ymd = std::chrono::floor<std::chrono::days>(time);
time = std::chrono::sys_days{ymd + (2024y - 1970y)};
std::cout << time << '\n';   // 2024-01-01 00:00:00.000000000

В качестве демонстрации использования альтернативных календарных вычислений рассмотрим следующее:

std::chrono::year_month_weekday ymd = std::chrono::floor<std::chrono::days>(time);
time = std::chrono::sys_days{ymd + (2024y - 1970y)};
std::cout << time << '\n';   // 2024-01-04 00:00:00.000000000

Теперь вместо того, чтобы прибавлять 54 года к 1 января 1970 года, к первому четвергу января 1970 года прибавляют 54 года, чтобы получить первый четверг января 2024 года. Действительно, year_month_weekday действительно является полной демонстрацией альтернативного календаря. Также можно использовать написанные пользователем календари, моделирующие юлианский, китайский или исламский календари, которые, вероятно, также дадут разные результаты.


Я не рекомендую возвращаться к API синхронизации C, если только вам не нужно это делать для взаимодействия с устаревшим кодом. В C++20 и более поздних версиях имеется расширенный набор функций даты и времени старого C API. Это также намного безопаснее и проще в использовании.

Если вы определили в API синхронизации C функциональность, которую не знаете, как реализовать в современном <chrono>, просто спросите здесь, в stackoverflow, и я уверен, что кто-нибудь будет рад показать вам, как это сделать.

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