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 尝试就不存在这个问题了。

一个 Windows 对时小工具

由于在 CERNET 内,我经常需要用代理上网,没办法直连到 NTP 服务器,因此不能使用 Windows 时间服务对时。偶尔维修电脑或者不小心调整错时间,再加上电脑时钟本身就有一定的漂移,对时就变成了件麻烦的事情。

手动调时也没个参照,误差往往比较大。IPv6 网络上存在一些 NTP 服务器,Linux 下有 ntpdate 是支持 IPv6 NTP 服务器的,但是我搜索了半天,才在一篇文章上看到有人评论说 Windows 下只有一款 NTP 客户端支持 IPv6,还是收费软件——可他也没给出名字。

无奈之下想到 Python 的 httplib 是支持 IPv6 连接的,于是我就仿照 htpdate 写了一个利用 Google 的 IPv6 Web 服务器进行对时的 Python 小工具 htpdate.py。虽然误差比 NTP 大不少,但是还是在可接受范围内(不到 1 秒),而且比较方便,连日期也一块更新了。下面是代码,比较粗糙。

#!/usr/bin/env python
import httplib, time
from os import system

def main():
  conn = httplib.HTTPConnection('google.com')
  time.clock()
  conn.request('HEAD', '')
  t_rtt = time.clock()
  res_time = conn.getresponse().getheader('date')
  t = time.localtime(time.mktime(time.strptime(res_time,
                                 '%a, %d %b %Y %H:%M:%S %Z')) - time.timezone)
  time_str = time.strftime('%H:%M:%S', t)
  local_time = time.asctime()
  t_exe = time.clock()
  centi_sec = (t_exe - t_rtt/2)*100
  if centi_sec > 99:
    centi_sec = 99
  system('time %s.%2.0f' % (time_str, centi_sec))
  date_str = time.strftime('%Y-%m-%d', t)
  system('date %s' % date_str)
  print 'LOCAL  TIME: ' + local_time
  print 'SERVER TIME: ' + time.asctime(t)
  print 'LOCAL  TIME: ' + time.asctime()
  if (t_exe - t_rtt/2) >= 1:
    print 'Round trip time is too long. Time error might be larger than 1 sec.'

if __name__ == '__main__':
  main()