linux时区的几个代码片段

这两天学习了Linux环境下的时区方面的东西。做一些小笔记,也包括代码方面。

一、时区名称

从查阅到的资料看,如果没有使用夏令时的话,时区名称形式为“stdoffset”,即时区名加上时间偏移,时间偏移为正数表示西几区,负数表示东几区。如我们国家使用“CST-8”,即东八区。Linux目录/usr/share/zoneinfo/存储着不同的国家/地区的时区信息文件。一般嵌入式设备如果空间有限,可以精简掉部分信息,比如统一使用目录/usr/share/zoneinfo/Etc/下的文件。注意,这个目录的文件都是GMT**形式。东八区为GMT-8,而不是常识中认为的GMT+8,因为GMT+8一般理解为GMT时间加上8小时。但如果设置时区为GMT+8,系统时间与实际时间就会相差16个小时。

二、代码片段

1、获取时区:

一种实现方法是使用date +%z获取时区偏移值,单位为小时,有正负数之分。这个值是正常认知,即正数表示实际时间比GMT多多少个小时。使用date +%Z可获取时区名称。
代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 获取时区
int get_timezone(int* timezone)
{
const char* date_cmd = "date +%z";
FILE *fp = NULL;
char timezone_info[16] = {0};
int tmp_time_zone = 0;

fp = popen(date_cmd, "r");
if (fp == NULL)
{
return -1;
}
fread(timezone_info, sizeof(timezone_info), 1, fp);
pclose(fp);

timezone_info[strlen(timezone_info) - 1] = '\0';

if (isdigit(timezone_info[1]) && isdigit(timezone_info[2]))
{
tmp_time_zone = (timezone_info[1] - '0') * 10 + (timezone_info[2] - '0');
if (timezone_info[0] == '-')
{
tmp_time_zone = - tmp_time_zone;
}
*timezone = tmp_time_zone;

return 0;
}
else
{
*timezone = 0;
return -1;
}
return 0;
}

另一种方式是使用timezone这个全局变量,这个变量存储的时间单位为秒,正负数同上第一节分析时区名称含义相同。负数表示东几区。
实现代码如下:

1
2
3
4
5
6
7
8
int get_timezone(int* timezone)
{
extern long timezone; // 时区,单位为秒,如GMT-8,为-28800秒

tzset(); // 必须执行
*tz = -(int)(timezone / 3600); // 注意这里的负号
return 0;
}

函数get_timezone返回的值的正负数经过转换后,方便程序计算。比如ONVIF设置的时间只有UTC而不是当地时间,所以将UTC时间加上这个函数返回的时区值即可算出当地时间。除了timezone外,还有tzname和daylight这两个全局变量。它们的使用示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
* @brief 获取时区名称、时间偏移值
*
* @param tz1[OUT] 时间名称0
* @param tz2[OUT] 时间名称1
* @param tz_seconds[OUT] 时间偏移值,单位为秒,如东八区,与GMT时间相差-28800秒
* @param daylight_saving[OUT] 夏令时 为0不使用,非0则使用
*
* @return 0: 成功 -1:失败
* @note 使用东八区测试,daylight_saving为1,不知是否正常

测试:
PST8PDT时区
rm /etc/localtime
ln -s /usr/share/zoneinfo/PST8PDT /etc/localtime
tzname[0]:PST
tzname[1]: PDT
timezone: 28800 (正数表示西几区)
*/
void get_tz_name(char* tz1, char* tz2, long* tz_seconds, int* daylight_saving)
{
extern char *tzname[2];
extern long timezone; // 时区,单位为秒,如-8区,为-28800秒
extern int daylight;
tzset(); // 必须执行

strcpy(tz1, tzname[0]);
strcpy(tz2, tzname[1]);
*tz_seconds = timezone;
*daylight_saving = daylight;
}

2、时区名称和时区值转换:

