简体   繁体   English

在C ++中将序列日期(Excel)转换为年月日的算法

[英]Algorithm for converting Serial Date (Excel) to Year-Month-Day in C++

This post here provides a very neat & pure C++ algorithm for converting a serial date (Excel) to its explicit year-month-day representation (and back). 此处的帖子提供了一种非常整洁的纯C ++算法,用于将序列日期(Excel)转换为其明确的年月日表示形式(并返回)。 Let me paste a compressed version for convenience: 为了方便起见,我粘贴一个压缩版本:

void ExcelSerialDateToDMY(int nSerialDate, int& nDay, int& nMonth, int& nYear)
{
    // Modified Julian to DMY calculation with an addition of 2415019
    int l  = nSerialDate + 68569 + 2415019;
    int n  = int(( 4 * l ) / 146097);
    l      = l - int(( 146097 * n + 3 ) / 4);
    int i  = int(( 4000 * ( l + 1 ) ) / 1461001);
    l      = l - int(( 1461 * i ) / 4) + 31;
    int j  = int(( 80 * l ) / 2447);
    nDay   = l - int(( 2447 * j ) / 80);
    l      = int(j / 11);
    nMonth = j + 2 - ( 12 * l );
    nYear  = 100 * ( n - 49 ) + i + l;
}

int DMYToExcelSerialDate(int nDay, int nMonth, int nYear)
{
    // DMY to Modified Julian calculated with an extra subtraction of 2415019.
    return int(( 1461 * ( nYear + 4800 + int(( nMonth - 14 ) / 12) ) ) / 4) +
           int(( 367 * ( nMonth - 2 - 12 * ( ( nMonth - 14 ) / 12 ) ) ) / 12) -
           int(( 3 * ( int(( nYear + 4900 + int(( nMonth - 14 ) / 12) ) / 100) ) ) / 4) +
           nDay - 2415019 - 32075;
}

For example 例如

 2019-06-22 <--> 43638
 2000-01-28 <--> 36553
 1989-09-21 <--> 32772

The above post is from 2002, so I am wondering whether there are alternative implementations, which are better. 上面的帖子来自2002年,所以我想知道是否有其他更好的实现。 By "better" I mean eg faster, shorter or less obscure. “更好”是指例如更快,更短或更模糊。 Or even algorithms, which perhaps provide a certain amount of pre-calculations (eg record 1 Jan serial date for a desired range of years, say 1900 to 2200, and then perform a fast look up). 甚至甚至可能提供一定数量的预计算的算法(例如,记录1月1日的序列日期为所需的年份范围(例如1900至2200,然后执行快速查找))。

The algorithms you show are very good. 您显示的算法非常好。 On my platform (clang++ -O3) they produce object code with no branches (pipeline stallers) and no accesses to far away memory (cache misses). 在我的平台(clang ++ -O3)上,它们生成的目标代码没有分支(管道停滞),也无法访问遥远的内存(缓存未命中)。 As a pair, there is a range of validity from -4800-03-01 to millions of years in the future (plenty of range). 作为一个对,有效范围从-4800-03-01到未来的数百万年(很多范围)。 Throughout this range they model the Gregorian calendar. 在此范围内,他们对公历进行建模。

Here are some alternative algorithms that are very similar. 这是一些非常相似的替代算法 One difference is that yours have an epoch of 1900-01-01 and the ones I'm presenting have an epoch of 1970-01-01. 一个区别是您的纪元是1900-01-01,而我要介绍的纪元是1970-01-01。 However it is very easy to adjust the epoch by the difference of these epochs (25569 days) as shown below: 但是,通过这些历元(25569天)之间的差异来调整历元非常容易,如下所示:

