2011年13月32日24点60分60秒

这是一个测试时候发现的问题,2011 年 13 月 32 日 24 点 60 分 60 秒,这个时间存在吗?

我觉得这应该是个错误的时间,但很不幸地是,如果往 struct tm 中填入这个时间,mktime() 时并不会报错。下面是一段简单的测试代码:

#include <stdio.h>
#include <string.h>
#include <time.h>

int main()
{
    struct tm t;
    memset(&t, 0, sizeof(t));
    t.tm_year = 2011-1900;
    t.tm_mon = 13-1;
    t.tm_mday = 32;
    t.tm_hour = 24;
    t.tm_min = 60;
    t.tm_sec = 60;
    printf("tm_mon=%d, tm_mday=%d, tm_hour=%d, tm_min=%d, tm_sec=%d\n",
            t.tm_mon, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec);
    time_t tt = mktime(&t);
    printf("%s", ctime(&tt));
    printf("tm_mon=%d, tm_mday=%d, tm_hour=%d, tm_min=%d, tm_sec=%d\n",
            t.tm_mon, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec);
}

这个程序执行的结果是:

$ ./a.out  
tm_mon=12, tm_mday=32, tm_hour=24, tm_min=60, tm_sec=60
Thu Feb  2 01:01:00 2012
tm_mon=1, tm_mday=2, tm_hour=1, tm_min=1, tm_sec=0

查看 man mktime,的确有对这个特性的解释:

...if structure members are outside their valid interval, they will be normalized
 (so that, for example,  40  October  is  changed into  9  November);...

这就意味着,对输入时间做严格的检查无法依赖于标准库中的 mktime() 函数,只能自己来进行。这应该也是 date 命令曾经改进过的地方。

$ date -d "2011-13-32" # date (coreutils) 5.2.1
Wed Feb  1 00:00:00 CST 2012
$ date -d "2011-13-32" # date (GNU coreutils) 8.5
date: invalid date `2011-13-32' 

Shell Tips: Unix 时间到字面

我的工作需要天天跟报表数据打交道,在交换的文件中,一般时间的字段内容都是 Unix 时间。为了检查数据的正确性,不可避免地需要转换 Unix 时间到人类可读的字面时间。

下面想分享的是一个在 Shell 下转换 Unix 时间到字面的小方法。与前面几篇一样,这个小 shell 函数仍然可以放在 ~/.bashrc 中方便快捷使用。

# 转换 Unix 时间到本地时间字符串
function ctime()
{   
    date -d "UTC 1970-01-01 $1 secs"
}

使用方法很简单:

$ ctime 1234567890
Sat Feb 14 07:31:30 CST 2009

对 date 命令熟悉的同学会说,date 不是已经有直接转 Unix 时间的参数了吗?

$ date -d @1234567890
Sat Feb 14 07:31:30     2009

但是不好意思的是,小弟有时候用的 date 程序好老,不支持 @ 符号。

$ date --version
date (coreutils) 5.2.1
Written by David MacKenzie.

Copyright (C) 2004 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

PS: 写完这篇博文,我又想到了一个有趣的事情,既然很多 Linux 64 位版本的 time_t 已经是 long long 格式了,那么 date 命令有没有 year 2038 问题呢?

下面是 date (coreutils) 5.2.1 在 64 位服务器上的尝试结果:

$ date +%s -d "Tue Jan 19 11:14:07 CST 2038"
2147483647
$ date +%s -d "Tue Jan 19 11:14:08 CST 2038"
2147483648
$ date +%s -d "Tue Jan 19 11:14:09 CST 2999"
32473710849
$ ctime 2147483647
Tue Jan 19 11:14:07 CST 2038
$ ctime 2147483648
Sat Dec 14 04:51:44 LMT 1901
$ ctime 32473710849
Mon Mar 28 07:33:53 LMT 1910

看来字面时间和 Unix 时间之间互转存在着问题啊!但是用 Ubuntu 11.04 的 date (GNU coreutils) 8.5 尝试就不存在这个问题了。