Обработка юлианских дат в C ++ 11/14

Какой самый лучший / самый простой способ иметь дело с Юлианские даты в С ++? Я хочу иметь возможность конвертировать между юлианскими и григорианскими датами. У меня есть C ++ 11 и C ++ 14. Может ли <chrono> библиотека поможет с этой проблемой?

14

Решение

Для преобразования между Юлианская дата а также 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 с эпохи (durations являются «нейтральными по отношению к часам»), складывают или вычитают смещение между эпохами, а затем преобразуют 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.

26

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]