1%% 2%% %CopyrightBegin% 3%% 4%% Copyright Ericsson AB 1997-2017. All Rights Reserved. 5%% 6%% Licensed under the Apache License, Version 2.0 (the "License"); 7%% you may not use this file except in compliance with the License. 8%% You may obtain a copy of the License at 9%% 10%% http://www.apache.org/licenses/LICENSE-2.0 11%% 12%% Unless required by applicable law or agreed to in writing, software 13%% distributed under the License is distributed on an "AS IS" BASIS, 14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15%% See the License for the specific language governing permissions and 16%% limitations under the License. 17%% 18%% %CopyrightEnd% 19%% 20 21-module(time_SUITE). 22-compile({nowarn_deprecated_function, {erlang,now,0}}). 23 24%% "Time is on my side." -- The Rolling Stones 25 26%% Tests the BIFs: 27%% erlang:localtime_to_universaltime/1 28%% erlang:universaltime_to_localtime/1 29%% date/0 30%% time/0 31%% now/0 32%% 33 34-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, 35 init_per_group/2,end_per_group/2, univ_to_local/1, local_to_univ/1, 36 bad_univ_to_local/1, bad_local_to_univ/1, 37 univ_to_seconds/1, seconds_to_univ/1, 38 consistency/1, 39 now_unique/1, now_update/1, timestamp/1, 40 time_warp_modes/1, 41 monotonic_time_monotonicity/1, 42 monotonic_time_monotonicity_parallel/1, 43 time_unit_conversion/1, 44 signed_time_unit_conversion/1, 45 erlang_timestamp/1]). 46 47-export([init_per_testcase/2, end_per_testcase/2]). 48 49-export([local_to_univ_utc/1]). 50 51-include_lib("common_test/include/ct.hrl"). 52 53-export([linear_time/1]). 54 55%% The following defines the timezone in which the test is run. 56%% It is interpreted as the number of hours to be added to UTC 57%% to obtain the local time. The number will be positive east 58%% of Greenwhich, negative west of Greenwhich. 59%% 60%% Allowable range is -12 through 11. 61 62-define(timezone, 1). 63 64%% Similarly to timezone, but the difference when Daylight Saving Time 65%% is in use. [Same range.] 66 67-define(dst_timezone, 2). 68 69init_per_testcase(Func, Config) when is_atom(Func), is_list(Config) -> 70 [{testcase, Func}|Config]. 71 72end_per_testcase(_Func, _Config) -> 73 ok. 74 75suite() -> [{ct_hooks,[ts_install_cth]}]. 76 77all() -> 78 [univ_to_local, local_to_univ, local_to_univ_utc, 79 bad_univ_to_local, bad_local_to_univ, 80 univ_to_seconds, seconds_to_univ, 81 consistency, 82 {group, now}, timestamp, 83 time_warp_modes, 84 monotonic_time_monotonicity, 85 monotonic_time_monotonicity_parallel, 86 time_unit_conversion, 87 signed_time_unit_conversion, 88 erlang_timestamp]. 89 90groups() -> 91 [{now, [], [now_unique, now_update]}]. 92 93init_per_suite(Config) -> 94 Config. 95 96end_per_suite(_Config) -> 97 ok. 98 99init_per_group(_GroupName, Config) -> 100 Config. 101 102end_per_group(_GroupName, Config) -> 103 Config. 104 105 106%% Test that DST = true on timezones without DST is ignored 107local_to_univ_utc(Config) when is_list(Config) -> 108 case os:type() of 109 {unix,_} -> 110 %% TZ variable has a meaning 111 {ok, Node} = 112 test_server:start_node(local_univ_utc,peer, 113 [{args, "-env TZ UTC"}]), 114 {{2008,8,1},{0,0,0}} = 115 rpc:call(Node, 116 erlang,localtime_to_universaltime, 117 [{{2008, 8, 1}, {0, 0, 0}}, 118 false]), 119 {{2008,8,1},{0,0,0}} = 120 rpc:call(Node, 121 erlang,localtime_to_universaltime, 122 [{{2008, 8, 1}, {0, 0, 0}}, 123 true]), 124 [{{2008,8,1},{0,0,0}}] = 125 rpc:call(Node, 126 calendar,local_time_to_universal_time_dst, 127 [{{2008, 8, 1}, {0, 0, 0}}]), 128 test_server:stop_node(Node), 129 ok; 130 _ -> 131 {skip,"Only valid on Unix"} 132 end. 133 134 135%% Tests conversion from universal to local time. 136 137univ_to_local(Config) when is_list(Config) -> 138 test_univ_to_local(test_data()). 139 140test_univ_to_local([{Utc, Local}|Rest]) -> 141 io:format("Testing ~p => ~p~n", [Local, Utc]), 142 Local = erlang:universaltime_to_localtime(Utc), 143 test_univ_to_local(Rest); 144test_univ_to_local([]) -> 145 ok. 146 147%% Tests conversion from local to universal time. 148 149local_to_univ(Config) when is_list(Config) -> 150 test_local_to_univ(test_data()). 151 152test_local_to_univ([{Utc, Local}|Rest]) -> 153 io:format("Testing ~p => ~p~n", [Utc, Local]), 154 Utc = erlang:localtime_to_universaltime(Local), 155 test_local_to_univ(Rest); 156test_local_to_univ([]) -> 157 ok. 158 159%% Test bad arguments to erlang:universaltime_to_localtime; should 160%% generate a badarg. 161 162bad_univ_to_local(Config) when is_list(Config) -> 163 bad_test_univ_to_local(bad_dates()). 164 165bad_test_univ_to_local([Utc|Rest]) -> 166 io:format("Testing ~p~n", [Utc]), 167 case catch erlang:universaltime_to_localtime(Utc) of 168 {'EXIT', {badarg, _}} -> bad_test_univ_to_local(Rest) 169 end; 170bad_test_univ_to_local([]) -> 171 ok. 172 173%% Test bad arguments to erlang:localtime_to_universaltime/1; should 174%% generate a badarg. 175 176bad_local_to_univ(Config) when is_list(Config) -> 177 bad_test_local_to_univ(bad_dates()). 178 179bad_test_local_to_univ([Local|Rest]) -> 180 io:format("Testing ~p~n", [Local]), 181 case catch erlang:localtime_to_universaltime(Local) of 182 {'EXIT', {badarg, _}} -> bad_test_local_to_univ(Rest) 183 end; 184bad_test_local_to_univ([]) -> 185 ok. 186 187 188%% Test universaltime to seconds conversions 189univ_to_seconds(Config) when is_list(Config) -> 190 test_univ_to_seconds(ok_utc_seconds()). 191 192test_univ_to_seconds([{Datetime, Seconds}|DSs]) -> 193 io:format("universaltime = ~p -> seconds = ~p", [Datetime, Seconds]), 194 Seconds = erlang:universaltime_to_posixtime(Datetime), 195 test_univ_to_seconds(DSs); 196test_univ_to_seconds([]) -> 197 ok. 198 199%% Test seconds to universaltime conversions 200seconds_to_univ(Config) when is_list(Config) -> 201 test_seconds_to_univ(ok_utc_seconds()). 202 203test_seconds_to_univ([{Datetime, Seconds}|DSs]) -> 204 io:format("universaltime = ~p <- seconds = ~p", [Datetime, Seconds]), 205 Datetime = erlang:posixtime_to_universaltime(Seconds), 206 test_seconds_to_univ(DSs); 207test_seconds_to_univ([]) -> 208 ok. 209 210 211%% Test that the the different time functions return 212%% consistent results. 213consistency(_Config) -> 214 %% Test that: 215 %% * date() & time() gives the same time as erlang:localtime() 216 %% 217 %% * the difference between erlang:universaltime() and 218 %% erlang:localtime() is reasonable (with assuming any 219 %% particular timezone) 220 221 ok = compare_date_time_and_localtime(16), 222 compare_local_and_universal(16). 223 224compare_date_time_and_localtime(Times) when Times > 0 -> 225 {Year, Mon, Day} = date(), 226 {Hour, Min, Sec} = time(), 227 case erlang:localtime() of 228 {{Year, Mon, Day}, {Hour, Min, Sec}} -> ok; 229 _ -> compare_date_time_and_localtime(Times-1) 230 end; 231compare_date_time_and_localtime(0) -> 232 error. 233 234compare_local_and_universal(Times) when Times > 0 -> 235 Utc = erlang:universaltime(), 236 Local = erlang:localtime(), 237 io:format("local = ~p, utc = ~p", [Local,Utc]), 238 239 AcceptableDiff = 14*3600, 240 case linear_time(Utc) - linear_time(Local) of 241 Diff when abs(Diff) < AcceptableDiff -> 242 ok; 243 Diff -> 244 io:format("More than ~p seconds difference betwen " 245 "local and universal time", [Diff]), 246 ct:fail(huge_diff) 247 end. 248 249%% This function converts a date and time to a linear time. 250%% Two linear times can be subtracted to give their difference 251%% in seconds. 252%% 253%% XXX Limitations: Simplified leap year calc will fail for 2100 :-) 254 255linear_time({{Year, Mon, Day}, {Hour, Min, Sec}}) -> 256 86400*(year_to_days(Year) + month_to_days(Year,Mon) + (Day-1)) + 257 3600*Hour + 60*Min + Sec. 258 259year_to_days(Year) -> 260 Year * 365 + (Year-1) div 4. 261 262month_to_days(Year, Mon) -> 263 DoM = [31,days_in_february(Year),31,30,31,30,31,31,30,31,30,31], 264 {PastMonths,_} = lists:split(Mon-1, DoM), 265 lists:sum(PastMonths). 266 267days_in_february(Year) -> 268 case (Year rem 4) of 269 0 -> 29; 270 _ -> 28 271 end. 272 273%% Test (the bif) os:timestamp/0, which is something quite like, but not 274%% similar to erlang:now... 275 276%% Test that os:timestamp works. 277timestamp(Config) when is_list(Config) -> 278 try 279 repeating_timestamp_check(100000) 280 catch 281 throw : {fail, Failure} -> 282 %% 283 %% Our time warping test machines currently warps 284 %% time every 6:th second. If we get a warp during 285 %% 10 seconds, assume this is a time warping test 286 %% and ignore the failure. 287 %% 288 case had_time_warp(10) of 289 true -> 290 {skip, "Seems to be time warp test run..."}; 291 false -> 292 ct:fail(Failure) 293 end 294 end. 295 296os_system_time_offset() -> 297 erlang:convert_time_unit(os:system_time() - erlang:monotonic_time(), 298 native, microsecond). 299 300had_time_warp(Secs) -> 301 had_time_warp(os_system_time_offset(), Secs). 302 303had_time_warp(_OrigOffs, 0) -> 304 false; 305had_time_warp(OrigOffs, N) -> 306 receive after 1000 -> ok end, 307 case OrigOffs - os_system_time_offset() of 308 Diff when Diff > 500000; Diff < -500000 -> 309 true; 310 _Diff -> 311 had_time_warp(OrigOffs, N-1) 312 end. 313 314repeating_timestamp_check(0) -> 315 ok; 316repeating_timestamp_check(N) -> 317 {A,B,C} = TS = os:timestamp(), 318 if 319 is_integer(A), 320 is_integer(B), 321 is_integer(C), 322 B < 1000000, 323 C < 1000000 -> 324 ok; 325 true -> 326 ct:fail("Strange return from os:timestamp/0 ~w~n",[TS]) 327 end, 328 %% I assume the now and timestamp should not differ more than 1 hour, 329 %% which is safe assuming the system has not had a large time-warp 330 %% during the testrun... 331 Secs = A*1000000+B+round(C/1000000), 332 {NA,NB,NC} = erlang:now(), 333 NSecs = NA*1000000+NB+round(NC/1000000), 334 case Secs - NSecs of 335 TooLarge when TooLarge > 3600 -> 336 throw({fail, 337 lists:flatten( 338 io_lib:format("os:timestamp/0 is ~w s more than erlang:now/0", 339 [TooLarge]))}); 340 TooSmall when TooSmall < -3600 -> 341 throw({fail, 342 lists:flatten( 343 io_lib:format("os:timestamp/0 is ~w s less than erlang:now/0", 344 [-TooSmall]))}); 345 _ -> 346 ok 347 end, 348 repeating_timestamp_check(N-1). 349 350 351%% Test now/0. 352 353 354%% Tests that successive calls to now/0 returns different values. 355%% Also returns a comment string with the median difference between 356%% times (in microseconds). 357 358now_unique(Config) when is_list(Config) -> 359 now_unique(1000, now(), []), 360 fast_now_unique(100000, now()). 361 362now_unique(N, Previous, Result) when N > 0 -> 363 case now() of 364 Previous -> 365 ct:fail("now/0 returned the same value twice"); 366 New -> 367 now_unique(N-1, New, [New|Result]) 368 end; 369now_unique(0, _, [Then|Rest]) -> 370 now_calc_increment(Rest, microsecs(Then), []). 371 372now_calc_increment([Then|Rest], Previous, _Result) -> 373 This = microsecs(Then), 374 now_calc_increment(Rest, This, [Previous-This]); 375now_calc_increment([], _, Differences) -> 376 {comment, "Median increment: " ++ integer_to_list(median(Differences))}. 377 378fast_now_unique(0, _) -> ok; 379fast_now_unique(N, Then) -> 380 case now() of 381 Then -> 382 ct:fail("now/0 returned the same value twice"); 383 Now -> 384 fast_now_unique(N-1, Now) 385 end. 386 387median(Unsorted_List) -> 388 Length = length(Unsorted_List), 389 List = lists:sort(Unsorted_List), 390 case Length rem 2 of 391 0 -> % Even length. 392 [A, B] = lists:nthtail((Length div 2)-1, List), 393 (A+B)/2; 394 1 -> % Odd list length. 395 lists:nth((Length div 2)+1, List) 396 end. 397 398microsecs({Mega_Secs, Secs, Microsecs}) -> 399 (Mega_Secs*1000000+Secs)*1000000+Microsecs. 400 401%% Test that the time differences returned by two calls to 402%% now/0 one second apart is comparable to the difference of two 403%% calls to erlang:localtime(). 404 405now_update(Config) when is_list(Config) -> 406 case test_server:is_debug() of 407 false -> now_update1(10); 408 true -> {skip,"Unreliable in DEBUG build"} 409 end. 410 411 412now_update1(N) when N > 0 -> 413 T1_linear = linear_time(erlang:localtime()), 414 T1_now = microsecs(now()), 415 416 receive after 1008 -> ok end, 417 418 T2_linear = linear_time(erlang:localtime()), 419 T2_now = microsecs(now()), 420 421 Linear_Diff = (T2_linear-T1_linear)*1000000, 422 Now_Diff = T2_now-T1_now, 423 io:format("Localtime diff = ~p; now() diff = ~p", [Linear_Diff, Now_Diff]), 424 case abs(Linear_Diff - Now_Diff) of 425 Abs_Delta when Abs_Delta =< 40000 -> ok; 426 _ -> now_update1(N-1) 427 end; 428now_update1(0) -> 429 ct:fail("now_update zero"). 430 431time_warp_modes(Config) when is_list(Config) -> 432 %% All time warp modes always supported in 433 %% combination with no time correction... 434 check_time_warp_mode(Config, false, no_time_warp), 435 check_time_warp_mode(Config, false, single_time_warp), 436 check_time_warp_mode(Config, false, multi_time_warp), 437 438 erts_debug:set_internal_state(available_internal_state, true), 439 try 440 case erts_debug:get_internal_state({check_time_config, 441 true, no_time_warp}) of 442 false -> ok; 443 true -> check_time_warp_mode(Config, true, no_time_warp) 444 end, 445 case erts_debug:get_internal_state({check_time_config, 446 true, single_time_warp}) of 447 false -> ok; 448 true -> check_time_warp_mode(Config, true, single_time_warp) 449 end, 450 case erts_debug:get_internal_state({check_time_config, 451 true, multi_time_warp}) of 452 false -> ok; 453 true -> check_time_warp_mode(Config, true, multi_time_warp) 454 end 455 after 456 erts_debug:set_internal_state(available_internal_state, false) 457 end. 458 459check_time_warp_mode(Config, TimeCorrection, TimeWarpMode) -> 460 io:format("~n~n~n***** Testing TimeCorrection=~p TimeWarpMode=~p *****~n", 461 [TimeCorrection, TimeWarpMode]), 462 Mon = erlang:monitor(time_offset, clock_service), 463 _ = erlang:time_offset(), 464 Start = erlang:monotonic_time(1000), 465 MonotonicityTimeout = 2000, 466 {ok, Node} = start_node(Config, 467 "+c " ++ atom_to_list(TimeCorrection) 468 ++ " +C " ++ atom_to_list(TimeWarpMode)), 469 StartTime = rpc:call(Node, erlang, system_info, [start_time]), 470 Me = self(), 471 MonotincityTestStarted = make_ref(), 472 MonotincityTestDone = make_ref(), 473 spawn_link(Node, 474 fun () -> 475 Me ! MonotincityTestStarted, 476 cmp_times(erlang:start_timer(MonotonicityTimeout, 477 self(), 478 timeout), 479 erlang:monotonic_time()), 480 Me ! MonotincityTestDone 481 end), 482 receive MonotincityTestStarted -> ok end, 483 check_time_offset(Node, TimeWarpMode), 484 TimeWarpMode = rpc:call(Node, erlang, system_info, [time_warp_mode]), 485 TimeCorrection = rpc:call(Node, erlang, system_info, [time_correction]), 486 receive MonotincityTestDone -> ok end, 487 MonotonicTime = rpc:call(Node, erlang, monotonic_time, []), 488 MonotonicTimeUnit = rpc:call(Node, 489 erlang, 490 convert_time_unit, 491 [1, second, native]), 492 UpMilliSeconds = erlang:convert_time_unit(MonotonicTime - StartTime, 493 MonotonicTimeUnit, 494 millisecond), 495 io:format("UpMilliSeconds=~p~n", [UpMilliSeconds]), 496 End = erlang:monotonic_time(millisecond), 497 stop_node(Node), 498 try 499 true = (UpMilliSeconds > (98*MonotonicityTimeout) div 100), 500 true = (UpMilliSeconds < (102*(End-Start)) div 100) 501 catch 502 error:_ -> 503 io:format("Uptime inconsistency", []), 504 case {TimeCorrection, erlang:system_info(time_correction)} of 505 {true, true} -> 506 ct:fail(uptime_inconsistency); 507 {true, false} -> 508 _ = erlang:time_offset(), 509 receive 510 {'CHANGE', Mon, time_offset, clock_service, _} -> 511 ignore 512 after 1000 -> 513 ct:fail(uptime_inconsistency) 514 end; 515 _ -> 516 ignore 517 end 518 end, 519 erlang:demonitor(Mon, [flush]), 520 ok. 521 522check_time_offset(Node, no_time_warp) -> 523 final = rpc:call(Node, erlang, system_info, [time_offset]), 524 final = rpc:call(Node, erlang, system_flag, [time_offset, finalize]), 525 final = rpc:call(Node, erlang, system_info, [time_offset]); 526check_time_offset(Node, single_time_warp) -> 527 preliminary = rpc:call(Node, erlang, system_info, [time_offset]), 528 preliminary = rpc:call(Node, erlang, system_flag, [time_offset, finalize]), 529 final = rpc:call(Node, erlang, system_info, [time_offset]), 530 final = rpc:call(Node, erlang, system_flag, [time_offset, finalize]); 531check_time_offset(Node, multi_time_warp) -> 532 volatile = rpc:call(Node, erlang, system_info, [time_offset]), 533 volatile = rpc:call(Node, erlang, system_flag, [time_offset, finalize]), 534 volatile = rpc:call(Node, erlang, system_info, [time_offset]). 535 536monotonic_time_monotonicity(Config) when is_list(Config) -> 537 Done = erlang:start_timer(10000,self(),timeout), 538 cmp_times(Done, erlang:monotonic_time()). 539 540cmp_times(Done, X0) -> 541 X1 = erlang:monotonic_time(), 542 X2 = erlang:monotonic_time(), 543 X3 = erlang:monotonic_time(), 544 X4 = erlang:monotonic_time(), 545 X5 = erlang:monotonic_time(), 546 true = (X0 =< X1), 547 true = (X1 =< X2), 548 true = (X2 =< X3), 549 true = (X3 =< X4), 550 true = (X4 =< X5), 551 receive 552 {timeout, Done, timeout} -> 553 ok 554 after 0 -> 555 cmp_times(Done, X5) 556 end. 557 558-define(NR_OF_MONOTONIC_CALLS, 100000). 559 560monotonic_time_monotonicity_parallel(Config) when is_list(Config) -> 561 Me = self(), 562 Result = make_ref(), 563 Go = make_ref(), 564 UpAndRunning = make_ref(), 565 NoOnlnScheds = erlang:system_info(schedulers_online), 566 OffsetUI = erlang:unique_integer([monotonic]), 567 OffsetMT = erlang:monotonic_time(), 568 MinHSz = ?NR_OF_MONOTONIC_CALLS*(2 569 + 3 570 + erts_debug:flat_size(OffsetUI) 571 + erts_debug:flat_size(OffsetMT)), 572 Ps = lists:map( 573 fun (Sched) -> 574 spawn_opt( 575 fun () -> 576 Me ! {self(), UpAndRunning}, 577 receive Go -> ok end, 578 Res = fetch_monotonic(?NR_OF_MONOTONIC_CALLS, []), 579 Me ! {self(), Result, Sched, Res} 580 end, 581 [{scheduler, Sched}, 582 {priority, max}, 583 {min_heap_size, MinHSz}]) 584 end, 585 lists:seq(1, NoOnlnScheds)), 586 lists:foreach(fun (P) -> receive {P, UpAndRunning} -> ok end end, Ps), 587 lists:foreach(fun (P) -> P ! Go end, Ps), 588 TMs = recv_monotonics(Result, OffsetMT, OffsetUI, NoOnlnScheds, []), 589 true = check_monotonic_result(TMs, OffsetMT, OffsetUI, true). 590 591check_monotonic_result([{_Sched, _PrevUI, _MT, _PostUI}], 592 _OffsetMT, _OffsetUI, Res) -> 593 Res; 594check_monotonic_result([{_ASched, _APrevUI, AMT, APostUI} = A, 595 {_BSched, BPrevUI, BMT, _BPostUI} = B | _] = L, 596 OffsetMT, OffsetUI, Res) -> 597 NewRes = case (AMT =< BMT) orelse (BPrevUI < APostUI) of 598 true -> 599 Res; 600 false -> 601 io:format("INCONSISTENCY: ~p ~p~n", [A, B]), 602 false 603 end, 604 check_monotonic_result(tl(L), OffsetMT, OffsetUI, NewRes). 605 606recv_monotonics(_Result, _OffsetMT, _OffsetUI, 0, Acc) -> 607 lists:keysort(2, Acc); 608recv_monotonics(Result, OffsetMT, OffsetUI, N, Acc) -> 609 receive 610 {_, Result, Sched, Res} -> 611 CRes = convert_monotonic(Sched, OffsetMT, OffsetUI, Res, []), 612 recv_monotonics(Result, OffsetMT, OffsetUI, N-1, CRes ++ Acc) 613 end. 614 615convert_monotonic(_Sched, _OffsetMT, _OffsetUI, [{_MT, _UI}], Acc) -> 616 Acc; 617convert_monotonic(Sched, OffsetMT, OffsetUI, 618 [{MT, UI}, {_PrevMT, PrevUI} | _] = L, Acc) -> 619 convert_monotonic(Sched, OffsetMT, OffsetUI, tl(L), 620 [{Sched, PrevUI-OffsetUI, MT-OffsetMT, UI-OffsetUI} 621 | Acc]). 622 623fetch_monotonic(0, Acc) -> 624 Acc; 625fetch_monotonic(N, Acc) -> 626 MT = erlang:monotonic_time(), 627 UI = erlang:unique_integer([monotonic]), 628 fetch_monotonic(N-1, [{MT, UI} | Acc]). 629 630-define(CHK_RES_CONVS_TIMEOUT, 400). 631 632time_unit_conversion(Config) when is_list(Config) -> 633 Mon = erlang:monitor(time_offset, clock_service), 634 start_check_res_convs(Mon, 1000000000000), 635 start_check_res_convs(Mon, 2333333333333), 636 start_check_res_convs(Mon, 5732678356789), 637 erlang:demonitor(Mon, [flush]). 638 639start_check_res_convs(Mon, Res) -> 640 io:format("Checking ~p time_unit~n", [Res]), 641 check_res_convs(Mon, 642 erlang:start_timer(?CHK_RES_CONVS_TIMEOUT, 643 self(), 644 timeout), 645 Res). 646 647 648check_res_convs(Mon, Done, Res) -> 649 receive 650 {timeout, Done, timeout} -> 651 case Res div 10 of 652 0 -> 653 ok; 654 NewRes -> 655 start_check_res_convs(Mon, NewRes) 656 end 657 after 0 -> 658 do_check_res_convs(Mon, Done, Res) 659 end. 660 661do_check_res_convs(Mon, Done, Res) -> 662 TStart = erlang:monotonic_time(), 663 T = erlang:monotonic_time(Res), 664 TEnd = erlang:monotonic_time(), 665 TMin = erlang:convert_time_unit(TStart, native, Res), 666 TMax = erlang:convert_time_unit(TEnd, native, Res), 667 %io:format("~p =< ~p =< ~p~n", [TMin, T, TEnd]), 668 true = (TMin =< T), 669 true = (TMax >= T), 670 check_time_offset_res_conv(Mon, Res), 671 check_res_convs(Mon, Done, Res). 672 673 674check_time_offset_res_conv(Mon, Res) -> 675 TORes = erlang:time_offset(Res), 676 TO = erlang:time_offset(), 677 case erlang:convert_time_unit(TO, native, Res) of 678 TORes -> 679 ok; 680 TORes2 -> 681 case check_time_offset_change(Mon, TO, 1000) of 682 {TO, false} -> 683 ct:fail({time_unit_conversion_inconsistency, 684 TO, TORes, TORes2}); 685 {_NewTO, true} -> 686 io:format("time_offset changed", []), 687 check_time_offset_res_conv(Mon, Res) 688 end 689 end. 690 691signed_time_unit_conversion(Config) when is_list(Config) -> 692 chk_strc(1000000000, 1000000), 693 chk_strc(1000000000, 1000), 694 chk_strc(1000000000, 1), 695 chk_strc(1000000, 1000), 696 chk_strc(1000000, 1), 697 chk_strc(1000, 1), 698 chk_strc(4711, 17), 699 chk_strc(1 bsl 10, 1), 700 chk_strc(1 bsl 16, 10), 701 chk_strc(1 bsl 17, 1 bsl 8), 702 chk_strc((1 bsl 17) + 1, (1 bsl 8) - 1), 703 chk_strc(1 bsl 17, 11), 704 ok. 705 706chk_strc(Res0, Res1) -> 707 case (Res0 /= Res1) andalso (Res0 =< 1000000) andalso (Res1 =< 1000000) of 708 true -> 709 {FromRes, ToRes} = case Res0 > Res1 of 710 true -> {Res0, Res1}; 711 false -> {Res1, Res0} 712 end, 713 MinFromValuesPerToValue = FromRes div ToRes, 714 MaxFromValuesPerToValue = ((FromRes-1) div ToRes)+1, 715 io:format("~p -> ~p [~p, ~p]~n", 716 [FromRes, ToRes, 717 MinFromValuesPerToValue, MaxFromValuesPerToValue]), 718 chk_values_per_value(FromRes, ToRes, 719 -10*FromRes, 10*FromRes, 720 MinFromValuesPerToValue, 721 MaxFromValuesPerToValue, 722 undefined, MinFromValuesPerToValue); 723 _ -> 724 ok 725 end, 726 chk_random_values(Res0, Res1), 727 chk_random_values(Res1, Res0), 728 ok. 729 730chk_random_values(FR, TR) -> 731 io:format("rand values ~p -> ~p~n", [FR, TR]), 732 rand:seed(exsplus, {268438039,268440479,268439161}), 733 Values = lists:map(fun (_) -> rand:uniform(1 bsl 65) - (1 bsl 64) end, 734 lists:seq(1, 100000)), 735 CheckFun = fun (V) -> 736 CV = erlang:convert_time_unit(V, FR, TR), 737 case {(FR*CV) div TR =< V, 738 (FR*(CV+1)) div TR >= V} of 739 {true, true} -> 740 ok; 741 Failure -> 742 ct:fail({Failure, CV, V, FR, TR}) 743 end 744 end, 745 lists:foreach(CheckFun, Values). 746 747 748chk_values_per_value(_FromRes, _ToRes, 749 EndValue, EndValue, 750 MinFromValuesPerToValue, MaxFromValuesPerToValue, 751 _ToValue, FromValueCount) -> 752% io:format("~p [~p]~n", [EndValue, FromValueCount]), 753 case ((MinFromValuesPerToValue =< FromValueCount) 754 andalso (FromValueCount =< MaxFromValuesPerToValue)) of 755 false -> 756 ct:fail({MinFromValuesPerToValue, 757 FromValueCount, 758 MaxFromValuesPerToValue}); 759 true -> 760 ok 761 end; 762chk_values_per_value(FromRes, ToRes, Value, EndValue, 763 MinFromValuesPerToValue, MaxFromValuesPerToValue, 764 ToValue, FromValueCount) -> 765 case erlang:convert_time_unit(Value, FromRes, ToRes) of 766 ToValue -> 767 chk_values_per_value(FromRes, ToRes, 768 Value+1, EndValue, 769 MinFromValuesPerToValue, 770 MaxFromValuesPerToValue, 771 ToValue, FromValueCount+1); 772 NewToValue -> 773 case ((MinFromValuesPerToValue =< FromValueCount) 774 andalso (FromValueCount =< MaxFromValuesPerToValue)) of 775 false -> 776 ct:fail({MinFromValuesPerToValue, 777 FromValueCount, 778 MaxFromValuesPerToValue}); 779 true -> 780 % io:format("~p -> ~p [~p]~n", 781 % [Value, NewToValue, FromValueCount]), 782 chk_values_per_value(FromRes, ToRes, 783 Value+1, EndValue, 784 MinFromValuesPerToValue, 785 MaxFromValuesPerToValue, 786 NewToValue, 1) 787 end 788 end. 789 790erlang_timestamp(Config) when is_list(Config) -> 791 Mon = erlang:monitor(time_offset, clock_service), 792 {TO, _} = check_time_offset_change(Mon, 793 erlang:time_offset(), 794 0), 795 Done = erlang:start_timer(10000,self(),timeout), 796 ok = check_erlang_timestamp(Done, Mon, TO). 797 798check_erlang_timestamp(Done, Mon, TO) -> 799 receive 800 {timeout, Done, timeout} -> 801 erlang:demonitor(Mon, [flush]), 802 ok 803 after 0 -> 804 do_check_erlang_timestamp(Done, Mon, TO) 805 end. 806 807do_check_erlang_timestamp(Done, Mon, TO) -> 808 MinMon = erlang:monotonic_time(), 809 {MegaSec, Sec, MicroSec} = erlang:timestamp(), 810 MaxMon = erlang:monotonic_time(), 811 TsMin = erlang:convert_time_unit(MinMon+TO, 812 native, 813 microsecond), 814 TsMax = erlang:convert_time_unit(MaxMon+TO, 815 native, 816 microsecond), 817 TsTime = (MegaSec*1000000+Sec)*1000000+MicroSec, 818 case (TsMin =< TsTime) andalso (TsTime =< TsMax) of 819 true -> 820 NewTO = case erlang:time_offset() of 821 TO -> 822 TO; 823 _ -> 824 check_time_offset_change(Mon, TO, 0) 825 end, 826 check_erlang_timestamp(Done, Mon, NewTO); 827 false -> 828 io:format("TsMin=~p TsTime=~p TsMax=~p~n", [TsMin, TsTime, TsMax]), 829 io:format("Detected inconsistency; " 830 "checking for time_offset change...", []), 831 case check_time_offset_change(Mon, TO, 1000) of 832 {TO, false} -> 833 ct:fail(timestamp_inconsistency); 834 {NewTO, true} -> 835 io:format("time_offset changed", []), 836 check_erlang_timestamp(Done, Mon, NewTO) 837 end 838 end. 839 840check_time_offset_change(Mon, TO, Wait) -> 841 process_changed_time_offset(Mon, TO, false, Wait). 842 843process_changed_time_offset(Mon, TO, Changed, Wait) -> 844 receive 845 {'CHANGE', Mon, time_offset, clock_service, NewTO} -> 846 process_changed_time_offset(Mon, NewTO, true, Wait) 847 after Wait -> 848 case erlang:time_offset() of 849 TO -> 850 {TO, Changed}; 851 _OtherTO -> 852 receive 853 {'CHANGE', Mon, time_offset, clock_service, NewTO} -> 854 process_changed_time_offset(Mon, NewTO, true, Wait) 855 end 856 end 857 end. 858 859 860 861%% Returns the test data: a list of {Utc, Local} tuples. 862 863test_data() -> 864 {TZ,DSTTZ} = 865 case os:type() of 866 {unix,_} -> 867 case os:cmd("date '+%Z'") of 868 "SAST"++_ -> 869 {2,2}; 870 _ -> 871 {?timezone,?dst_timezone} 872 end; 873 _ -> 874 {?timezone,?dst_timezone} 875 end, 876 test_data(nondst_dates(), TZ) ++ 877 test_data(dst_dates(), DSTTZ) ++ 878 crossover_test_data(crossover_dates(), TZ). 879 880 881%% test_data1() -> 882%% test_data(nondst_dates(), ?timezone) ++ 883%% test_data(dst_dates(), ?dst_timezone) ++ 884%% crossover_test_data(crossover_dates(), ?timezone). 885 886crossover_test_data([{Year, Month, Day}|Rest], TimeZone) when TimeZone > 0 -> 887 Hour = 23, 888 Min = 35, 889 Sec = 55, 890 Utc = {{Year, Month, Day}, {Hour, Min, Sec}}, 891 Local = {{Year, Month, Day+1}, {Hour+TimeZone-24, Min, Sec}}, 892 [{Utc, Local}|crossover_test_data(Rest, TimeZone)]; 893crossover_test_data([{Year, Month, Day}|Rest], TimeZone) when TimeZone < 0 -> 894 Hour = 0, 895 Min = 23, 896 Sec = 12, 897 Utc = {{Year, Month, Day}, {Hour, Min, Sec}}, 898 Local = {{Year, Month, Day-1}, {Hour+TimeZone+24, Min, Sec}}, 899 [{Utc, Local}|crossover_test_data(Rest, TimeZone)]; 900crossover_test_data([], _) -> 901 []. 902 903test_data([Date|Rest], TimeZone) -> 904 Hour = 12, 905 Min = 45, 906 Sec = 7, 907 Utc = {Date, {Hour, Min, Sec}}, 908 Local = {Date, {Hour+TimeZone, Min, Sec}}, 909 [{Utc, Local}|test_data(Rest, TimeZone)]; 910test_data([], _) -> 911 []. 912 913nondst_dates() -> 914 [{1996, 01, 30}, 915 {1997, 01, 30}, 916 {1998, 01, 30}, 917 {1999, 01, 30}, 918 {1996, 02, 29}, 919 {1997, 02, 28}, 920 {1998, 02, 28}, 921 {1999, 02, 28}, 922 {1996, 03, 2}, 923 {1997, 03, 2}, 924 {1998, 03, 2}, 925 {1999, 03, 2}]. 926 927dst_dates() -> 928 [{1996, 06, 1}, 929 {1997, 06, 2}, 930 {1998, 06, 3}, 931 {1999, 06, 4}]. 932 933%% exakt utc {date(), time()} which corresponds to the same seconds since 1 jan 1970 934%% negative seconds are ok 935%% generated with date --date='1979-05-28 12:30:35 UTC' +%s 936ok_utc_seconds() -> [ 937 { {{1970, 1, 1},{ 0, 0, 0}}, 0 }, 938 { {{1970, 1, 1},{ 0, 0, 1}}, 1 }, 939 { {{1969,12,31},{23,59,59}}, -1 }, 940 { {{1920,12,31},{23,59,59}}, -1546300801 }, 941 { {{1600,02,19},{15,14,08}}, -11671807552 }, 942 { {{1979,05,28},{12,30,35}}, 296742635 }, 943 { {{1999,12,31},{23,59,59}}, 946684799 }, 944 { {{2000, 1, 1},{ 0, 0, 0}}, 946684800 }, 945 { {{2000, 1, 1},{ 0, 0, 1}}, 946684801 }, 946 947 { {{2038, 1,19},{03,14,07}}, 2147483647 }, % Sint32 full - 1 948 { {{2038, 1,19},{03,14,08}}, 2147483648 }, % Sint32 full 949 { {{2038, 1,19},{03,14,09}}, 2147483649 }, % Sint32 full + 1 950 951 { {{2106, 2, 7},{ 6,28,14}}, 4294967294 }, % Uint32 full 0xFFFFFFFF - 1 952 { {{2106, 2, 7},{ 6,28,15}}, 4294967295 }, % Uint32 full 0xFFFFFFFF 953 { {{2106, 2, 7},{ 6,28,16}}, 4294967296 }, % Uint32 full 0xFFFFFFFF + 1 954 { {{2012,12, 6},{16,28,08}}, 1354811288 }, 955 { {{2412,12, 6},{16,28,08}}, 13977592088 } 956 ]. 957 958 959%% The following dates should not be near the end or beginning of 960%% a month, because they will be used to test when the dates are 961%% different in UTC and local time. 962 963crossover_dates() -> 964 [{1996, 01, 25}, 965 {1997, 01, 25}, 966 {1998, 01, 25}, 967 {1999, 01, 25}, 968 {1996, 02, 27}, 969 {1997, 02, 27}, 970 {1998, 02, 27}, 971 {1999, 02, 27}]. 972 973bad_dates() -> 974 [{{1900, 7, 1}, {12, 0, 0}}, % Year 975 976 {{1996, 0, 20}, {12, 0, 0}}, % Month 977 {{1996, 13, 20}, {12, 0, 0}}, 978 979 {{1996, 1, 0}, {12, 0, 0}}, % Date 980 {{1996, 1, 32}, {12, 0, 0}}, 981 {{1996, 2, 30}, {12, 0, 0}}, 982 {{1997, 2, 29}, {12, 0, 0}}, 983 {{1998, 2, 29}, {12, 0, 0}}, 984 {{1999, 2, 29}, {12, 0, 0}}, 985 {{1996, 4, 31}, {12, 0, 0}}, 986 987 {{1996, 4, 30}, {-1, 0, 0}}, % Hour 988 {{1996, 4, 30}, {25, 0, 0}}, 989 990 {{1996, 4, 30}, {12,-1, 0}}, % Minute 991 {{1996, 4, 30}, {12, 60, 0}}, 992 993 {{1996, 4, 30}, {12, 0, -1}}, % Sec 994 {{1996, 4, 30}, {12, 0, 60}}]. 995 996start_node(Config, Args) -> 997 TestCase = proplists:get_value(testcase, Config), 998 PA = filename:dirname(code:which(?MODULE)), 999 ESTime = erlang:monotonic_time(1) + erlang:time_offset(1), 1000 Unique = erlang:unique_integer([positive]), 1001 Name = list_to_atom(atom_to_list(?MODULE) 1002 ++ "-" 1003 ++ atom_to_list(TestCase) 1004 ++ "-" 1005 ++ integer_to_list(ESTime) 1006 ++ "-" 1007 ++ integer_to_list(Unique)), 1008 test_server:start_node(Name, 1009 slave, 1010 [{args, "-pa " ++ PA ++ " " ++ Args}]). 1011 1012stop_node(Node) -> 1013 test_server:stop_node(Node). 1014