%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 1997-2011. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. %% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. %% %% %CopyrightEnd% %% -module(time_SUITE). %% "Time is on my side." -- The Rolling Stones %% Tests the BIFs: %% erlang:localtime_to_universaltime/1 %% erlang:universaltime_to_localtime/1 %% date/0 %% time/0 %% now/0 %% -export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, init_per_group/2,end_per_group/2, univ_to_local/1, local_to_univ/1, bad_univ_to_local/1, bad_local_to_univ/1, consistency/1, now_unique/1, now_update/1, timestamp/1]). -export([local_to_univ_utc/1]). -include_lib("test_server/include/test_server.hrl"). -export([linear_time/1]). %% The following defines the timezone in which the test is run. %% It is interpreted as the number of hours to be added to UTC %% to obtain the local time. The number will be positive east %% of Greenwhich, negative west of Greenwhich. %% %% Allowable range is -12 through 11. -define(timezone, 1). %% Similarly to timezone, but the difference when Daylight Saving Time %% is in use. [Same range.] -define(dst_timezone, 2). suite() -> [{ct_hooks,[ts_install_cth]}]. all() -> [univ_to_local, local_to_univ, local_to_univ_utc, bad_univ_to_local, bad_local_to_univ, consistency, {group, now}, timestamp]. groups() -> [{now, [], [now_unique, now_update]}]. init_per_suite(Config) -> Config. end_per_suite(_Config) -> ok. init_per_group(_GroupName, Config) -> Config. end_per_group(_GroupName, Config) -> Config. local_to_univ_utc(suite) -> []; local_to_univ_utc(doc) -> ["Test that DST = true on timezones without DST is ignored"]; local_to_univ_utc(Config) when is_list(Config) -> case os:type() of {unix,_} -> %% TZ variable has a meaning ?line {ok, Node} = test_server:start_node(local_univ_utc,peer, [{args, "-env TZ UTC"}]), ?line {{2008,8,1},{0,0,0}} = rpc:call(Node, erlang,localtime_to_universaltime, [{{2008, 8, 1}, {0, 0, 0}}, false]), ?line {{2008,8,1},{0,0,0}} = rpc:call(Node, erlang,localtime_to_universaltime, [{{2008, 8, 1}, {0, 0, 0}}, true]), ?line [{{2008,8,1},{0,0,0}}] = rpc:call(Node, calendar,local_time_to_universal_time_dst, [{{2008, 8, 1}, {0, 0, 0}}]), ?line test_server:stop_node(Node), ok; _ -> {skip,"Only valid on Unix"} end. %% Tests conversion from univeral to local time. univ_to_local(Config) when is_list(Config) -> ?line test_univ_to_local(test_data()). test_univ_to_local([{Utc, Local}|Rest]) -> ?line io:format("Testing ~p => ~p~n", [Local, Utc]), ?line Local = erlang:universaltime_to_localtime(Utc), ?line test_univ_to_local(Rest); test_univ_to_local([]) -> ok. %% Tests conversion from local to universal time. local_to_univ(Config) when is_list(Config) -> ?line test_local_to_univ(test_data()). test_local_to_univ([{Utc, Local}|Rest]) -> ?line io:format("Testing ~p => ~p~n", [Utc, Local]), ?line Utc = erlang:localtime_to_universaltime(Local), ?line test_local_to_univ(Rest); test_local_to_univ([]) -> ok. %% Test bad arguments to erlang:universaltime_to_localtime; should %% generate a badarg. bad_univ_to_local(Config) when is_list(Config) -> ?line bad_test_univ_to_local(bad_dates()). bad_test_univ_to_local([Utc|Rest]) -> ?line io:format("Testing ~p~n", [Utc]), ?line case catch erlang:universaltime_to_localtime(Utc) of {'EXIT', {badarg, _}} -> bad_test_univ_to_local(Rest) end; bad_test_univ_to_local([]) -> ok. %% Test bad arguments to erlang:localtime_to_universaltime/1; should %% generate a badarg. bad_local_to_univ(Config) when is_list(Config) -> ?line bad_test_local_to_univ(bad_dates()). bad_test_local_to_univ([Local|Rest]) -> ?line io:format("Testing ~p~n", [Local]), ?line case catch erlang:localtime_to_universaltime(Local) of {'EXIT', {badarg, _}} -> bad_test_local_to_univ(Rest) end; bad_test_local_to_univ([]) -> ok. %% Test that the the different time functions return %% consistent results. (See the test case for assumptions %% and limitations.) consistency(Config) when is_list(Config) -> %% Test the following equations: %% date() & time() == erlang:localtime() %% erlang:universaltime() + timezone == erlang:localtime() %% %% Assumptions: %% Middle-European time zone, EU rules for daylight-saving time. %% %% Limitations: %% Localtime and universaltime must be in the same month. %% Daylight-saving calculations are incorrect from the last %% Sunday of March and October to the end of the month. ?line ok = compare_date_time_and_localtime(16), ?line ok = compare_local_and_universal(16). compare_date_time_and_localtime(Times) when Times > 0 -> ?line {Year, Mon, Day} = date(), ?line {Hour, Min, Sec} = time(), ?line case erlang:localtime() of {{Year, Mon, Day}, {Hour, Min, Sec}} -> ok; _ -> compare_date_time_and_localtime(Times-1) end; compare_date_time_and_localtime(0) -> error. compare_local_and_universal(Times) when Times > 0 -> case compare(erlang:universaltime(), erlang:localtime()) of true -> ok; false -> compare_local_and_universal(Times-1) end; compare_local_and_universal(0) -> error. compare(Utc0, Local) -> io:format("local = ~p, utc = ~p", [Local, Utc0]), Utc = linear_time(Utc0)+effective_timezone(Utc0)*3600, case linear_time(Local) of Utc -> true; Other -> io:format("Failed: local = ~p, utc = ~p~n", [Other, Utc]), false end. %% This function converts a date and time to a linear time. %% Two linear times can be subtracted to give their difference %% in seconds. %% %% XXX Limitations: The length of months and leap years are not %% taken into account; thus a comparision of dates is only %% valid if they are in the SAME month. linear_time({{Year, Mon, Day}, {Hour, Min, Sec}}) -> 86400*(366*Year + 31*(Mon-1) + (Day-1)) + 3600*Hour + 60*Min + Sec. %% This functions returns either the normal timezone or the %% the DST timezone, depending on the given UTC time. %% %% XXX This function uses an approximation of the EU rule for %% daylight saving time. This function will fail in the %% following intervals: After the last Sunday in March upto %% the end of March, and after the last Sunday in October %% upto the end of October. effective_timezone(Time) -> case os:type() of {unix,_} -> case os:cmd("date '+%Z'") of "SAST"++_ -> 2; _ -> effective_timezone1(Time) end; _ -> effective_timezone1(Time) end. effective_timezone1({{_Year,Mon,_Day}, _}) when Mon < 4 -> ?timezone; effective_timezone1({{_Year,Mon,_Day}, _}) when Mon > 10 -> ?timezone; effective_timezone1(_) -> ?dst_timezone. %% Test (the bif) os:timestamp/0, which is something quite like, but not %% similar to erlang:now... timestamp(suite) -> []; timestamp(doc) -> ["Test that os:timestamp works."]; timestamp(Config) when is_list(Config) -> repeating_timestamp_check(100000). repeating_timestamp_check(0) -> ok; repeating_timestamp_check(N) -> {A,B,C} = TS = os:timestamp(), if is_integer(A), is_integer(B), is_integer(C), B < 1000000, C < 1000000 -> ok; true -> test_server:fail( lists:flatten( io_lib:format("Strange return from os:timestamp/0 ~w~n",[TS]))) end, %% I assume the now and timestamp should not differ more than 1 hour, %% which is safe assuming the system has not had a large time-warp %% during the testrun... Secs = A*1000000+B+round(C/1000000), {NA,NB,NC} = erlang:now(), NSecs = NA*1000000+NB+round(NC/1000000), case Secs - NSecs of TooLarge when TooLarge > 3600 -> test_server:fail( lists:flatten( io_lib:format("os:timestamp/0 is ~w s more than erlang:now/0", [TooLarge]))); TooSmall when TooSmall < -3600 -> test_server:fail( lists:flatten( io_lib:format("os:timestamp/0 is ~w s less than erlang:now/0", [-TooSmall]))); _ -> ok end, repeating_timestamp_check(N-1). %% Test now/0. %% Tests that successive calls to now/0 returns different values. %% Also returns a comment string with the median difference between %% times (in microseconds). now_unique(Config) when is_list(Config) -> ?line now_unique(1000, now(), []), ?line fast_now_unique(100000, now()). now_unique(N, Previous, Result) when N > 0 -> ?line case now() of Previous -> test_server:fail("now/0 returned the same value twice"); New -> now_unique(N-1, New, [New|Result]) end; now_unique(0, _, [Then|Rest]) -> ?line now_calc_increment(Rest, microsecs(Then), []). now_calc_increment([Then|Rest], Previous, _Result) -> ?line This = microsecs(Then), ?line now_calc_increment(Rest, This, [Previous-This]); now_calc_increment([], _, Differences) -> {comment, "Median increment: " ++ integer_to_list(median(Differences))}. fast_now_unique(0, _) -> ok; fast_now_unique(N, Then) -> case now() of Then -> ?line ?t:fail("now/0 returned the same value twice"); Now -> fast_now_unique(N-1, Now) end. median(Unsorted_List) -> ?line Length = length(Unsorted_List), ?line List = lists:sort(Unsorted_List), ?line case Length rem 2 of 0 -> % Even length. [A, B] = lists:nthtail((Length div 2)-1, List), (A+B)/2; 1 -> % Odd list length. lists:nth((Length div 2)+1, List) end. microsecs({Mega_Secs, Secs, Microsecs}) -> (Mega_Secs*1000000+Secs)*1000000+Microsecs. %% Test that the time differences returned by two calls to %% now/0 one second apart is comparable to the difference of two %% calls to erlang:localtime(). now_update(Config) when is_list(Config) -> case ?t:is_debug() of false -> ?line now_update1(10); true -> {skip,"Unreliable in DEBUG build"} end. now_update1(N) when N > 0 -> ?line T1_linear = linear_time(erlang:localtime()), ?line T1_now = microsecs(now()), ?line receive after 1008 -> ok end, ?line T2_linear = linear_time(erlang:localtime()), ?line T2_now = microsecs(now()), ?line Linear_Diff = (T2_linear-T1_linear)*1000000, ?line Now_Diff = T2_now-T1_now, test_server:format("Localtime diff = ~p; now() diff = ~p", [Linear_Diff, Now_Diff]), ?line case abs(Linear_Diff - Now_Diff) of Abs_Delta when Abs_Delta =< 40000 -> ok; _ -> now_update1(N-1) end; now_update1(0) -> ?line test_server:fail(). %% Returns the test data: a list of {Utc, Local} tuples. test_data() -> {TZ,DSTTZ} = case os:type() of {unix,_} -> case os:cmd("date '+%Z'") of "SAST"++_ -> {2,2}; _ -> {?timezone,?dst_timezone} end; _ -> {?timezone,?dst_timezone} end, ?line test_data(nondst_dates(), TZ) ++ test_data(dst_dates(), DSTTZ) ++ crossover_test_data(crossover_dates(), TZ). %% test_data1() -> %% ?line test_data(nondst_dates(), ?timezone) ++ %% test_data(dst_dates(), ?dst_timezone) ++ %% crossover_test_data(crossover_dates(), ?timezone). crossover_test_data([{Year, Month, Day}|Rest], TimeZone) when TimeZone > 0 -> Hour = 23, Min = 35, Sec = 55, ?line Utc = {{Year, Month, Day}, {Hour, Min, Sec}}, ?line Local = {{Year, Month, Day+1}, {Hour+TimeZone-24, Min, Sec}}, ?line [{Utc, Local}|crossover_test_data(Rest, TimeZone)]; crossover_test_data([{Year, Month, Day}|Rest], TimeZone) when TimeZone < 0 -> Hour = 0, Min = 23, Sec = 12, ?line Utc = {{Year, Month, Day}, {Hour, Min, Sec}}, ?line Local = {{Year, Month, Day-1}, {Hour+TimeZone+24, Min, Sec}}, ?line [{Utc, Local}|crossover_test_data(Rest, TimeZone)]; crossover_test_data([], _) -> []. test_data([Date|Rest], TimeZone) -> Hour = 12, Min = 45, Sec = 7, ?line Utc = {Date, {Hour, Min, Sec}}, ?line Local = {Date, {Hour+TimeZone, Min, Sec}}, ?line [{Utc, Local}|test_data(Rest, TimeZone)]; test_data([], _) -> []. nondst_dates() -> [{1996, 01, 30}, {1997, 01, 30}, {1998, 01, 30}, {1999, 01, 30}, {1996, 02, 29}, {1997, 02, 28}, {1998, 02, 28}, {1999, 02, 28}, {1996, 03, 2}, {1997, 03, 2}, {1998, 03, 2}, {1999, 03, 2}]. dst_dates() -> [{1996, 06, 1}, {1997, 06, 2}, {1998, 06, 3}, {1999, 06, 4}]. %% The following dates should not be near the end or beginning of %% a month, because they will be used to test when the dates are %% different in UTC and local time. crossover_dates() -> [{1996, 01, 25}, {1997, 01, 25}, {1998, 01, 25}, {1999, 01, 25}, {1996, 02, 27}, {1997, 02, 27}, {1998, 02, 27}, {1999, 02, 27}]. bad_dates() -> [{{1900, 7, 1}, {12, 0, 0}}, % Year {{1996, 0, 20}, {12, 0, 0}}, % Month {{1996, 13, 20}, {12, 0, 0}}, {{1996, 1, 0}, {12, 0, 0}}, % Date {{1996, 1, 32}, {12, 0, 0}}, {{1996, 2, 30}, {12, 0, 0}}, {{1997, 2, 29}, {12, 0, 0}}, {{1998, 2, 29}, {12, 0, 0}}, {{1999, 2, 29}, {12, 0, 0}}, {{1996, 4, 31}, {12, 0, 0}}, {{1996, 4, 30}, {-1, 0, 0}}, % Hour {{1996, 4, 30}, {25, 0, 0}}, {{1996, 4, 30}, {12,-1, 0}}, % Minute {{1996, 4, 30}, {12, 60, 0}}, {{1996, 4, 30}, {12, 0, -1}}, % Sec {{1996, 4, 30}, {12, 0, 60}}].