Linux下的Time处理

HTTP协议中,有很多机制都和Time有关,这里先撇开缓存控制及过期机制,就拿Date开刀。

Date的格式大致如下:

Tue, 21 Jul 2015 21:19:41 GMT

好,现在问题就来了,如何得到这个字符串?先别急,本文将会对Linux下Time的格式化以及转换做一下解读。

0x00 一切的起点——time_t

从某个纪元开始time_t这个概念就存在了,这里不谈什么历史故事,感兴趣的可以自行wiki。获取time_t值可以通过如下的函数获得:

time_t time(time_t *t);

我们可以如此使用:

time_t t;
time(&t);

当然也可以这么使用:

time_t t = time(NULL);

至于使用何种方式,就得看你是否需要指定值的存储位置了,这里并不涉及什么线程安全的问题,大家大可放心。

tip: linux下线程安全的函数大多会采用_r结尾的方式,比如sterror_r。

其实,单纯一个值并没有什么作用,我们只是将time_t的值作为衡量时间的基准,当涉及时间比较等计算问题时,它的作用就会凸显。

0x01 格式化的基础——tm

tm是什么?简单来说tm是time_t和格式化的时间字符串之间的一种中间表示,有了这个中间表示,我们才能按需定制时间字符串的格式。那么首先来看下tm结构体的声明:

struct tm {
    int tm_sec;    /* Seconds (0-60) */
    int tm_min;    /* Minutes (0-59) */
    int tm_hour;   /* Hours (0-23) */
    int tm_mday;   /* Day of the month (1-31) */
    int tm_mon;    /* Month (0-11) */
    int tm_year;   /* Year - 1900 */
    int tm_wday;   /* Day of the week (0-6, Sunday = 0) */
    int tm_yday;   /* Day in the year (0-365, 1 Jan = 0) */
    int tm_isdst;  /* Daylight saving time */
};

想必现在大家应该明白为何tm是中间表示了吧,它将time_t从一个单纯的数值转换成了包含各类详细信息的结构,这些信息包含时分秒、日月年、星期等。

那么,应该如何得到tm结构?且看如下函数声明:

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

如上给出的是线程安全版本的函数,分别获得本地时间和GM时间,两者的差距在于timezone的不同,在处理local和gm时间的转换之前,不需要关注这个问题。

既然time_t能够转换到tm,想必应该也有个方法能够给转回去。确实有这么个函数,声明如下:

time_t mktime(struct tm *tm);

0x02 时间的格式化——strftime

本节就是关于如何得到格式化时间的内容了,这里我们考虑如何将tm格式化成Date中所使用的格式,采用的如下函数:

size_t strftime(char *s, size_t max, const char *format, const struct tm *tm);

函数参数中,s表示的是用来存放字符串的位置的指针,max为最大长度,它和format有关,format为格式化的规则字符串。函数的返回值代表字符串的实际长度,注意不包括‘\0’,因为平常使用的时候,基本都能够估计好具体的长度,因此这个返回值似乎也只是用来判断错误了。

让我们通过一个例子来学习使用时间的格式化——获取Date中的gm时间字符串:

time_t current_time = time(NULL);
struct tm current_tm;
gmtime_r(&current_time, &current_tm);
// 此格式为通用的Date标准格式之一
char* format = "%a, %d %b %Y %H:%M:%S GMT";
// 注意为‘\0’预留一个坑
char time_str[30];
strftime(time_str, 30, format, &current_tm);
// 判断是否出错,此处略

这里的format内容可以参照manual,内容比较多,但是应该不难理解,在此不再复述。

0x03 时间字符串的解析——strptime

前文既然谈到了过期机制,那么必然涉及到时间字符串的解析,毕竟直接拿类似Date中的格式来处理是很不现实的,我们必须将其转换到time_t。好在,大家并不需要自己编写解析函数,已经有现成的了,如下为strptime函数的声明:

char *strptime(const char *s, const char *format, struct tm *tm);

和strftime差不多,只不过少了个最大长度,它就是strftime的逆过程,但是返回值需要注意一下,当解析出错的时候,返回的是NULL。

现在我们来将上一个例子中的字符串给转到原始的time_t:

struct tm res_tm;
char* format = "%a, %d %b %Y %H:%M:%S GMT";
strptime(time_str, format, &res_tm);
// 判断是否出错,此处略
time_t res_time = mktime(&res_tm);

通过上述步骤,我们就简单地将字符串转成了time_t,很方便,不是吗?

0x04 local和gm的转换

最后一部分,来点干货,谈谈local和gm之间的转换,这部分需求的解决方案可不好找呐。

造成local和gm时间不一致的原始是因为时区,因此,起初我想总有函数是处理时区offset的吧,但是很遗憾,没找到。

然后,我在tm的结构中发现了tm_gmtoff,然后尝试通过设置这个值来转换,但是这并没有什么卵用。。。

走投无路了,干脆自己来处理偏移吧,思想是同一个time_t能够分别得到gm和local,且tm能够转换成time_t,那么我们就能够精确地得到这个偏移。

如下为取得这个偏移的代码:

time_t local = time_t(NULL);
struct tm gmtm;
gmtime(&local, &gmtm);
time_t gmt = mktime(&gmtm);
const time_t OFFSET = gmt - local;
说两句: