首页 文章

C:将朱利安日期转换为格里高利

提问于
浏览
5

我需要编写一个函数,将Julian日期(年,日,日和小时)转换为标准格式(年,月,日,小时和分钟)并将其表示为字符串 . 我认为必须有一个人已经编写了一个库或组件,可以进行从日期到月份和月份的转换 . 我查看了几个众所周知的日期时间库:

  • ctime - 特别是使用tm结构和 mktime(tm *timeptr) ,因为这通常将tm结构的值设置到适当的位置,除了"The original values of the members tm_wday and tm_yday of timeptr are ignored..."这没有帮助 .

  • Boost::DateTime - Gregorian构造 date(greg_year, greg_month, greg_day) ,但没有帮助 . 但是,他们确实有 date_from_tm(tm datetm) 但是"The fields: tm_wday , tm_yday , tm_hour, tm_min, tm_sec, and tm_isdst are ignored."再次,没有帮助 .

  • COleDateTime - 这个项目包含COM,为什么不呢? COleDateTime构造函数 COleDateTime( int nYear, int nMonth, int nDay, int nHour, int nMin, int nSec ) 没有't help. And I don't看到任何其他转换函数 .

正如您所看到的,这些都需要月份和月份,这正是我首先想要避免的 . 我必须要么缺少某些东西,要么没有找到合适的地方(不完美,尽我所能 . )

有人可以帮忙吗?我宁愿避免自己编写,因为几乎总会有一些我想念的问题 .

2 回答

  • 5

    我偶然发现了这个老问题,并认为我可能会向它添加一些新信息 . 我在Thomas Pornin写这个单一的现有答案是一个很好的答案,我认为这是一个挑战,以改善它 . 如果我们能够以两倍的速度产生相同的答案怎么办?也许更快?

    为了测试这一努力,我在一个函数中包含了Thomas的答案:

    #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};
    }
    

    我改善它的尝试是基于:

    与时间兼容的低级日期算法

    上述文章没有直接解决这种情况 . 然而,它确实详细描述了与日期操作有关的算法,甚至包括“一年中的一天”概念,尽管该概念与此问题中指定的概念不同:

    在这个问题中,"day of year"是一个基于1的计数,1月01日是年初(1月1日= 1日) . chrono-compatible Low-Level Date Algorithms在算法civil_from_days中有一个类似的"day of year"概念,但它是过去的3月1日(3月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";
    }
    

    此测试中有三个循环:

    • 测试两种算法在/ - 一百万年的范围内产生相同的结果 .

    • 时间第一个算法 .

    • 第二个算法的时间 .

    事实证明,这两种算法都是快速的...我正在测试的iMac Core i5上有几纳秒的时间 . 因此引入皮秒以获得分数纳秒的一阶估计 .

    typedef duration<long long, pico> picoseconds;
    

    我想指出两件事:

    • 我们开始使用皮秒作为度量单位有多酷?

    • std::chrono 如何轻松地与皮秒互操作有多酷?

    对我来说,这个测试打印出来(大约):

    8660ps
    2631ps
    

    表示 ymd_from_ydoy2ymd_from_ydoy1 快约3.3倍 .

    希望这可以帮助 . 从这个答案中得到的重要事项:

    • chrono-compatible Low-Level Date Algorithms具有有用且高效的日期操作算法 . 即使您必须分开选择算法并重新组合它们,它们也很有用,如本例所示 . 算法的解释是 enable you 将它们分开并在诸如此类的示例中重新应用它们 .

    • <chrono> 可以非常灵活地测量非常快速的功能 . 比快速快三倍仍然是一个很好的胜利 .

  • 2

    从一年中的某一天开始计算月份和日期似乎很容易 . 这应该这样做:

    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;
    }
    

    请注意,这会计算从1月开始为零的月份,但假设日期计数(一年中的某一天或一天中的某一天)从一开始计算 . 如果年份日期计数无效(超出年末),则生成的 month 值为12("month after December") .

    “朱利安”是一个混乱的来源,因为它也代表“儒略历”,它与格里高利历相差几十天,以及闰年的计算 . 我在这假设您只是想在一个特定的格里高利年的背景下将“一年中的一天”计数转换为“月和日” .

相关问题