C ++:将Julian日期转换为Gregorian

时间:2010-04-14 14:58:28

标签: c++ datetime date julian-date

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

  • ctime - 特别是使用tm结构和mktime(tm *timeptr),因为这通常将tm结构的值设置到适当的位置,除了“忽略timeptr的成员tm_wday和tm_yday的原始值......“这没有帮助。
  • 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 个答案:

答案 0 :(得分:5)

我偶然发现了这个老问题,并认为我可以为它添加一些新信息。我在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};
}

我改善它的尝试是基于:

  

chrono-compatible Low-Level Date Algorithms

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

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

此测试中有三个循环:

  1. 测试两种算法在+/-一百万年的范围内产生相同的结果。
  2. 计算第一个算法。
  3. 计算第二个算法的时间。
  4. 事实证明,两种算法都是快速的...我正在测试iMac Core i5上的几纳秒。因此引入皮秒以获得分数纳秒的一阶估计。

        typedef duration<long long, pico> picoseconds;
    

    我想指出两件事:

    1. 我们开始使用皮秒作为度量单位有多酷?
    2. std::chrono如何让它与皮秒的互操作变得如此容易,这有多酷?
    3. 对我来说,这个测试打印出来(大约):

      8660ps
      2631ps
      

      表示ymd_from_ydoy2ymd_from_ydoy1快约3.3倍。

      希望这会有所帮助。从这个答案中得到的重要事项:

      1. chrono-compatible Low-Level Date Algorithms具有有用且高效的日期操作算法。即使您必须分开选择算法并重新组合它们,它们也很有用,如本例所示。算法的解释是使您能够将它们分开并在诸如此类的示例中重新应用它们。
      2. <chrono>可以非常灵活地测量非常快速的功能。比快速快三倍仍然是一个很好的胜利。

答案 1 :(得分: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(“12月之后的月份”)。

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