Current section

Files

Jump to
cuttlefish src cuttlefish_duration.erl
Raw

src/cuttlefish_duration.erl

%% -------------------------------------------------------------------
%%
%% cuttlefish_duration: complexity for parsing durations
%%
%% Copyright (c) 2013 Basho Technologies, Inc. All Rights Reserved.
%%
%% This file is provided to you 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.
%%
%% -------------------------------------------------------------------
-module(cuttlefish_duration).
-opaque time_unit() :: f | w | d | h | m | s | ms.
-export_type([time_unit/0]).
-include("cuttlefish_duration.hrl").
-ifdef(TEST).
-include_lib("eunit/include/eunit.hrl").
-endif.
-export([parse/1, parse/2, to_string/2]).
-spec to_string(integer(), time_unit()) -> string().
to_string(Fortnights, f) -> milliseconds(Fortnights * ?FORTNIGHT);
to_string(Weeks, w) -> milliseconds(Weeks * ?WEEK);
to_string(Days, d) -> milliseconds(Days * ?DAY);
to_string(Hours, h) -> milliseconds(Hours * ?HOUR);
to_string(Minutes, m) -> milliseconds(Minutes * ?MINUTE);
to_string(Seconds, s) -> milliseconds(Seconds * ?SECOND);
to_string(Millis, ms) -> milliseconds(Millis).
milliseconds(Millis) ->
Units = lists:filter(fun({N, _Unit}) -> N =/= 0 end, [
{ Millis div ?WEEK, "w"},
{(Millis rem ?WEEK) div ?DAY, "d"},
{(Millis rem ?DAY) div ?HOUR, "h"},
{(Millis rem ?HOUR) div ?MINUTE, "m"},
{(Millis rem ?MINUTE) div ?SECOND, "s"},
{ Millis rem ?SECOND, "ms"}
]),
lists:flatten([
integer_to_list(N) ++ Unit
|| {N, Unit} <- Units]).
-spec parse(string(), time_unit()) -> integer().
parse(DurationString, ms) -> parse(DurationString);
parse(DurationString, Unit) ->
case parse(DurationString) of
{error, _}=Err ->
Err;
Num when is_number(Num) ->
{Unit, Multiplier} = lists:keyfind(Unit, 1, ?MULTIPLIERS),
cuttlefish_util:ceiling(Num / Multiplier)
end.
-spec parse(string()) -> integer() | cuttlefish_error:error().
parse(InputDurationString) ->
DurationString = string:to_lower(InputDurationString),
case cuttlefish_duration_parse:parse(DurationString) of
Float when is_float(Float) ->
cuttlefish_util:ceiling(Float);
Int when is_integer(Int) ->
Int;
_ ->
{error, {duration, InputDurationString}}
end.
-ifdef(TEST).
milliseconds_test() ->
?assertEqual("500ms", milliseconds(500)),
?assertEqual("1s500ms", milliseconds(1500)),
?assertEqual("30m", milliseconds(1800000)),
?assertEqual("1w1d1h1m1s1ms", milliseconds(694861001)),
ok.
seconds_test() ->
?assertEqual("50s", to_string(50, s)),
?assertEqual("1m1s", to_string(61, s)),
?assertEqual("30m", to_string(1800, s)),
?assertEqual("1w1d1h1m1s", to_string(694861, s)),
ok.
parse_test() ->
test_parse(500, "500ms"),
test_parse(500, ".5s"),
test_parse(1001, "1s1ms"),
test_parse(1599, "1s599ms"),
test_parse(1599, "1.599s"),
test_parse(1600, "1.5999s"),
test_parse(60000, "1m"),
test_parse(60000, "60s"),
test_parse(60000, "60000ms"),
test_parse(90000, "1.5m"),
test_parse(90000, "1m30s"),
test_parse(90000, "1m29s1000ms"),
test_parse(1800000, ".5h"),
test_parse(3600000, "1h"),
test_parse(3601000, "1h1s"),
test_parse(3660000, "1h1m"),
test_parse(3661000, "1h1m1s"),
test_parse(3661001, "1h1m1s1ms"),
test_parse(3600000, "60m"),
test_parse(5400000, "90m"),
test_parse(5401000, "90m1s"),
test_parse(5401001, "90m1s1ms"),
test_parse(5400000, "1h30m"),
test_parse(5401000, "1h30m1s"),
test_parse(3660000, "1h1m"),
test_parse(86400000, "1d"),
test_parse(86401000, "1d1s"),
test_parse(86401001, "1d1s1ms"),
test_parse(604800000, "1w"),
test_parse(691200000, "1w1d"),
test_parse(694800000, "1w1d1h"),
test_parse(694860000, "1w1d1h1m"),
test_parse(694861000, "1w1d1h1m1s"),
test_parse(694861001, "1w1d1h1m1s1ms"),
%% Weird but ok?
test_parse(121001, "1m1s1ms1m"),
%% Easter Egg
test_parse(1904461001, "1f1w1d1h1m1s1ms"),
ok.
test_parse(ExpectedMillis, StringToParse) ->
?assertEqual(ExpectedMillis, parse(StringToParse)).
parse_2_test() ->
?assertEqual(1, parse("1ms", ms)),
?assertEqual(1, parse("1ms", s)),
?assertEqual(1, parse("1ms", m)),
?assertEqual(1, parse("1ms", h)),
?assertEqual(1, parse("1ms", d)),
?assertEqual(1, parse("1ms", w)),
?assertEqual(1, parse("1ms", f)),
ok.
to_string_test() ->
?assertEqual("2w", to_string(1, f)),
?assertEqual("2w", to_string(2, w)),
?assertEqual("1w", to_string(1, w)),
?assertEqual("1d", to_string(1, d)),
?assertEqual("1w", to_string(7, d)),
?assertEqual("1h", to_string(1, h)),
?assertEqual("1m", to_string(1, m)),
ok.
parse_error_test() ->
?assertMatch({error, _}, parse("1q")),
%% This previously raised badarith because it did not check the
%% return value of parse/1.
?assertMatch({error, _}, try parse("1q", h) catch _:E -> E end),
ok.
-endif.