Какой самый лучший / самый простой способ иметь дело с Юлианские даты в С ++? Я хочу иметь возможность конвертировать между юлианскими и григорианскими датами. У меня есть C ++ 11 и C ++ 14. Может ли <chrono>
библиотека поможет с этой проблемой?
Для преобразования между Юлианская дата а также std::chrono::system_clock::time_point
Первое, что нужно сделать, это выяснить разницу между эпохами.
system_clock
не имеет официальной эпохи, но де-факто стандартной эпохой является 1970-01-01 00:00:00 UTC (григорианский календарь). Для удобства удобно указать Юлианская дата эпоха с точки зрения пролептический григорианский календарь. Этот календарь расширяет текущие правила в обратном направлении и включает год 0. Это облегчает арифметику, но нужно позаботиться о том, чтобы преобразовать годы до нашей эры в отрицательные, вычитая 1 и отрицая (например, 2BC — год -1). Юлианская дата эпоха -4713-11-24 12:00:00 UTC (грубо говоря).
<chrono>
библиотека может удобно обрабатывать единицы времени в этом масштабе. Дополнительно, эта библиотека дат можно удобно конвертировать между григорианскими датами и system_clock::time_point
, Найти разницу между этими двумя эпохами просто:
constexpr
auto
jdiff()
{
using namespace date;
using namespace std::chrono_literals;
return sys_days{jan/1/1970} - (sys_days{nov/24/-4713} + 12h);
}
Это возвращает std::chrono::duration
с периодом часов. В C ++ 14 это может быть constexpr
и мы можем использовать хрональный литерал продолжительности 12h
вместо std::chrono::hours{12}
,
Если вы не хотите использовать библиотека дат, это просто постоянное количество часов, которое можно переписать в более загадочную форму:
constexpr
auto
jdiff()
{
using namespace std::chrono_literals;
return 58574100h;
}
В любом случае, вы пишете это, эффективность одинакова. Это просто функция, которая возвращает константу 58574100
, Это также может быть constexpr
глобальный, но тогда вы должны утратить ваши объявления об использовании или принять решение не использовать их.
Далее удобно создать юлианские часы с датой (jdate_clock
). Поскольку нам нужно иметь дело с единицами, по крайней мере, с полдня, и обычно юлианские даты выражаются как дни с плавающей запятой, я сделаю jdate_clock::time_point
количество двойных дней эпохи:
struct jdate_clock
{
using rep = double;
using period = std::ratio<86400>;
using duration = std::chrono::duration<rep, period>;
using time_point = std::chrono::time_point<jdate_clock>;
static constexpr bool is_steady = false;
static time_point now() noexcept
{
using namespace std::chrono;
return time_point{duration{system_clock::now().time_since_epoch()} + jdiff()};
}
};
Примечание о реализации:
Я преобразовал возвращение из
system_clock::now()
вduration
немедленно, чтобы избежать переполнения для тех систем, гдеsystem_clock::duration
это наносекунды.
jdate_clock
в настоящее время полностью соответствует и полностью функционирует <chrono>
Часы. Например, я могу узнать, который сейчас час:
std::cout << std::fixed;
std::cout << jdate_clock::now().time_since_epoch().count() << '\n';
который просто выводит:
2457354.310832
Это типобезопасная система в этом jdate_clock::time_point
а также system_clock::time_point
два разных типа, в которых нельзя случайно выполнить смешанную арифметику. И все же вы можете получить все богатые преимущества от <chrono>
библиотека, такая как сложение и вычитание продолжительности в / из вашего jdate_clock::time_point
,
using namespace std::chrono_literals;
auto jnow = jdate_clock::now();
auto jpm = jnow + 1min;
auto jph = jnow + 1h;
auto tomorrow = jnow + 24h;
auto diff = tomorrow - jnow;
assert(diff == 24h);
Но если бы я случайно сказал:
auto tomorrow = system_clock::now() + 24h;
auto diff = tomorrow - jnow;
Я получил бы ошибку, такую как эта:
error: invalid operands to binary expression
('std::chrono::time_point<std::chrono::system_clock, std::chrono::duration<long long,
std::ratio<1, 1000000> > >' and 'std::chrono::time_point<jdate_clock, std::chrono::duration<double,
std::ratio<86400, 1> > >')
auto diff = tomorrow - jnow;
~~~~~~~~ ^ ~~~~
На английском: вы не можете вычесть jdate_clock::time_point
из std::chrono::system_clock::time_point
,
Но иногда я делать хочу преобразовать jdate_clock::time_point
к system_clock::time_point
или наоборот. Для этого можно легко написать пару вспомогательных функций:
template <class Duration>
constexpr
auto
sys_to_jdate(std::chrono::time_point<std::chrono::system_clock, Duration> tp) noexcept
{
using namespace std::chrono;
static_assert(jdate_clock::duration{jdiff()} < Duration::max(),
"Overflow in sys_to_jdate");
const auto d = tp.time_since_epoch() + jdiff();
return time_point<jdate_clock, decltype(d)>{d};
}
template <class Duration>
constexpr
auto
jdate_to_sys(std::chrono::time_point<jdate_clock, Duration> tp) noexcept
{
using namespace std::chrono;
static_assert(jdate_clock::duration{-jdiff()} > Duration::min(),
"Overflow in jdate_to_sys");
const auto d = tp.time_since_epoch() - jdiff();
return time_point<system_clock, decltype(d)>{d};
}
Примечание о реализации:
Я добавил проверку статического диапазона, которая, вероятно, сработает, если вы будете использовать наносекунды или 32-битную минуту как длительность в вашем источнике
time_point
,
Общий рецепт — получить duration
с эпохи (duration
s являются «нейтральными по отношению к часам»), складывают или вычитают смещение между эпохами, а затем преобразуют duration
в желаемый time_point
,
Они будут конвертировать среди двух часов time_point
с помощью любой Точность, все в типичной безопасности. Если он компилируется, он работает. Если вы допустили программную ошибку, она появляется во время компиляции. Допустимые примеры использования включают в себя:
auto tp = sys_to_jdate(system_clock::now());
tp
это jdate::time_point
за исключением того, что он имеет интегральное представление с точностью независимо от вашего system_clock::duration
есть (для меня это микросекунды). Имейте в виду, что если для вас это наносекунды (gcc), это будет переполнено, поскольку наносекунды имеют диапазон +/- 292 года.
Вы можете изменить точность следующим образом:
auto tp = sys_to_jdate(time_point_cast<hours>(system_clock::now()));
И сейчас tp
является целым числом часов с момента jdate
эпоха.
Если вы готовы использовать эта библиотека дат, Можно легко использовать приведенные выше утилиты для преобразования юлианской даты с плавающей запятой в григорианскую дату с любой точностью, которую вы пожелаете. Например:
using namespace std::chrono;
using namespace date;
std::cout << std::fixed;
auto jtp = jdate_clock::time_point{jdate_clock::duration{2457354.310832}};
auto tp = floor<seconds>(jdate_to_sys(jtp));
std::cout << "Julian date " << jtp.time_since_epoch().count()
<< " is " << tp << " UTC\n";
Мы используем наши jdate_clock
создать jdate_clock::time_point
, Тогда мы используем наши jdate_to_sys
функция преобразования для преобразования jtp
в system_clock::time_point
, Это будет иметь представление двойной и период часов. Это не очень важно, хотя. Какие является важно преобразовать его в любое представление и точность, которую вы хочу. Я сделал это выше с floor<seconds>
, Я также мог бы использовать time_point_cast<seconds>
и это сделало бы то же самое. floor
происходит от библиотека дат, всегда усекается до отрицательной бесконечности, и его легче записать.
Это выведет:
Julian date 2457354.310832 is 2015-11-27 19:27:35 UTC
Если бы я хотел округлить до ближайшей секунды вместо пола, это было бы просто:
auto tp = round<seconds>(jdate_to_sys(jtp));
Julian date 2457354.310832 is 2015-11-27 19:27:36 UTC
Или, если я хотел это с точностью до миллисекунды:
auto tp = round<milliseconds>(jdate_to_sys(jtp));
Julian date 2457354.310832 is 2015-11-27 19:27:35.885 UTC
Обновить
floor
а также round
функции, упомянутые выше как часть Библиотека дат Говарда Хиннанта теперь также доступны в пространстве имен std::chrono
как часть C ++ 17.
Других решений пока нет …