constexpr
std::tuple<int, unsigned, unsigned>
civil_from_days(int z) noexcept
{
    static_assert(std::numeric_limits<unsigned>::digits >= 18,
             "This algorithm has not been ported to a 16 bit unsigned integer");
    static_assert(std::numeric_limits<int>::digits >= 20,
             "This algorithm has not been ported to a 16 bit signed integer");
    z += 719468 - 25569;
    const int era = (z >= 0 ? z : z - 146096) / 146097;
    const unsigned doe = static_cast<unsigned>(z - era * 146097);          // [0, 146096]
    const unsigned yoe = (doe - doe/1460 + doe/36524 - doe/146096) / 365;  // [0, 399]
    const int y = static_cast<int>(yoe) + era * 400;
    const unsigned doy = doe - (365*yoe + yoe/4 - yoe/100);                // [0, 365]
    const unsigned mp = (5*doy + 2)/153;                                   // [0, 11]
    const unsigned d = doy - (153*mp+2)/5 + 1;                             // [1, 31]
    const unsigned m = mp + (mp < 10 ? 3 : -9);                            // [1, 12]
    return std::tuple<int, unsigned, unsigned>(y + (m <= 2), m, d);
}

constexpr
int
days_from_civil(int y, unsigned m, unsigned d) noexcept
{
    static_assert(std::numeric_limits<unsigned>::digits >= 18,
             "This algorithm has not been ported to a 16 bit unsigned integer");
    static_assert(std::numeric_limits<int>::digits >= 20,
             "This algorithm has not been ported to a 16 bit signed integer");
    y -= m <= 2;
    const int era = (y >= 0 ? y : y-399) / 400;
    const unsigned yoe = static_cast<unsigned>(y - era * 400);      // [0, 399]
    const unsigned doy = (153*(m + (m > 2 ? -3 : 9)) + 2)/5 + d-1;  // [0, 365]
    const unsigned doe = yoe * 365 + yoe/4 - yoe/100 + doy;         // [0, 146096]
    return era * 146097 + static_cast<int>(doe) - (719468 - 25569);
}

These algorithms are valid for millions of years both forward and backwards (including prior to -4800-03-01). 这些算法向前和向后(包括-4800-03-01之前)的有效期为数百万年。 Though that extra range won't buy you much because the Gregorian calendar didn't even start until 1582-10-15. 尽管这个额外的范围不会给您带来多少收益,因为公历甚至直到1582-10-15才开始。

I compiled both pairs of algorithms on macOS using clang++ -O3 -S and the set I have produces slightly smaller object code (about 10%). 我使用clang++ -O3 -S在macOS上编译了两对算法,并且我所生成的算法产生的目标代码略小(约10%)。 Though they are all so small, branch-less and cache-miss-free, trying to verify that benefit by measuring performance would be a challenging exercise. 尽管它们是如此之小,无分支且无缓存丢失,但尝试通过评估性能来验证其收益将是一项艰巨的任务。

I do not find the readability of either set superior to the other. 我没有发现任何一套的可读性都优于另一套。 However this pair of algorithms does come with an irritatingly exhaustive derivation for those who are curious how these algorithms work, and unit tests to ensure the algorithms are working over a range of +/-1 million years. 但是,对于那些好奇这些算法如何工作的人,这对算法确实具有令人烦恼的详尽推导 ,并且进行了单元测试以确保算法在+/- 100万年的范围内工作。

One could gain a very slight bit of performance in the above algorithms by limiting the range of validity to [2000-03-01, 2400-02-29] by setting const int era = 5 in both algorithms. 通过在两种算法中将const int era = 5 age设置const int era = 5 ,将有效范围限制为[2000-03-01,2400-02-29],可以在上述算法中获得很小的性能。 I have not performance tested this option. 我尚未对该选项进行性能测试。 I would expect such a gain to be in the noise level. 我希望这样的增益可以达到噪声水平。

Or there might be some miniscule performance advantage by limiting the range from [0000-03-01, millions of years forward] by not accounting for negative values of era : 或者有可能是一些微不足道的性能优势从限制范围[0000-03-01,数百万年前进]通过不占负值era

In civil_from_days : civil_from_days

const int era = z / 146097;

In days_from_civil : days_from_civil

const int era = y / 400;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM