C ++: преобразование юлианских дат в григорианские - PullRequest
5 голосов
/ 14 апреля 2010

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

  • 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 ]

5 голосов
/ 03 сентября 2013

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

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

#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, но это дни после 01 марта (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> может быть очень гибким в измерении очень быстрых функций. В три раза быстрее, чем очень быстро, все еще хорошая победа.
2 голосов
/ 14 апреля 2010

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

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 («месяц после декабря»).

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

...