381 lines
12 KiB
Erlang
381 lines
12 KiB
Erlang
-module(gleam@time@duration).
|
|
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
|
|
-define(FILEPATH, "src/gleam/time/duration.gleam").
|
|
-export([approximate/1, compare/2, difference/2, add/2, seconds/1, minutes/1, hours/1, milliseconds/1, nanoseconds/1, to_seconds/1, to_seconds_and_nanoseconds/1, to_iso8601_string/1]).
|
|
-export_type([duration/0, unit/0]).
|
|
|
|
-if(?OTP_RELEASE >= 27).
|
|
-define(MODULEDOC(Str), -moduledoc(Str)).
|
|
-define(DOC(Str), -doc(Str)).
|
|
-else.
|
|
-define(MODULEDOC(Str), -compile([])).
|
|
-define(DOC(Str), -compile([])).
|
|
-endif.
|
|
|
|
-opaque duration() :: {duration, integer(), integer()}.
|
|
|
|
-type unit() :: nanosecond |
|
|
microsecond |
|
|
millisecond |
|
|
second |
|
|
minute |
|
|
hour |
|
|
day |
|
|
week |
|
|
month |
|
|
year.
|
|
|
|
-file("src/gleam/time/duration.gleam", 110).
|
|
?DOC(
|
|
" Ensure the duration is represented with `nanoseconds` being positive and\n"
|
|
" less than 1 second.\n"
|
|
"\n"
|
|
" This function does not change the amount of time that the duratoin refers\n"
|
|
" to, it only adjusts the values used to represent the time.\n"
|
|
).
|
|
-spec normalise(duration()) -> duration().
|
|
normalise(Duration) ->
|
|
Multiplier = 1000000000,
|
|
Nanoseconds = case Multiplier of
|
|
0 -> 0;
|
|
Gleam@denominator -> erlang:element(3, Duration) rem Gleam@denominator
|
|
end,
|
|
Overflow = erlang:element(3, Duration) - Nanoseconds,
|
|
Seconds = erlang:element(2, Duration) + (case Multiplier of
|
|
0 -> 0;
|
|
Gleam@denominator@1 -> Overflow div Gleam@denominator@1
|
|
end),
|
|
case Nanoseconds >= 0 of
|
|
true ->
|
|
{duration, Seconds, Nanoseconds};
|
|
|
|
false ->
|
|
{duration, Seconds - 1, Multiplier + Nanoseconds}
|
|
end.
|
|
|
|
-file("src/gleam/time/duration.gleam", 76).
|
|
?DOC(
|
|
" Convert a duration to a number of the largest number of a unit, serving as\n"
|
|
" a rough description of the duration that a human can understand.\n"
|
|
"\n"
|
|
" The size used for each unit are described in the documentation for the\n"
|
|
" `Unit` type.\n"
|
|
"\n"
|
|
" ```gleam\n"
|
|
" seconds(125)\n"
|
|
" |> approximate\n"
|
|
" // -> #(2, Minute)\n"
|
|
" ```\n"
|
|
"\n"
|
|
" This function rounds _towards zero_. This means that if a duration is just\n"
|
|
" short of 2 days then it will approximate to 1 day.\n"
|
|
"\n"
|
|
" ```gleam\n"
|
|
" hours(47)\n"
|
|
" |> approximate\n"
|
|
" // -> #(1, Day)\n"
|
|
" ```\n"
|
|
).
|
|
-spec approximate(duration()) -> {integer(), unit()}.
|
|
approximate(Duration) ->
|
|
{duration, S, Ns} = Duration,
|
|
Minute = 60,
|
|
Hour = Minute * 60,
|
|
Day = Hour * 24,
|
|
Week = Day * 7,
|
|
Year = (Day * 365) + (Hour * 6),
|
|
Month = Year div 12,
|
|
Microsecond = 1000,
|
|
Millisecond = Microsecond * 1000,
|
|
case nil of
|
|
_ when S < 0 ->
|
|
{Amount, Unit} = begin
|
|
_pipe = {duration, - S, - Ns},
|
|
_pipe@1 = normalise(_pipe),
|
|
approximate(_pipe@1)
|
|
end,
|
|
{- Amount, Unit};
|
|
|
|
_ when S >= Year ->
|
|
{case Year of
|
|
0 -> 0;
|
|
Gleam@denominator -> S div Gleam@denominator
|
|
end, year};
|
|
|
|
_ when S >= Month ->
|
|
{case Month of
|
|
0 -> 0;
|
|
Gleam@denominator@1 -> S div Gleam@denominator@1
|
|
end, month};
|
|
|
|
_ when S >= Week ->
|
|
{case Week of
|
|
0 -> 0;
|
|
Gleam@denominator@2 -> S div Gleam@denominator@2
|
|
end, week};
|
|
|
|
_ when S >= Day ->
|
|
{case Day of
|
|
0 -> 0;
|
|
Gleam@denominator@3 -> S div Gleam@denominator@3
|
|
end, day};
|
|
|
|
_ when S >= Hour ->
|
|
{case Hour of
|
|
0 -> 0;
|
|
Gleam@denominator@4 -> S div Gleam@denominator@4
|
|
end, hour};
|
|
|
|
_ when S >= Minute ->
|
|
{case Minute of
|
|
0 -> 0;
|
|
Gleam@denominator@5 -> S div Gleam@denominator@5
|
|
end, minute};
|
|
|
|
_ when S > 0 ->
|
|
{S, second};
|
|
|
|
_ when Ns >= Millisecond ->
|
|
{case Millisecond of
|
|
0 -> 0;
|
|
Gleam@denominator@6 -> Ns div Gleam@denominator@6
|
|
end, millisecond};
|
|
|
|
_ when Ns >= Microsecond ->
|
|
{case Microsecond of
|
|
0 -> 0;
|
|
Gleam@denominator@7 -> Ns div Gleam@denominator@7
|
|
end, microsecond};
|
|
|
|
_ ->
|
|
{Ns, nanosecond}
|
|
end.
|
|
|
|
-file("src/gleam/time/duration.gleam", 140).
|
|
?DOC(
|
|
" Compare one duration to another, indicating whether the first spans a\n"
|
|
" larger amount of time (and so is greater) or smaller amount of time (and so\n"
|
|
" is lesser) than the second.\n"
|
|
"\n"
|
|
" # Examples\n"
|
|
"\n"
|
|
" ```gleam\n"
|
|
" compare(seconds(1), seconds(2))\n"
|
|
" // -> order.Lt\n"
|
|
" ```\n"
|
|
"\n"
|
|
" Whether a duration is negative or positive doesn't matter for comparing\n"
|
|
" them, only the amount of time spanned matters.\n"
|
|
"\n"
|
|
" ```gleam\n"
|
|
" compare(seconds(-2), seconds(1))\n"
|
|
" // -> order.Gt\n"
|
|
" ```\n"
|
|
).
|
|
-spec compare(duration(), duration()) -> gleam@order:order().
|
|
compare(Left, Right) ->
|
|
Parts = fun(X) -> case erlang:element(2, X) >= 0 of
|
|
true ->
|
|
{erlang:element(2, X), erlang:element(3, X)};
|
|
|
|
false ->
|
|
{(erlang:element(2, X) * -1) - 1,
|
|
1000000000 - erlang:element(3, X)}
|
|
end end,
|
|
{Ls, Lns} = Parts(Left),
|
|
{Rs, Rns} = Parts(Right),
|
|
_pipe = gleam@int:compare(Ls, Rs),
|
|
gleam@order:break_tie(_pipe, gleam@int:compare(Lns, Rns)).
|
|
|
|
-file("src/gleam/time/duration.gleam", 164).
|
|
?DOC(
|
|
" Calculate the difference between two durations.\n"
|
|
"\n"
|
|
" This is effectively substracting the first duration from the second.\n"
|
|
"\n"
|
|
" # Examples\n"
|
|
"\n"
|
|
" ```gleam\n"
|
|
" difference(seconds(1), seconds(5))\n"
|
|
" // -> seconds(4)\n"
|
|
" ```\n"
|
|
).
|
|
-spec difference(duration(), duration()) -> duration().
|
|
difference(Left, Right) ->
|
|
_pipe = {duration,
|
|
erlang:element(2, Right) - erlang:element(2, Left),
|
|
erlang:element(3, Right) - erlang:element(3, Left)},
|
|
normalise(_pipe).
|
|
|
|
-file("src/gleam/time/duration.gleam", 178).
|
|
?DOC(
|
|
" Add two durations together.\n"
|
|
"\n"
|
|
" # Examples\n"
|
|
"\n"
|
|
" ```gleam\n"
|
|
" add(seconds(1), seconds(5))\n"
|
|
" // -> seconds(6)\n"
|
|
" ```\n"
|
|
).
|
|
-spec add(duration(), duration()) -> duration().
|
|
add(Left, Right) ->
|
|
_pipe = {duration,
|
|
erlang:element(2, Left) + erlang:element(2, Right),
|
|
erlang:element(3, Left) + erlang:element(3, Right)},
|
|
normalise(_pipe).
|
|
|
|
-file("src/gleam/time/duration.gleam", 225).
|
|
-spec nanosecond_digits(integer(), integer(), binary()) -> binary().
|
|
nanosecond_digits(N, Position, Acc) ->
|
|
case Position of
|
|
9 ->
|
|
Acc;
|
|
|
|
_ when (Acc =:= <<""/utf8>>) andalso ((N rem 10) =:= 0) ->
|
|
nanosecond_digits(N div 10, Position + 1, Acc);
|
|
|
|
_ ->
|
|
Acc@1 = <<(erlang:integer_to_binary(N rem 10))/binary, Acc/binary>>,
|
|
nanosecond_digits(N div 10, Position + 1, Acc@1)
|
|
end.
|
|
|
|
-file("src/gleam/time/duration.gleam", 239).
|
|
?DOC(" Create a duration of a number of seconds.\n").
|
|
-spec seconds(integer()) -> duration().
|
|
seconds(Amount) ->
|
|
{duration, Amount, 0}.
|
|
|
|
-file("src/gleam/time/duration.gleam", 244).
|
|
?DOC(" Create a duration of a number of minutes.\n").
|
|
-spec minutes(integer()) -> duration().
|
|
minutes(Amount) ->
|
|
seconds(Amount * 60).
|
|
|
|
-file("src/gleam/time/duration.gleam", 249).
|
|
?DOC(" Create a duration of a number of hours.\n").
|
|
-spec hours(integer()) -> duration().
|
|
hours(Amount) ->
|
|
seconds((Amount * 60) * 60).
|
|
|
|
-file("src/gleam/time/duration.gleam", 254).
|
|
?DOC(" Create a duration of a number of milliseconds.\n").
|
|
-spec milliseconds(integer()) -> duration().
|
|
milliseconds(Amount) ->
|
|
Remainder = Amount rem 1000,
|
|
Overflow = Amount - Remainder,
|
|
Nanoseconds = Remainder * 1000000,
|
|
Seconds = Overflow div 1000,
|
|
_pipe = {duration, Seconds, Nanoseconds},
|
|
normalise(_pipe).
|
|
|
|
-file("src/gleam/time/duration.gleam", 273).
|
|
?DOC(
|
|
" Create a duration of a number of nanoseconds.\n"
|
|
"\n"
|
|
" # JavaScript int limitations\n"
|
|
"\n"
|
|
" Remember that JavaScript can only perfectly represent ints between positive\n"
|
|
" and negative 9,007,199,254,740,991! If you use a single call to this\n"
|
|
" function to create durations larger than that number of nanoseconds then\n"
|
|
" you will likely not get exactly the value you expect. Use `seconds` and\n"
|
|
" `milliseconds` as much as possible for large durations.\n"
|
|
).
|
|
-spec nanoseconds(integer()) -> duration().
|
|
nanoseconds(Amount) ->
|
|
_pipe = {duration, 0, Amount},
|
|
normalise(_pipe).
|
|
|
|
-file("src/gleam/time/duration.gleam", 283).
|
|
?DOC(
|
|
" Convert the duration to a number of seconds.\n"
|
|
"\n"
|
|
" There may be some small loss of precision due to `Duration` being\n"
|
|
" nanosecond accurate and `Float` not being able to represent this.\n"
|
|
).
|
|
-spec to_seconds(duration()) -> float().
|
|
to_seconds(Duration) ->
|
|
Seconds = erlang:float(erlang:element(2, Duration)),
|
|
Nanoseconds = erlang:float(erlang:element(3, Duration)),
|
|
Seconds + (Nanoseconds / 1000000000.0).
|
|
|
|
-file("src/gleam/time/duration.gleam", 292).
|
|
?DOC(
|
|
" Convert the duration to a number of seconds and nanoseconds. There is no\n"
|
|
" loss of precision with this conversion on any target.\n"
|
|
).
|
|
-spec to_seconds_and_nanoseconds(duration()) -> {integer(), integer()}.
|
|
to_seconds_and_nanoseconds(Duration) ->
|
|
{erlang:element(2, Duration), erlang:element(3, Duration)}.
|
|
|
|
-file("src/gleam/time/duration.gleam", 192).
|
|
?DOC(
|
|
" Convert the duration to an [ISO8601][1] formatted duration string.\n"
|
|
"\n"
|
|
" The ISO8601 duration format is ambiguous without context due to months and\n"
|
|
" years having different lengths, and because of leap seconds. This function\n"
|
|
" encodes the duration as days, hours, and seconds without any leap seconds.\n"
|
|
" Be sure to take this into account when using the duration strings.\n"
|
|
"\n"
|
|
" [1]: https://en.wikipedia.org/wiki/ISO_8601#Durations\n"
|
|
).
|
|
-spec to_iso8601_string(duration()) -> binary().
|
|
to_iso8601_string(Duration) ->
|
|
gleam@bool:guard(
|
|
Duration =:= {duration, 0, 0},
|
|
<<"PT0S"/utf8>>,
|
|
fun() ->
|
|
Split = fun(Total, Limit) ->
|
|
Amount = case Limit of
|
|
0 -> 0;
|
|
Gleam@denominator -> Total rem Gleam@denominator
|
|
end,
|
|
Remainder = case Limit of
|
|
0 -> 0;
|
|
Gleam@denominator@1 -> (Total - Amount) div Gleam@denominator@1
|
|
end,
|
|
{Amount, Remainder}
|
|
end,
|
|
{Seconds, Rest} = Split(erlang:element(2, Duration), 60),
|
|
{Minutes, Rest@1} = Split(Rest, 60),
|
|
{Hours, Rest@2} = Split(Rest@1, 24),
|
|
Days = Rest@2,
|
|
Add = fun(Out, Value, Unit) -> case Value of
|
|
0 ->
|
|
Out;
|
|
|
|
_ ->
|
|
<<<<Out/binary,
|
|
(erlang:integer_to_binary(Value))/binary>>/binary,
|
|
Unit/binary>>
|
|
end end,
|
|
Output = begin
|
|
_pipe = <<"P"/utf8>>,
|
|
_pipe@1 = Add(_pipe, Days, <<"D"/utf8>>),
|
|
_pipe@2 = gleam@string:append(_pipe@1, <<"T"/utf8>>),
|
|
_pipe@3 = Add(_pipe@2, Hours, <<"H"/utf8>>),
|
|
Add(_pipe@3, Minutes, <<"M"/utf8>>)
|
|
end,
|
|
case {Seconds, erlang:element(3, Duration)} of
|
|
{0, 0} ->
|
|
Output;
|
|
|
|
{_, 0} ->
|
|
<<<<Output/binary,
|
|
(erlang:integer_to_binary(Seconds))/binary>>/binary,
|
|
"S"/utf8>>;
|
|
|
|
{_, _} ->
|
|
F = nanosecond_digits(
|
|
erlang:element(3, Duration),
|
|
0,
|
|
<<""/utf8>>
|
|
),
|
|
<<<<<<<<Output/binary,
|
|
(erlang:integer_to_binary(Seconds))/binary>>/binary,
|
|
"."/utf8>>/binary,
|
|
F/binary>>/binary,
|
|
"S"/utf8>>
|
|
end
|
|
end
|
|
).
|