[Erlang 0059] Erlang日期与时间处理
在开发过程中,有两个概念是和地区区域相关的:字符编码和时间;编码和时间的规范演变过程中有文化的冲突有历史的遗留,是软件开发中充满人文气息的一角;关于字符编码我之前整理过一篇文章, [Erlang 0024]Erlang二进制数据处理 这部分知识很有意思,特别是格列佛游记所引出的大端小端概念,妙趣横生;平时笔记中也零零散散记录了一些和时间处理相关的内容,今天按图索骥把相关的资料整理汇集于此.
有关时间的概念
理论上来说,格林尼治标准时间的正午是指当太阳横穿格林尼治子午线时的时间.由于地球在它的椭圆轨道里的运动速度不均匀,这个时刻可能与实际的太阳时有误差,最大误差达16分钟.由于地球每天的自转是有些不规则的,而且正在缓慢减速,因此格林尼治时间已经不再被作为标准时间使用.现在的标准时间,是由原子钟报时的协调世界时(UTC).
理论时区以被15整除的子午线为中心,向东西两侧延伸7.5度,即每15°划分一个时区,这是理论时区.理论时区的时间采用其中央经线(或标准经线)的地方时.所以每差一个时区,区时相差一个小时,相差多少个时区,就相差多少个小时.东边的时区比西边的时区时间来得早.为了避免日期的紊乱,提出国际日期变更线的概念。为了避开国界线,有的时区的形状并不规则,而且比较大的国家以国家内部行政分界线为时区界线,这是实际时区,即法定时区.
Erlang Calendar
erlang:now() Returns the tuple {MegaSecs, Secs, MicroSecs} which is the elapsed time since 00:00 GMT, January 1, 1970 (zero hour) on the assumption that the underlying OS supports this. Otherwise, some other point in time is chosen. It is also guaranteed that subsequent calls to this BIF returns continuously increasing values. Hence, the return value from now() can be used to generate unique time-stamps, and if it is called in a tight loop on a fast machine the time of the node can become skewed.
It can only be used to check the local time of day if the time-zone info of the underlying operating system is properly configured.
• there are 365 days in an ordinary year
• there are 366 days in a leap year
• there are 1461 days in a 4 year period
• there are 36524 days in a 100 year period
• there are 146097 days in a 400 year period
• there are 719528 days between Jan 1, 0 and Jan 1, 1970.
• Y is divisible by 400.
常用代码
{{2012,5,17},{14,32,6}}
{{2012,5,17},{6,33,2}}
17>
false
21> calendar:valid_date({0,1,0}).
false
22> calendar:valid_date({0,1,1}).
{{2012,5,17},{14,13,14}}
2> b().
Day = 17
Hour = 14
Min = 13
Month = 5
Second = 14
Year = 2012
ok
{{2012,5,17},{6,16,27}}
[{{2012,3,11},{19,23,12}}]
11> calendar:local_time_to_universal_time_dst({{2012,3,12},{4,23,12}}).
[{{2012,3,11},{20,23,12}}]
12> calendar:local_time_to_universal_time_dst({{2012,3,12},{5,23,12}}).
[{{2012,3,11},{21,23,12}}]
13> calendar:local_time_to_universal_time_dst({{2012,3,12},{15,23,12}}).
[{{2012,3,12},{7,23,12}}]
14> calendar:local_time_to_universal_time_dst({{2012,3,12},{16,23,12}}).
[{{2012,3,12},{8,23,12}}]
{{2012,3,12},{11,23,12}}
13> calendar:universal_time_to_local_time({{2012,3,12},{5,23,12}}).
{{2012,3,12},{13,23,12}}
14> calendar:universal_time_to_local_time({{2012,3,12},{20,23,12}}).
{{2012,3,13},{4,23,12}}
2
14> calendar:day_of_the_week(2012,5,17).
4
false
6> calendar:is_leap_year(2000).
true
29
8> calendar:last_day_of_the_month(2000,3).
31
9> calendar:last_day_of_the_month(1990,2).
28
{{2012,5,17},{5,41,24}}
5> calendar:seconds_to_daystime(87400).
{1,{0,16,40}}
6> calendar:seconds_to_daystime(97400).
{1,{3,3,20}}
7> calendar:seconds_to_daystime(80400).
{0,{22,20,0}}
{22,20,0}
9> calendar:seconds_to_time(86400).
** exception error: no function clause matching calendar:seconds_to_time(86400) (calendar.erl, line 357)
80400
11> calendar:time_to_seconds({1,{22,20,0}}).
** exception error: no function clause matching calendar:time_to_seconds({1,{22,20,0}}) (calendar.erl, line 390)
calendar:datetime_to_gregorian_seconds(erlang:universaltime()).
{M, S, _} = erlang:now(),
M * 1000000 + S.
"2012-05-17T16:14:42.195510+08:00"
17> test:now_to_utc_string(erlang:now()).
"2012-05-17T08:15:26.907466Z"
18> test:timestamp_to_iso(calendar:local_time()).
"20120517T16:27:18"
timestamp_to_iso({{Year, Month, Day}, {Hour, Minute, Second}}) -> lists:flatten( io_lib:format("~4..0w~2..0w~2..0wT~2..0w:~2..0w:~2..0w", [Year, Month, Day, Hour, Minute, Second])). now_to_utc_string({MegaSecs, Secs, MicroSecs}) -> {{Year, Month, Day}, {Hour, Minute, Second}} = calendar:now_to_universal_time({MegaSecs, Secs, MicroSecs}), lists:flatten( io_lib:format("~4..0w-~2..0w-~2..0wT~2..0w:~2..0w:~2..0w.~6..0wZ", [Year, Month, Day, Hour, Minute, Second, MicroSecs])). now_to_local_string({MegaSecs, Secs, MicroSecs}) -> LocalTime = calendar:now_to_local_time({MegaSecs, Secs, MicroSecs}), UTCTime = calendar:now_to_universal_time({MegaSecs, Secs, MicroSecs}), Seconds = calendar:datetime_to_gregorian_seconds(LocalTime) - calendar:datetime_to_gregorian_seconds(UTCTime), {{H, M, _}, Sign} = if Seconds < 0 -> {calendar:seconds_to_time(-Seconds), "-"}; true -> {calendar:seconds_to_time(Seconds), "+"} end, {{Year, Month, Day}, {Hour, Minute, Second}} = LocalTime, lists:flatten( io_lib:format("~4..0w-~2..0w-~2..0wT~2..0w:~2..0w:~2..0w.~6..0w~s~2..0w:~2..0w", [Year, Month, Day, Hour, Minute, Second, MicroSecs, Sign, H, M])). % yyyy-mm-ddThh:mm:ss[.sss]{Z|{+|-}hh:mm} -> {MegaSecs, Secs, MicroSecs} datetime_string_to_timestamp(TimeStr) -> case catch parse_datetime(TimeStr) of {'EXIT', _Err} -> undefined; TimeStamp -> TimeStamp end. parse_datetime(TimeStr) -> [Date, Time] = string:tokens(TimeStr, "T"), D = parse_date(Date), {T, MS, TZH, TZM} = parse_time(Time), S = calendar:datetime_to_gregorian_seconds({D, T}), S1 = calendar:datetime_to_gregorian_seconds({{1970, 1, 1}, {0, 0, 0}}), Seconds = (S - S1) - TZH * 60 * 60 - TZM * 60, {Seconds div 1000000, Seconds rem 1000000, MS}. % yyyy-mm-dd parse_date(Date) -> [Y, M, D] = string:tokens(Date, "-"), Date1 = {list_to_integer(Y), list_to_integer(M), list_to_integer(D)}, case calendar:valid_date(Date1) of true -> Date1; _ -> false end. % hh:mm:ss[.sss]TZD parse_time(Time) -> case string:str(Time, "Z") of 0 -> parse_time_with_timezone(Time); _ -> [T | _] = string:tokens(Time, "Z"), {TT, MS} = parse_time1(T), {TT, MS, 0, 0} end. parse_time_with_timezone(Time) -> case string:str(Time, "+") of 0 -> case string:str(Time, "-") of 0 -> false; _ -> parse_time_with_timezone(Time, "-") end; _ -> parse_time_with_timezone(Time, "+") end. parse_time_with_timezone(Time, Delim) -> [T, TZ] = string:tokens(Time, Delim), {TZH, TZM} = parse_timezone(TZ), {TT, MS} = parse_time1(T), case Delim of "-" -> {TT, MS, -TZH, -TZM}; "+" -> {TT, MS, TZH, TZM} end. parse_timezone(TZ) -> [H, M] = string:tokens(TZ, ":"), {[H1, M1], true} = check_list([{H, 12}, {M, 60}]), {H1, M1}. parse_time1(Time) -> [HMS | T] = string:tokens(Time, "."), MS = case T of [] -> 0; [Val] -> list_to_integer(string:left(Val, 6, $0)) end, [H, M, S] = string:tokens(HMS, ":"), {[H1, M1, S1], true} = check_list([{H, 24}, {M, 60}, {S, 60}]), {{H1, M1, S1}, MS}. check_list(List) -> lists:mapfoldl( fun({L, N}, B)-> V = list_to_integer(L), if (V >= 0) and (V =< N) -> {V, B}; true -> {false, false} end end, true, List).
构造日期字符串
% a function to format date/time properly (e.g. 09 instead of 9) return_2columns(X) -> case length(X) of 1 -> "0" ++ X; _ -> X end. %%% 显然这里可以直接使用 io_lib:format("~2..0B", [X]) % returns date/time as a properly formatted string (e.g. "01-01-2000 12:12:12") get_current_time() -> {{Y, M, D}, {H, Mi, S}} = calendar:local_time(), L = lists:map(fun(X) -> X2=integer_to_list(X), return_2columns(X2) end, [Y, M, D, H, Mi, S] ), [Y2, M2, D2, H2, Mi2, S2] = L, Y2 ++ "-" ++ M2 ++ "-" ++ D2 ++ " " ++ H2 ++ ":" ++ Mi2 ++ ":" ++ S2.
当然下面的代码段也很有可能被用到:
day(1) -> "Mon"; day(2) -> "Tue"; day(3) -> "Wed"; day(4) -> "Thu"; day(5) -> "Fri"; day(6) -> "Sat"; day(7) -> "Sun". month_to_list(1) -> "Jan"; month_to_list(2) -> "Feb"; month_to_list(3) -> "Mar"; month_to_list(4) -> "Apr"; month_to_list(5) -> "May"; month_to_list(6) -> "Jun"; month_to_list(7) -> "Jul"; month_to_list(8) -> "Aug"; month_to_list(9) -> "Sep"; month_to_list(10) -> "Oct"; month_to_list(11) -> "Nov"; month_to_list(12) -> "Dec". list_to_month("Jan") -> 1; list_to_month("Feb") -> 2; list_to_month("Mar") -> 3; list_to_month("Apr") -> 4; list_to_month("May") -> 5; list_to_month("Jun") -> 6; list_to_month("Jul") -> 7; list_to_month("Aug") -> 8; list_to_month("Sep") -> 9; list_to_month("Oct") -> 10; list_to_month("Nov") -> 11; list_to_month("Dec") -> 12.
Note:天(Day)这个概念是和时区有关的,使用Unix时间戳计算的时候要考虑到时区差异,比如两个Unix时间戳是否在同一天,一天的起始时间的时间戳,等等;软件多语言版本的时候这个问题特别要关注
晚 安 !