越南时区中,erlang中unix时间戳和日期转换问题

环境

系统: CENTOS 7
语言: ERLANG OTP 17.5.3
时区: Asia/Ho_Chi_Minh

问题

先来看一段代码

1
2
3
4
5
6
7
8
9
10
11
%% util.erl

-define(SECONDS_FROM_0_TO_1970, 62167219200).

date_to_time({{_Y, _M, _D}, {_H, _Mi, _S}} = Date) ->
calendar:datetime_to_gregorian_seconds(Date) -
calendar:datetime_to_gregorian_seconds(calendar:universal_time_to_local_time({{1970,1,1}, {0,0,0}})).

seconds_to_localtime(Seconds) ->
DateTime = calendar:gregorian_seconds_to_datetime(Seconds+?SECONDS_FROM_0_TO_1970),
calendar:universal_time_to_local_time(DateTime).

这是日期和unix时间戳互转的代码,貌似没错,运行大部分时间也没错
但是修改为Asia/Ho_Chi_Minh(东七区)(cp -f /usr/share/zoneinfo/Asia/Ho_Chi_Minh /etc/localtime)

查看时区也确实是+0700

1
2
[root@localhost ~]# date -R
Mon, 22 May 2017 19:02:21 +0700

1
2
1> util:seconds_to_localtime(0).
{{1970,1,1},{8,0,0}}

原因

经排查,发现calendar:universal_time_to_local_time/1存在问题

1
2
3
4
39> calendar:universal_time_to_local_time({{1970,1,1},{0,0,0}}).
{{1970,1,1},{8,0,0}}

%% 这里应该是{7,0,0}

解决方案

手动进行时区修正

1
2
3
4
5
6
Now = erlang:now(),
LocalTime = calendar:now_to_local_time(Now),
UTCTime = calendar:now_to_universal_time(Now),

calendar:datetime_to_gregorian_seconds(LocalTime) -
calendar:datetime_to_gregorian_seconds(UTCTime).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
%% util.erl

-define(SECONDS_FROM_0_TO_1970, 62167219200).

date_to_time({{_Y, _M, _D}, {_H, _Mi, _S}} = Date) ->
Now = erlang:now(),
LocalTime = calendar:now_to_local_time(Now),
UTCTime = calendar:now_to_universal_time(Now),

calendar:datetime_to_gregorian_seconds(Date) -
calendar:datetime_to_gregorian_seconds({{1970,1,1}, {0,0,0}}) -
%% 时区修正
calendar:datetime_to_gregorian_seconds(LocalTime) +
calendar:datetime_to_gregorian_seconds(UTCTime).

seconds_to_localtime(Seconds) ->
Now = erlang:now(),
LocalTime = calendar:now_to_local_time(Now),
UTCTime = calendar:now_to_universal_time(Now),

Second1 = Seconds + ?SECONDS_FROM_0_TO_1970 +
%% 时区修正
calendar:datetime_to_gregorian_seconds(LocalTime) - calendar:datetime_to_gregorian_seconds(UTCTime),
calendar:gregorian_seconds_to_datetime(Second1).