mktime.c 计算 UNIX 时间戳
最近尝试阅读 Linux-0.12 源码,让 AI 帮我挑选一个简单的给我,于是有了本文的内容。
在正式介绍之前,有个背景知识需要补充:Linux 使用 1970 年 1 月 1 日作为纪元时间,UNIX 时间戳指的是从这天开始到当前时间的间隔秒数。1970 年 1 月 1 日差不多就是 UNIX 诞生的时间。
mktime.c 中仅仅只有一个函数 kernel_mktime(),目的是将时间结构体 struct tm 转换为 UNIX 时间戳。
代码首先定义了年、日、时、分四个宏,用于将其直接转换称对应秒。
#define MINUTE 60
#define HOUR (60*MINUTE)
#define DAY (24*HOUR)
#define YEAR (365*DAY)
这里没有关于月的宏,因为一个月的天数是不定的。这里使用一个长度为 12 的数组,一个元素代表抵达到这个月的时间,并假定为闰年(2 月有 29 天)。
/* interestingly, we assume leap-years */
static int month[12] = {
0,
DAY*(31),
DAY*(31+29),
DAY*(31+29+31),
DAY*(31+29+31+30),
DAY*(31+29+31+30+31),
DAY*(31+29+31+30+31+30),
DAY*(31+29+31+30+31+30+31),
DAY*(31+29+31+30+31+30+31+31),
DAY*(31+29+31+30+31+30+31+31+30),
DAY*(31+29+31+30+31+30+31+31+30+31),
DAY*(31+29+31+30+31+30+31+31+30+31+30)
};
具体转换方法:
-
年转换为秒
除了年份减去 70 之外,还需要判断当年距 1970 年之间出现了几次闰年,并补上额外的天数。
这里有一个很巧妙的方法:(year+1)/4:从 1970 年开始,到 1972 为第一个闰年,但是此时是无需额外补充闰日的,要到 1973 年也就是 (3+1)/4 = 1 才补充一天闰日。
注:这里并没有考虑 2000 年及以后的情况。
-
月转换为秒
查预先设置好的月份表,因为提前假定是闰年,所以当是平年且月份大于1时,需要额外减去一天时间。
-
日转换为秒
因为 tm_mday 取值范围时 1-31,因此在转换时需要减去 1 天(当天还没过去)。
struct tm { int tm_sec; /* seconds after the minute [0-60] */ int tm_min; /* minutes after the hour [0-59] */ int tm_hour; /* hours since midnight [0-23] */ int tm_mday; /* day of the month [1-31] */ int tm_mon; /* months since January [0-11] */ int tm_year; /* years since 1900 */ int tm_wday; /* days since Sunday [0-6] */ int tm_yday; /* days since January 1 [0-365] */ int tm_isdst; /* Daylight Savings Time flag */ long tm_gmtoff; /* offset from UTC in seconds */ char *tm_zone; /* timezone abbreviation */ }; -
时、分、秒转换为秒
直接按照对应宏进行转换。
源码很短,将其全部贴在这里:
/*
* linux/kernel/mktime.c
*
* (C) 1991 Linus Torvalds
*/
#include <time.h>
/*
* This isn't the library routine, it is only used in the kernel.
* as such, we don't care about years<1970 etc, but assume everything
* is ok. Similarly, TZ etc is happily ignored. We just do everything
* as easily as possible. Let's find something public for the library
* routines (although I think minix times is public).
*/
/*
* PS. I hate whoever though up the year 1970 - couldn't they have gotten
* a leap-year instead? I also hate Gregorius, pope or no. I'm grumpy.
*/
#define MINUTE 60
#define HOUR (60*MINUTE)
#define DAY (24*HOUR)
#define YEAR (365*DAY)
/* interestingly, we assume leap-years */
static int month[12] = {
0,
DAY*(31),
DAY*(31+29),
DAY*(31+29+31),
DAY*(31+29+31+30),
DAY*(31+29+31+30+31),
DAY*(31+29+31+30+31+30),
DAY*(31+29+31+30+31+30+31),
DAY*(31+29+31+30+31+30+31+31),
DAY*(31+29+31+30+31+30+31+31+30),
DAY*(31+29+31+30+31+30+31+31+30+31),
DAY*(31+29+31+30+31+30+31+31+30+31+30)
};
long kernel_mktime(struct tm * tm)
{
long res;
int year;
year = tm->tm_year - 70;
/* magic offsets (y+1) needed to get leapyears right.*/
res = YEAR*year + DAY*((year+1)/4);
res += month[tm->tm_mon];
/* and (y+2) here. If it wasn't a leap-year, we have to adjust */
if (tm->tm_mon>1 && ((year+2)%4))
res -= DAY;
res += DAY*(tm->tm_mday-1);
res += HOUR*tm->tm_hour;
res += MINUTE*tm->tm_min;
res += tm->tm_sec;
return res;
}
一个比较有趣的彩蛋是:Linus 在注释中说自己讨厌 1970 并吐槽为什么不直接用闰年作为起始年。