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

标签 c++ date datetime parsing c++17

post here提供了一个非常简洁和纯 C++ 算法,用于将序列日期 (Excel) 转换为其明确的年-月-日表示(和返回)。为了方便起见,让我粘贴一个压缩版本:

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

例如

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

上面的帖子是 2002 年的,所以我想知道是否有更好的替代实现。 “更好”是指例如更快、更短或更隐蔽。甚至可能提供一定数量的预计算的算法(例如,记录所需年份范围的 1 月 1 日序列日期,比如 1900 到 2200,然后执行快速查找)。

最佳答案

你展示的算法非常好。在我的平台 (clang++ -O3) 上,它们生成的目标代码没有分支(流水线拖延器)并且无法访问遥远的内存(缓存未命中)。作为一对,有效期从 -4800-03-01 到 future 数百万年(范围很大)。在整个范围内,他们模拟公历。

Here are some alternative algorithms非常相似。一个区别是您的纪元为 1900-01-01,而我展示的纪元为 1970-01-01。然而,很容易通过这些时期(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);
}

这些算法向前和向后数百万年(包括 -4800-03-01 之前)都有效。尽管这个额外的范围不会给您带来太多好处,因为公历直到 1582 年 10 月 15 日才开始。

我使用 clang++ -O3 -S 在 macOS 上编译了两对算法,我生成的目标代码略小(大约 10%)。尽管它们都非常小、无分支且无缓存丢失,但尝试通过衡量性能来验证其优势将是一项具有挑战性的工作。

我没有发现任何一组的可读性优于另一组。然而,这对算法确实带有 irritatingly exhaustive derivation对于那些好奇这些算法如何工作的人,以及单元测试以确保算法在 +/-100 万年的范围内工作。

通过设置 const int era = 5 将有效性范围限制为 [2000-03-01, 2400-02-29],可以在上述算法中获得非常轻微的性能在这两种算法中。我没有对这个选项进行性能测试。我预计噪音水平会有所提高。

或者通过不考虑 era 的负值来限制从 [0000-03-01,数百万年前] 的范围可能会有一些微小的性能优势:

civil_from_days 中:

const int era = z / 146097;

days_from_civil 中:

const int era = y / 400;

关于c++ - 在 C++ 中将序列日期 (Excel) 转换为年月日的算法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56717088/

相关文章:

c# - 从 C# ESP 值调用 C++ DLL

javascript - 如何返回土耳其格式的日期?

c++ - 单FD调用 `FD_SET`后还需要调用 `select`吗?

c++ - 有人可以向我解释下面指数函数背后的数学原理吗?

iOS-Sqlite 存储为 `DATE` 或 `day` 、 `month` 、 `year` ?

java - Hibernate 5 不能正确处理 LocalDate

C# 字符串到 SqlDateTime : how to set format for recognition?

python - 根据日期和时间分割数据

c++ - 帮助我理解这个简单的 C++ 类定义、构造函数和初始化

date - 无法在使用查询的 Elasticsearch 中实现日期范围过滤器