1: %% 2: %% %CopyrightBegin% 3: %% 4: %% Copyright Ericsson AB 1997-2013. All Rights Reserved. 5: %% 6: %% The contents of this file are subject to the Erlang Public License, 7: %% Version 1.1, (the "License"); you may not use this file except in 8: %% compliance with the License. You should have received a copy of the 9: %% Erlang Public License along with this software. If not, it can be 10: %% retrieved online at http://www.erlang.org/. 11: %% 12: %% Software distributed under the License is distributed on an "AS IS" 13: %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See 14: %% the License for the specific language governing rights and limitations 15: %% under the License. 16: %% 17: %% %CopyrightEnd% 18: %% 19: 20: -module(time_SUITE). 21: 22: %% "Time is on my side." -- The Rolling Stones 23: 24: %% Tests the BIFs: 25: %% erlang:localtime_to_universaltime/1 26: %% erlang:universaltime_to_localtime/1 27: %% date/0 28: %% time/0 29: %% now/0 30: %% 31: 32: -export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, 33: init_per_group/2,end_per_group/2, univ_to_local/1, local_to_univ/1, 34: bad_univ_to_local/1, bad_local_to_univ/1, 35: univ_to_seconds/1, seconds_to_univ/1, 36: consistency/1, 37: now_unique/1, now_update/1, timestamp/1]). 38: 39: -export([local_to_univ_utc/1]). 40: 41: -include_lib("test_server/include/test_server.hrl"). 42: 43: -export([linear_time/1]). 44: 45: %% The following defines the timezone in which the test is run. 46: %% It is interpreted as the number of hours to be added to UTC 47: %% to obtain the local time. The number will be positive east 48: %% of Greenwhich, negative west of Greenwhich. 49: %% 50: %% Allowable range is -12 through 11. 51: 52: -define(timezone, 1). 53: 54: %% Similarly to timezone, but the difference when Daylight Saving Time 55: %% is in use. [Same range.] 56: 57: -define(dst_timezone, 2). 58: 59: suite() -> [{ct_hooks,[ts_install_cth]}]. 60: 61: all() -> 62: [univ_to_local, local_to_univ, local_to_univ_utc, 63: bad_univ_to_local, bad_local_to_univ, 64: univ_to_seconds, seconds_to_univ, 65: consistency, 66: {group, now}, timestamp]. 67: 68: groups() -> 69: [{now, [], [now_unique, now_update]}]. 70: 71: init_per_suite(Config) -> 72: Config. 73: 74: end_per_suite(_Config) -> 75: ok. 76: 77: init_per_group(_GroupName, Config) -> 78: Config. 79: 80: end_per_group(_GroupName, Config) -> 81: Config. 82: 83: 84: local_to_univ_utc(suite) -> 85: []; 86: local_to_univ_utc(doc) -> 87: ["Test that DST = true on timezones without DST is ignored"]; 88: local_to_univ_utc(Config) when is_list(Config) -> 89: case os:type() of 90: {unix,_} -> 91: %% TZ variable has a meaning 92: ?line {ok, Node} = 93: test_server:start_node(local_univ_utc,peer, 94: [{args, "-env TZ UTC"}]), 95: ?line {{2008,8,1},{0,0,0}} = 96: rpc:call(Node, 97: erlang,localtime_to_universaltime, 98: [{{2008, 8, 1}, {0, 0, 0}}, 99: false]), 100: ?line {{2008,8,1},{0,0,0}} = 101: rpc:call(Node, 102: erlang,localtime_to_universaltime, 103: [{{2008, 8, 1}, {0, 0, 0}}, 104: true]), 105: ?line [{{2008,8,1},{0,0,0}}] = 106: rpc:call(Node, 107: calendar,local_time_to_universal_time_dst, 108: [{{2008, 8, 1}, {0, 0, 0}}]), 109: ?line test_server:stop_node(Node), 110: ok; 111: _ -> 112: {skip,"Only valid on Unix"} 113: end. 114: 115: 116: %% Tests conversion from univeral to local time. 117: 118: univ_to_local(Config) when is_list(Config) -> 119: ?line test_univ_to_local(test_data()). 120: 121: test_univ_to_local([{Utc, Local}|Rest]) -> 122: ?line io:format("Testing ~p => ~p~n", [Local, Utc]), 123: ?line Local = erlang:universaltime_to_localtime(Utc), 124: ?line test_univ_to_local(Rest); 125: test_univ_to_local([]) -> 126: ok. 127: 128: %% Tests conversion from local to universal time. 129: 130: local_to_univ(Config) when is_list(Config) -> 131: ?line test_local_to_univ(test_data()). 132: 133: test_local_to_univ([{Utc, Local}|Rest]) -> 134: ?line io:format("Testing ~p => ~p~n", [Utc, Local]), 135: ?line Utc = erlang:localtime_to_universaltime(Local), 136: ?line test_local_to_univ(Rest); 137: test_local_to_univ([]) -> 138: ok. 139: 140: %% Test bad arguments to erlang:universaltime_to_localtime; should 141: %% generate a badarg. 142: 143: bad_univ_to_local(Config) when is_list(Config) -> 144: ?line bad_test_univ_to_local(bad_dates()). 145: 146: bad_test_univ_to_local([Utc|Rest]) -> 147: ?line io:format("Testing ~p~n", [Utc]), 148: ?line case catch erlang:universaltime_to_localtime(Utc) of 149: {'EXIT', {badarg, _}} -> bad_test_univ_to_local(Rest) 150: end; 151: bad_test_univ_to_local([]) -> 152: ok. 153: 154: %% Test bad arguments to erlang:localtime_to_universaltime/1; should 155: %% generate a badarg. 156: 157: bad_local_to_univ(Config) when is_list(Config) -> 158: ?line bad_test_local_to_univ(bad_dates()). 159: 160: bad_test_local_to_univ([Local|Rest]) -> 161: ?line io:format("Testing ~p~n", [Local]), 162: ?line case catch erlang:localtime_to_universaltime(Local) of 163: {'EXIT', {badarg, _}} -> bad_test_local_to_univ(Rest) 164: end; 165: bad_test_local_to_univ([]) -> 166: ok. 167: 168: 169: %% Test universaltime to seconds conversions 170: univ_to_seconds(Config) when is_list(Config) -> 171: test_univ_to_seconds(ok_utc_seconds()). 172: 173: test_univ_to_seconds([{Datetime, Seconds}|DSs]) -> 174: io:format("universaltime = ~p -> seconds = ~p", [Datetime, Seconds]), 175: Seconds = erlang:universaltime_to_posixtime(Datetime), 176: test_univ_to_seconds(DSs); 177: test_univ_to_seconds([]) -> 178: ok. 179: 180: %% Test seconds to universaltime conversions 181: seconds_to_univ(Config) when is_list(Config) -> 182: test_seconds_to_univ(ok_utc_seconds()). 183: 184: test_seconds_to_univ([{Datetime, Seconds}|DSs]) -> 185: io:format("universaltime = ~p <- seconds = ~p", [Datetime, Seconds]), 186: Datetime = erlang:posixtime_to_universaltime(Seconds), 187: test_seconds_to_univ(DSs); 188: test_seconds_to_univ([]) -> 189: ok. 190: 191: 192: %% Test that the the different time functions return 193: %% consistent results. (See the test case for assumptions 194: %% and limitations.) 195: consistency(Config) when is_list(Config) -> 196: %% Test the following equations: 197: %% date() & time() == erlang:localtime() 198: %% erlang:universaltime() + timezone == erlang:localtime() 199: %% 200: %% Assumptions: 201: %% Middle-European time zone, EU rules for daylight-saving time. 202: %% 203: %% Limitations: 204: %% Localtime and universaltime must be in the same month. 205: %% Daylight-saving calculations are incorrect from the last 206: %% Sunday of March and October to the end of the month. 207: 208: ?line ok = compare_date_time_and_localtime(16), 209: ?line ok = compare_local_and_universal(16). 210: 211: compare_date_time_and_localtime(Times) when Times > 0 -> 212: ?line {Year, Mon, Day} = date(), 213: ?line {Hour, Min, Sec} = time(), 214: ?line case erlang:localtime() of 215: {{Year, Mon, Day}, {Hour, Min, Sec}} -> ok; 216: _ -> compare_date_time_and_localtime(Times-1) 217: end; 218: compare_date_time_and_localtime(0) -> 219: error. 220: 221: compare_local_and_universal(Times) when Times > 0 -> 222: case compare(erlang:universaltime(), erlang:localtime()) of 223: true -> ok; 224: false -> compare_local_and_universal(Times-1) 225: end; 226: compare_local_and_universal(0) -> 227: error. 228: 229: compare(Utc0, Local) -> 230: io:format("local = ~p, utc = ~p", [Local, Utc0]), 231: Utc = linear_time(Utc0)+effective_timezone(Utc0)*3600, 232: case linear_time(Local) of 233: Utc -> true; 234: Other -> 235: io:format("Failed: local = ~p, utc = ~p~n", 236: [Other, Utc]), 237: false 238: end. 239: 240: %% This function converts a date and time to a linear time. 241: %% Two linear times can be subtracted to give their difference 242: %% in seconds. 243: %% 244: %% XXX Limitations: Simplified leap year calc will fail for 2100 :-) 245: 246: linear_time({{Year, Mon, Day}, {Hour, Min, Sec}}) -> 247: 86400*(year_to_days(Year) + month_to_days(Year,Mon) + (Day-1)) + 248: 3600*Hour + 60*Min + Sec. 249: 250: year_to_days(Year) -> 251: Year * 365 + (Year-1) div 4. 252: 253: month_to_days(Year, Mon) -> 254: DoM = [31,days_in_february(Year),31,30,31,30,31,31,30,31,30,31], 255: {PastMonths,_} = lists:split(Mon-1, DoM), 256: lists:sum(PastMonths). 257: 258: days_in_february(Year) -> 259: case (Year rem 4) of 260: 0 -> 29; 261: _ -> 28 262: end. 263: 264: %% This functions returns either the normal timezone or the 265: %% the DST timezone, depending on the given UTC time. 266: %% 267: %% XXX This function uses an approximation of the EU rule for 268: %% daylight saving time. This function will fail in the 269: %% following intervals: After the last Sunday in March upto 270: %% the end of March, and after the last Sunday in October 271: %% upto the end of October. 272: 273: effective_timezone(Time) -> 274: case os:type() of 275: {unix,_} -> 276: case os:cmd("date '+%Z'") of 277: "SAST"++_ -> 278: 2; 279: _ -> 280: effective_timezone1(Time) 281: end; 282: _ -> 283: effective_timezone1(Time) 284: end. 285: 286: effective_timezone1({{_Year,Mon,_Day}, _}) when Mon < 4 -> 287: ?timezone; 288: effective_timezone1({{_Year,Mon,_Day}, _}) when Mon > 10 -> 289: ?timezone; 290: effective_timezone1(_) -> 291: ?dst_timezone. 292: 293: %% Test (the bif) os:timestamp/0, which is something quite like, but not 294: %% similar to erlang:now... 295: 296: timestamp(suite) -> 297: []; 298: timestamp(doc) -> 299: ["Test that os:timestamp works."]; 300: timestamp(Config) when is_list(Config) -> 301: repeating_timestamp_check(100000). 302: 303: repeating_timestamp_check(0) -> 304: ok; 305: repeating_timestamp_check(N) -> 306: {A,B,C} = TS = os:timestamp(), 307: if 308: is_integer(A), 309: is_integer(B), 310: is_integer(C), 311: B < 1000000, 312: C < 1000000 -> 313: ok; 314: true -> 315: test_server:fail( 316: lists:flatten( 317: io_lib:format("Strange return from os:timestamp/0 ~w~n",[TS]))) 318: end, 319: %% I assume the now and timestamp should not differ more than 1 hour, 320: %% which is safe assuming the system has not had a large time-warp 321: %% during the testrun... 322: Secs = A*1000000+B+round(C/1000000), 323: {NA,NB,NC} = erlang:now(), 324: NSecs = NA*1000000+NB+round(NC/1000000), 325: case Secs - NSecs of 326: TooLarge when TooLarge > 3600 -> 327: test_server:fail( 328: lists:flatten( 329: io_lib:format("os:timestamp/0 is ~w s more than erlang:now/0", 330: [TooLarge]))); 331: TooSmall when TooSmall < -3600 -> 332: test_server:fail( 333: lists:flatten( 334: io_lib:format("os:timestamp/0 is ~w s less than erlang:now/0", 335: [-TooSmall]))); 336: _ -> 337: ok 338: end, 339: repeating_timestamp_check(N-1). 340: 341: 342: %% Test now/0. 343: 344: 345: %% Tests that successive calls to now/0 returns different values. 346: %% Also returns a comment string with the median difference between 347: %% times (in microseconds). 348: 349: now_unique(Config) when is_list(Config) -> 350: ?line now_unique(1000, now(), []), 351: ?line fast_now_unique(100000, now()). 352: 353: now_unique(N, Previous, Result) when N > 0 -> 354: ?line case now() of 355: Previous -> 356: test_server:fail("now/0 returned the same value twice"); 357: New -> 358: now_unique(N-1, New, [New|Result]) 359: end; 360: now_unique(0, _, [Then|Rest]) -> 361: ?line now_calc_increment(Rest, microsecs(Then), []). 362: 363: now_calc_increment([Then|Rest], Previous, _Result) -> 364: ?line This = microsecs(Then), 365: ?line now_calc_increment(Rest, This, [Previous-This]); 366: now_calc_increment([], _, Differences) -> 367: {comment, "Median increment: " ++ integer_to_list(median(Differences))}. 368: 369: fast_now_unique(0, _) -> ok; 370: fast_now_unique(N, Then) -> 371: case now() of 372: Then -> 373: ?line ?t:fail("now/0 returned the same value twice"); 374: Now -> 375: fast_now_unique(N-1, Now) 376: end. 377: 378: median(Unsorted_List) -> 379: ?line Length = length(Unsorted_List), 380: ?line List = lists:sort(Unsorted_List), 381: ?line case Length rem 2 of 382: 0 -> % Even length. 383: [A, B] = lists:nthtail((Length div 2)-1, List), 384: (A+B)/2; 385: 1 -> % Odd list length. 386: lists:nth((Length div 2)+1, List) 387: end. 388: 389: microsecs({Mega_Secs, Secs, Microsecs}) -> 390: (Mega_Secs*1000000+Secs)*1000000+Microsecs. 391: 392: %% Test that the time differences returned by two calls to 393: %% now/0 one second apart is comparable to the difference of two 394: %% calls to erlang:localtime(). 395: 396: now_update(Config) when is_list(Config) -> 397: case ?t:is_debug() of 398: false -> ?line now_update1(10); 399: true -> {skip,"Unreliable in DEBUG build"} 400: end. 401: 402: 403: now_update1(N) when N > 0 -> 404: ?line T1_linear = linear_time(erlang:localtime()), 405: ?line T1_now = microsecs(now()), 406: 407: ?line receive after 1008 -> ok end, 408: 409: ?line T2_linear = linear_time(erlang:localtime()), 410: ?line T2_now = microsecs(now()), 411: 412: ?line Linear_Diff = (T2_linear-T1_linear)*1000000, 413: ?line Now_Diff = T2_now-T1_now, 414: test_server:format("Localtime diff = ~p; now() diff = ~p", 415: [Linear_Diff, Now_Diff]), 416: ?line case abs(Linear_Diff - Now_Diff) of 417: Abs_Delta when Abs_Delta =< 40000 -> ok; 418: _ -> now_update1(N-1) 419: end; 420: now_update1(0) -> 421: ?line test_server:fail(). 422: 423: %% Returns the test data: a list of {Utc, Local} tuples. 424: 425: test_data() -> 426: {TZ,DSTTZ} = 427: case os:type() of 428: {unix,_} -> 429: case os:cmd("date '+%Z'") of 430: "SAST"++_ -> 431: {2,2}; 432: _ -> 433: {?timezone,?dst_timezone} 434: end; 435: _ -> 436: {?timezone,?dst_timezone} 437: end, 438: ?line test_data(nondst_dates(), TZ) ++ 439: test_data(dst_dates(), DSTTZ) ++ 440: crossover_test_data(crossover_dates(), TZ). 441: 442: 443: %% test_data1() -> 444: %% ?line test_data(nondst_dates(), ?timezone) ++ 445: %% test_data(dst_dates(), ?dst_timezone) ++ 446: %% crossover_test_data(crossover_dates(), ?timezone). 447: 448: crossover_test_data([{Year, Month, Day}|Rest], TimeZone) when TimeZone > 0 -> 449: Hour = 23, 450: Min = 35, 451: Sec = 55, 452: ?line Utc = {{Year, Month, Day}, {Hour, Min, Sec}}, 453: ?line Local = {{Year, Month, Day+1}, {Hour+TimeZone-24, Min, Sec}}, 454: ?line [{Utc, Local}|crossover_test_data(Rest, TimeZone)]; 455: crossover_test_data([{Year, Month, Day}|Rest], TimeZone) when TimeZone < 0 -> 456: Hour = 0, 457: Min = 23, 458: Sec = 12, 459: ?line Utc = {{Year, Month, Day}, {Hour, Min, Sec}}, 460: ?line Local = {{Year, Month, Day-1}, {Hour+TimeZone+24, Min, Sec}}, 461: ?line [{Utc, Local}|crossover_test_data(Rest, TimeZone)]; 462: crossover_test_data([], _) -> 463: []. 464: 465: test_data([Date|Rest], TimeZone) -> 466: Hour = 12, 467: Min = 45, 468: Sec = 7, 469: ?line Utc = {Date, {Hour, Min, Sec}}, 470: ?line Local = {Date, {Hour+TimeZone, Min, Sec}}, 471: ?line [{Utc, Local}|test_data(Rest, TimeZone)]; 472: test_data([], _) -> 473: []. 474: 475: nondst_dates() -> 476: [{1996, 01, 30}, 477: {1997, 01, 30}, 478: {1998, 01, 30}, 479: {1999, 01, 30}, 480: {1996, 02, 29}, 481: {1997, 02, 28}, 482: {1998, 02, 28}, 483: {1999, 02, 28}, 484: {1996, 03, 2}, 485: {1997, 03, 2}, 486: {1998, 03, 2}, 487: {1999, 03, 2}]. 488: 489: dst_dates() -> 490: [{1996, 06, 1}, 491: {1997, 06, 2}, 492: {1998, 06, 3}, 493: {1999, 06, 4}]. 494: 495: %% exakt utc {date(), time()} which corresponds to the same seconds since 1 jan 1970 496: %% negative seconds are ok 497: %% generated with date --date='1979-05-28 12:30:35 UTC' +%s 498: ok_utc_seconds() -> [ 499: { {{1970, 1, 1},{ 0, 0, 0}}, 0 }, 500: { {{1970, 1, 1},{ 0, 0, 1}}, 1 }, 501: { {{1969,12,31},{23,59,59}}, -1 }, 502: { {{1920,12,31},{23,59,59}}, -1546300801 }, 503: { {{1600,02,19},{15,14,08}}, -11671807552 }, 504: { {{1979,05,28},{12,30,35}}, 296742635 }, 505: { {{1999,12,31},{23,59,59}}, 946684799 }, 506: { {{2000, 1, 1},{ 0, 0, 0}}, 946684800 }, 507: { {{2000, 1, 1},{ 0, 0, 1}}, 946684801 }, 508: 509: { {{2038, 1,19},{03,14,07}}, 2147483647 }, % Sint32 full - 1 510: { {{2038, 1,19},{03,14,08}}, 2147483648 }, % Sint32 full 511: { {{2038, 1,19},{03,14,09}}, 2147483649 }, % Sint32 full + 1 512: 513: { {{2106, 2, 7},{ 6,28,14}}, 4294967294 }, % Uint32 full 0xFFFFFFFF - 1 514: { {{2106, 2, 7},{ 6,28,15}}, 4294967295 }, % Uint32 full 0xFFFFFFFF 515: { {{2106, 2, 7},{ 6,28,16}}, 4294967296 }, % Uint32 full 0xFFFFFFFF + 1 516: { {{2012,12, 6},{16,28,08}}, 1354811288 }, 517: { {{2412,12, 6},{16,28,08}}, 13977592088 } 518: ]. 519: 520: 521: %% The following dates should not be near the end or beginning of 522: %% a month, because they will be used to test when the dates are 523: %% different in UTC and local time. 524: 525: crossover_dates() -> 526: [{1996, 01, 25}, 527: {1997, 01, 25}, 528: {1998, 01, 25}, 529: {1999, 01, 25}, 530: {1996, 02, 27}, 531: {1997, 02, 27}, 532: {1998, 02, 27}, 533: {1999, 02, 27}]. 534: 535: bad_dates() -> 536: [{{1900, 7, 1}, {12, 0, 0}}, % Year 537: 538: {{1996, 0, 20}, {12, 0, 0}}, % Month 539: {{1996, 13, 20}, {12, 0, 0}}, 540: 541: {{1996, 1, 0}, {12, 0, 0}}, % Date 542: {{1996, 1, 32}, {12, 0, 0}}, 543: {{1996, 2, 30}, {12, 0, 0}}, 544: {{1997, 2, 29}, {12, 0, 0}}, 545: {{1998, 2, 29}, {12, 0, 0}}, 546: {{1999, 2, 29}, {12, 0, 0}}, 547: {{1996, 4, 31}, {12, 0, 0}}, 548: 549: {{1996, 4, 30}, {-1, 0, 0}}, % Hour 550: {{1996, 4, 30}, {25, 0, 0}}, 551: 552: {{1996, 4, 30}, {12,-1, 0}}, % Minute 553: {{1996, 4, 30}, {12, 60, 0}}, 554: 555: {{1996, 4, 30}, {12, 0, -1}}, % Sec 556: {{1996, 4, 30}, {12, 0, 60}}]. 557: