%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 1997-2013. 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). -compile({nowarn_deprecated_function, {erlang,now,0}}). %% "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, univ_to_seconds/1, seconds_to_univ/1, consistency/1, now_unique/1, now_update/1, timestamp/1, time_warp_modes/1, monotonic_time_monotonicity/1, time_unit_conversion/1, signed_time_unit_conversion/1, erlang_timestamp/1]). -export([init_per_testcase/2, end_per_testcase/2]). -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). init_per_testcase(Func, Config) when is_atom(Func), is_list(Config) -> [{testcase, Func}|Config]. end_per_testcase(_Func, Config) -> ok. 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, univ_to_seconds, seconds_to_univ, consistency, {group, now}, timestamp, time_warp_modes, monotonic_time_monotonicity, time_unit_conversion, signed_time_unit_conversion, erlang_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 universaltime to seconds conversions univ_to_seconds(Config) when is_list(Config) -> test_univ_to_seconds(ok_utc_seconds()). test_univ_to_seconds([{Datetime, Seconds}|DSs]) -> io:format("universaltime = ~p -> seconds = ~p", [Datetime, Seconds]), Seconds = erlang:universaltime_to_posixtime(Datetime), test_univ_to_seconds(DSs); test_univ_to_seconds([]) -> ok. %% Test seconds to universaltime conversions seconds_to_univ(Config) when is_list(Config) -> test_seconds_to_univ(ok_utc_seconds()). test_seconds_to_univ([{Datetime, Seconds}|DSs]) -> io:format("universaltime = ~p <- seconds = ~p", [Datetime, Seconds]), Datetime = erlang:posixtime_to_universaltime(Seconds), test_seconds_to_univ(DSs); test_seconds_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: Simplified leap year calc will fail for 2100 :-) linear_time({{Year, Mon, Day}, {Hour, Min, Sec}}) -> 86400*(year_to_days(Year) + month_to_days(Year,Mon) + (Day-1)) + 3600*Hour + 60*Min + Sec. year_to_days(Year) -> Year * 365 + (Year-1) div 4. month_to_days(Year, Mon) -> DoM = [31,days_in_february(Year),31,30,31,30,31,31,30,31,30,31], {PastMonths,_} = lists:split(Mon-1, DoM), lists:sum(PastMonths). days_in_february(Year) -> case (Year rem 4) of 0 -> 29; _ -> 28 end. %% 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(). time_warp_modes(Config) when is_list(Config) -> %% All time warp modes always supported in %% combination with no time correction... check_time_warp_mode(Config, false, no_time_warp), check_time_warp_mode(Config, false, single_time_warp), check_time_warp_mode(Config, false, multi_time_warp), erts_debug:set_internal_state(available_internal_state, true), try case erts_debug:get_internal_state({check_time_config, true, no_time_warp}) of false -> ok; true -> check_time_warp_mode(Config, true, no_time_warp) end, case erts_debug:get_internal_state({check_time_config, true, single_time_warp}) of false -> ok; true -> check_time_warp_mode(Config, true, single_time_warp) end, case erts_debug:get_internal_state({check_time_config, true, multi_time_warp}) of false -> ok; true -> check_time_warp_mode(Config, true, multi_time_warp) end after erts_debug:set_internal_state(available_internal_state, false) end. check_time_warp_mode(Config, TimeCorrection, TimeWarpMode) -> io:format("~n~n~n***** Testing TimeCorrection=~p TimeWarpMode=~p *****~n", [TimeCorrection, TimeWarpMode]), Mon = erlang:monitor(time_offset, clock_service), _ = erlang:time_offset(), Start = erlang:monotonic_time(1000), MonotonicityTimeout = 2000, {ok, Node} = start_node(Config, "+c " ++ atom_to_list(TimeCorrection) ++ " +C " ++ atom_to_list(TimeWarpMode)), StartTime = rpc:call(Node, erlang, system_info, [start_time]), Me = self(), MonotincityTestStarted = make_ref(), MonotincityTestDone = make_ref(), spawn_link(Node, fun () -> Me ! MonotincityTestStarted, cmp_times(erlang:start_timer(MonotonicityTimeout, self(), timeout), erlang:monotonic_time()), Me ! MonotincityTestDone end), receive MonotincityTestStarted -> ok end, check_time_offset(Node, TimeWarpMode), TimeWarpMode = rpc:call(Node, erlang, system_info, [time_warp_mode]), TimeCorrection = rpc:call(Node, erlang, system_info, [time_correction]), receive MonotincityTestDone -> ok end, MonotonicTime = rpc:call(Node, erlang, monotonic_time, []), MonotonicTimeUnit = rpc:call(Node, erlang, convert_time_unit, [1, seconds, native]), UpMilliSeconds = erlang:convert_time_unit(MonotonicTime - StartTime, MonotonicTimeUnit, milli_seconds), io:format("UpMilliSeconds=~p~n", [UpMilliSeconds]), End = erlang:monotonic_time(milli_seconds), stop_node(Node), try true = (UpMilliSeconds > (98*MonotonicityTimeout) div 100), true = (UpMilliSeconds < (102*(End-Start)) div 100) catch error:_ -> io:format("Uptime inconsistency", []), case {TimeCorrection, erlang:system_info(time_correction)} of {true, true} -> ?t:fail(uptime_inconsistency); {true, false} -> _ = erlang:time_offset(), receive {'CHANGE', Mon, time_offset, clock_service, _} -> ignore after 1000 -> ?t:fail(uptime_inconsistency) end; _ -> ignore end end, erlang:demonitor(Mon, [flush]), ok. check_time_offset(Node, no_time_warp) -> final = rpc:call(Node, erlang, system_info, [time_offset]), final = rpc:call(Node, erlang, system_flag, [time_offset, finalize]), final = rpc:call(Node, erlang, system_info, [time_offset]); check_time_offset(Node, single_time_warp) -> preliminary = rpc:call(Node, erlang, system_info, [time_offset]), preliminary = rpc:call(Node, erlang, system_flag, [time_offset, finalize]), final = rpc:call(Node, erlang, system_info, [time_offset]), final = rpc:call(Node, erlang, system_flag, [time_offset, finalize]); check_time_offset(Node, multi_time_warp) -> volatile = rpc:call(Node, erlang, system_info, [time_offset]), volatile = rpc:call(Node, erlang, system_flag, [time_offset, finalize]), volatile = rpc:call(Node, erlang, system_info, [time_offset]). monotonic_time_monotonicity(Config) when is_list(Config) -> Done = erlang:start_timer(10000,self(),timeout), cmp_times(Done, erlang:monotonic_time()). cmp_times(Done, X0) -> X1 = erlang:monotonic_time(), X2 = erlang:monotonic_time(), X3 = erlang:monotonic_time(), X4 = erlang:monotonic_time(), X5 = erlang:monotonic_time(), true = (X0 =< X1), true = (X1 =< X2), true = (X2 =< X3), true = (X3 =< X4), true = (X4 =< X5), receive {timeout, Done, timeout} -> ok after 0 -> cmp_times(Done, X5) end. -define(CHK_RES_CONVS_TIMEOUT, 400). time_unit_conversion(Config) when is_list(Config) -> Mon = erlang:monitor(time_offset, clock_service), start_check_res_convs(Mon, 1000000000000), start_check_res_convs(Mon, 2333333333333), start_check_res_convs(Mon, 5732678356789), erlang:demonitor(Mon, [flush]). start_check_res_convs(Mon, Res) -> io:format("Checking ~p time_unit~n", [Res]), check_res_convs(Mon, erlang:start_timer(?CHK_RES_CONVS_TIMEOUT, self(), timeout), Res). check_res_convs(Mon, Done, Res) -> receive {timeout, Done, timeout} -> case Res div 10 of 0 -> ok; NewRes -> start_check_res_convs(Mon, NewRes) end after 0 -> do_check_res_convs(Mon, Done, Res) end. do_check_res_convs(Mon, Done, Res) -> TStart = erlang:monotonic_time(), T = erlang:monotonic_time(Res), TEnd = erlang:monotonic_time(), TMin = erlang:convert_time_unit(TStart, native, Res), TMax = erlang:convert_time_unit(TEnd, native, Res), %io:format("~p =< ~p =< ~p~n", [TMin, T, TEnd]), true = (TMin =< T), true = (TMax >= T), check_time_offset_res_conv(Mon, Res), check_res_convs(Mon, Done, Res). check_time_offset_res_conv(Mon, Res) -> TORes = erlang:time_offset(Res), TO = erlang:time_offset(), case erlang:convert_time_unit(TO, native, Res) of TORes -> ok; TORes2 -> case check_time_offset_change(Mon, TO, 1000) of {TO, false} -> ?t:fail({time_unit_conversion_inconsistency, TO, TORes, TORes2}); {_NewTO, true} -> ?t:format("time_offset changed", []), check_time_offset_res_conv(Mon, Res) end end. signed_time_unit_conversion(Config) when is_list(Config) -> chk_strc(1000000000, 1000000), chk_strc(1000000000, 1000), chk_strc(1000000000, 1), chk_strc(1000000, 1000), chk_strc(1000000, 1), chk_strc(1000, 1), chk_strc(4711, 17), chk_strc(1 bsl 10, 1), chk_strc(1 bsl 16, 10), chk_strc(1 bsl 17, 1 bsl 8), chk_strc((1 bsl 17) + 1, (1 bsl 8) - 1), chk_strc(1 bsl 17, 11), ok. chk_strc(Res0, Res1) -> case (Res0 /= Res1) andalso (Res0 =< 1000000) andalso (Res1 =< 1000000) of true -> {FromRes, ToRes} = case Res0 > Res1 of true -> {Res0, Res1}; false -> {Res1, Res0} end, MinFromValuesPerToValue = FromRes div ToRes, MaxFromValuesPerToValue = ((FromRes-1) div ToRes)+1, io:format("~p -> ~p [~p, ~p]~n", [FromRes, ToRes, MinFromValuesPerToValue, MaxFromValuesPerToValue]), chk_values_per_value(FromRes, ToRes, -10*FromRes, 10*FromRes, MinFromValuesPerToValue, MaxFromValuesPerToValue, undefined, MinFromValuesPerToValue); _ -> ok end, chk_random_values(Res0, Res1), chk_random_values(Res1, Res0), ok. chk_random_values(FR, TR) -> % case (FR rem TR == 0) orelse (TR rem FR == 0) of % true -> io:format("rand values ~p -> ~p~n", [FR, TR]), random:seed(268438039, 268440479, 268439161), Values = lists:map(fun (_) -> random:uniform(1 bsl 65) - (1 bsl 64) end, lists:seq(1, 100000)), CheckFun = fun (V) -> CV = erlang:convert_time_unit(V, FR, TR), case {(FR*CV) div TR =< V, (FR*(CV+1)) div TR >= V} of {true, true} -> ok; Failure -> ?t:fail({Failure, CV, V, FR, TR}) end end, lists:foreach(CheckFun, Values).%; % false -> ok % end. chk_values_per_value(_FromRes, _ToRes, EndValue, EndValue, MinFromValuesPerToValue, MaxFromValuesPerToValue, _ToValue, FromValueCount) -> % io:format("~p [~p]~n", [EndValue, FromValueCount]), case ((MinFromValuesPerToValue =< FromValueCount) andalso (FromValueCount =< MaxFromValuesPerToValue)) of false -> ?t:fail({MinFromValuesPerToValue, FromValueCount, MaxFromValuesPerToValue}); true -> ok end; chk_values_per_value(FromRes, ToRes, Value, EndValue, MinFromValuesPerToValue, MaxFromValuesPerToValue, ToValue, FromValueCount) -> case erlang:convert_time_unit(Value, FromRes, ToRes) of ToValue -> chk_values_per_value(FromRes, ToRes, Value+1, EndValue, MinFromValuesPerToValue, MaxFromValuesPerToValue, ToValue, FromValueCount+1); NewToValue -> case ((MinFromValuesPerToValue =< FromValueCount) andalso (FromValueCount =< MaxFromValuesPerToValue)) of false -> ?t:fail({MinFromValuesPerToValue, FromValueCount, MaxFromValuesPerToValue}); true -> % io:format("~p -> ~p [~p]~n", % [Value, NewToValue, FromValueCount]), chk_values_per_value(FromRes, ToRes, Value+1, EndValue, MinFromValuesPerToValue, MaxFromValuesPerToValue, NewToValue, 1) end end. erlang_timestamp(Config) when is_list(Config) -> Mon = erlang:monitor(time_offset, clock_service), {TO, _} = check_time_offset_change(Mon, erlang:time_offset(), 0), Done = erlang:start_timer(10000,self(),timeout), ok = check_erlang_timestamp(Done, Mon, TO). check_erlang_timestamp(Done, Mon, TO) -> receive {timeout, Done, timeout} -> erlang:demonitor(Mon, [flush]), ok after 0 -> do_check_erlang_timestamp(Done, Mon, TO) end. do_check_erlang_timestamp(Done, Mon, TO) -> MinMon = erlang:monotonic_time(), {MegaSec, Sec, MicroSec} = erlang:timestamp(), MaxMon = erlang:monotonic_time(), TsMin = erlang:convert_time_unit(MinMon+TO, native, micro_seconds), TsMax = erlang:convert_time_unit(MaxMon+TO, native, micro_seconds), TsTime = (MegaSec*1000000+Sec)*1000000+MicroSec, case (TsMin =< TsTime) andalso (TsTime =< TsMax) of true -> NewTO = case erlang:time_offset() of TO -> TO; _ -> check_time_offset_change(Mon, TO, 0) end, check_erlang_timestamp(Done, Mon, NewTO); false -> io:format("TsMin=~p TsTime=~p TsMax=~p~n", [TsMin, TsTime, TsMax]), ?t:format("Detected inconsistency; " "checking for time_offset change...", []), case check_time_offset_change(Mon, TO, 1000) of {TO, false} -> ?t:fail(timestamp_inconsistency); {NewTO, true} -> ?t:format("time_offset changed", []), check_erlang_timestamp(Done, Mon, NewTO) end end. check_time_offset_change(Mon, TO, Wait) -> process_changed_time_offset(Mon, TO, false, Wait). process_changed_time_offset(Mon, TO, Changed, Wait) -> receive {'CHANGE', Mon, time_offset, clock_service, NewTO} -> process_changed_time_offset(Mon, NewTO, true, Wait) after Wait -> case erlang:time_offset() of TO -> {TO, Changed}; _OtherTO -> receive {'CHANGE', Mon, time_offset, clock_service, NewTO} -> process_changed_time_offset(Mon, NewTO, true, Wait) end end end. %% 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}]. %% exakt utc {date(), time()} which corresponds to the same seconds since 1 jan 1970 %% negative seconds are ok %% generated with date --date='1979-05-28 12:30:35 UTC' +%s ok_utc_seconds() -> [ { {{1970, 1, 1},{ 0, 0, 0}}, 0 }, { {{1970, 1, 1},{ 0, 0, 1}}, 1 }, { {{1969,12,31},{23,59,59}}, -1 }, { {{1920,12,31},{23,59,59}}, -1546300801 }, { {{1600,02,19},{15,14,08}}, -11671807552 }, { {{1979,05,28},{12,30,35}}, 296742635 }, { {{1999,12,31},{23,59,59}}, 946684799 }, { {{2000, 1, 1},{ 0, 0, 0}}, 946684800 }, { {{2000, 1, 1},{ 0, 0, 1}}, 946684801 }, { {{2038, 1,19},{03,14,07}}, 2147483647 }, % Sint32 full - 1 { {{2038, 1,19},{03,14,08}}, 2147483648 }, % Sint32 full { {{2038, 1,19},{03,14,09}}, 2147483649 }, % Sint32 full + 1 { {{2106, 2, 7},{ 6,28,14}}, 4294967294 }, % Uint32 full 0xFFFFFFFF - 1 { {{2106, 2, 7},{ 6,28,15}}, 4294967295 }, % Uint32 full 0xFFFFFFFF { {{2106, 2, 7},{ 6,28,16}}, 4294967296 }, % Uint32 full 0xFFFFFFFF + 1 { {{2012,12, 6},{16,28,08}}, 1354811288 }, { {{2412,12, 6},{16,28,08}}, 13977592088 } ]. %% 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}}]. start_node(Config) -> start_node(Config, ""). start_node(Config, Args) -> TestCase = ?config(testcase, Config), PA = filename:dirname(code:which(?MODULE)), ESTime = erlang:monotonic_time(1) + erlang:time_offset(1), Unique = erlang:unique_integer([positive]), Name = list_to_atom(atom_to_list(?MODULE) ++ "-" ++ atom_to_list(TestCase) ++ "-" ++ integer_to_list(ESTime) ++ "-" ++ integer_to_list(Unique)), test_server:start_node(Name, slave, [{args, "-pa " ++ PA ++ " " ++ Args}]). stop_node(Node) -> test_server:stop_node(Node).