Glibc mktime函数时区信息分析
source link: http://just4coding.com/2022/09/24/mktime/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Glibc mktime函数时区信息分析
2022-09-24 MISC
在程序中, 时间一般有两种表示方法:
UNIX
时间戳(UNIX timestamp
): 表示的是从UTC
时间1970年1月1日0时0分0秒起至现在的总秒数, 它也叫做epoch
,UNIX
时间,POSIX
时间等等。在同一时刻,全球所有地方的UNIX
时间戳都相同。本地时间: 是以人类可读的格式表示的时间,比如
2022-9-24 00:00:00
。由于时区
概念的存在,在同一UNIX
时间戳所表示的时间点,各时区的本地时间是不同的,如下图:
CST(China Standard Time)
时区中时间为2022-09-24 00:00:00
的时刻,UTC
时间为2022-09-23 16:00:00
。
支持国际化的程序往往将时间值存储为UNIX
时间戳, 使用时在两种格式之间进行转换。
C
语言程序可以使用glibc
的mktime
和localtime
函数在两种格式之间进行转换。timelocal
和mktime
功能一致。
mktime
是将本地时间转换为UNIX
时间戳,所以它所返回的时间戳是和本地时区
有关系的。
下面看一下mktime
的具体实现是如何使用时区
信息的。
以下源码来源于CentOS 7.8.2003
的glibc-2.17-307.el7.1.src.rpm
。
mktime
的实现位于glibc
源码的time/mktime.c
文件中:
/* Convert *TP to a time_t value. */
time_t
mktime (struct tm *tp)
{
#ifdef _LIBC
/* POSIX.1 8.1.1 requires that whenever mktime() is called, the
time zone names contained in the external variable 'tzname' shall
be set as if the tzset() function had been called. */
__tzset ();
#endif
return __mktime_internal (tp, __localtime_r, &localtime_offset);
}
#ifdef weak_alias
weak_alias (mktime, timelocal)
#endif
可以看到,mktime
会自动调用__tzset
来处理时区信息。
从代码中也可以看到timelocal
就是mktime
的弱引用,是相同的函数。
__tzset
实现位于time/tzset.c
:
void
__tzset (void)
{
__libc_lock_lock (tzset_lock);
tzset_internal (1, 1);
if (!__use_tzfile)
{
/* Set `tzname'. */
__tzname[0] = (char *) tz_rules[0].name;
__tzname[1] = (char *) tz_rules[1].name;
}
__libc_lock_unlock (tzset_lock);
}
weak_alias (__tzset, tzset)
函数调用tzset_internal
来处理TZ
环境变量:
/* Interpret the TZ envariable. */
static void
internal_function
tzset_internal (int always, int explicit)
{
static int is_initialized;
const char *tz;
if (is_initialized && !always)
return;
is_initialized = 1;
/* Examine the TZ environment variable. */
tz = getenv ("TZ");
if (tz == NULL && !explicit)
/* Use the site-wide default. This is a file name which means we
would not see changes to the file if we compare only the file
name for change. We want to notice file changes if tzset() has
been called explicitly. Leave TZ as NULL in this case. */
tz = TZDEFAULT;
if (tz && *tz == '\0')
/* User specified the empty string; use UTC explicitly. */
tz = "Universal";
/* A leading colon means "implementation defined syntax".
We ignore the colon and always use the same algorithm:
try a data file, and if none exists parse the 1003.1 syntax. */
if (tz && *tz == ':')
++tz;
/* Check whether the value changed since the last run. */
if (old_tz != NULL && tz != NULL && strcmp (tz, old_tz) == 0)
/* No change, simply return. */
return;
if (tz == NULL)
/* No user specification; use the site-wide default. */
tz = TZDEFAULT;
tz_rules[0].name = NULL;
tz_rules[1].name = NULL;
/* Save the value of `tz'. */
free (old_tz);
old_tz = tz ? __strdup (tz) : NULL;
/* Try to read a data file. */
__tzfile_read (tz, 0, NULL);
if (__use_tzfile)
return;
/* No data file found. Default to UTC if nothing specified. */
if (tz == NULL || *tz == '\0'
|| (TZDEFAULT != NULL && strcmp (tz, TZDEFAULT) == 0))
{
memset (tz_rules, '\0', sizeof tz_rules);
tz_rules[0].name = tz_rules[1].name = "UTC";
if (J0 != 0)
tz_rules[0].type = tz_rules[1].type = J0;
tz_rules[0].change = tz_rules[1].change = (time_t) -1;
update_vars ();
return;
}
__tzset_parse_tz (tz);
}
当TZ
环境变量的值为空(""
)时,使用Universal
时区文件,表示UTC
时间。
TZ
环境变量支持的格式参考:
如果没有指定TZ
变量,则使用TZDEFAULT
。
TZDEFAULT
在timezone/tzfile.h
中定义为localtime
:
#ifndef TZDEFAULT
#define TZDEFAULT "localtime"
#endif /* !defined TZDEFAULT */
接着,调用__tzfile_read
尝试去解析时区文件。
__tzfile_read
定义在time/tzfile.c
中:
static const char default_tzdir[] = TZDIR;
......
if (*file != '/')
{
const char *tzdir;
tzdir = getenv ("TZDIR");
if (tzdir == NULL || *tzdir == '\0')
tzdir = default_tzdir;
if (__asprintf (&new, "%s/%s", tzdir, file) == -1)
goto ret_free_transitions;
file = new;
}
当环境变量TZ
值不是绝对路径地址,尝试使用环境变量TZDIR
的值作为时区文件目录。如果没有值,则使用宏TZDIR
。
它定义在timezone/tzfile.h
:
#ifndef TZDIR
#define TZDIR "/usr/local/etc/zoneinfo" /* Time zone object file directory */
#endif /* !defined TZDIR */
但实际上最终的目录不是这里,源码time/Makefile
中指定了宏定义:
tz-cflags = -DTZDIR='"$(zonedir)"' \
-DTZDEFAULT='"$(localtime-file)"' \
-DTZDEFRULES='"$(posixrules-file)"'
而zonedir
定义在Makeconfig
:
# Where to install the timezone data files (which are machine-independent).
ifndef zonedir
zonedir = $(datadir)/zoneinfo
endif
inst_zonedir = $(install_root)$(zonedir)
# Where to install machine-independent data files.
# These are the timezone database, and the locale database.
ifndef datadir
datadir = $(prefix)/share
endif
prefix
是由glibc.spec
,configure
,make
一路传递。
在glibc.spec
中使用的是%{_prefix}
, 它的默认值为/usr
, 因而默认的时区文件目录为/usr/share/zoneinfo
。
我的机器本地时区是CST
:
[root@default ~]# timedatectl
Local time: Sat 2022-09-24 16:04:11 CST
Universal time: Sat 2022-09-24 08:04:11 UTC
RTC time: Sat 2022-09-24 08:04:09
Time zone: Asia/Shanghai (CST, +0800)
NTP enabled: yes
NTP synchronized: yes
RTC in local TZ: no
DST active: n/a
CST
时间2022-09-24 16:00:00
的时间戳为1664006400
。
如果想要转换非本地时区的时间,可以在程序中设置TZ
环境变量,如:
#include <stdio.h>
#include <time.h>
#include <string.h>
int main(int argc, char *argv[]) {
struct tm tm;
time_t t1;
if (argc > 1) {
setenv("TZ", argv[1], 1);
}
tm.tm_year = 2022 - 1900;
tm.tm_mon = 9 - 1;
tm.tm_mday = 24;
tm.tm_hour = 16;
tm.tm_min = 0;
tm.tm_sec = 0;
t1 = mktime(&tm);
printf("timestamp: %d\n", t1);
return 0;
}
执行结果:
[root@default ~]# ./a.out
timestamp: 1664006400
[root@default ~]# ./a.out 'Asia/Tokyo'
timestamp: 1664002800
可以看到当把时区设置为东京时区,时间戳减少了3600
,因为东京时区是东九区,而北京时间是东八区。
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK