aboutsummaryrefslogtreecommitdiffstats
path: root/erts/emulator/test/time_SUITE.erl
diff options
context:
space:
mode:
Diffstat (limited to 'erts/emulator/test/time_SUITE.erl')
-rw-r--r--erts/emulator/test/time_SUITE.erl439
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}}].
+