Linux mktime 源代码简析
這里選擇從另外一個角度再次解析這部分代碼,建議先閱讀上面的博客內容:
?
?
?/* 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 */
}
?
這個函數的功能是獲得1970年1月1日至今的秒數,我設這個數為totol_sec
首先需要獲得公元元年1月1日至今的天數,我們設這個數為days
?
顯然:
?
totol_sec = ((days * 24 + hour) * 60 + min) * 60 + sec后面的部分比較簡單,難點在于獲取days
設leapdays為公元0年到今天之前(不包含今天)的閏天數
設ydays為今年1月1日至今的天數
另: 公元0年到1970年1月1日的天數為719162天
因此:
?
days = leapdays + (year0 - 1) * 365 + ydays - 719162 (1)
為了更好的理解上面的源代碼,我們引入變量mon, year, magic,其中mon和year都是源碼中用到的變量
?
?當mon0 > 2 時, mon = mon0 - 2, year = year0 (2a)
當mon0 <= 2 時, mon = mon0 + 10, year = year0 - 1 (2b)
magic = 367 * mon / 12
上面的(2a) (2b)等價于源碼中的
?
?/* 1..12 -> 11,12,1..10 */
if (0 >= (int) (mon -= 2)) {
mon += 12; /* Puts Feb last since it has leap day */
year -= 1;
}
?
遍歷mon0(參見上面的博客內容)我們可以發現magic有如下的性質
?
?當mon0 > 2 時, ydays = magic + day + 28 (3a)
當mon0 <= 2 時, ydays = magic + day - 365 + 28 (3b)
同時,無論mon0為何值,year是否等于year0都有
?
leapdays = year/4 - year/100 + year/400 (4)
結合(1)(2a)(3a)(4), 當mon0 > 2 時
?
?days = year/4 - year/100 + year/400 + (year - 1) * 365
+ 367 * mon / 12 + day + 28 - 719162
?
結合(1)(2b)(3b)(4), 當mon0 <= 2 時
?
?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
這也就是源碼中獲取經過天數的公式。
?
可以看到源碼中最精彩最難以理解的部分就是
?
367*mon/12我們再來看看這個算式的計算結果:
?
?計算值
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
可以發現這個計算結果實際上隱含了一個信息就是,它恰好代表著之前月份天數的和month_day_in_year ,只是這個和是一個經過偏轉的值即
?
value = month_day_in_year - 291月和2月比較特殊,因為他們被加上了12個月,也就是1年,他們的value實際上應該先減去365,也就是
?
value - 365 = month_day_in_year - 29
也就是說367 * mon / 12這個算式經過取整運算剛好能得出月份天數和的信息,這應該是一個數學上的巧合。
據說是高斯最先提出這種算法,實在是佩服這些天才。
我也參看了glibc相同功能的代碼(參見glicb源碼time/mktime.c),在glibc中是通過查表法來獲得月份天數信息的
?
?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 }
};
因為linux源碼中已經處理了閏年的情況,所以我們只需要關注Normal years的部分就行了。根據上面的分析倒算回去對比一下,其實是一樣的。
從效率上來說,linux和glibc的mktime都是差不多的,但是linux版本的mktime不需要依靠全局變量。
總結
以上是生活随笔為你收集整理的Linux mktime 源代码简析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux mktime函数会受当前环境
- 下一篇: Linux系统时间函数