C++: Converting Julian dates to Gregorian
我需要编写一个函数,将Julian日期(年,日,日和小时)转换为标准格式(年,月,日,小时和分钟)并将其表示为字符串。我认为必须有一个人已经编写了一个库或组件,可以进行从日期到月份和月份的转换。我查看了几个众所周知的日期时间库:
-
ctime - 特别是使用tm结构和
mktime(tm *timeptr) ,因为这通常将tm结构的值设置到适当的位置,除了"忽略了成员的原始值tm_wday和tm_yday的timeptr ..."这没有帮助。 -
Boost :: DateTime - Gregorian构造
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 ) 没有帮助。而且我没有看到任何其他转换功能。
正如您所看到的,这些都需要月份和月份,这正是我首先想要避免的。我必须要么缺少某些东西,要么没有找到合适的地方(不完美,尽我所能。)
有人可以帮忙吗?我宁愿避免自己编写,因为几乎总会有一些我想念的问题。
我偶然发现了这个老问题,并认为我可能会向它添加一些新信息。 Thomas Pornin写的这个单一的现有答案是一个很好的答案,我对它进行了投票。然而,我把它作为改善它的挑战。如果我们能够以两倍的速度产生相同的答案怎么办?也许更快?
为了测试这一努力,我在一个函数中包含了Thomas的答案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | #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日)。时间兼容的低级别日期算法在算法
我的想法是我可以从
1 2 3 4 5 6 7 8 9 10 11 | 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}; } |
仍有分支,但较少。为了测试这个替代方案的正确性和性能,我写了以下内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 | #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 "; cout << ps2.count() <<"ps "; } |
此测试中有三个循环:
事实证明,这两种算法都是快速的...我正在测试的iMac Core i5上有几纳秒的时间。因此引入皮秒以获得分数纳秒的一阶估计。
1 | typedef duration<long long, pico> picoseconds; |
我想指出两件事:
对我来说,这个测试打印出来(大约):
1 2 | 8660ps 2631ps |
表示
希望这可以帮助。从这个答案中得到的重要事项:
从一年中的某一天开始计算月份和日期似乎很容易。这应该这样做:
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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月开始为零的月份,但假设日期计数(一年中的某一天或一天??中的某一天)从一开始计算。如果年份日期计数无效(超出年末),则结果
"朱利安"是一个混乱的来源,因为它也代表"儒略历",它与格里高利历相差几十天,以及闰年的计算。在这里,我只是假设您只是想在一个特定的格里高利年的背景下将"一年中的一天"计数转换为"月和日"。