C++: преобразование юлианских дат в григорианские

Мне нужно написать функцию, которая преобразует юлианские даты (год, день года, час дня и минуты) в стандартную форму (год, месяц, день месяца, час дня и минуты) и выражает ее в виде строки. Я думаю, что должен быть кто-то, кто уже написал библиотеку или компонент, который может сделать преобразование из Дня Года в Месяц и День Месяца. Я посмотрел на несколько известных библиотек datetime:

  • ctime - специально используя структуру tm и mktime(tm *timeptr) так как это обычно устанавливает значения структуры tm в соответствующих местах, за исключением того, что "исходные значения членов tm_wday и tm_yday timeptr игнорируются...", что не помогает.
  • Boost:: DateTime - построен григорианский date(greg_year, greg_month, greg_day) что не помогает Тем не менее, у них есть date_from_tm(tm datetm) но "поля: tm_wday, tm_yday, tm_hour, tm_min, tm_sec и tm_isdst игнорируются". Опять не поможет.
  • COleDateTime - Этот проект содержит COM, так почему бы и нет? Конструктор COleDateTime COleDateTime( int nYear, int nMonth, int nDay, int nHour, int nMin, int nSec ) не помогает И я не вижу никаких других функций преобразования, чтобы пойти с этим.

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

Кто-нибудь может помочь? Я бы предпочел не писать свои собственные, потому что почти всегда есть что-то, что я скучаю.

2 ответа

Я наткнулся на этот старый вопрос и подумал, что смогу добавить к нему новую информацию. Единственный существующий ответ, когда я пишу это Thomas Pornin, является хорошим ответом, и я проголосовал за него. Однако я принял это как вызов, чтобы улучшить это. Что если бы мы могли выдать один и тот же ответ в два раза быстрее? Может быть, даже быстрее?

Чтобы проверить это, я обернул ответ Томаса в функцию:

#include <tuple>

