%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 1997-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. %% You may obtain a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, software %% distributed under the License is distributed on an "AS IS" BASIS, %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %% See the License for the specific language governing permissions 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, monotonic_time_monotonicity_parallel/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("common_test/include/ct.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, monotonic_time_monotonicity_parallel, 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. %% 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 {ok, Node} = test_server:start_node(local_univ_utc,peer, [{args, "-env TZ UTC"}]), {{2008,8,1},{0,0,0}} = rpc:call(Node, erlang,localtime_to_universaltime, [{{2008, 8, 1}, {0, 0, 0}}, false]), {{2008,8,1},{0,0,0}} = rpc:call(Node, erlang,localtime_to_universaltime, [{{2008, 8, 1}, {0, 0, 0}}, true]), [{{2008,8,1},{0,0,0}}] = rpc:call(Node, calendar,local_time_to_universal_time_dst, [{{2008, 8, 1}, {0, 0, 0}}]), test_server:stop_node(Node), ok; _ -> {skip,"Only valid on Unix"} end. %% Tests conversion from universal to local time. univ_to_local(Config) when is_list(Config) -> test_univ_to_local(test_data()). test_univ_to_local([{Utc, Local}|Rest]) -> io:format("Testing ~p => ~p~n", [Local, Utc]), Local = erlang:universaltime_to_localtime(Utc), 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) -> test_local_to_univ(test_data()). test_local_to_univ([{Utc, Local}|Rest]) -> io:format("Testing ~p => ~p~n", [Utc, Local]), Utc = erlang:localtime_to_universaltime(Local), 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) -> bad_test_univ_to_local(bad_dates()). bad_test_univ_to_local([Utc|Rest]) -> io:format("Testing ~p~n", [Utc]), 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) -> bad_test_local_to_univ(bad_dates()). bad_test_local_to_univ([Local|Rest]) -> io:format("Testing ~p~n", [Local]), 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. consistency(_Config) -> %% Test that: %% * date() & time() gives the same time as erlang:localtime() %% %% * the difference between erlang:universaltime() and %% erlang:localtime() is reasonable (with assuming any %% particular timezone) ok = compare_date_time_and_localtime(16), compare_local_and_universal(16). compare_date_time_and_localtime(Times) when Times > 0 -> {Year, Mon, Day} = date(), {Hour, Min, Sec} = time(), 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 -> Utc = erlang:universaltime(), Local = erlang:localtime(), io:format("local = ~p, utc = ~p", [Local,Utc]), AcceptableDiff = 14*3600, case linear_time(Utc) - linear_time(Local) of Diff when abs(Diff) < AcceptableDiff -> ok; Diff -> io:format("More than ~p seconds difference betwen " "local and universal time", [Diff]), ct:fail(huge_diff) 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. %% Test (the bif) os:timestamp/0, which is something quite like, but not %% similar to erlang:now... %% Test that os:timestamp works. timestamp(Config) when is_list(Config) -> try repeating_timestamp_check(100000) catch throw : {fail, Failure} -> %% %% Our time warping test machines currently warps %% time every 6:th second. If we get a warp during %% 10 seconds, assume this is a time warping test %% and ignore the failure. %% case had_time_warp(10) of true -> {skip, "Seems to be time warp test run..."}; false -> ct:fail(Failure) end end. os_system_time_offset() -> erlang:convert_time_unit(os:system_time() - erlang:monotonic_time(), native, microsecond). had_time_warp(Secs) -> had_time_warp(os_system_time_offset(), Secs). had_time_warp(_OrigOffs, 0) -> false; had_time_warp(OrigOffs, N) -> receive after 1000 -> ok end, case OrigOffs - os_system_time_offset() of Diff when Diff > 500000; Diff < -500000 -> true; _Diff -> had_time_warp(OrigOffs, N-1) end. 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 -> ct:fail("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 -> throw({fail, lists:flatten( io_lib:format("os:timestamp/0 is ~w s more than erlang:now/0", [TooLarge]))}); TooSmall when TooSmall < -3600 -> throw({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) -> now_unique(1000, now(), []), fast_now_unique(100000, now()). now_unique(N, Previous, Result) when N > 0 -> case now() of Previous -> ct:fail("now/0 returned the same value twice"); New -> now_unique(N-1, New, [New|Result]) end; now_unique(0, _, [Then|Rest]) -> now_calc_increment(Rest, microsecs(Then), []). now_calc_increment([Then|Rest], Previous, _Result) -> This = microsecs(Then), 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 -> ct:fail("now/0 returned the same value twice"); Now -> fast_now_unique(N-1, Now) end. median(Unsorted_List) -> Length = length(Unsorted_List), List = lists:sort(Unsorted_List), 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 test_server:is_debug() of false -> now_update1(10); true -> {skip,"Unreliable in DEBUG build"} end. now_update1(N) when N > 0 -> T1_linear = linear_time(erlang:localtime()), T1_now = microsecs(now()), receive after 1008 -> ok end, T2_linear = linear_time(erlang:localtime()), T2_now = microsecs(now()), Linear_Diff = (T2_linear-T1_linear)*1000000, Now_Diff = T2_now-T1_now, io:format("Localtime diff = ~p; now() diff = ~p", [Linear_Diff, Now_Diff]), case abs(Linear_Diff - Now_Diff) of Abs_Delta when Abs_Delta =< 40000 -> ok; _ -> now_update1(N-1) end; now_update1(0) -> ct:fail("now_update zero"). 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, second, native]), UpMilliSeconds = erlang:convert_time_unit(MonotonicTime - StartTime, MonotonicTimeUnit, millisecond), io:format("UpMilliSeconds=~p~n", [UpMilliSeconds]), End = erlang:monotonic_time(millisecond), 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} -> ct:fail(uptime_inconsistency); {true, false} -> _ = erlang:time_offset(), receive {'CHANGE', Mon, time_offset, clock_service, _} -> ignore after 1000 -> ct: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(NR_OF_MONOTONIC_CALLS, 100000). monotonic_time_monotonicity_parallel(Config) when is_list(Config) -> Me = self(), Result = make_ref(), Go = make_ref(), UpAndRunning = make_ref(), NoOnlnScheds = erlang:system_info(schedulers_online), OffsetUI = erlang:unique_integer([monotonic]), OffsetMT = erlang:monotonic_time(), MinHSz = ?NR_OF_MONOTONIC_CALLS*(2 + 3 + erts_debug:flat_size(OffsetUI) + erts_debug:flat_size(OffsetMT)), Ps = lists:map( fun (Sched) -> spawn_opt( fun () -> Me ! {self(), UpAndRunning}, receive Go -> ok end, Res = fetch_monotonic(?NR_OF_MONOTONIC_CALLS, []), Me ! {self(), Result, Sched, Res} end, [{scheduler, Sched}, {priority, max}, {min_heap_size, MinHSz}]) end, lists:seq(1, NoOnlnScheds)), lists:foreach(fun (P) -> receive {P, UpAndRunning} -> ok end end, Ps), lists:foreach(fun (P) -> P ! Go end, Ps), TMs = recv_monotonics(Result, OffsetMT, OffsetUI, NoOnlnScheds, []), true = check_monotonic_result(TMs, OffsetMT, OffsetUI, true). check_monotonic_result([{_Sched, _PrevUI, _MT, _PostUI}], _OffsetMT, _OffsetUI, Res) -> Res; check_monotonic_result([{_ASched, _APrevUI, AMT, APostUI} = A, {_BSched, BPrevUI, BMT, _BPostUI} = B | _] = L, OffsetMT, OffsetUI, Res) -> NewRes = case (AMT =< BMT) orelse (BPrevUI < APostUI) of true -> Res; false -> io:format("INCONSISTENCY: ~p ~p~n", [A, B]), false end, check_monotonic_result(tl(L), OffsetMT, OffsetUI, NewRes). recv_monotonics(_Result, _OffsetMT, _OffsetUI, 0, Acc) -> lists:keysort(2, Acc); recv_monotonics(Result, OffsetMT, OffsetUI, N, Acc) -> receive {_, Result, Sched, Res} -> CRes = convert_monotonic(Sched, OffsetMT, OffsetUI, Res, []), recv_monotonics(Result, OffsetMT, OffsetUI, N-1, CRes ++ Acc) end. convert_monotonic(_Sched, _OffsetMT, _OffsetUI, [{_MT, _UI}], Acc) -> Acc; convert_monotonic(Sched, OffsetMT, OffsetUI, [{MT, UI}, {_PrevMT, PrevUI} | _] = L, Acc) -> convert_monotonic(Sched, OffsetMT, OffsetUI, tl(L), [{Sched, PrevUI-OffsetUI, MT-OffsetMT, UI-OffsetUI} | Acc]). fetch_monotonic(0, Acc) -> Acc; fetch_monotonic(N, Acc) -> MT = erlang:monotonic_time(), UI = erlang:unique_integer([monotonic]), fetch_monotonic(N-1, [{MT, UI} | Acc]). -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} -> ct:fail({time_unit_conversion_inconsistency, TO, TORes, TORes2}); {_NewTO, true} -> io: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) -> io:format("rand values ~p -> ~p~n", [FR, TR]), rand:seed(exsplus, {268438039,268440479,268439161}), Values = lists:map(fun (_) -> rand: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 -> ct:fail({Failure, CV, V, FR, TR}) end end, lists:foreach(CheckFun, Values). 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 -> ct: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 -> ct: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, microsecond), TsMax = erlang:convert_time_unit(MaxMon+TO, native, microsecond), 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]), io:format("Detected inconsistency; " "checking for time_offset change...", []), case check_time_offset_change(Mon, TO, 1000) of {TO, false} -> ct:fail(timestamp_inconsistency); {NewTO, true} -> io: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, test_data(nondst_dates(), TZ) ++ test_data(dst_dates(), DSTTZ) ++ crossover_test_data(crossover_dates(), TZ). %% test_data1() -> %% 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, Utc = {{Year, Month, Day}, {Hour, Min, Sec}}, Local = {{Year, Month, Day+1}, {Hour+TimeZone-24, Min, Sec}}, [{Utc, Local}|crossover_test_data(Rest, TimeZone)]; crossover_test_data([{Year, Month, Day}|Rest], TimeZone) when TimeZone < 0 -> Hour = 0, Min = 23, Sec = 12, Utc = {{Year, Month, Day}, {Hour, Min, Sec}}, Local = {{Year, Month, Day-1}, {Hour+TimeZone+24, Min, Sec}}, [{Utc, Local}|crossover_test_data(Rest, TimeZone)]; crossover_test_data([], _) -> []. test_data([Date|Rest], TimeZone) -> Hour = 12, Min = 45, Sec = 7, Utc = {Date, {Hour, Min, Sec}}, Local = {Date, {Hour+TimeZone, Min, Sec}}, [{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, Args) -> TestCase = proplists:get_value(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).