Linux mktime 源代码简析
這里選擇從另外一個(gè)角度再次解析這部分代碼,建議先閱讀上面的博客內(nèi)容:
?
?
?/* Converts Gregorian date to seconds since 1970-01-01 00:00:00.
* Assumes input in normal date format, i.e. 1980-12-31 23:59:59
* => year=1980, mon=12, day=31, hour=23, min=59, sec=59.
*
* [For the Julian calendar (which was used in Russia before 1917,
* Britain & colonies before 1752, anywhere else before 1582,
* and is still in use by some communities) leave out the
* -year/100+year/400 terms, and add 10.]
*
* This algorithm was first published by Gauss (I think).
*
* WARNING: this function will overflow on 2106-02-07 06:28:16 on
* machines where long is 32-bit! (However, as time_t is signed, we
* will already get problems at other places on 2038-01-19 03:14:08)
*/
unsigned long
mktime(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 ((((unsigned long)
(year/4 - year/100 + year/400 + 367*mon/12 + day) +
year*365 - 719499
)*24 + hour /* now have hours */
)*60 + min /* now have minutes */
)*60 + sec; /* finally seconds */
}
?
這個(gè)函數(shù)的功能是獲得1970年1月1日至今的秒數(shù),我設(shè)這個(gè)數(shù)為totol_sec
首先需要獲得公元元年1月1日至今的天數(shù),我們?cè)O(shè)這個(gè)數(shù)為days
?
顯然:
?
totol_sec = ((days * 24 + hour) * 60 + min) * 60 + sec后面的部分比較簡(jiǎn)單,難點(diǎn)在于獲取days
設(shè)leapdays為公元0年到今天之前(不包含今天)的閏天數(shù)
設(shè)ydays為今年1月1日至今的天數(shù)
另: 公元0年到1970年1月1日的天數(shù)為719162天
因此:
?
days = leapdays + (year0 - 1) * 365 + ydays - 719162 (1)
為了更好的理解上面的源代碼,我們引入變量mon, year, magic,其中mon和year都是源碼中用到的變量
?
?當(dāng)mon0 > 2 時(shí), mon = mon0 - 2, year = year0 (2a)
當(dāng)mon0 <= 2 時(shí), mon = mon0 + 10, year = year0 - 1 (2b)
magic = 367 * mon / 12
上面的(2a) (2b)等價(jià)于源碼中的
?
?/* 1..12 -> 11,12,1..10 */
if (0 >= (int) (mon -= 2)) {
mon += 12; /* Puts Feb last since it has leap day */
year -= 1;
}
?
遍歷mon0(參見(jiàn)上面的博客內(nèi)容)我們可以發(fā)現(xiàn)magic有如下的性質(zhì)
?
?當(dāng)mon0 > 2 時(shí), ydays = magic + day + 28 (3a)
當(dāng)mon0 <= 2 時(shí), ydays = magic + day - 365 + 28 (3b)
同時(shí),無(wú)論mon0為何值,year是否等于year0都有
?
leapdays = year/4 - year/100 + year/400 (4)
結(jié)合(1)(2a)(3a)(4), 當(dāng)mon0 > 2 時(shí)
?
?days = year/4 - year/100 + year/400 + (year - 1) * 365
+ 367 * mon / 12 + day + 28 - 719162
?
結(jié)合(1)(2b)(3b)(4), 當(dāng)mon0 <= 2 時(shí)
?
?days = year/4 - year/100 + year/400 + year * 365
+ 367 * mon / 12 + day - 365 + 28 - 719162
得到相同的等式:
?
?days = (year/4 - year/100 + year/400 + 367*mon/12 + day)
+ year*365 - 719499
這也就是源碼中獲取經(jīng)過(guò)天數(shù)的公式。
?
可以看到源碼中最精彩最難以理解的部分就是
?
367*mon/12我們?cè)賮?lái)看看這個(gè)算式的計(jì)算結(jié)果:
?
?計(jì)算值
mon mon0 value
1 (3) 30
2 (4) 61
3 (5) 91
4 (6) 122
5 (7) 152
6 (8) 183
7 (9) 214
8 (10) 244
9 (11) 275
10 (12) 305
11 (1) 336
12 (2) 367
可以發(fā)現(xiàn)這個(gè)計(jì)算結(jié)果實(shí)際上隱含了一個(gè)信息就是,它恰好代表著之前月份天數(shù)的和month_day_in_year ,只是這個(gè)和是一個(gè)經(jīng)過(guò)偏轉(zhuǎn)的值即
?
value = month_day_in_year - 291月和2月比較特殊,因?yàn)樗麄儽患由狭?2個(gè)月,也就是1年,他們的value實(shí)際上應(yīng)該先減去365,也就是
?
value - 365 = month_day_in_year - 29
也就是說(shuō)367 * mon / 12這個(gè)算式經(jīng)過(guò)取整運(yùn)算剛好能得出月份天數(shù)和的信息,這應(yīng)該是一個(gè)數(shù)學(xué)上的巧合。
據(jù)說(shuō)是高斯最先提出這種算法,實(shí)在是佩服這些天才。
我也參看了glibc相同功能的代碼(參見(jiàn)glicb源碼time/mktime.c),在glibc中是通過(guò)查表法來(lái)獲得月份天數(shù)信息的
?
?const unsigned short int __mon_yday[2][13] =
{
/* Normal years. */
{ 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 },
/* Leap years. */
{ 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 }
};
因?yàn)閘inux源碼中已經(jīng)處理了閏年的情況,所以我們只需要關(guān)注Normal years的部分就行了。根據(jù)上面的分析倒算回去對(duì)比一下,其實(shí)是一樣的。
從效率上來(lái)說(shuō),linux和glibc的mktime都是差不多的,但是linux版本的mktime不需要依靠全局變量。
總結(jié)
以上是生活随笔為你收集整理的Linux mktime 源代码简析的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: linux mktime函数会受当前环境
- 下一篇: Linux系统时间函数