-module(calendar@time). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/calendar/time.gleam"). -export([new/5, new_simple/3, to_seconds_after_midnight/1, to_seconds_after_midnight_tuple/1, to_microseconds_after_midnight/1, to_timestamp/1, utc_now_with_precision/2, utc_now/0, new_unchecked_bang/5, to_erl/1, from_erl/3, from_erl_unchecked/3, compare/2, before/2, 'after'/2, convert/2, convert_unchecked/2, diff/3, truncate/2, is_valid/4, max_precision/1, convert_unit/3, midnight/0, noon/0, is_midnight/1, second_of_day/1, normalize_precision/2, from_iso8601/1, from_iso8601_unchecked/1, to_string/1, to_iso8601_with_format/2, to_iso8601/1, inspect/1, from_seconds_after_midnight_with_microsecond/3, from_seconds_after_midnight/1, from_timestamp/1, add/3, shift/2]). -export_type([time/0, time_error/0, time_format/0, time_unit/0, time_comparison/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 time() :: {time, integer(), integer(), integer(), {integer(), integer()}, binary()}. -type time_error() :: invalid_time | invalid_hour | invalid_minute | invalid_second | invalid_microsecond. -type time_format() :: extended | basic. -type time_unit() :: second | millisecond | microsecond | native. -type time_comparison() :: lt | eq | gt. -file("src/calendar/time.gleam", 75). ?DOC(" Validates if the given time components are valid.\n"). -spec is_valid_time(integer(), integer(), integer(), {integer(), integer()}) -> boolean(). is_valid_time(Hour, Minute, Second, Microsecond) -> {Ms, Precision} = Microsecond, (((((((((Hour >= 0) andalso (Hour =< 23)) andalso (Minute >= 0)) andalso (Minute =< 59)) andalso (Second >= 0)) andalso (Second =< 59)) andalso (Ms >= 0)) andalso (Ms < 1000000)) andalso (Precision >= 0)) andalso (Precision =< 6). -file("src/calendar/time.gleam", 44). ?DOC( " Creates a new Time struct.\n" "\n" " Returns an error if the time is invalid.\n" ). -spec new(integer(), integer(), integer(), {integer(), integer()}, binary()) -> {ok, time()} | {error, time_error()}. new(Hour, Minute, Second, Microsecond, Calendar) -> case is_valid_time(Hour, Minute, Second, Microsecond) of true -> {ok, {time, Hour, Minute, Second, Microsecond, Calendar}}; false -> {error, invalid_time} end. -file("src/calendar/time.gleam", 66). ?DOC( " Creates a new Time struct with default values.\n" " Uses microsecond #(0, 0) and Calendar.ISO as defaults.\n" ). -spec new_simple(integer(), integer(), integer()) -> {ok, time()} | {error, time_error()}. new_simple(Hour, Minute, Second) -> new(Hour, Minute, Second, {0, 0}, <<"Calendar.ISO"/utf8>>). -file("src/calendar/time.gleam", 143). ?DOC(" Converts time to total seconds since midnight.\n"). -spec to_seconds_after_midnight(time()) -> integer(). to_seconds_after_midnight(Time) -> ((erlang:element(2, Time) * 3600) + (erlang:element(3, Time) * 60)) + erlang:element( 4, Time ). -file("src/calendar/time.gleam", 148). ?DOC(" Converts time to total seconds since midnight as a tuple with microseconds.\n"). -spec to_seconds_after_midnight_tuple(time()) -> {integer(), integer()}. to_seconds_after_midnight_tuple(Time) -> {Ms, _} = erlang:element(5, Time), Total_seconds = ((erlang:element(2, Time) * 3600) + (erlang:element(3, Time) * 60)) + erlang:element(4, Time), {Total_seconds, Ms}. -file("src/calendar/time.gleam", 155). ?DOC(" Converts time to total microseconds since midnight.\n"). -spec to_microseconds_after_midnight(time()) -> integer(). to_microseconds_after_midnight(Time) -> {Ms, _} = erlang:element(5, Time), Total_seconds = ((erlang:element(2, Time) * 3600) + (erlang:element(3, Time) * 60)) + erlang:element(4, Time), (Total_seconds * 1000000) + Ms. -file("src/calendar/time.gleam", 187). ?DOC( " Converts a Time to a Unix timestamp (seconds since epoch).\n" " Note: This requires a date to be meaningful. This function assumes \n" " the Unix epoch date (1970-01-01) for demonstration purposes.\n" ). -spec to_timestamp(time()) -> integer(). to_timestamp(Time) -> to_seconds_after_midnight(Time). -file("src/calendar/time.gleam", 204). ?DOC(" Returns the current time in UTC with specified precision and calendar.\n"). -spec utc_now_with_precision(time_unit(), binary()) -> {ok, time()} | {error, time_error()}. utc_now_with_precision(Precision, Calendar) -> {_, Nanoseconds} = gleam_time_ffi:system_time(), Total_seconds = Nanoseconds div 1000000000, Remaining_nanos = Nanoseconds rem 1000000000, Microseconds = Remaining_nanos div 1000, Hours = (Total_seconds div 3600) rem 24, Remaining = Total_seconds rem 3600, Minutes = Remaining div 60, Seconds = Remaining rem 60, Adjusted_precision = case Precision of second -> {0, 0}; millisecond -> {(Microseconds div 1000) * 1000, 3}; microsecond -> {Microseconds, 6}; native -> {Microseconds, 6} end, new(Hours, Minutes, Seconds, Adjusted_precision, Calendar). -file("src/calendar/time.gleam", 199). ?DOC(" Returns the current time in UTC.\n"). -spec utc_now() -> {ok, time()} | {error, time_error()}. utc_now() -> utc_now_with_precision(microsecond, <<"Calendar.ISO"/utf8>>). -file("src/calendar/time.gleam", 235). ?DOC(" Creates a new Time, panicking if invalid.\n"). -spec new_unchecked_bang( integer(), integer(), integer(), {integer(), integer()}, binary() ) -> time(). new_unchecked_bang(Hour, Minute, Second, Microsecond, Calendar) -> case new(Hour, Minute, Second, Microsecond, Calendar) of {ok, Time} -> Time; {error, _} -> erlang:error(#{gleam_error => panic, message => <<"Invalid time"/utf8>>, file => <>, module => <<"calendar/time"/utf8>>, function => <<"new_unchecked_bang"/utf8>>, line => 244}) end. -file("src/calendar/time.gleam", 266). ?DOC(" Converts time to Erlang time tuple.\n"). -spec to_erl(time()) -> {integer(), integer(), integer()}. to_erl(Time) -> {erlang:element(2, Time), erlang:element(3, Time), erlang:element(4, Time)}. -file("src/calendar/time.gleam", 271). ?DOC(" Creates time from Erlang time tuple.\n"). -spec from_erl( {integer(), integer(), integer()}, {integer(), integer()}, binary() ) -> {ok, time()} | {error, time_error()}. from_erl(Erl_time, Microsecond, Calendar) -> {Hour, Minute, Second} = Erl_time, new(Hour, Minute, Second, Microsecond, Calendar). -file("src/calendar/time.gleam", 281). ?DOC(" Creates time from Erlang time tuple, panicking on error.\n"). -spec from_erl_unchecked( {integer(), integer(), integer()}, {integer(), integer()}, binary() ) -> time(). from_erl_unchecked(Erl_time, Microsecond, Calendar) -> case from_erl(Erl_time, Microsecond, Calendar) of {ok, Time} -> Time; {error, _} -> erlang:error(#{gleam_error => panic, message => <<"Invalid Erlang time tuple"/utf8>>, file => <>, module => <<"calendar/time"/utf8>>, function => <<"from_erl_unchecked"/utf8>>, line => 288}) end. -file("src/calendar/time.gleam", 335). -spec compare(time(), time()) -> time_comparison(). compare(Time1, Time2) -> Ms1 = to_microseconds_after_midnight(Time1), Ms2 = to_microseconds_after_midnight(Time2), case Ms1 < Ms2 of true -> lt; false -> case Ms1 > Ms2 of true -> gt; false -> eq end end. -file("src/calendar/time.gleam", 350). ?DOC(" Check if first time is before second time.\n"). -spec before(time(), time()) -> boolean(). before(Time1, Time2) -> case compare(Time1, Time2) of lt -> true; _ -> false end. -file("src/calendar/time.gleam", 358). ?DOC(" Check if first time is after second time.\n"). -spec 'after'(time(), time()) -> boolean(). 'after'(Time1, Time2) -> case compare(Time1, Time2) of gt -> true; _ -> false end. -file("src/calendar/time.gleam", 366). ?DOC(" Convert time between calendars.\n"). -spec convert(time(), binary()) -> {ok, time()} | {error, time_error()}. convert(Time, Target_calendar) -> case erlang:element(6, Time) =:= Target_calendar of true -> {ok, Time}; false -> case new( erlang:element(2, Time), erlang:element(3, Time), erlang:element(4, Time), erlang:element(5, Time), Target_calendar ) of {ok, T} -> {ok, T}; {error, E} -> {error, E} end end. -file("src/calendar/time.gleam", 388). ?DOC(" Convert time between calendars, panicking on error.\n"). -spec convert_unchecked(time(), binary()) -> time(). convert_unchecked(Time, Target_calendar) -> case convert(Time, Target_calendar) of {ok, Converted} -> Converted; {error, _} -> erlang:error(#{gleam_error => panic, message => <<"Calendar conversion failed"/utf8>>, file => <>, module => <<"calendar/time"/utf8>>, function => <<"convert_unchecked"/utf8>>, line => 391}) end. -file("src/calendar/time.gleam", 396). ?DOC(" Calculate difference between two times.\n"). -spec diff(time(), time(), time_unit()) -> integer(). diff(Time1, Time2, Unit) -> Ms1 = to_microseconds_after_midnight(Time1), Ms2 = to_microseconds_after_midnight(Time2), Diff_microseconds = Ms1 - Ms2, case Unit of microsecond -> Diff_microseconds; native -> Diff_microseconds; millisecond -> Diff_microseconds div 1000; second -> Diff_microseconds div 1000000 end. -file("src/calendar/time.gleam", 423). ?DOC(" Truncate time precision.\n"). -spec truncate(time(), integer()) -> time(). truncate(Time, Precision) -> {Ms, _} = erlang:element(5, Time), Truncated_ms = case Precision of 0 -> {0, 0}; P when P >= 6 -> {Ms, 6}; P@1 -> Divisor = case P@1 of 1 -> 100000; 2 -> 10000; 3 -> 1000; 4 -> 100; 5 -> 10; _ -> 1 end, {(case Divisor of 0 -> 0; Gleam@denominator -> Ms div Gleam@denominator end) * Divisor, P@1} end, case new( erlang:element(2, Time), erlang:element(3, Time), erlang:element(4, Time), Truncated_ms, erlang:element(6, Time) ) of {ok, T} -> T; {error, _} -> Time end. -file("src/calendar/time.gleam", 454). ?DOC(" Check if a time is valid.\n"). -spec is_valid(integer(), integer(), integer(), {integer(), integer()}) -> boolean(). is_valid(Hour, Minute, Second, Microsecond) -> is_valid_time(Hour, Minute, Second, Microsecond). -file("src/calendar/time.gleam", 464). ?DOC(" Get the maximum precision for a time unit.\n"). -spec max_precision(time_unit()) -> integer(). max_precision(Unit) -> case Unit of second -> 0; millisecond -> 3; microsecond -> 6; native -> 6 end. -file("src/calendar/time.gleam", 491). ?DOC(" Convert between time units.\n"). -spec convert_unit(integer(), time_unit(), time_unit()) -> integer(). convert_unit(Amount, From_unit, To_unit) -> Microseconds = case From_unit of second -> Amount * 1000000; millisecond -> Amount * 1000; microsecond -> Amount; native -> Amount end, case To_unit of second -> Microseconds div 1000000; millisecond -> Microseconds div 1000; microsecond -> Microseconds; native -> Microseconds end. -file("src/calendar/time.gleam", 506). ?DOC(" Get the time at midnight (00:00:00).\n"). -spec midnight() -> {ok, time()} | {error, time_error()}. midnight() -> new(0, 0, 0, {0, 0}, <<"Calendar.ISO"/utf8>>). -file("src/calendar/time.gleam", 511). ?DOC(" Get the time at noon (12:00:00).\n"). -spec noon() -> {ok, time()} | {error, time_error()}. noon() -> new(12, 0, 0, {0, 0}, <<"Calendar.ISO"/utf8>>). -file("src/calendar/time.gleam", 516). ?DOC(" Check if a time is at midnight.\n"). -spec is_midnight(time()) -> boolean(). is_midnight(Time) -> (((erlang:element(2, Time) =:= 0) andalso (erlang:element(3, Time) =:= 0)) andalso (erlang:element(4, Time) =:= 0)) andalso (erlang:element(5, Time) =:= {0, 0}). -file("src/calendar/time.gleam", 524). ?DOC(" Get the second of the day (0-86399).\n"). -spec second_of_day(time()) -> integer(). second_of_day(Time) -> ((erlang:element(2, Time) * 3600) + (erlang:element(3, Time) * 60)) + erlang:element( 4, Time ). -file("src/calendar/time.gleam", 572). -spec int_pow(integer(), integer()) -> integer(). int_pow(Base, Exp) -> case Exp of 0 -> 1; N when N > 0 -> Base * int_pow(Base, N - 1); _ -> 1 end. -file("src/calendar/time.gleam", 473). ?DOC(" Normalize microseconds to a specific precision.\n"). -spec normalize_precision({integer(), integer()}, integer()) -> {integer(), integer()}. normalize_precision(Microsecond, Target_precision) -> {Ms, Current_precision} = Microsecond, case Target_precision >= Current_precision of true -> {Ms * int_pow(10, Target_precision - Current_precision), Target_precision}; false -> {case int_pow(10, Current_precision - Target_precision) of 0 -> 0; Gleam@denominator -> Ms div Gleam@denominator end, Target_precision} end. -file("src/calendar/time.gleam", 565). -spec normalize_microseconds(integer(), integer()) -> integer(). normalize_microseconds(Ms, Precision) -> case Precision of P when P >= 6 -> Ms; P@1 -> Ms * int_pow(10, 6 - P@1) end. -file("src/calendar/time.gleam", 530). -spec parse_iso8601_time(binary()) -> {ok, {integer(), integer(), integer(), {integer(), integer()}}} | {error, nil}. parse_iso8601_time(Time_string) -> case gleam@string:split(Time_string, <<":"/utf8>>) of [Hour_str, Minute_str, Second_part] -> case {gleam_stdlib:parse_int(Hour_str), gleam_stdlib:parse_int(Minute_str)} of {{ok, Hour}, {ok, Minute}} -> case gleam@string:split(Second_part, <<"."/utf8>>) of [Second_str] -> case gleam_stdlib:parse_int(Second_str) of {ok, Second} -> {ok, {Hour, Minute, Second, {0, 0}}}; {error, _} -> {error, nil} end; [Second_str@1, Ms_str] -> case {gleam_stdlib:parse_int(Second_str@1), gleam_stdlib:parse_int(Ms_str)} of {{ok, Second@1}, {ok, Ms}} -> Precision = string:length(Ms_str), Normalized_ms = normalize_microseconds( Ms, Precision ), {ok, {Hour, Minute, Second@1, {Normalized_ms, Precision}}}; {_, _} -> {error, nil} end; _ -> {error, nil} end; {_, _} -> {error, nil} end; _ -> {error, nil} end. -file("src/calendar/time.gleam", 249). ?DOC(" Parses an ISO8601 time string.\n"). -spec from_iso8601(binary()) -> {ok, time()} | {error, time_error()}. from_iso8601(Time_string) -> case parse_iso8601_time(Time_string) of {ok, {Hour, Minute, Second, Microsecond}} -> new(Hour, Minute, Second, Microsecond, <<"Calendar.ISO"/utf8>>); {error, _} -> {error, invalid_time} end. -file("src/calendar/time.gleam", 258). ?DOC(" Parses an ISO8601 time string, panicking on error.\n"). -spec from_iso8601_unchecked(binary()) -> time(). from_iso8601_unchecked(Time_string) -> case from_iso8601(Time_string) of {ok, Time} -> Time; {error, _} -> erlang:error(#{gleam_error => panic, message => <<"Invalid ISO8601 time format"/utf8>>, file => <>, module => <<"calendar/time"/utf8>>, function => <<"from_iso8601_unchecked"/utf8>>, line => 261}) end. -file("src/calendar/time.gleam", 581). ?DOC(" Helper function to pad single digits with leading zero.\n"). -spec pad_zero(integer()) -> binary(). pad_zero(N) -> case N < 10 of true -> <<"0"/utf8, (erlang:integer_to_binary(N))/binary>>; false -> erlang:integer_to_binary(N) end. -file("src/calendar/time.gleam", 589). ?DOC(" Helper function to format microseconds with proper padding.\n"). -spec pad_microseconds(integer(), integer()) -> binary(). pad_microseconds(Microseconds, Precision) -> Ms_str = erlang:integer_to_binary(Microseconds), 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, gleam@string:slice(Padded, 0, Precision). -file("src/calendar/time.gleam", 96). ?DOC(" Converts a Time to a string in ISO8601 format.\n"). -spec to_string(time()) -> binary(). to_string(Time) -> Hour_str = pad_zero(erlang:element(2, Time)), Minute_str = pad_zero(erlang:element(3, Time)), Second_str = pad_zero(erlang:element(4, Time)), {Ms, Precision} = erlang:element(5, Time), case (Ms =:= 0) orelse (Precision =:= 0) of true -> <<<<<<<>/binary, Minute_str/binary>>/binary, ":"/utf8>>/binary, Second_str/binary>>; false -> Ms_str = pad_microseconds(Ms, Precision), <<<<<<<<<<<>/binary, Minute_str/binary>>/binary, ":"/utf8>>/binary, Second_str/binary>>/binary, "."/utf8>>/binary, Ms_str/binary>> end. -file("src/calendar/time.gleam", 122). ?DOC(" Converts a Time to ISO8601 format with specified format.\n"). -spec to_iso8601_with_format(time(), time_format()) -> binary(). to_iso8601_with_format(Time, Format) -> case Format of extended -> to_string(Time); basic -> Hour_str = pad_zero(erlang:element(2, Time)), Minute_str = pad_zero(erlang:element(3, Time)), Second_str = pad_zero(erlang:element(4, Time)), {Ms, Precision} = erlang:element(5, Time), case (Ms =:= 0) orelse (Precision =:= 0) of true -> <<<>/binary, Second_str/binary>>; false -> Ms_str = pad_microseconds(Ms, Precision), <<<<<<<>/binary, Second_str/binary>>/binary, "."/utf8>>/binary, Ms_str/binary>> end end. -file("src/calendar/time.gleam", 112). ?DOC(" Converts a Time to ISO8601 format.\n"). -spec to_iso8601(time()) -> binary(). to_iso8601(Time) -> to_iso8601_with_format(Time, extended). -file("src/calendar/time.gleam", 449). ?DOC(" Get string representation for inspection.\n"). -spec inspect(time()) -> binary(). inspect(Time) -> <<<<"~T["/utf8, (to_string(Time))/binary>>/binary, "]"/utf8>>. -file("src/calendar/time.gleam", 167). ?DOC(" Creates a Time from seconds since midnight with microsecond precision.\n"). -spec from_seconds_after_midnight_with_microsecond( integer(), {integer(), integer()}, binary() ) -> {ok, time()} | {error, time_error()}. from_seconds_after_midnight_with_microsecond(Seconds, Microsecond, Calendar) -> case (Seconds >= 0) andalso (Seconds < 86400) of true -> Hours = Seconds div 3600, Remaining_seconds = Seconds rem 3600, Minutes = Remaining_seconds div 60, Secs = Remaining_seconds rem 60, new(Hours, Minutes, Secs, Microsecond, Calendar); false -> {error, invalid_time} end. -file("src/calendar/time.gleam", 162). ?DOC(" Creates a Time from seconds since midnight.\n"). -spec from_seconds_after_midnight(integer()) -> {ok, time()} | {error, time_error()}. from_seconds_after_midnight(Seconds) -> from_seconds_after_midnight_with_microsecond( Seconds, {0, 0}, <<"Calendar.ISO"/utf8>> ). -file("src/calendar/time.gleam", 193). ?DOC( " Creates a Time from a Unix timestamp.\n" " Note: This extracts only the time portion, ignoring the date.\n" ). -spec from_timestamp(integer()) -> {ok, time()} | {error, time_error()}. from_timestamp(Timestamp) -> Seconds_in_day = case 86400 of 0 -> 0; Gleam@denominator -> Timestamp rem Gleam@denominator end, from_seconds_after_midnight(Seconds_in_day). -file("src/calendar/time.gleam", 293). ?DOC(" Adds a duration to a time.\n"). -spec add(time(), integer(), time_unit()) -> {ok, time()} | {error, time_error()}. add(Time, Amount, Unit) -> Total_microseconds = to_microseconds_after_midnight(Time), Microseconds_to_add = case Unit of second -> Amount * 1000000; millisecond -> Amount * 1000; microsecond -> Amount; native -> Amount end, New_total = Total_microseconds + Microseconds_to_add, New_seconds = New_total div 1000000, New_microseconds = New_total rem 1000000, case from_seconds_after_midnight(case 86400 of 0 -> 0; Gleam@denominator -> New_seconds rem Gleam@denominator end) of {ok, Base_time} -> {_, Precision} = erlang:element(5, Time), case new( erlang:element(2, Base_time), erlang:element(3, Base_time), erlang:element(4, Base_time), {New_microseconds, Precision}, erlang:element(6, Time) ) of {ok, T} -> {ok, T}; {error, E} -> {error, E} end; {error, Err} -> {error, Err} end. -file("src/calendar/time.gleam", 409). ?DOC(" Shift time by a duration.\n"). -spec shift(time(), calendar@duration:duration()) -> {ok, time()} | {error, time_error()}. shift(Time, Duration) -> Total_seconds = ((erlang:element(6, Duration) * 3600) + (erlang:element( 7, Duration ) * 60)) + erlang:element(8, Duration), {Duration_ms, _} = erlang:element(9, Duration), Total_microseconds = (Total_seconds * 1000000) + Duration_ms, add(Time, Total_microseconds, microsecond).