std::tuple<int, int, int>
ymd_from_ydoy1(int year, int day_of_year)
{
    static const int month_len[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

    int leap = (year % 4 == 0) && (year % 100 != 0 || year % 400 == 0);
    int day_of_month = day_of_year;
    int month;
    for (month = 0; month < 12; month ++) {
        int mlen = month_len[month];
        if (leap && month == 1)
            mlen ++;
        if (day_of_month <= mlen)
            break;
        day_of_month -= mlen;
    }
    return {year, month, day_of_month};
}

И моя попытка улучшить это основана на:

хроносовместимые алгоритмы дат низкого уровня

Приведенная выше статья не касается этой ситуации напрямую. Тем не менее, он подробно описывает алгоритмы, связанные с манипуляциями с датами, и даже включает понятие "день года", хотя это понятие отличается от того, что указано в этом вопросе:

В этом вопросе "день года" - это подсчет на основе 1, где 01 января - начало года (1 января == день 1). Хроносовместимые низкоуровневые алгоритмы дат имеют похожую концепцию "день года" в алгоритме civil_from_days но это дни после 1 марта (1 марта == день 0).

Я думаю, что я мог бы выбрать кусочки из civil_from_days и создать новый ymd_from_ydoy который не должен был повторяться в течение 12 месяцев, чтобы найти желаемый результат. Вот что я придумал:

std::tuple<int, int, int>
ymd_from_ydoy2(int year, int day_of_year)
{
    int leap = (year % 4 == 0) && (year % 100 != 0 || year % 400 == 0);
    if (day_of_year < 60 + leap)
        return {year, day_of_year/32, day_of_year - (day_of_year/32)*31};
    day_of_year -= 60 + leap;
    int mp = (5*day_of_year + 2)/153;
    int day_of_month = day_of_year - (153*mp+2)/5 + 1;
    return {year, mp + 2, day_of_month};
}

Есть еще филиалы, но их меньше. Чтобы проверить правильность и производительность этой альтернативы, я написал следующее:

#include <iostream>
#include <chrono>
#include <cassert>

template <class Int>
constexpr
bool
is_leap(Int y) noexcept
{
    return  y % 4 == 0 && (y % 100 != 0 || y % 400 == 0);
}

constexpr
unsigned
last_day_of_month_common_year(unsigned m) noexcept
{
    constexpr unsigned char a[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
    return a[m-1];
}

template <class Int>
constexpr
unsigned
last_day_of_month(Int y, unsigned m) noexcept
{
    return m != 2 || !is_leap(y) ? last_day_of_month_common_year(m) : 29u;
}
int
main()
{
    using namespace std;
    using namespace std::chrono;
    typedef duration<long long, pico> picoseconds;
    picoseconds ps1{0};
    picoseconds ps2{0};
    int count = 0;
    const int ymax = 1000000;
    for (int y = -ymax; y <= ymax; ++y)
    {
        bool leap = is_leap(y);
        for (int doy = 1; doy <= 365 + leap; ++doy, ++count)
        {
            auto d1 = ymd_from_ydoy1(y, doy);
            auto d2 = ymd_from_ydoy2(y, doy);
            assert(d1 == d2);
        }
    }
    auto t0 = high_resolution_clock::now();
    for (int y = -ymax; y <= ymax; ++y)
    {
        bool leap = is_leap(y);
        for (int doy = 1; doy <= 365 + leap; ++doy)
        {
            auto d1 = ymd_from_ydoy1(y, doy);
            auto d2 = ymd_from_ydoy1(y, doy);
            assert(d1 == d2);
        }
    }
    auto t1 = high_resolution_clock::now();
    for (int y = -ymax; y <= ymax; ++y)
    {
        bool leap = is_leap(y);
        for (int doy = 1; doy <= 365 + leap; ++doy)
        {
            auto d1 = ymd_from_ydoy2(y, doy);
            auto d2 = ymd_from_ydoy2(y, doy);
            assert(d1 == d2);
        }
    }
    auto t2 = high_resolution_clock::now();
    ps1 = picoseconds(t1-t0)/(count*2);
    ps2 = picoseconds(t2-t1)/(count*2);
    cout << ps1.count() << "ps\n";
    cout << ps2.count() << "ps\n";
}

В этом тесте есть три цикла:

  1. Проверьте, что два алгоритма дают одинаковые результаты в диапазоне +/- миллиона лет.
  2. Время первый алгоритм.
  3. Время второй алгоритм.

Оказывается, оба алгоритма работают быстро... несколько наносекунд на iMac Core i5, на котором я тестирую. И, таким образом, введение пикосекунд позволяет получить оценку первого порядка дробных наносекунд.

    typedef duration<long long, pico> picoseconds;

Я хотел бы указать на две вещи:

  1. Насколько круто, что мы начинаем использовать пикосекунды в качестве единицы измерения?
  2. Как это круто std::chrono облегчает взаимодействие с пикосекундами?

Для меня этот тест распечатывает (примерно):

8660ps
2631ps

Указывая, что ymd_from_ydoy2 примерно в 3,3 раза быстрее, чем ymd_from_ydoy1,

Надеюсь это поможет. Важные вещи, которые можно получить из этого ответа:

  1. Хроносовместимые низкоуровневые алгоритмы дат имеют полезные и эффективные алгоритмы для манипулирования датами. Они могут быть полезны, даже если вам нужно разделить алгоритмы и собрать их, как в этом примере. Объяснения алгоритмов приведены для того, чтобы вы могли выделить их и применить их в таких примерах.
  2. <chrono> может быть очень гибким в измерении очень быстрых функций. В три раза быстрее, чем очень быстро, все еще хорошая победа.

Кажется, легко вычислить месяц и день месяца от дня года. Это должно сделать это:

static const int month_len[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

int leap = (year % 4 == 0) && (year % 100 != 0 || year % 400 == 0);
int day_of_month = day_of_year;
int month;
for (month = 0; month < 12; month ++) {
    int mlen = month_len[month];
    if (leap && month == 1)
        mlen ++;
    if (day_of_month <= mlen)
        break;
    day_of_month -= mlen;
}

Обратите внимание, что это вычисляет месяц, начинающийся с нуля для января, но предполагает, что число дней (день года или день месяца) начинается с единицы. Если число дней в году недопустимо (после конца года), то результирующее month значение 12 ("месяц после декабря").

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

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