You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Linux内核mktime64实现简洁性:巧合还是刻意设计?

背景

说明:此前曾发布此内容但未符合平台规范。

偶然发现Linux内核中用于将公历日期转换为Unix时间戳(1970/01/01 00:00:00 UTC以来的秒数)的mktime64实现,惊讶于其代码行数极少,因此希望理解该公式的工作原理及设计逻辑。主要有以下疑问:

  • 魔法数367和719499的来源是什么?
  • 月、日参数为何采用1索引?例如2012年3月3日表示为(2012, 3, 3)

以下是代码片段:

time64_t mktime64(const unsigned int year0, const unsigned int mon0,
        const unsigned int day, const unsigned int hour,
        const unsigned int min, const unsigned int sec)
{
    unsigned int mon = mon0, year = year0;

    /* 1..12 -> 11,12,1..10 */
    if (0 >= (int) (mon -= 2)) {
        mon += 12;  /* Puts Feb last since it has leap day */
        year -= 1;
    }

    return ((((time64_t)
          (year/4 - year/100 + year/400 + 367*mon/12 + day) +
          year*365 - 719499
        )*24 + hour /* now have hours - midnight tomorrow handled here */
      )*60 + min /* now have minutes */
    )*60 + sec; /* finally seconds */
}

该实现可表示为以下公式:
Unix时间戳公式

其中日历日期偏移了-2个月。

本文将尝试解释公式中各术语的含义,说明该函数的假设与限制,并推导这些魔法常量的来源。为简化表述与提升可读性,后续代码片段均采用Python编写。

直观部分:已知时间及1970年1月1日以来天数求时间戳

已知天数和时间求Unix时间戳

非直观部分:已知日历日期求1970年1月1日以来天数

代码中的条件块将计算基准年的起始日调整为3月1日,因为这一天是2月闰日的次日。这会将闰日计算的基准日期从0000/01/01偏移到0000/03/01,而这一偏移已被魔法数719499抵消。

这使得我们可以通过简单的整数除法计算自公元0年(公元前1年)以来公历的闰日数:
公历自0000/01/01以来闰年数公式

这与代码中的year/4 - year/100 + year/400等价,因为C语言中的整数除法为向下取整。

我们先定义一个计算自公历0000/03/01以来天数的公式,其中年份和月份已通过上述条件代码调整:
自公历0000/01/01以来总天数公式

若我们将Unix时间戳基准日期(1970, 1, 1)通过上述条件代码调整为(1969, 11, 1)并代入公式,即可得到第一个魔法数的来源:
公历基准日到1970年1月1日的天数

因此:
1970年1月1日以来的天数

其中年份和月份已偏移-2个月。

“简化”原函数

我们用Python重写原函数,并将公式中直观与复杂的部分拆分为独立函数。

def days_since_gregorian_y0(year, month, day):
    if (0 >= (month := month-2)):
        month += 12
        year -= 1
    return (
        (year//4 - year//100 + year//400) +  # 自0000/03/01以来的闰日数
        (367 * month) // 12 + day +  # 以03-01为基准的年内天数(?)
        year * 365  # 每年的天数
    )


def mktime64(year, month, day, hour, minute, second):
    return (
        (
            (
                days_since_gregorian_y0(year, month, day) -
                days_since_gregorian_y0(1970, 1, 1)
            ) * 24 + hour
        ) * 60 + minute
    ) * 60 + second

日、月采用1索引的原因

我们已确定1970年1月1日以来的天数是两个自0000/03/01以来天数的差值。只要被减的两个值(目标日期与基准日期)的索引保持一致,结果就合理。由于魔法数719499是通过1索引的月、日计算得出的,因此调用mktime64时也需使用1索引的月、日。

魔法数367的来源

若我们对比各月天数数组与1索引(1-12)的月份对应的(367 * m) // 12项的连续差值数组,会发现两者相似,仅存在一位偏移及2月天数的差异。

month_days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
unknown_term_values = [(367 * (m+1)) // 12 - (367 * m) // 12 for m in range(12)]

print(month_days[1:] + month_days[:1])
[28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 31]

print(unknown_term_values)
[30, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 31]

幸运的是,由于月份采用1索引,且始终计算的是自公历0000/03/01以来天数的差值,367*m//12公式在m=1(对应2月)时的误差会被完全抵消。

总结

该简洁公式的实现得益于以下几点的契合:

  • 计算时将年起始日调整为3月1日,因为该日期的前一天是当年的闰日(若存在)。
  • 367*m1//12 - 367*m0//12的差值可准确计算任意两个月份之间的天数。
  • 日、月采用1索引不仅提升易用性,还能抵消367*m//12公式在2月的误差。

mktime64的文档字符串提到高斯是该公式部分内容的来源,但尚未找到相关资料。

这种简洁性要么纯属巧合,要么是罗马历法制定者利用367*m//12的商来确定各月天数。

若有人知晓该算法的来源,以及367*m//12项是否确实用于推导各月天数,欢迎提供相关资料!

感谢!:-)

内容的提问来源于stack exchange,提问作者Campbell McDiarmid

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 05:39:59