-module(datetime_iso8601). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/datetime_iso8601.gleam"). -export([parse/1]). -export_type([date_time/0, time/0, day_of_week/0, timezone/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 date_time() :: {calendar_date, integer(), gleam@time@calendar:month(), integer(), gleam@option:option(time())} | {calendar_month_date, integer(), gleam@time@calendar:month()} | {calendar_year_date, integer()} | {ordinal_date, integer(), integer(), gleam@option:option(time())} | {week_date, integer(), integer(), day_of_week(), gleam@option:option(time())} | {year_week, integer(), integer()}. -type time() :: {time_second, integer(), integer(), integer(), gleam@option:option(integer()), gleam@option:option(timezone())} | {time_minute, integer(), integer(), gleam@option:option(integer()), gleam@option:option(timezone())} | {time_hour, integer(), gleam@option:option(integer()), gleam@option:option(timezone())}. -type day_of_week() :: monday | tuesday | wednesday | thursday | friday | saturday | sunday. -type timezone() :: utc | {offset, integer(), integer()}. -file("src/datetime_iso8601.gleam", 587). -spec int_to_day_of_week(integer()) -> {ok, day_of_week()} | {error, nil}. int_to_day_of_week(Day) -> case Day of 1 -> {ok, monday}; 2 -> {ok, tuesday}; 3 -> {ok, wednesday}; 4 -> {ok, thursday}; 5 -> {ok, friday}; 6 -> {ok, saturday}; 7 -> {ok, sunday}; _ -> {error, nil} end. -file("src/datetime_iso8601.gleam", 657). -spec is_leap_year(integer()) -> boolean(). is_leap_year(Year) -> ((Year rem 4) =:= 0) andalso (((Year rem 100) /= 0) orelse ((Year rem 400) =:= 0)). -file("src/datetime_iso8601.gleam", 600). -spec validate_day(integer(), gleam@time@calendar:month(), integer()) -> {ok, nil} | {error, nil}. validate_day(Year, Month, Day) -> Max_day = case Month of january -> 31; march -> 31; may -> 31; july -> 31; august -> 31; october -> 31; december -> 31; april -> 30; june -> 30; september -> 30; november -> 30; february -> case is_leap_year(Year) of true -> 29; false -> 28 end end, case (Day >= 1) andalso (Day =< Max_day) of true -> {ok, nil}; false -> {error, nil} end. -file("src/datetime_iso8601.gleam", 623). -spec validate_day_of_year(integer(), integer()) -> {ok, nil} | {error, nil}. validate_day_of_year(Year, Day) -> Max_day = case is_leap_year(Year) of true -> 366; false -> 365 end, case (Day >= 1) andalso (Day =< Max_day) of true -> {ok, nil}; false -> {error, nil} end. -file("src/datetime_iso8601.gleam", 642). -spec weeks_in_year(integer()) -> integer(). weeks_in_year(Year) -> Y = Year - 1, Jan1_weekday = ((((1 + Y) + (Y div 4)) - (Y div 100)) + (Y div 400)) rem 7, Days_in_year = case is_leap_year(Year) of true -> 366; false -> 365 end, Dec31_weekday = ((Jan1_weekday + Days_in_year) - 1) rem 7, case (Jan1_weekday =:= 4) orelse (Dec31_weekday =:= 4) of true -> 53; false -> 52 end. -file("src/datetime_iso8601.gleam", 634). -spec validate_week(integer(), integer()) -> {ok, nil} | {error, nil}. validate_week(Year, Week) -> Max_week = weeks_in_year(Year), case (Week >= 1) andalso (Week =< Max_week) of true -> {ok, nil}; false -> {error, nil} end. -file("src/datetime_iso8601.gleam", 661). -spec validate_hour(integer()) -> {ok, nil} | {error, nil}. validate_hour(Hour) -> case (Hour >= 0) andalso (Hour =< 24) of true -> {ok, nil}; false -> {error, nil} end. -file("src/datetime_iso8601.gleam", 668). -spec validate_midnight(time()) -> {ok, nil} | {error, nil}. validate_midnight(Time) -> case Time of {time_second, 24, Minute, Second, Nanosecond, _} -> case ((Minute =:= 0) andalso (Second =:= 0)) andalso (Nanosecond =:= none) of true -> {ok, nil}; false -> {error, nil} end; {time_minute, 24, Minute@1, Nanosecond@1, _} -> case (Minute@1 =:= 0) andalso (Nanosecond@1 =:= none) of true -> {ok, nil}; false -> {error, nil} end; {time_hour, 24, Nanosecond@2, _} -> case Nanosecond@2 =:= none of true -> {ok, nil}; false -> {error, nil} end; _ -> {ok, nil} end. -file("src/datetime_iso8601.gleam", 689). -spec validate_minute(integer()) -> {ok, nil} | {error, nil}. validate_minute(Minute) -> case (Minute >= 0) andalso (Minute =< 59) of true -> {ok, nil}; false -> {error, nil} end. -file("src/datetime_iso8601.gleam", 696). -spec validate_second(integer()) -> {ok, nil} | {error, nil}. validate_second(Second) -> case (Second >= 0) andalso (Second =< 59) of true -> {ok, nil}; false -> {error, nil} end. -file("src/datetime_iso8601.gleam", 558). -spec parse_sign(bitstring()) -> {ok, {binary(), bitstring()}} | {error, nil}. parse_sign(Bytes) -> case Bytes of <> when Byte =:= 16#2B -> {ok, {<<"+"/utf8>>, Rest}}; <> when Byte@1 =:= 16#2D -> {ok, {<<"-"/utf8>>, Rest@1}}; _ -> {error, nil} end. -file("src/datetime_iso8601.gleam", 491). -spec parse_fraction_as_nanoseconds_loop(bitstring(), integer(), integer()) -> {ok, {integer(), bitstring()}} | {error, nil}. parse_fraction_as_nanoseconds_loop(Bytes, Acc, Power) -> Power@1 = Power div 10, case Bytes of <> when ((Byte >= 16#30) andalso (Byte =< 16#39)) andalso (Power@1 < 1) -> parse_fraction_as_nanoseconds_loop(Rest, Acc, Power@1); <> when (Byte@1 >= 16#30) andalso (Byte@1 =< 16#39) -> Digit = Byte@1 - 16#30, parse_fraction_as_nanoseconds_loop( Rest@1, Acc + (Digit * Power@1), Power@1 ); _ -> {ok, {Acc, Bytes}} end. -file("src/datetime_iso8601.gleam", 476). -spec parse_fraction_as_nanoseconds(bitstring()) -> {ok, {integer(), bitstring()}} | {error, nil}. parse_fraction_as_nanoseconds(Bytes) -> case Bytes of <> when (Byte >= 16#30) andalso (Byte =< 16#39) -> parse_fraction_as_nanoseconds_loop( <>, 0, 1000000000 ); _ -> {error, nil} end. -file("src/datetime_iso8601.gleam", 464). -spec parse_optional_fraction_as_nanoseconds(bitstring()) -> {ok, {gleam@option:option(integer()), bitstring()}} | {error, nil}. parse_optional_fraction_as_nanoseconds(Bytes) -> case Bytes of <> when Byte =:= 16#2E -> gleam@result:'try'( parse_fraction_as_nanoseconds(Rest), fun(_use0) -> {Ns, Bytes@1} = _use0, {ok, {{some, Ns}, Bytes@1}} end ); _ -> {ok, {none, Bytes}} end. -file("src/datetime_iso8601.gleam", 573). -spec parse_digits_loop(bitstring(), integer(), integer(), integer()) -> {ok, {integer(), bitstring()}} | {error, nil}. parse_digits_loop(Bytes, Count, Acc, K) -> case Bytes of _ when K >= Count -> {ok, {Acc, Bytes}}; <> when (Byte >= 16#30) andalso (Byte =< 16#39) -> parse_digits_loop(Rest, Count, (Acc * 10) + (Byte - 16#30), K + 1); _ -> {error, nil} end. -file("src/datetime_iso8601.gleam", 566). -spec parse_digits(bitstring(), integer()) -> {ok, {integer(), bitstring()}} | {error, nil}. parse_digits(Bytes, Count) -> parse_digits_loop(Bytes, Count, 0, 0). -file("src/datetime_iso8601.gleam", 523). -spec parse_timezone_offset(bitstring()) -> {ok, {gleam@option:option(timezone()), bitstring()}} | {error, nil}. parse_timezone_offset(Bytes) -> gleam@result:'try'( parse_sign(Bytes), fun(_use0) -> {Sign, Bytes@1} = _use0, gleam@result:'try'( parse_digits(Bytes@1, 2), fun(_use0@1) -> {Hours, Bytes@2} = _use0@1, case Bytes@2 of <> when Byte =:= 16#3A -> gleam@result:'try'( parse_digits(Rest, 2), fun(_use0@2) -> {Minutes, Bytes@3} = _use0@2, Hours@1 = case Sign of <<"-"/utf8>> -> - Hours; _ -> Hours end, {ok, {{some, {offset, Hours@1, Minutes}}, Bytes@3}} end ); <> when (Byte@1 >= 16#30) andalso (Byte@1 =< 16#39) -> gleam@result:'try'( parse_digits(Bytes@2, 2), fun(_use0@3) -> {Minutes@1, Bytes@4} = _use0@3, Hours@2 = case Sign of <<"-"/utf8>> -> - Hours; _ -> Hours end, {ok, {{some, {offset, Hours@2, Minutes@1}}, Bytes@4}} end ); _ -> Hours@3 = case Sign of <<"-"/utf8>> -> - Hours; _ -> Hours end, {ok, {{some, {offset, Hours@3, 0}}, Bytes@2}} end end ) end ). -file("src/datetime_iso8601.gleam", 510). -spec parse_optional_timezone(bitstring()) -> {ok, {gleam@option:option(timezone()), bitstring()}} | {error, nil}. parse_optional_timezone(Bytes) -> case Bytes of <> when (Byte =:= 16#5A) orelse (Byte =:= 16#7A) -> {ok, {{some, utc}, Rest}}; <> when (Byte@1 =:= 16#2B) orelse (Byte@1 =:= 16#2D) -> parse_timezone_offset(Bytes); _ -> {ok, {none, Bytes}} end. -file("src/datetime_iso8601.gleam", 402). -spec parse_time_extended_after_hour(bitstring(), integer()) -> {ok, {time(), bitstring()}} | {error, nil}. parse_time_extended_after_hour(Bytes, Hour) -> gleam@result:'try'( parse_digits(Bytes, 2), fun(_use0) -> {Minute, Bytes@1} = _use0, gleam@result:'try'( validate_minute(Minute), fun(_) -> case Bytes@1 of <> when Byte =:= 16#3A -> gleam@result:'try'( parse_digits(Rest, 2), fun(_use0@1) -> {Second, Bytes@2} = _use0@1, gleam@result:'try'( validate_second(Second), fun(_) -> gleam@result:'try'( parse_optional_fraction_as_nanoseconds( Bytes@2 ), fun(_use0@2) -> {Nanosecond, Bytes@3} = _use0@2, gleam@result:'try'( parse_optional_timezone( Bytes@3 ), fun(_use0@3) -> {Timezone, Bytes@4} = _use0@3, {ok, {{time_second, Hour, Minute, Second, Nanosecond, Timezone}, Bytes@4}} end ) end ) end ) end ); <> when Byte@1 =:= 16#2E -> gleam@result:'try'( parse_fraction_as_nanoseconds(Rest@1), fun(_use0@4) -> {Nanosecond@1, Bytes@5} = _use0@4, gleam@result:'try'( parse_optional_timezone(Bytes@5), fun(_use0@5) -> {Timezone@1, Bytes@6} = _use0@5, {ok, {{time_minute, Hour, Minute, {some, Nanosecond@1}, Timezone@1}, Bytes@6}} end ) end ); _ -> gleam@result:'try'( parse_optional_timezone(Bytes@1), fun(_use0@6) -> {Timezone@2, Bytes@7} = _use0@6, {ok, {{time_minute, Hour, Minute, none, Timezone@2}, Bytes@7}} end ) end end ) end ). -file("src/datetime_iso8601.gleam", 433). -spec parse_time_basic_after_hour(bitstring(), integer()) -> {ok, {time(), bitstring()}} | {error, nil}. parse_time_basic_after_hour(Bytes, Hour) -> gleam@result:'try'( parse_digits(Bytes, 2), fun(_use0) -> {Minute, Bytes@1} = _use0, gleam@result:'try'( validate_minute(Minute), fun(_) -> case Bytes@1 of <> when (Byte >= 16#30) andalso (Byte =< 16#39) -> gleam@result:'try'( parse_digits(Bytes@1, 2), fun(_use0@1) -> {Second, Bytes@2} = _use0@1, gleam@result:'try'( validate_second(Second), fun(_) -> gleam@result:'try'( parse_optional_fraction_as_nanoseconds( Bytes@2 ), fun(_use0@2) -> {Nanosecond, Bytes@3} = _use0@2, gleam@result:'try'( parse_optional_timezone( Bytes@3 ), fun(_use0@3) -> {Timezone, Bytes@4} = _use0@3, {ok, {{time_second, Hour, Minute, Second, Nanosecond, Timezone}, Bytes@4}} end ) end ) end ) end ); <> when Byte@1 =:= 16#2E -> gleam@result:'try'( parse_fraction_as_nanoseconds(Rest), fun(_use0@4) -> {Nanosecond@1, Bytes@5} = _use0@4, gleam@result:'try'( parse_optional_timezone(Bytes@5), fun(_use0@5) -> {Timezone@1, Bytes@6} = _use0@5, {ok, {{time_minute, Hour, Minute, {some, Nanosecond@1}, Timezone@1}, Bytes@6}} end ) end ); _ -> gleam@result:'try'( parse_optional_timezone(Bytes@1), fun(_use0@6) -> {Timezone@2, Bytes@7} = _use0@6, {ok, {{time_minute, Hour, Minute, none, Timezone@2}, Bytes@7}} end ) end end ) end ). -file("src/datetime_iso8601.gleam", 369). -spec parse_time_after_hour(bitstring(), integer()) -> {ok, {time(), bitstring()}} | {error, nil}. parse_time_after_hour(Bytes, Hour) -> case Bytes of <> when Byte =:= 16#3A -> parse_time_extended_after_hour(Rest, Hour); <> when Byte@1 =:= 16#2E -> gleam@result:'try'( parse_fraction_as_nanoseconds(Rest@1), fun(_use0) -> {Nanosecond, Bytes@1} = _use0, gleam@result:'try'( parse_optional_timezone(Bytes@1), fun(_use0@1) -> {Timezone, Bytes@2} = _use0@1, {ok, {{time_hour, Hour, {some, Nanosecond}, Timezone}, Bytes@2}} end ) end ); <> when (((Byte@2 =:= 16#5A) orelse (Byte@2 =:= 16#7A)) orelse (Byte@2 =:= 16#2B)) orelse (Byte@2 =:= 16#2D) -> gleam@result:'try'( parse_optional_timezone(Bytes), fun(_use0@2) -> {Timezone@1, Bytes@3} = _use0@2, {ok, {{time_hour, Hour, none, Timezone@1}, Bytes@3}} end ); <> when (Byte@3 >= 16#30) andalso (Byte@3 =< 16#39) -> parse_time_basic_after_hour(Bytes, Hour); <<>> -> {ok, {{time_hour, Hour, none, none}, Bytes}}; _ -> {error, nil} end. -file("src/datetime_iso8601.gleam", 361). -spec parse_time(bitstring()) -> {ok, {time(), bitstring()}} | {error, nil}. parse_time(Bytes) -> gleam@result:'try'( parse_digits(Bytes, 2), fun(_use0) -> {Hour, Bytes@1} = _use0, gleam@result:'try'( validate_hour(Hour), fun(_) -> gleam@result:'try'( parse_time_after_hour(Bytes@1, Hour), fun(_use0@1) -> {Time, Bytes@2} = _use0@1, gleam@result:'try'( validate_midnight(Time), fun(_) -> {ok, {Time, Bytes@2}} end ) end ) end ) end ). -file("src/datetime_iso8601.gleam", 345). -spec parse_optional_time(bitstring()) -> {ok, {gleam@option:option(time()), bitstring()}} | {error, nil}. parse_optional_time(Bytes) -> case Bytes of <> when ((Byte =:= 16#54) orelse (Byte =:= 16#74)) orelse (Byte =:= 16#20) -> gleam@result:'try'( parse_time(Rest), fun(_use0) -> {Time, Bytes@1} = _use0, {ok, {{some, Time}, Bytes@1}} end ); _ -> {ok, {none, Bytes}} end. -file("src/datetime_iso8601.gleam", 228). -spec parse_extended_calendar_or_ordinal(bitstring(), integer()) -> {ok, date_time()} | {error, nil}. parse_extended_calendar_or_ordinal(Bytes, Year) -> gleam@result:'try'( parse_digits(Bytes, 2), fun(_use0) -> {First_two, Bytes@1} = _use0, case Bytes@1 of <> when Byte =:= 16#2D -> Month = First_two, gleam@result:'try'( gleam@time@calendar:month_from_int(Month), fun(Month@1) -> gleam@result:'try'( parse_digits(Rest, 2), fun(_use0@1) -> {Day, Bytes@2} = _use0@1, gleam@result:'try'( validate_day(Year, Month@1, Day), fun(_) -> gleam@result:'try'( parse_optional_time(Bytes@2), fun(_use0@2) -> {Time, Bytes@3} = _use0@2, case Bytes@3 of <<>> -> {ok, {calendar_date, Year, Month@1, Day, Time}}; _ -> {error, nil} end end ) end ) end ) end ); <> when (Byte@1 >= 16#30) andalso (Byte@1 =< 16#39) -> gleam@result:'try'( parse_digits(Bytes@1, 1), fun(_use0@3) -> {Third_digit, Bytes@4} = _use0@3, Day_of_year = (First_two * 10) + Third_digit, gleam@result:'try'( validate_day_of_year(Year, Day_of_year), fun(_) -> gleam@result:'try'( parse_optional_time(Bytes@4), fun(_use0@4) -> {Time@1, Bytes@5} = _use0@4, case Bytes@5 of <<>> -> {ok, {ordinal_date, Year, Day_of_year, Time@1}}; _ -> {error, nil} end end ) end ) end ); <<>> -> Month@2 = First_two, gleam@result:'try'( gleam@time@calendar:month_from_int(Month@2), fun(Month@3) -> {ok, {calendar_month_date, Year, Month@3}} end ); <> when ((Byte@2 =:= 16#54) orelse (Byte@2 =:= 16#74)) orelse (Byte@2 =:= 16#20) -> {error, nil}; _ -> {error, nil} end end ). -file("src/datetime_iso8601.gleam", 277). -spec parse_extended_week(bitstring(), integer()) -> {ok, date_time()} | {error, nil}. parse_extended_week(Bytes, Year) -> gleam@result:'try'( parse_digits(Bytes, 2), fun(_use0) -> {Week, Bytes@1} = _use0, gleam@result:'try'( validate_week(Year, Week), fun(_) -> case Bytes@1 of <> when Byte =:= 16#2D -> gleam@result:'try'( parse_digits(Rest, 1), fun(_use0@1) -> {Day_num, Bytes@2} = _use0@1, gleam@result:'try'( int_to_day_of_week(Day_num), fun(Day_of_week) -> gleam@result:'try'( parse_optional_time(Bytes@2), fun(_use0@2) -> {Time, Bytes@3} = _use0@2, case Bytes@3 of <<>> -> {ok, {week_date, Year, Week, Day_of_week, Time}}; _ -> {error, nil} end end ) end ) end ); <<>> -> {ok, {year_week, Year, Week}}; _ -> {error, nil} end end ) end ). -file("src/datetime_iso8601.gleam", 219). -spec parse_extended_format(bitstring(), integer()) -> {ok, date_time()} | {error, nil}. parse_extended_format(Bytes, Year) -> case Bytes of <> when (Byte =:= 16#57) orelse (Byte =:= 16#77) -> parse_extended_week(Rest, Year); _ -> parse_extended_calendar_or_ordinal(Bytes, Year) end. -file("src/datetime_iso8601.gleam", 295). -spec parse_basic_week(bitstring(), integer()) -> {ok, date_time()} | {error, nil}. parse_basic_week(Bytes, Year) -> gleam@result:'try'( parse_digits(Bytes, 2), fun(_use0) -> {Week, Bytes@1} = _use0, gleam@result:'try'( validate_week(Year, Week), fun(_) -> case Bytes@1 of <> when (Byte >= 16#30) andalso (Byte =< 16#39) -> Day_num = Byte - 16#30, gleam@result:'try'( int_to_day_of_week(Day_num), fun(Day_of_week) -> gleam@result:'try'( parse_optional_time(Rest), fun(_use0@1) -> {Time, Bytes@2} = _use0@1, case Bytes@2 of <<>> -> {ok, {week_date, Year, Week, Day_of_week, Time}}; _ -> {error, nil} end end ) end ); <<>> -> {ok, {year_week, Year, Week}}; _ -> {error, nil} end end ) end ). -file("src/datetime_iso8601.gleam", 313). -spec parse_basic_format(bitstring(), integer()) -> {ok, date_time()} | {error, nil}. parse_basic_format(Bytes, Year) -> gleam@result:'try'( parse_digits(Bytes, 1), fun(_use0) -> {Digit1, Bytes@1} = _use0, gleam@result:'try'( parse_digits(Bytes@1, 1), fun(_use0@1) -> {Digit2, Bytes@2} = _use0@1, gleam@result:'try'( parse_digits(Bytes@2, 1), fun(_use0@2) -> {Digit3, Bytes@3} = _use0@2, case parse_digits(Bytes@3, 1) of {ok, {Digit4, Bytes@4}} -> Month = (Digit1 * 10) + Digit2, gleam@result:'try'( gleam@time@calendar:month_from_int( Month ), fun(Month@1) -> Day = (Digit3 * 10) + Digit4, gleam@result:'try'( validate_day(Year, Month@1, Day), fun(_) -> gleam@result:'try'( parse_optional_time( Bytes@4 ), fun(_use0@3) -> {Time, Bytes@5} = _use0@3, case Bytes@5 of <<>> -> {ok, {calendar_date, Year, Month@1, Day, Time}}; _ -> {error, nil} end end ) end ) end ); {error, nil} -> Day_of_year = ((Digit1 * 100) + (Digit2 * 10)) + Digit3, gleam@result:'try'( validate_day_of_year(Year, Day_of_year), fun(_) -> gleam@result:'try'( parse_optional_time(Bytes@3), fun(_use0@4) -> {Time@1, Bytes@6} = _use0@4, case Bytes@6 of <<>> -> {ok, {ordinal_date, Year, Day_of_year, Time@1}}; _ -> {error, nil} end end ) end ) end end ) end ) end ). -file("src/datetime_iso8601.gleam", 207). -spec parse_after_year(bitstring(), integer()) -> {ok, date_time()} | {error, nil}. parse_after_year(Bytes, Year) -> case Bytes of <<>> -> {ok, {calendar_year_date, Year}}; <> when Byte =:= 16#2D -> parse_extended_format(Rest, Year); <> when (Byte@1 =:= 16#57) orelse (Byte@1 =:= 16#77) -> parse_basic_week(Rest@1, Year); _ -> parse_basic_format(Bytes, Year) end. -file("src/datetime_iso8601.gleam", 202). ?DOC( " Parse an ISO8601 formatted date-time.\n" "\n" " The ISO8601 date-time format is complex. Unless you have particular need\n" " for things like `2024-W11` you are better off using the RFC3339 format,\n" " which the `gleam_time` package supports.\n" ). -spec parse(binary()) -> {ok, date_time()} | {error, nil}. parse(Input) -> gleam@result:'try'( parse_digits(<>, 4), fun(_use0) -> {Year, Bytes} = _use0, parse_after_year(Bytes, Year) end ).