一行代码看懂 Linux 内核的时间转换:__month_to_secs 逐行拆解
在 Linux 内核源码中,时间转换是最基础也最高频的操作之一。今天拆解一个极简但极精巧的函数——__month_to_secs,它把月份直接映射成从年初到该月1日的秒数。
函数原型
int __month_to_secs(int month, int is_leap)month:0~11,0 代表 1 月is_leap:是否闰年,0 或 1- 返回值:从 1 月 1 日 00:00:00 到
month月 1 日 00:00:00 经过的秒数
核心:一张表搞定所有月份
static const int secs_through_month[] = { 0, 31*86400, 59*86400, 90*86400, 120*86400, 151*86400, 181*86400, 212*86400, 243*86400, 273*86400, 304*86400, 334*86400 };这张表存储的是每个月1日之前累计经过的秒数。
我们来验证几个值:
| month | 含义 | 累计天数 | 秒数 |
|---|---|---|---|
| 0 | 1月1日 | 0 | 0 |
| 1 | 2月1日 | 31(1月) | 31×86400 |
| 2 | 3月1日 | 31+28=59 | 59×86400 |
| 3 | 4月1日 | 31+28+31=90 | 90×86400 |
| ... | ... | ... | ... |
| 11 | 12月1日 | 334 | 334×86400 |
注意:这是平年的数据,2 月按 28 天算。
闰年修正:一行 if 解决
int t = secs_through_month[month]; if (is_leap && month >= 2) t += 86400; return t;闰年多出的 2 月 29 日,影响的是3 月及以后的所有月份。
所以判断条件是month >= 2(即 3 月、4 月……12 月),补上一天的秒数86400。
为什么不建两张表(平年/闰年)?
因为一张表 + 一次分支判断,比两张表更省空间。现代 CPU 分支预测准确率极高,这个 if 几乎零代价。
为什么用static const?
const:数据放在只读段(.rodata),不占用栈空间static:保证只初始化一次,函数多次调用不重复计算- 编译器会把
31*86400这类表达式直接折成立即数,运行时就是一次内存读取
性能对比
| 方案 | 时间复杂度 | 空间 | 评价 |
|---|---|---|---|
| 逐月累加循环 | O(month) | O(1) | 慢,不可取 |
| 双表法(平年/闰年) | O(1) | 2×48B | 快但浪费空间 |
| 本方案(单表+if) | O(1) | 48B | 最优解 |
实际使用场景
这个函数在 Linux 内核中被__tm_to_time等时间转换函数调用,用于将struct tm(年/月/日/时/分/秒)转换为时间戳(秒数)。
总结
| 要点 | 说明 |
|---|---|
| 查表 | O(1) 拿到基础秒数 |
| 闰年修正 | month >= 2时 +86400 |
| 月份范围 | 0~11,0=1月 |
| 返回值 | 该月1日 0点的秒数,非月末 |
一行代码的智慧:用空间换时间,用一次判断换一张表。
如果这篇拆解对你有帮助,点个赞再走 👆
参考:Linux kernel source, time/time.c
