作者 mail

    c++中时间主要分为GMT时间和本地时间。GMT时间叫做格林威治时间,也就是UTC时间,这个时间有点特殊,它所在时区为0,在这个时区内,本地时间和GMT时间完全一致,地球上其他地方的时间都得在这个时间基础上加一个时区,这才是其他地方的本地时间。地球被分为24个时区,相邻时区相差一个小时。比如中国上海是+8区,意思就是,假如格林威治现在是27日1点0分0秒,而中国上海就是27日早上9点0分0秒。我们比格林威治早8个小时进入27号。具体的时区信息请看《 http://zh.wikipedia.org/wiki/时区列表 》,(时区从UTC-12到UTC+14)

    时区信息存放在/usr/share/zoneinfo/中,如上海时区文件就是/usr/share/zoneinfo/Asia/Shanghai中。时区可以由环境变量TZ设置,上海的就可以export TZ="Asia/Shanghai"。在c++程序中调用tzset()函数可以由环境变量TZ初始化时区信息。如果没有环境变量TZ,tzset()还会使用/etc/localtime来初始化时区信息。TZ环境变量可以指向绝对路径,也可以是相对路径。如果是相对路径则以/usr/share/zoneinfo/为基准路径。


    日光节约时间(daylight saving time; DST)又称夏令时间或夏时制,由于夏季太阳升起的时间比冬季早(特别是高纬度地区),人们为了充分利用日照来节省照明用电,因此特地将时间提起一个小时让民众能够早睡早期,这个制度叫做日光节约时间。世界上一百多个国家或地区实行,包含台湾,香港,日本,美国,欧洲,但中国大陆好像没有实行。在struct tm中有一项tm_isdst表示日光节约时间,当为0时,表示没有日光节约时间,当小于0时表示根据系统的时区信息来判断是否执行日光节约时间。当大于0时,表示应该加的日光节约时间,单位为秒。


    在c++中有一个时间点比较重要,那就是GMT的1970-01-01 00:00:00 +0000 (UTC)。这是在0时区的时间,即格林威治时间或UTC时间,这个时间学名叫做Epoch,新纪元。为什么这个时间比较特殊?因为如果你在那个时刻调用time()函数,你会发现返回的值是0! 现在我们随便去调一把,返回的都是10位数了。我刚才调了一下是1309164941,是UTC时间的2011-06-27 08:55:41 +0000 (UTC),也是中国时间的2011-06-27 16:55:41 +7080 (CST)。7080是16进制数,等于10进制的28800秒,即8个小时。


    c++的时间中,有几个数据结构比较常用,struct tm。


struct tm {
               int tm_sec;         /* seconds */
               int tm_min;         /* minutes */
               int tm_hour;        /* hours */
               int tm_mday;        /* day of the month */
               int tm_mon;         /* month */
               int tm_year;        /* year */
               int tm_wday;        /* day of the week */
               int tm_yday;        /* day in the year */
               int tm_isdst;       /* daylight saving time */
           };
           struct timeval {
               time_t      tv_sec;     /* seconds */
               suseconds_t tv_usec;    /* microseconds */
           };
           struct timezone {
               int tz_minuteswest;     /* minutes west of Greenwich */
               int tz_dsttime;         /* type of DST correction */
           };



    下面介绍c++中的几个重要时间函数。


time_t time(time_t *t);



    这大概能算是最常用的一个时间函数了,该函数返回从Epoch开始的秒数,如果t != NULL,则返回值填充到t所指的内存中。注意:返回的是UTC标准的秒数,如果你在格林威治和上海同时调用这个函数,则这2个值是一样的,都是格林威治的当前秒数,并没有算时区。



int gettimeofday(struct timeval *tv, struct timezone *tz);
       int settimeofday(const struct timeval *tv, const struct timezone *tz);



    这个timeval是相对time_t更精确的时间,包含了微妙数。tv_usec最大为999999,再加1则为1秒,超过1秒就进位到tv_sec。timezone一般传入NULL,linux没有处理timezone中的tz_dsttime信息。



struct tm *gmtime(const time_t *timep);
       struct tm *gmtime_r(const time_t *timep, struct tm *result);



    这两个函数意思一样,将time_t这个秒数转换成以UTC时区为标准的年月日时分秒时间。gmtime_r是线程安全的,推荐使用这个。gmtime返回的是一个struct tm*,这个指针指向一个静态的内存,这块区域是会经常被改动的。你刚调用gmtime(),执行了其他几条命令,然后想使用刚才gmtime()得到struct tm,会发现内容不对了,所以很危险,我就被搞得一头雾水,再gdb了多次之后,发现是这么个情况,改用gmtime_r后就没有问题,gmtime_r会将结果保存到你传入的内存中。



struct tm *localtime(const time_t *timep);
       struct tm *localtime_r(const time_t *timep, struct tm *result);



    这两个函数意思也一样,会根据时区信息得到本地时间,在上海同时调用localtime_r 和gmtime_r会发现,localtime_r得到小时数会多8,因为我们是+8区。同样建议使用localtime_r版本。



time_t mktime(struct tm *tm);



    将已经根据时区信息计算好的struct tm转换成time_t的秒数。计算出的秒数是以UTC时间为标准的,跟调用time()得到的秒数是同一个概念。这个struct tm是本地时间,如:上海时间2011-08-20 13:20:40,struct tm中各个值就是此时间的对应值,因为这个时间中已经包含了时区信息。如果在格林威治和上海对同一个struct tm调用这个函数,返回的值是不一样的,上海的值会比格林威治少8个小时。



    再介绍一下本地时间和UTC时间之间的转换。本地时间是在UTC时间基础上根据时区和日光节约时间调整得到。<time.h>中如下函数和变量起到了转换本地时间和UTC时间的作用。


void tzset (void);
       extern char *tzname[2];
       extern long timezone;
       extern int daylight;



    具体代码:


#include <time.h>
    tzset ();
    time_t current_timet;
    time(¤t_timet);//得到当前时间秒数
    struct tm utc_tm;
    gmtime_r (¤t_timet, &utc_tm);//得到GMT,即UTC时间
    struct tm local_tm;
    localtime_r(¤t_timet, &local_tm);//得到本地时间,根据如下打印看出两者相差8小时。
    printf ("current_timet: %d, timezone info:[%d / %d]\n", current_timet, timezone, daylight);
    printf ("utc_tm: %d-%d-%d %d:%d:%dZ\n", utc_tm.tm_year, utc_tm.tm_mon, utc_tm.tm_mday, utc_tm.tm_hour, utc_tm.tm_min, utc_tm.tm_sec);
    printf ("local_tm: %d-%d-%d %d:%d:%d\n", local_tm.tm_year, local_tm.tm_mon, local_tm.tm_mday, local_tm.tm_hour, local_tm.tm_min, local_tm.tm_sec);
    struct tm local_tm2 = utc_tm;
    local_tm2.tm_sec -= timezone;
    local_tm2.tm_isdst = daylight; //将utc时间转换成本地时间
    time_t local_timet2 = mktime(&local_tm2);//得到秒数
    printf ("\nlocal_timet2=%d\n", local_timet2);//可以看出,和一开始得到的当前时间秒数相同。daylight部分没有仔细测。




    痛苦的回顾,我曾经写过如下代码


time_t now;
time(&now);
DEBUG ("now:%d",now)
struct tm* timeNow = gmtime (&now);
    DEBUG ("timeNOw:sec=%d, min=%d, hour=%d, mday=%d, mon=%d, year=%d, wday=%d, yday=%d, isdst=%d",
           timeNow->tm_sec, timeNow->tm_min, timeNow->tm_hour, timeNow->tm_mday, timeNow->tm_mon,
           timeNow->tm_year, timeNow->tm_wday, timeNow->tm_yday, timeNow->tm_isdst);
struct tm tm1 = *timeNow;
    DEBUG ("tm1    :sec=%d, min=%d, hour=%d, mday=%d, mon=%d, year=%d, wday=%d, yday=%d, isdst=%d",
           tm1.tm_sec, tm1.tm_min, tm1.tm_hour, tm1.tm_mday, tm1.tm_mon,
           tm1.tm_year, tm1.tm_wday, tm1.tm_yday, tm1.tm_isdst);



结果,打印出来的出乎意料。我认为timeNow和tm1的内容应该是一样的,可是tm_hour竟然相差8。后来发现DEBUG()中有时间处理的,使得静态内存被修改了。但类似DEBUG()这种函数的问题谁会很关注。所以,时间方面最好使用可重入版本。