代码分析很多,这里不细讲,着重分析其中一些设计技巧:
static inline unsigned long mktime (unsigned int year, unsigned int mon, unsigned int day, unsigned int hour, unsigned int min, unsigned int sec) { if (0 >= (int) (mon -= 2)) { /* 1..12 -> 11,12,1..10 */ 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 */ }
第一个有趣的地方是,将1、2月前移。为什么这么做呢?
举个例子:2012/12/23,这一天,怎么计算0001/01/01到这一天的闰年数呢?
year/4 - year/100 + year/400
这个式子的确是正确的,可以正确计算出闰年数,但是我们真正计算的不是闰年数,而是闰年增加的天数。由于是2012/02/23,没有经过二月,所以在计算由于闰年增加的天数的时候不能计算2012年的,因此要对上式进行纠正:
year/4 - year/100 + year/400 - 1
因此在计算闰年对整个天数的影响的时候需要考虑是否经过了2月,就引入了判断条件,实现更为复杂了。
设计者巧妙地将1、2月前移,规避掉了这个问题。现在每年是从三月开始的,2012/02对应2011/12,2012/03对应2012/01,这里就天然规避掉了上述问题,可以保证
year/4 - year/100 + year/400
计算增加的天数必然是正确的。
第二个有趣的地方是这个式子:367*mon/12,367是什么鬼?
带入不同的mon计算下:
30、61、91、122、152、183、214、244、275、305、336、367
算一算序列前后的差值:
30、31、30、31、30、31、31、30、31、30、31、31
对比一下正常的每月天数(2月按30算):
31、30、31、30、31、30、31、31、30、31、30、31
刚好就是正常月份天数循环左移一个月的结果,很神奇有木有!!
这样就好解释367*mon/12的意思了,还是举个例子说明:
计算2012/02/23,先前移2011/12/23,367*mon/12的值为367,367-30-1=336,这就表示前十一个月(实际是2011/03/23-2012/02/23)已经经过的天数(-30是去掉多加的367*1/12,-1是因为不能算今天,这两个计算体现在719499中)。
这里又有一个巧妙的地方:由于2月现在到了最后面,2011/12/23(真实日期2012/02/23),所以就可以不考虑闰年不闰年的问题了,336+day=359。如果日期是2011/12/29(真实日期2012/02/29),336+day=365,结果完全正确。
看到这里,有没有觉得有什么不对的?嗯,对,完全没有输入合法性判断,输入日期2011/02/29和2011/03/01结果会是一样的。
由于这个函数主要是内核用来转换其他时间结构的,比如struct tm,输入合法性在其他地方应该是有判断,但是如果是自己要使用这么一个函数,务必注意检测下输入的合法性。(虽然也可以计算出结果,但未免有些奇怪,不排除是调用者写错了;除了格式正确外,还要记得判断计算不要溢出了哦)。
PS:标准库<time.h>也有个转换函数:time_t mktime(struct tm *timeptr)