diff options
Diffstat (limited to 'erts/emulator/test/time_SUITE.erl')
-rw-r--r-- | erts/emulator/test/time_SUITE.erl | 439 |
1 files changed, 439 insertions, 0 deletions
diff --git a/erts/emulator/test/time_SUITE.erl b/erts/emulator/test/time_SUITE.erl new file mode 100644 index 0000000000..2ad1f0d201 --- /dev/null +++ b/erts/emulator/test/time_SUITE.erl @@ -0,0 +1,439 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1997-2009. 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/1, univ_to_local/1, local_to_univ/1, + bad_univ_to_local/1, bad_local_to_univ/1, + consistency/1, + now/1, now_unique/1, now_update/1, timestamp/1]). + +-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). + +all(suite) -> [univ_to_local, local_to_univ, + bad_univ_to_local, bad_local_to_univ, consistency, now, timestamp]. + + +%% 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. + +now(suite) -> [now_unique, now_update]. + +%% 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}}]. + |