-module(birl@time). -compile([no_auto_import, nowarn_unused_vars]). -export([compare/2, difference/2, add/2, subtract/2, now/0, utc_now/0, now_with_offset/1, to_parts/1, to_iso8601/1, from_parts/3, from_iso8601/1, weekday/1]). -export_type([time/0, week_day/0]). -opaque time() :: {time, integer(), integer(), gleam@option:option(integer())}. -type week_day() :: monday | tuesday | wednesday | thursday | friday | saturday | sunday. -spec compare(time(), time()) -> gleam@order:order(). compare(A, B) -> {time, Wta, Oa, Mta} = A, {time, Wtb, Ob, Mtb} = B, {Ta@1, Tb@1} = case {Mta, Mtb} of {{some, Ta}, {some, Tb}} -> {Ta, Tb}; _ -> {Wta + Oa, Wtb + Ob} end, case Ta@1 =:= Tb@1 of true -> eq; false -> case Ta@1 < Tb@1 of true -> lt; false -> gt end end. -spec difference(time(), time()) -> birl@duration:duration(). difference(A, B) -> {time, Wta, Oa, Mta} = A, {time, Wtb, Ob, Mtb} = B, {Ta@1, Tb@1} = case {Mta, Mtb} of {{some, Ta}, {some, Tb}} -> {Ta, Tb}; _ -> {Wta + Oa, Wtb + Ob} end, {duration, Ta@1 - Tb@1}. -spec add(time(), birl@duration:duration()) -> time(). add(Value, Duration) -> {time, Wt, O, Mt} = Value, {duration, Duration@1} = Duration, case Mt of {some, Mt@1} -> {time, Wt + Duration@1, O, {some, Mt@1 + Duration@1}}; none -> {time, Wt + Duration@1, O, none} end. -spec subtract(time(), birl@duration:duration()) -> time(). subtract(Value, Duration) -> {time, Wt, O, Mt} = Value, {duration, Duration@1} = Duration, case Mt of {some, Mt@1} -> {time, Wt - Duration@1, O, {some, Mt@1 - Duration@1}}; none -> {time, Wt - Duration@1, O, none} end. -spec parse_offset(binary()) -> {ok, integer()} | {error, nil}. parse_offset(Offset) -> _assert_subject = gleam@regex:from_string(<<"([+-])"/utf8>>), {ok, Re} = case _assert_subject of {ok, _} -> _assert_subject; _assert_fail -> erlang:error(#{gleam_error => assert, message => <<"Assertion pattern match failed"/utf8>>, value => _assert_fail, module => <<"birl/time"/utf8>>, function => <<"parse_offset"/utf8>>, line => 253}) end, gleam@result:then(case gleam@regex:split(Re, Offset) of [<<""/utf8>>, <<"+"/utf8>>, Offset@1] -> {ok, {1, Offset@1}}; [<<""/utf8>>, <<"-"/utf8>>, Offset@2] -> {ok, {-1, Offset@2}}; [_] -> {ok, {1, Offset}}; _ -> {error, nil} end, fun(_use0) -> {Sign, Offset@3} = _use0, case gleam@string:split(Offset@3, <<":"/utf8>>) of [Hour_str, Minute_str] -> gleam@result:then( gleam@int:parse(Hour_str), fun(Hour) -> gleam@result:then( gleam@int:parse(Minute_str), fun(Minute) -> {ok, ((Sign * ((Hour * 60) + Minute)) * 60) * 1000000} end ) end ); [Offset@4] -> case gleam@string:length(Offset@4) of 1 -> gleam@result:then( gleam@int:parse(Offset@4), fun(Hour@1) -> {ok, ((Sign * Hour@1) * 3600) * 1000000} end ); 2 -> gleam@result:then( gleam@int:parse(Offset@4), fun(Hour@1) -> {ok, ((Sign * Hour@1) * 3600) * 1000000} end ); 3 -> _assert_subject@1 = gleam@string:first(Offset@4), {ok, Hour_str@1} = case _assert_subject@1 of {ok, _} -> _assert_subject@1; _assert_fail@1 -> erlang:error(#{gleam_error => assert, message => <<"Assertion pattern match failed"/utf8>>, value => _assert_fail@1, module => <<"birl/time"/utf8>>, function => <<"parse_offset"/utf8>>, line => 274}) end, Minute_str@1 = gleam@string:slice(Offset@4, 1, 2), gleam@result:then( gleam@int:parse(Hour_str@1), fun(Hour@2) -> gleam@result:then( gleam@int:parse(Minute_str@1), fun(Minute@1) -> {ok, ((Sign * ((Hour@2 * 60) + Minute@1)) * 60) * 1000000} end ) end ); 4 -> Hour_str@2 = gleam@string:slice(Offset@4, 0, 2), Minute_str@2 = gleam@string:slice(Offset@4, 2, 2), gleam@result:then( gleam@int:parse(Hour_str@2), fun(Hour@3) -> gleam@result:then( gleam@int:parse(Minute_str@2), fun(Minute@2) -> {ok, ((Sign * ((Hour@3 * 60) + Minute@2)) * 60) * 1000000} end ) end ); _ -> {error, nil} end; _ -> {error, nil} end end). -spec generate_offset(integer()) -> {ok, binary()} | {error, nil}. generate_offset(Offset) -> case Offset of 0 -> {ok, <<"Z"/utf8>>}; _ -> case begin _pipe = [{Offset, micro_second}], _pipe@1 = birl@duration:new(_pipe), birl@duration:decompose(_pipe@1) end of [{Hour, hour}, {Minute, minute}] -> _pipe@10 = [case Hour > 0 of true -> gleam@string:concat( [<<"+"/utf8>>, begin _pipe@2 = Hour, _pipe@3 = gleam@int:to_string( _pipe@2 ), gleam@string:pad_left( _pipe@3, 2, <<"0"/utf8>> ) end] ); false -> gleam@string:concat( [<<"-"/utf8>>, begin _pipe@4 = Hour, _pipe@5 = gleam@int:absolute_value( _pipe@4 ), _pipe@6 = gleam@int:to_string( _pipe@5 ), gleam@string:pad_left( _pipe@6, 2, <<"0"/utf8>> ) end] ) end, begin _pipe@7 = Minute, _pipe@8 = gleam@int:absolute_value(_pipe@7), _pipe@9 = gleam@int:to_string(_pipe@8), gleam@string:pad_left(_pipe@9, 2, <<"0"/utf8>>) end], _pipe@11 = gleam@string:join(_pipe@10, <<":"/utf8>>), {ok, _pipe@11}; _ -> {error, nil} end end. -spec parse_iso_section(binary(), binary(), integer()) -> {ok, list(integer())} | {error, nil}. parse_iso_section(Section, Pattern_string, Default) -> _assert_subject = gleam@regex:from_string(Pattern_string), {ok, Pattern} = case _assert_subject of {ok, _} -> _assert_subject; _assert_fail -> erlang:error(#{gleam_error => assert, message => <<"Assertion pattern match failed"/utf8>>, value => _assert_fail, module => <<"birl/time"/utf8>>, function => <<"parse_iso_section"/utf8>>, line => 338}) end, _pipe = case gleam@regex:scan(Pattern, Section) of [{match, _, [{some, Major}]}] -> [gleam@int:parse(Major), {ok, Default}, {ok, Default}]; [{match, _, [{some, Major}, none]}] -> [gleam@int:parse(Major), {ok, Default}, {ok, Default}]; [{match, _, [{some, Major@1}, {some, Middle}]}] -> [gleam@int:parse(Major@1), gleam@int:parse(Middle), {ok, Default}]; [{match, _, [{some, Major@1}, {some, Middle}, none]}] -> [gleam@int:parse(Major@1), gleam@int:parse(Middle), {ok, Default}]; [{match, _, [{some, Major@2}, {some, Middle@1}, {some, Minor}]}] -> [gleam@int:parse(Major@2), gleam@int:parse(Middle@1), gleam@int:parse(Minor)]; _ -> [{error, nil}] end, gleam@list:try_map(_pipe, fun(Part) -> case Part of {ok, Inner} -> {ok, Inner}; {error, nil} -> {error, nil} end end). -spec now() -> time(). now() -> Now = birl_ffi:now(), Offset_in_minutes = birl_ffi:local_offset(), Monotonic_now = birl_ffi:monotonic_now(), {time, Now, Offset_in_minutes * 60000000, {some, Monotonic_now}}. -spec utc_now() -> time(). utc_now() -> Now = birl_ffi:now(), Monotonic_now = birl_ffi:monotonic_now(), {time, Now, 0, {some, Monotonic_now}}. -spec now_with_offset(binary()) -> {ok, time()} | {error, nil}. now_with_offset(Offset) -> gleam@result:then( parse_offset(Offset), fun(Offset@1) -> Now = birl_ffi:now(), Monotonic_now = birl_ffi:monotonic_now(), _pipe = {time, Now, Offset@1, {some, Monotonic_now}}, {ok, _pipe} end ). -spec to_parts(time()) -> {{integer(), integer(), integer()}, {integer(), integer(), integer(), integer()}, binary()}. to_parts(Value) -> case Value of {time, T, O, _} -> {Date, Time} = birl_ffi:to_parts(T + O), _assert_subject = generate_offset(O), {ok, Offset} = case _assert_subject of {ok, _} -> _assert_subject; _assert_fail -> erlang:error(#{gleam_error => assert, message => <<"Assertion pattern match failed"/utf8>>, value => _assert_fail, module => <<"birl/time"/utf8>>, function => <<"to_parts"/utf8>>, line => 58}) end, {Date, Time, Offset} end. -spec to_iso8601(time()) -> binary(). to_iso8601(Value) -> {{Year, Month, Day}, {Hour, Minute, Second, Milli_second}, Offset} = to_parts( Value ), <<<<<<<<<<<<<<<<<<<<<<<<<<(gleam@int:to_string(Year))/binary, "-"/utf8>>/binary, (begin _pipe = Month, _pipe@1 = gleam@int:to_string( _pipe ), gleam@string:pad_left( _pipe@1, 2, <<"0"/utf8>> ) end)/binary>>/binary, "-"/utf8>>/binary, (begin _pipe@2 = Day, _pipe@3 = gleam@int:to_string( _pipe@2 ), gleam@string:pad_left( _pipe@3, 2, <<"0"/utf8>> ) end)/binary>>/binary, "T"/utf8>>/binary, (begin _pipe@4 = Hour, _pipe@5 = gleam@int:to_string(_pipe@4), gleam@string:pad_left( _pipe@5, 2, <<"0"/utf8>> ) end)/binary>>/binary, ":"/utf8>>/binary, (begin _pipe@6 = Minute, _pipe@7 = gleam@int:to_string(_pipe@6), gleam@string:pad_left(_pipe@7, 2, <<"0"/utf8>>) end)/binary>>/binary, ":"/utf8>>/binary, (begin _pipe@8 = Second, _pipe@9 = gleam@int:to_string(_pipe@8), gleam@string:pad_left(_pipe@9, 2, <<"0"/utf8>>) end)/binary>>/binary, "."/utf8>>/binary, (begin _pipe@10 = Milli_second, _pipe@11 = gleam@int:to_string(_pipe@10), gleam@string:pad_left(_pipe@11, 3, <<"0"/utf8>>) end)/binary>>/binary, Offset/binary>>. -spec from_parts( {integer(), integer(), integer()}, {integer(), integer(), integer(), integer()}, binary() ) -> {ok, time()} | {error, nil}. from_parts(Date, Time, Offset) -> gleam@result:then( parse_offset(Offset), fun(Offset_number) -> _pipe = birl_ffi:from_parts({Date, Time}, Offset_number), _pipe@1 = {time, _pipe, Offset_number, none}, {ok, _pipe@1} end ). -spec from_iso8601(binary()) -> {ok, time()} | {error, nil}. from_iso8601(Value) -> _assert_subject = gleam@regex:from_string(<<"(.*)([+|\\-].*)"/utf8>>), {ok, Offset_pattern} = case _assert_subject of {ok, _} -> _assert_subject; _assert_fail -> erlang:error(#{gleam_error => assert, message => <<"Assertion pattern match failed"/utf8>>, value => _assert_fail, module => <<"birl/time"/utf8>>, function => <<"from_iso8601"/utf8>>, line => 106}) end, Value@1 = gleam@string:trim(Value), {Date_string@2, Offsetted_time_string@1} = case gleam@string:split( Value@1, <<"T"/utf8>> ) of [Date_string] -> {Date_string, <<"00"/utf8>>}; [Date_string@1, Offsetted_time_string] -> {Date_string@1, Offsetted_time_string} end, {Time_string@1, Offset_string@1} = case gleam@string:ends_with( Offsetted_time_string@1, <<"Z"/utf8>> ) of true -> {gleam@string:drop_right(Offsetted_time_string@1, 1), <<"+00:00"/utf8>>}; false -> case gleam@regex:scan(Offset_pattern, Offsetted_time_string@1) of [{match, _, [{some, Time_string}, {some, Offset_string}]}] -> {Time_string, Offset_string}; [] -> Local_offset_in_minutes = birl_ffi:local_offset(), _assert_subject@1 = generate_offset( Local_offset_in_minutes * 60000000 ), {ok, Local_offset_string} = case _assert_subject@1 of {ok, _} -> _assert_subject@1; _assert_fail@1 -> erlang:error(#{gleam_error => assert, message => <<"Assertion pattern match failed"/utf8>>, value => _assert_fail@1, module => <<"birl/time"/utf8>>, function => <<"from_iso8601"/utf8>>, line => 129}) end, {Offsetted_time_string@1, Local_offset_string} end end, Date_string@3 = gleam@string:replace( Date_string@2, <<"-"/utf8>>, <<""/utf8>> ), Time_string@2 = gleam@string:replace( Time_string@1, <<":"/utf8>>, <<""/utf8>> ), {Time_string@5, Milli_seconds_result} = case gleam@string:split( Time_string@2, <<"."/utf8>> ) of [Time_string@3] -> {Time_string@3, {ok, 0}}; [Time_string@4, Milli_seconds_string] -> {Time_string@4, gleam@int:parse(Milli_seconds_string)} end, case Milli_seconds_result of {ok, Milli_seconds} -> gleam@result:then( parse_iso_section( Date_string@3, <<"(\\d{4})(\\d{2})?(\\d{2})?"/utf8>>, 1 ), fun(_use0) -> [Year, Month, Day] = _use0, gleam@result:then( parse_iso_section( Time_string@5, <<"(\\d{2})(\\d{2})?(\\d{2})?"/utf8>>, 0 ), fun(_use0@1) -> [Hour, Minute, Second] = _use0@1, case from_parts( {Year, Month, Day}, {Hour, Minute, Second, Milli_seconds}, Offset_string@1 ) of {ok, {time, Timestamp, Offset, none}} -> {ok, {time, Timestamp, Offset, none}}; {error, nil} -> {error, nil} end end ) end ); {error, nil} -> {error, nil} end. -spec weekday(time()) -> week_day(). weekday(Value) -> case Value of {time, T, O, _} -> _assert_subject = gleam@list:at( [monday, tuesday, wednesday, thursday, friday, saturday, sunday], birl_ffi:weekday(T + O) ), {ok, Weekday} = case _assert_subject of {ok, _} -> _assert_subject; _assert_fail -> erlang:error(#{gleam_error => assert, message => <<"Assertion pattern match failed"/utf8>>, value => _assert_fail, module => <<"birl/time"/utf8>>, function => <<"weekday"/utf8>>, line => 246}) end, Weekday end.