ONVIF使用的时区名称是posix标准,但要从中知道是哪一个时区,以便对应于GMT**的形式。转换代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
/** 
* @brief 时区名称转换为时区数值
*
* @param tzname[IN] 时区名称,posix标准,如东八区时区为GMT-8,西八区为GMT+8,西八区也可以用PST8PDT表示
*
* @return -12~12: 成功 1024:失败
* @note 这里返回的时区数值是为了程序方便计算,比如返回8表示是东八区时间(与上述的时区名称不同),
* 这样在GMT时间加上8小时就是当地时间,其它同理
*/
int convert_tzname2tz(const char* tzname)
{
#define IS_TZ(foo) (!strncmp(tzname, foo, strlen(foo)))


// "GMTxxx" first: GMT0/GMT+0/GMT-0 GMT-8 GMT-08
// CST
if (IS_TZ("GMT") || IS_TZ("CST"))
{
char *endptr;
long val = strtol(&tzname[3], &endptr, 10);
if (val >= -12 && val <= 12) return -val;
else return 1024;
}
else if (IS_TZ("IDLW"))
{
return -12;
}
else if (IS_TZ("NT")||IS_TZ("NUT")||IS_TZ("SST"))
{
return -11;
}
else if (IS_TZ("AHST") || IS_TZ("CAT")||IS_TZ("HST")||IS_TZ("HDT")||IS_TZ("TAHT"))
{
return -10;
}
else if (IS_TZ("YST")||IS_TZ("YDT")||IS_TZ("GAMT"))
{
return -9;
}
else if (IS_TZ("PST")||IS_TZ("PDT"))
{
return -8;
}
else if (IS_TZ("MST")||IS_TZ("MDT"))
{
return -7;
}
else if (IS_TZ("CDT"))
{
return -6;
}
else if (IS_TZ("EST")||IS_TZ("EDT")||IS_TZ("EAST"))
{
return -5;
}
else if (IS_TZ("AST")||IS_TZ("ADT")||IS_TZ("GYT")||IS_TZ("WART"))
{
return -4;
}
else if (IS_TZ("CLT")||IS_TZ("BRT")||IS_TZ("PMST")||IS_TZ("WGT"))
{
return -3;
}
else if (IS_TZ("AT")||IS_TZ("GST"))
{
return -2;
}
else if (IS_TZ("WAT"))
{
return -1;
}
else if (IS_TZ("Zulu")||IS_TZ("UT")||IS_TZ("UTC")||IS_TZ("UCT")|| IS_TZ("Greenwich")||IS_TZ("BST"))
{
return 0;
}
else if (IS_TZ("CET")||IS_TZ("FWT")||IS_TZ("MET")||IS_TZ("MEWT")
||IS_TZ("SWT")||IS_TZ("MEST")||IS_TZ("MESZ")||IS_TZ("SST")
||IS_TZ("FST"))
{
return 1;
}
else if (IS_TZ("EET")||IS_TZ("IST"))
{
return 2;
}
else if (IS_TZ("BT")||IS_TZ("MSK"))
{
return 3;
}
else if (IS_TZ("ZP4"))
{
return 4;
}
else if (IS_TZ("ZP5"))
{
return 5;
}
else if (IS_TZ("ZP6"))
{
return 6;
}
else if (IS_TZ("ZP7"))
{
return 7;
}
else if (IS_TZ("WAST")||IS_TZ("HKT")||IS_TZ("SGT"))
{
return 8;
}
else if (IS_TZ("JST"))
{
return 9;
}
else if (IS_TZ("ACT"))
{
return 10;
}
else if (IS_TZ("EAST"))
{
return 11;
}
else if (IS_TZ("IDLE"))
{
return 12;
}
else
{
return 1024;
}
return 1024;
}

从时区数值转换为时区名称代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/*
g_tz_name是时区名称,请到 /usr/share/zoneinfo/Etc/目录查看文件。
数值前面正数表示时区位于本初子午线之西,负数表示时区位于本初子午线之东。
如不明白,请在linux环境执行命令“man tzset”阅读文档。
*/
const char* g_tz_name[25] = {
"GMT+12",
"GMT+11",
"GMT+10",
"GMT+9",
"GMT+8",
"GMT+7",
"GMT+6",
"GMT+5",
"GMT+4",
"GMT+3",
"GMT+2",
"GMT+1",
"GMT",
"GMT-1",
"GMT-2",
"GMT-3",
"GMT-4",
"GMT-5",
"GMT-6",
"GMT-7",
"GMT-8",
"GMT-9",
"GMT-10",
"GMT-11",
"GMT-12",
};


/**
* @brief 返回时区名称
*
* @param timezone[IN] 时区数值,如东八区为+8,西八区为-8
*
* @return 时区名称
*/
const char* get_timezone_info(int timezone)
{
return g_tz_name[timezone + 12];
}

李迟 2016.01.23 周六 今天特冷