Packages

A comprehensive calendar library for Gleam — date, time, datetime, naive datetime, duration, ISO 8601 parsing and formatting, timezone support. Port of Elixir's Calendar modules.

Current section

Files

Jump to
sidereal src calendar@duration.erl
Raw

src/calendar@duration.erl

-module(calendar@duration).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/calendar/duration.gleam").
-export([new/0, from_unit_pairs/1, add/2, subtract/2, multiply/2, negate/1, to_string/1, to_iso8601/1, from_iso8601/1, from_iso8601_unchecked/1, to_timeout/1]).
-export_type([duration/0, unit_pair/0, parse_error/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.
-type duration() :: {duration,
integer(),
integer(),
integer(),
integer(),
integer(),
integer(),
integer(),
{integer(), integer()}}.
-type unit_pair() :: {year, integer()} |
{month, integer()} |
{week, integer()} |
{day, integer()} |
{hour, integer()} |
{minute, integer()} |
{second, integer()} |
{microsecond, {integer(), integer()}}.
-type parse_error() :: invalid_format | invalid_duration.
-file("src/calendar/duration.gleam", 44).
?DOC(" Creates a new Duration struct with all units set to zero.\n").
-spec new() -> duration().
new() ->
{duration, 0, 0, 0, 0, 0, 0, 0, {0, 0}}.
-file("src/calendar/duration.gleam", 63).
-spec list_fold_duration(list(unit_pair()), duration()) -> duration().
list_fold_duration(Pairs, Acc) ->
case Pairs of
[] ->
Acc;
[{year, Value} | Rest] ->
list_fold_duration(
Rest,
{duration,
Value,
erlang:element(3, Acc),
erlang:element(4, Acc),
erlang:element(5, Acc),
erlang:element(6, Acc),
erlang:element(7, Acc),
erlang:element(8, Acc),
erlang:element(9, Acc)}
);
[{month, Value@1} | Rest@1] ->
list_fold_duration(
Rest@1,
{duration,
erlang:element(2, Acc),
Value@1,
erlang:element(4, Acc),
erlang:element(5, Acc),
erlang:element(6, Acc),
erlang:element(7, Acc),
erlang:element(8, Acc),
erlang:element(9, Acc)}
);
[{week, Value@2} | Rest@2] ->
list_fold_duration(
Rest@2,
{duration,
erlang:element(2, Acc),
erlang:element(3, Acc),
Value@2,
erlang:element(5, Acc),
erlang:element(6, Acc),
erlang:element(7, Acc),
erlang:element(8, Acc),
erlang:element(9, Acc)}
);
[{day, Value@3} | Rest@3] ->
list_fold_duration(
Rest@3,
{duration,
erlang:element(2, Acc),
erlang:element(3, Acc),
erlang:element(4, Acc),
Value@3,
erlang:element(6, Acc),
erlang:element(7, Acc),
erlang:element(8, Acc),
erlang:element(9, Acc)}
);
[{hour, Value@4} | Rest@4] ->
list_fold_duration(
Rest@4,
{duration,
erlang:element(2, Acc),
erlang:element(3, Acc),
erlang:element(4, Acc),
erlang:element(5, Acc),
Value@4,
erlang:element(7, Acc),
erlang:element(8, Acc),
erlang:element(9, Acc)}
);
[{minute, Value@5} | Rest@5] ->
list_fold_duration(
Rest@5,
{duration,
erlang:element(2, Acc),
erlang:element(3, Acc),
erlang:element(4, Acc),
erlang:element(5, Acc),
erlang:element(6, Acc),
Value@5,
erlang:element(8, Acc),
erlang:element(9, Acc)}
);
[{second, Value@6} | Rest@6] ->
list_fold_duration(
Rest@6,
{duration,
erlang:element(2, Acc),
erlang:element(3, Acc),
erlang:element(4, Acc),
erlang:element(5, Acc),
erlang:element(6, Acc),
erlang:element(7, Acc),
Value@6,
erlang:element(9, Acc)}
);
[{microsecond, Value@7} | Rest@7] ->
list_fold_duration(
Rest@7,
{duration,
erlang:element(2, Acc),
erlang:element(3, Acc),
erlang:element(4, Acc),
erlang:element(5, Acc),
erlang:element(6, Acc),
erlang:element(7, Acc),
erlang:element(8, Acc),
Value@7}
)
end.
-file("src/calendar/duration.gleam", 58).
?DOC(" Creates a new Duration from a list of unit pairs.\n").
-spec from_unit_pairs(list(unit_pair())) -> duration().
from_unit_pairs(Pairs) ->
Initial = new(),
list_fold_duration(Pairs, Initial).
-file("src/calendar/duration.gleam", 86).
?DOC(" Adds two durations together.\n").
-spec add(duration(), duration()) -> duration().
add(D1, D2) ->
{D1_ms, D1_p} = erlang:element(9, D1),
{D2_ms, D2_p} = erlang:element(9, D2),
Max_precision = case D1_p >= D2_p of
true ->
D1_p;
false ->
D2_p
end,
{duration,
erlang:element(2, D1) + erlang:element(2, D2),
erlang:element(3, D1) + erlang:element(3, D2),
erlang:element(4, D1) + erlang:element(4, D2),
erlang:element(5, D1) + erlang:element(5, D2),
erlang:element(6, D1) + erlang:element(6, D2),
erlang:element(7, D1) + erlang:element(7, D2),
erlang:element(8, D1) + erlang:element(8, D2),
{D1_ms + D2_ms, Max_precision}}.
-file("src/calendar/duration.gleam", 107).
?DOC(" Subtracts the second duration from the first.\n").
-spec subtract(duration(), duration()) -> duration().
subtract(D1, D2) ->
{D1_ms, D1_p} = erlang:element(9, D1),
{D2_ms, D2_p} = erlang:element(9, D2),
Max_precision = case D1_p >= D2_p of
true ->
D1_p;
false ->
D2_p
end,
{duration,
erlang:element(2, D1) - erlang:element(2, D2),
erlang:element(3, D1) - erlang:element(3, D2),
erlang:element(4, D1) - erlang:element(4, D2),
erlang:element(5, D1) - erlang:element(5, D2),
erlang:element(6, D1) - erlang:element(6, D2),
erlang:element(7, D1) - erlang:element(7, D2),
erlang:element(8, D1) - erlang:element(8, D2),
{D1_ms - D2_ms, Max_precision}}.
-file("src/calendar/duration.gleam", 128).
?DOC(" Multiplies a duration by an integer.\n").
-spec multiply(duration(), integer()) -> duration().
multiply(Duration, Multiplier) ->
{Ms, P} = erlang:element(9, Duration),
{duration,
erlang:element(2, Duration) * Multiplier,
erlang:element(3, Duration) * Multiplier,
erlang:element(4, Duration) * Multiplier,
erlang:element(5, Duration) * Multiplier,
erlang:element(6, Duration) * Multiplier,
erlang:element(7, Duration) * Multiplier,
erlang:element(8, Duration) * Multiplier,
{Ms * Multiplier, P}}.
-file("src/calendar/duration.gleam", 144).
?DOC(" Negates a duration (makes positive units negative and vice versa).\n").
-spec negate(duration()) -> duration().
negate(Duration) ->
{Ms, P} = erlang:element(9, Duration),
{duration,
- erlang:element(2, Duration),
- erlang:element(3, Duration),
- erlang:element(4, Duration),
- erlang:element(5, Duration),
- erlang:element(6, Duration),
- erlang:element(7, Duration),
- erlang:element(8, Duration),
{- Ms, P}}.
-file("src/calendar/duration.gleam", 316).
-spec plural(integer()) -> binary().
plural(N) ->
case (N =:= 1) orelse (N =:= -1) of
true ->
<<""/utf8>>;
false ->
<<"s"/utf8>>
end.
-file("src/calendar/duration.gleam", 186).
?DOC(" Converts a duration to a human-readable string.\n").
-spec to_string(duration()) -> binary().
to_string(Duration) ->
Parts = [],
Parts@1 = case erlang:element(2, Duration) /= 0 of
true ->
[<<<<(erlang:integer_to_binary(erlang:element(2, Duration)))/binary,
" year"/utf8>>/binary,
(plural(erlang:element(2, Duration)))/binary>> |
Parts];
false ->
Parts
end,
Parts@2 = case erlang:element(3, Duration) /= 0 of
true ->
[<<<<(erlang:integer_to_binary(erlang:element(3, Duration)))/binary,
" month"/utf8>>/binary,
(plural(erlang:element(3, Duration)))/binary>> |
Parts@1];
false ->
Parts@1
end,
Parts@3 = case erlang:element(4, Duration) /= 0 of
true ->
[<<<<(erlang:integer_to_binary(erlang:element(4, Duration)))/binary,
" week"/utf8>>/binary,
(plural(erlang:element(4, Duration)))/binary>> |
Parts@2];
false ->
Parts@2
end,
Parts@4 = case erlang:element(5, Duration) /= 0 of
true ->
[<<<<(erlang:integer_to_binary(erlang:element(5, Duration)))/binary,
" day"/utf8>>/binary,
(plural(erlang:element(5, Duration)))/binary>> |
Parts@3];
false ->
Parts@3
end,
Parts@5 = case erlang:element(6, Duration) /= 0 of
true ->
[<<<<(erlang:integer_to_binary(erlang:element(6, Duration)))/binary,
" hour"/utf8>>/binary,
(plural(erlang:element(6, Duration)))/binary>> |
Parts@4];
false ->
Parts@4
end,
Parts@6 = case erlang:element(7, Duration) /= 0 of
true ->
[<<<<(erlang:integer_to_binary(erlang:element(7, Duration)))/binary,
" minute"/utf8>>/binary,
(plural(erlang:element(7, Duration)))/binary>> |
Parts@5];
false ->
Parts@5
end,
Parts@7 = case erlang:element(8, Duration) /= 0 of
true ->
[<<<<(erlang:integer_to_binary(erlang:element(8, Duration)))/binary,
" second"/utf8>>/binary,
(plural(erlang:element(8, Duration)))/binary>> |
Parts@6];
false ->
Parts@6
end,
case Parts@7 of
[] ->
<<"0 seconds"/utf8>>;
_ ->
gleam@string:join(lists:reverse(Parts@7), <<", "/utf8>>)
end.
-file("src/calendar/duration.gleam", 333).
-spec strip_trailing_zeros(binary()) -> binary().
strip_trailing_zeros(S) ->
case gleam_stdlib:string_ends_with(S, <<"0"/utf8>>) of
true ->
strip_trailing_zeros(gleam@string:drop_end(S, 1));
false ->
S
end.
-file("src/calendar/duration.gleam", 323).
-spec format_microseconds(integer(), integer()) -> binary().
format_microseconds(Ms, Precision) ->
Ms_str = erlang:integer_to_binary(Ms),
Padded = case string:length(Ms_str) < Precision of
true ->
<<(gleam@string:repeat(
<<"0"/utf8>>,
Precision - string:length(Ms_str)
))/binary,
Ms_str/binary>>;
false ->
Ms_str
end,
_pipe = gleam@string:slice(Padded, 0, Precision),
strip_trailing_zeros(_pipe).
-file("src/calendar/duration.gleam", 252).
?DOC(" Converts a duration to ISO8601 format.\n").
-spec to_iso8601(duration()) -> binary().
to_iso8601(Duration) ->
Date_parts = [],
Date_parts@1 = case erlang:element(2, Duration) /= 0 of
true ->
[<<(erlang:integer_to_binary(erlang:element(2, Duration)))/binary,
"Y"/utf8>> |
Date_parts];
false ->
Date_parts
end,
Date_parts@2 = case erlang:element(3, Duration) /= 0 of
true ->
[<<(erlang:integer_to_binary(erlang:element(3, Duration)))/binary,
"M"/utf8>> |
Date_parts@1];
false ->
Date_parts@1
end,
Date_parts@3 = case erlang:element(4, Duration) /= 0 of
true ->
[<<(erlang:integer_to_binary(erlang:element(4, Duration)))/binary,
"W"/utf8>> |
Date_parts@2];
false ->
Date_parts@2
end,
Date_parts@4 = case erlang:element(5, Duration) /= 0 of
true ->
[<<(erlang:integer_to_binary(erlang:element(5, Duration)))/binary,
"D"/utf8>> |
Date_parts@3];
false ->
Date_parts@3
end,
Time_parts = [],
Time_parts@1 = case erlang:element(6, Duration) /= 0 of
true ->
[<<(erlang:integer_to_binary(erlang:element(6, Duration)))/binary,
"H"/utf8>> |
Time_parts];
false ->
Time_parts
end,
Time_parts@2 = case erlang:element(7, Duration) /= 0 of
true ->
[<<(erlang:integer_to_binary(erlang:element(7, Duration)))/binary,
"M"/utf8>> |
Time_parts@1];
false ->
Time_parts@1
end,
{Ms, Precision} = erlang:element(9, Duration),
Time_parts@3 = case (erlang:element(8, Duration) /= 0) orelse (Ms /= 0) of
true ->
Second_str = case (Ms =:= 0) orelse (Precision =:= 0) of
true ->
erlang:integer_to_binary(erlang:element(8, Duration));
false ->
<<<<(erlang:integer_to_binary(erlang:element(8, Duration)))/binary,
"."/utf8>>/binary,
(format_microseconds(Ms, Precision))/binary>>
end,
[<<Second_str/binary, "S"/utf8>> | Time_parts@2];
false ->
Time_parts@2
end,
Date_part = gleam@string:join(lists:reverse(Date_parts@4), <<""/utf8>>),
Time_part = case Time_parts@3 of
[] ->
<<""/utf8>>;
_ ->
<<"T"/utf8,
(gleam@string:join(lists:reverse(Time_parts@3), <<""/utf8>>))/binary>>
end,
case (Date_part =:= <<""/utf8>>) andalso (Time_part =:= <<""/utf8>>) of
true ->
<<"PT0S"/utf8>>;
false ->
<<<<"P"/utf8, Date_part/binary>>/binary, Time_part/binary>>
end.
-file("src/calendar/duration.gleam", 364).
-spec do_parse_date_part(list(binary()), binary(), duration()) -> {ok,
duration()} |
{error, nil}.
do_parse_date_part(Chars, Acc, Duration) ->
case Chars of
[] ->
case Acc of
<<""/utf8>> ->
{ok, Duration};
_ ->
{error, nil}
end;
[C | Rest] ->
case C of
<<"Y"/utf8>> ->
case gleam_stdlib:parse_int(Acc) of
{ok, N} ->
do_parse_date_part(
Rest,
<<""/utf8>>,
{duration,
N,
erlang:element(3, Duration),
erlang:element(4, Duration),
erlang:element(5, Duration),
erlang:element(6, Duration),
erlang:element(7, Duration),
erlang:element(8, Duration),
erlang:element(9, Duration)}
);
{error, _} ->
{error, nil}
end;
<<"M"/utf8>> ->
case gleam_stdlib:parse_int(Acc) of
{ok, N@1} ->
do_parse_date_part(
Rest,
<<""/utf8>>,
{duration,
erlang:element(2, Duration),
N@1,
erlang:element(4, Duration),
erlang:element(5, Duration),
erlang:element(6, Duration),
erlang:element(7, Duration),
erlang:element(8, Duration),
erlang:element(9, Duration)}
);
{error, _} ->
{error, nil}
end;
<<"W"/utf8>> ->
case gleam_stdlib:parse_int(Acc) of
{ok, N@2} ->
do_parse_date_part(
Rest,
<<""/utf8>>,
{duration,
erlang:element(2, Duration),
erlang:element(3, Duration),
N@2,
erlang:element(5, Duration),
erlang:element(6, Duration),
erlang:element(7, Duration),
erlang:element(8, Duration),
erlang:element(9, Duration)}
);
{error, _} ->
{error, nil}
end;
<<"D"/utf8>> ->
case gleam_stdlib:parse_int(Acc) of
{ok, N@3} ->
do_parse_date_part(
Rest,
<<""/utf8>>,
{duration,
erlang:element(2, Duration),
erlang:element(3, Duration),
erlang:element(4, Duration),
N@3,
erlang:element(6, Duration),
erlang:element(7, Duration),
erlang:element(8, Duration),
erlang:element(9, Duration)}
);
{error, _} ->
{error, nil}
end;
_ ->
do_parse_date_part(Rest, <<Acc/binary, C/binary>>, Duration)
end
end.
-file("src/calendar/duration.gleam", 360).
-spec parse_date_part(binary()) -> {ok, duration()} | {error, nil}.
parse_date_part(Date_part) ->
do_parse_date_part(gleam@string:to_graphemes(Date_part), <<""/utf8>>, new()).
-file("src/calendar/duration.gleam", 476).
-spec pad_to_6(binary()) -> binary().
pad_to_6(S) ->
Len = string:length(S),
case Len >= 6 of
true ->
gleam@string:slice(S, 0, 6);
false ->
<<S/binary, (gleam@string:repeat(<<"0"/utf8>>, 6 - Len))/binary>>
end.
-file("src/calendar/duration.gleam", 408).
-spec do_parse_time_part(list(binary()), binary(), duration()) -> {ok,
duration()} |
{error, nil}.
do_parse_time_part(Chars, Acc, Duration) ->
case Chars of
[] ->
case Acc of
<<""/utf8>> ->
{ok, Duration};
_ ->
{error, nil}
end;
[C | Rest] ->
case C of
<<"H"/utf8>> ->
case gleam_stdlib:parse_int(Acc) of
{ok, N} ->
do_parse_time_part(
Rest,
<<""/utf8>>,
{duration,
erlang:element(2, Duration),
erlang:element(3, Duration),
erlang:element(4, Duration),
erlang:element(5, Duration),
N,
erlang:element(7, Duration),
erlang:element(8, Duration),
erlang:element(9, Duration)}
);
{error, _} ->
{error, nil}
end;
<<"M"/utf8>> ->
case gleam_stdlib:parse_int(Acc) of
{ok, N@1} ->
do_parse_time_part(
Rest,
<<""/utf8>>,
{duration,
erlang:element(2, Duration),
erlang:element(3, Duration),
erlang:element(4, Duration),
erlang:element(5, Duration),
erlang:element(6, Duration),
N@1,
erlang:element(8, Duration),
erlang:element(9, Duration)}
);
{error, _} ->
{error, nil}
end;
<<"S"/utf8>> ->
case gleam_stdlib:contains_string(Acc, <<"."/utf8>>) of
true ->
case gleam@string:split(Acc, <<"."/utf8>>) of
[Sec_str, Frac_str] ->
case gleam_stdlib:parse_int(Sec_str) of
{ok, Sec} ->
Padded = pad_to_6(Frac_str),
case gleam_stdlib:parse_int(Padded) of
{ok, Us} ->
Precision = string:length(
Frac_str
),
do_parse_time_part(
Rest,
<<""/utf8>>,
{duration,
erlang:element(
2,
Duration
),
erlang:element(
3,
Duration
),
erlang:element(
4,
Duration
),
erlang:element(
5,
Duration
),
erlang:element(
6,
Duration
),
erlang:element(
7,
Duration
),
Sec,
{Us, Precision}}
);
{error, _} ->
{error, nil}
end;
{error, _} ->
{error, nil}
end;
_ ->
{error, nil}
end;
false ->
case gleam_stdlib:parse_int(Acc) of
{ok, N@2} ->
do_parse_time_part(
Rest,
<<""/utf8>>,
{duration,
erlang:element(2, Duration),
erlang:element(3, Duration),
erlang:element(4, Duration),
erlang:element(5, Duration),
erlang:element(6, Duration),
erlang:element(7, Duration),
N@2,
erlang:element(9, Duration)}
);
{error, _} ->
{error, nil}
end
end;
_ ->
do_parse_time_part(Rest, <<Acc/binary, C/binary>>, Duration)
end
end.
-file("src/calendar/duration.gleam", 404).
-spec parse_time_part(binary()) -> {ok, duration()} | {error, nil}.
parse_time_part(Time_part) ->
do_parse_time_part(gleam@string:to_graphemes(Time_part), <<""/utf8>>, new()).
-file("src/calendar/duration.gleam", 484).
-spec merge_durations(duration(), duration()) -> duration().
merge_durations(D1, D2) ->
add(D1, D2).
-file("src/calendar/duration.gleam", 340).
-spec parse_iso8601_duration(binary()) -> {ok, duration()} | {error, nil}.
parse_iso8601_duration(Duration_string) ->
case gleam_stdlib:string_starts_with(Duration_string, <<"P"/utf8>>) of
false ->
{error, nil};
true ->
Content = gleam@string:drop_start(Duration_string, 1),
case gleam@string:split_once(Content, <<"T"/utf8>>) of
{ok, {Date_part, Time_part}} ->
case {parse_date_part(Date_part),
parse_time_part(Time_part)} of
{{ok, Date_duration}, {ok, Time_duration}} ->
{ok, merge_durations(Date_duration, Time_duration)};
{{error, _}, _} ->
{error, nil};
{_, {error, _}} ->
{error, nil}
end;
{error, _} ->
parse_date_part(Content)
end
end.
-file("src/calendar/duration.gleam", 170).
?DOC(
" Parses an ISO8601 duration string.\n"
" \n"
" Examples:\n"
" - \"P1Y2M3DT4H5M6S\" -> 1 year, 2 months, 3 days, 4 hours, 5 minutes, 6 seconds\n"
" - \"P1W\" -> 1 week\n"
" - \"PT30M\" -> 30 minutes\n"
).
-spec from_iso8601(binary()) -> {ok, duration()} | {error, parse_error()}.
from_iso8601(Duration_string) ->
case parse_iso8601_duration(Duration_string) of
{ok, Duration} ->
{ok, Duration};
{error, _} ->
{error, invalid_format}
end.
-file("src/calendar/duration.gleam", 178).
?DOC(" Parses an ISO8601 duration string, panicking on invalid input.\n").
-spec from_iso8601_unchecked(binary()) -> duration().
from_iso8601_unchecked(Duration_string) ->
case from_iso8601(Duration_string) of
{ok, Duration} ->
Duration;
{error, _} ->
erlang:error(#{gleam_error => panic,
message => <<"Invalid ISO8601 duration format"/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"calendar/duration"/utf8>>,
function => <<"from_iso8601_unchecked"/utf8>>,
line => 181})
end.
-file("src/calendar/duration.gleam", 491).
?DOC(
" Converts a duration to a timeout value in milliseconds.\n"
" Raises if the duration contains non-zero year or month values,\n"
" as those cannot be reliably converted.\n"
).
-spec to_timeout(duration()) -> integer().
to_timeout(Duration) ->
{Microsecond_val, _} = erlang:element(9, Duration),
Ms_from_us = Microsecond_val div 1000,
(((((((((erlang:element(4, Duration) * 7) * 24) * 60) * 60) * 1000) + ((((erlang:element(
5,
Duration
)
* 24)
* 60)
* 60)
* 1000))
+ (((erlang:element(6, Duration) * 60) * 60) * 1000))
+ ((erlang:element(7, Duration) * 60) * 1000))
+ (erlang:element(8, Duration) * 1000))
+ Ms_from_us.