Обработка Юлианских дат в C++11/14
каков наилучший / самый простой способ справиться с Юлианские даты В C++? Я хочу иметь возможность конвертировать между Юлианскими датами и григорианскими датами. У меня есть C++11 и C++14. Может <chrono>
библиотека поможет с этой проблемой?
1 ответов
для преобразования между юлианская дата и std::chrono::system_clock::time_point
первое, что нужно сделать, это выяснить разницу между эпохами.
на system_clock
не имеет официальной эпохи, но де-факто стандартная эпоха 1970-01-01 00:00:00 UTC (Григорианский календарь). Для удобства удобно указать юлианская дата эпоха в терминах Григорианскому календарю. Этот календарь расширяет текущие правила в обратном направлении, и включает в себя год 0. Это упрощает арифметику, но нужно позаботиться о том, чтобы преобразовать годы BC в отрицательные годы, вычитая 1 и отрицая (например, 2BC-год -1). The юлианская дата эпоха -4713-11-24 12:00:00 UTC (грубо говоря).
на <chrono>
библиотека может удобно обрабатывать единицы времени в этом масштабе. Кроме того, этой дате библиотека может удобно конвертировать между григорианскими датами и system_clock::time_point
. Чтобы найти разницу между эти две эпохи просто:--65-->
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
global, но тогда вы должны утечь свои объявления using или решить не использовать их.
далее удобно создать Джулиан дата часы (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()
toduration
немедленно, чтобы избежать переполнения для тех систем, где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
.
но иногда я do хотите преобразовать 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
. Это будет иметь представление двойной и период часов. Хотя это не так уж важно. Что?!--119-- > is важно преобразовать его в любое представление и точность вы хочу. Я сделал это выше 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.