-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, [<> | 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, <>, 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 -> <>, 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, <>, 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 => <>, 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.