不同的国家(有时甚至是同一国家内的不同地区)使用不同的时区和夏时制。对于要输入和输出时间的程序来说,必须对系统所处的时区和夏时制加以考虑。所幸的是,所有这些细节都已经由 C 语言函数库包办了
时区定义
时区信息往往是既浩繁又多变的。出于这一原因,系统没有将其直接编码于程序会在函数库中,而是以标准格保存于文件中,并加以维护。
这些文件位于目录/usr/share/zoneinfo
中。该目录下的每个文件都包含了一个特定国家或地区内时区制度的相关信息,且往往根据其所描述的时区来加以命名,诸如 EST(美国东部标准时间)、CET(欧洲中部时间)、UTC、Turkey 和 Iran。此外,可以利用子目录对相关时区进行有层次的分组。例如,Pacific 目录就可能包含文件 Auckland、Port_Moresby 和 Galapagos。在程序中指定使用的时区,实际上是指定该目录下某一时区文件的相对路径名。
系统的本地时间由时区文件/etc/localtime
定义,通常链接到/usr/share/zoneinfo
下的一个文件。
时区文件的格式记述于 tzfile(5)手册页,其创建可通过 zic(8)(时区信息编译器,zoone information compiler)工具来完成。zdump(8)命令可根据指定时区文件中的时区来显示当前时间。
为程序指定时区
为运行中的程序指定一个时区,需要将 TZ 环境变量设置为由一冒号(:)和时区名称组成的字符串,其中时区名称定义于/usr/share/zoneinfo 中。设置时区会自动影响到函数 ctime()、localtime()、mktime()和 strftime()
NAME
tzset, tzname, timezone, daylight - 初始化时间转换信息
SYNOPSIS
#include <time.h>
void tzset (void);
extern char *tzname[2];
extern long timezone;
extern int daylight;
glibc的功能测试宏要求 (see feature_test_macros(7)):
tzset(): _POSIX_C_SOURCE >= 1 || _XOPEN_SOURCE || _POSIX_SOURCE
tzname: _POSIX_C_SOURCE >= 1 || _XOPEN_SOURCE || _POSIX_SOURCE
timezone: _SVID_SOURCE || _XOPEN_SOURCE
daylight: _SVID_SOURCE || _XOPEN_SOURCE
DESCRIPTION
tzset() 函数从TZ环境变量初始化tzname变量。 取决于时区的其他时间转换函数会自动调用此函数。 在类似于System-V的环境中,
它还将变量timezone(UTC以西的秒数)和daylight (如果此时区没有任何夏令时规则,则为0;如果一年中有某个时间应用夏令时,
则为非零)。
如果在环境中未出现TZ变量,则使用系统时区目录中的tzfile(5)格式文件localtime指定的最佳本地壁钟时间初始化tzname变量
(请参见下文)。 (通常还会在这里看到/ etc / localtime,这是指向系统时区目录中正确文件的符号链接。)
如果TZ变量确实出现在环境中,但其值为空或无法使用以下指定的任何格式解释其值,则使用UTC。
TZ的值可以是三种格式之一。 如果当地时区中没有夏令时,则使用第一种格式:
std offset
std字符串指定时区的名称,并且必须是三个或更多字母字符。 偏移量字符串紧接在std之后,并指定要添加到本地时间以获得UTC的时间值。
如果当地时区在本初子午线以西,则偏移为正;如果当地时区在东,则偏移为负。 小时必须在0到24之间,分钟和秒在0到59之间
为了获取当前的时区设置,上述函数都会调用tzset(3),对下面三个全局变量进行初始化:
char *tzname[2];
int daylight;
long timezone;
函数 tzset()会首先检查环境变量 TZ:
- 如果尚未设置该变量,那么就采用/etc/localtime 中定义的默认时区来初始化时区。
- 如果 TZ 环境变量的值为空,或无法与时区文件名相匹配,那么就使用 UTC。
- 还可将 TZDIR 环境变量(非标准的 GNU 扩展)设置为搜寻时区信息的目录名称,以替代默认的/usr/share/zoneinfo 目录。
可以通过下面程序来观察 TZ 变量的影响力
#include <time.h>
#include <locale.h>
#define BUF_SIZE 200
int
main(int argc, char *argv[])
{
time_t t;
struct tm *loc;
char buf[BUF_SIZE];
if (setlocale(LC_ALL, "") == NULL){
perror("setlocale"); /* Use locale settings in conversions */
exit(EXIT_FAILURE);
}
t = time(NULL);
printf("ctime() of time() value is: %s", ctime(&t));
loc = localtime(&t);
if (loc == NULL){
perror("localtime");
exit(EXIT_FAILURE);
}
printf("asctime() of local time is: %s", asctime(loc));
if (strftime(buf, BUF_SIZE, "%A, %d %B %Y, %H:%M:%S %Z", loc) == 0){
perror("strftime");
exit(EXIT_FAILURE);
}
printf("strftime() of local time is: %s\n", buf);
exit(EXIT_SUCCESS);
}
SUSv3 为设置 TZ 环境变量定义了两个通用方法。
- 如前所述,可将 TZ 设置为由冒号外加字符串组成的字符序列,其中的字符串用以标识时区,并随系统实现的不同而不同,通常为时区描述文件的路径名。(在采用这种形式时,Linux 和其他一些 UNIX 实现允许将冒号省略,但 SUSv3 并未规范这一行为。为了保证代码的可移植性,应当始终包含冒号。
- 设置 TZ 的另一种方法在 SUSv3 中有完整的定义。使用此方法,可以将如下形式的字符串赋给 TZ
- std 和 dst 部分是用以标识标准和 DST 时区名称的字符串。例如,CET 和 CEST 分别为欧洲中部时间和欧洲中部夏令时间。各种情况下的 offset 分别表示欲转换为 UTC,需要叠加在本地时间上的正、负调整值。最后四部分则提供了一个规则,描述何时从标准时间变更为夏令时
- 可以多种格式指定 date,其中之一是 Mm.n.d,意即:m(1~12)月中,第 n(1~5,每月的最后 d 天总为第 5 周)周,星期 d(0=星期一,6=星期天)。如果省略 time,则无论何种情况下均默认为 02:00:00(上午 2 点)。
为了便于阅读,在上面这行字符串中加入了空格,但实际上任何空格都不应出现在 TZ 中。方括号([])用来表示可选项
以下将 TZ 定义为 Central Europe ,该时区的标准时间比 UTC 提前 1 小时,且 DST 始于3 月的最后一个星期日,直至 10 月的最后一个星期日结束,提前 UTC 2 小时
此处省略了对 DST 转换时间的指定,因为默认其发生于 02:00:00。显然,较之于如下的Linux 专有格式,上述形式的确缺乏可读性