前面一直在讲tzset似乎只在初始化的时候才生效,当tzset调用多次时没用。
今天稍微深入的查找了glibc中的tzset的实现,发现其实不是这么回事,其真正的实现是每次都会去解析的。为什么之前会误认为tzset无效,是由于本地进程的环境变量和全局环境变量导致的。
具体的tzset的实现是tzset_internal,在这个函数中,其基本思路是:
1、首先检查环境变量中的TZ环境变量是否存在、有效
2、根据TZ环境变量获取指定的时区文件的路径和文件名
3、保存当前使用的这个环境变量TZ
4、尝试去读取数据文件,这里的数据文件即时区文件tzfile,注意其数据是按固定格式来的。
5、在上述的step4中,会根据tzfile文件,去刷新timezone和daylight的。确定解析tzfile文件成功之后,还会将一个名为__use_tzfile的变量置为1
6、根据上述解析的结果,根据 __use_tzfile是否为1来确定是否需要继续,若没有解析tzfile成功,则判断tz是否有效,无效则刷新tzname的值,认为默认使用UTC时间。
7、若上述操作过程中,发现tzfile解析失效,而tz是有效的,则需要最后去解析tz环境变量,这种情况下tz环境变量是是有时间的,其时间是用来设置夏冬令时的,即daylight saving time的。
上述解析的过程中,可以看到其实也是与man手册的说明是一致的。
手册中,已经明确说明了TZ环境变量的三种格式
(1)在本地时区中没有夏冬令时则可使用如下格式:std offset
std表示的时区的名字,是三个字母 表示的。而offset字符串则表示需要加上(或者减去)多少时间才能恢复成UTC时间,小时是0~24,分钟是0~59
(2)第二种格式是用作夏冬令时的,其格式类型是:std offset dst [offset].start[/time].end[/time ]
注意在上述的格式中是没有空格的。初始的std和offset指明的是标准时区。dst和offset指明的是相对应的夏冬令时时区的时区名称和偏移。如果offset被忽略掉了他默认是标准时间的前一个小时。
start字段标明了夏冬令时开始有效的时刻,而end字段则表明了何时将这种修改恢复成标准时间。
这些字段可能有如下两种格式:
Jn 标记的是Julian天用n表示1~365。2月29不会计入天数,即使是在闰年
n 标明的是Julian天用n表示0~365。在闰年中2月29会计入天数。
Mm.w.d 指明的是m(1~12)月中的第w(1~5)个星期的第d(0~6)天。0天表示是星期天
time字段表明的是何时本地时间开始生效更改为另一个时间。如果忽略没有了的话,则默认是02:00:00
例如标准的新西兰时间是NZST,是UTC之前的12个小时,而夏冬令时时间是NZDT,UTC时间之前的13个小时,生效间隔是从十月的第一个星期天,到三月的地三个星期天,而这个修改时间发生在默认的02:00:00
(3)第三种格式,用来给定读取的时区文件的文件信息: :[filespec]
如果上述的文件描述信息filespec被忽略掉的话,读取时区信息的文件将是系统时区目录下的 localtime文件,而这个目录现在一般是/usr/share/zoneinfo
.其文件格式是tzfile格式。filespec不会以/开头的,而且文件描述信息是以系统时区目录为相对路径的,例如,仍然以新西兰举例:
TZ=“:Pacific/Auckland”
现在了解了TZ环境变量的设置,以及tzset文件的实现原理。那么为什么之前利用export来更改TZ环境变量,然后调用tzset刷新时区信息无效呢?
(实际运行glibc中的tzset的测试例子却是有效的!!!!!)
原因在于:
export导出的环境变量,是session即会话的环境变量。
而putenv导出的环境变量,却是以进程为对象的环境变量。
根据上述例子测试,写了shell脚本(export调用导出)和程序(putenv)对比,发现结果果然是两者并不相互干扰,即使两个环境变量名是一样的!!!
结论是:
1、如果要设置时区,根据glibc的实现来看,最好还是用时区文件更改,或者建立链接到localtime的时区文件的方法,否则断电后之前修改肯定会丢失!
2、tzset的调用刷新来获取timezone实际上有效的,而且为了防止其他程序更改时区,如果所运行进程是时区敏感的,则务必要进行不间断的刷新才行。
3、注意环境变量TZ对时区信息的干扰,环境变量可以指定时区,也可以是指定时区文件。