Обработка Юлианских дат в 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() to 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.

но иногда я 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 С начала эпохи (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. Это будет иметь представление двойной и период часов. Хотя это не так уж важно. Что?!--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.