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: