作者 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()这种函数的问题谁会很关注。所以,时间方面最好使用可重入版本。