-module(gleam@time@timestamp).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/gleam/time/timestamp.gleam").
-export([compare/2, system_time/0, difference/2, add/2, to_calendar/2, to_rfc3339/2, from_unix_seconds/1, from_unix_seconds_and_nanoseconds/2, to_unix_seconds/1, to_unix_seconds_and_nanoseconds/1, from_calendar/3, parse_rfc3339/1]).
-export_type([timestamp/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.

?MODULEDOC(
    " Welcome to the timestamp module! This module and its `Timestamp` type are\n"
    " what you will be using most commonly when working with time in Gleam.\n"
    "\n"
    " A timestamp represents a moment in time, represented as an amount of time\n"
    " since the calendar time 00:00:00 UTC on 1 January 1970, also known as the\n"
    " _Unix epoch_.\n"
    "\n"
    " # Wall clock time and monotonicity\n"
    "\n"
    " Time is very complicated, especially on computers! While they generally do\n"
    " a good job of keeping track of what the time is, computers can get\n"
    " out-of-sync and start to report a time that is too late or too early. Most\n"
    " computers use \"network time protocol\" to tell each other what they think\n"
    " the time is, and computers that realise they are running too fast or too\n"
    " slow will adjust their clock to correct it. When this happens it can seem\n"
    " to your program that the current time has changed, and it may have even\n"
    " jumped backwards in time!\n"
    "\n"
    " This measure of time is called _wall clock time_, and it is what people\n"
    " commonly think of when they think of time. It is important to be aware that\n"
    " it can go backwards, and your program must not rely on it only ever going\n"
    " forwards at a steady rate. For example, for tracking what order events happen\n"
    " in. \n"
    "\n"
    " This module uses wall clock time. If your program needs time values to always\n"
    " increase you will need a _monotonic_ time instead. It's uncommon that you\n"
    " would need monotonic time, one example might be if you're making a\n"
    " benchmarking framework.\n"
    "\n"
    " The exact way that time works will depend on what runtime you use. The\n"
    " Erlang documentation on time has a lot of detail about time generally as well\n"
    " as how it works on the BEAM, it is worth reading.\n"
    " <https://www.erlang.org/doc/apps/erts/time_correction>.\n"
    "\n"
    " # Converting to local calendar time\n"
    "\n"
    " Timestamps don't take into account time zones, so a moment in time will\n"
    " have the same timestamp value regardless of where you are in the world. To\n"
    " convert them to local time you will need to know the offset for the time\n"
    " zone you wish to use, likely from a time zone database. See the\n"
    " `gleam/time/calendar` module for more information.\n"
    "\n"
).

-opaque timestamp() :: {timestamp, integer(), integer()}.

-file("src/gleam/time/timestamp.gleam", 119).
?DOC(
    " Ensure the time is represented with `nanoseconds` being positive and less\n"
    " than 1 second.\n"
    "\n"
    " This function does not change the time that the timestamp refers to, it\n"
    " only adjusts the values used to represent the time.\n"
).
-spec normalise(timestamp()) -> timestamp().
normalise(Timestamp) ->
    Multiplier = 1000000000,
    Nanoseconds = case Multiplier of
        0 -> 0;
        Gleam@denominator -> erlang:element(3, Timestamp) rem Gleam@denominator
    end,
    Overflow = erlang:element(3, Timestamp) - Nanoseconds,
    Seconds = erlang:element(2, Timestamp) + (case Multiplier of
        0 -> 0;
        Gleam@denominator@1 -> Overflow div Gleam@denominator@1
    end),
    case Nanoseconds >= 0 of
        true ->
            {timestamp, Seconds, Nanoseconds};

        false ->
            {timestamp, Seconds - 1, Multiplier + Nanoseconds}
    end.

-file("src/gleam/time/timestamp.gleam", 141).
?DOC(
    " Compare one timestamp to another, indicating whether the first is further\n"
    " into the future (greater) or further into the past (lesser) than the\n"
    " second.\n"
    "\n"
    " # Examples\n"
    "\n"
    " ```gleam\n"
    " compare(from_unix_seconds(1), from_unix_seconds(2))\n"
    " // -> order.Lt\n"
    " ```\n"
).
-spec compare(timestamp(), timestamp()) -> gleam@order:order().
compare(Left, Right) ->
    gleam@order:break_tie(
        gleam@int:compare(erlang:element(2, Left), erlang:element(2, Right)),
        gleam@int:compare(erlang:element(3, Left), erlang:element(3, Right))
    ).

-file("src/gleam/time/timestamp.gleam", 160).
?DOC(
    " Get the current system time.\n"
    "\n"
    " Note this time is not unique or monotonic, it could change at any time or\n"
    " even go backwards! The exact behaviour will depend on the runtime used. See\n"
    " the module documentation for more information.\n"
    "\n"
    " On Erlang this uses [`erlang:system_time/1`][1]. On JavaScript this uses\n"
    " [`Date.now`][2].\n"
    "\n"
    " [1]: https://www.erlang.org/doc/apps/erts/erlang#system_time/1\n"
    " [2]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/now\n"
).
-spec system_time() -> timestamp().
system_time() ->
    {Seconds, Nanoseconds} = gleam_time_ffi:system_time(),
    normalise({timestamp, Seconds, Nanoseconds}).

-file("src/gleam/time/timestamp.gleam", 180).
?DOC(
    " Calculate the difference between two timestamps.\n"
    "\n"
    " This is effectively substracting the first timestamp from the second.\n"
    "\n"
    " # Examples\n"
    "\n"
    " ```gleam\n"
    " difference(from_unix_seconds(1), from_unix_seconds(5))\n"
    " // -> duration.seconds(4)\n"
    " ```\n"
).
-spec difference(timestamp(), timestamp()) -> gleam@time@duration:duration().
difference(Left, Right) ->
    Seconds = gleam@time@duration:seconds(
        erlang:element(2, Right) - erlang:element(2, Left)
    ),
    Nanoseconds = gleam@time@duration:nanoseconds(
        erlang:element(3, Right) - erlang:element(3, Left)
    ),
    gleam@time@duration:add(Seconds, Nanoseconds).

-file("src/gleam/time/timestamp.gleam", 195).
?DOC(
    " Add a duration to a timestamp.\n"
    "\n"
    " # Examples\n"
    "\n"
    " ```gleam\n"
    " add(from_unix_seconds(1000), duration.seconds(5))\n"
    " // -> from_unix_seconds(1005)\n"
    " ```\n"
).
-spec add(timestamp(), gleam@time@duration:duration()) -> timestamp().
add(Timestamp, Duration) ->
    {Seconds, Nanoseconds} = gleam@time@duration:to_seconds_and_nanoseconds(
        Duration
    ),
    _pipe = {timestamp,
        erlang:element(2, Timestamp) + Seconds,
        erlang:element(3, Timestamp) + Nanoseconds},
    normalise(_pipe).

-file("src/gleam/time/timestamp.gleam", 262).
-spec pad_digit(integer(), integer()) -> binary().
pad_digit(Digit, Desired_length) ->
    _pipe = erlang:integer_to_binary(Digit),
    gleam@string:pad_start(_pipe, Desired_length, <<"0"/utf8>>).

-file("src/gleam/time/timestamp.gleam", 308).
-spec duration_to_minutes(gleam@time@duration:duration()) -> integer().
duration_to_minutes(Duration) ->
    erlang:round(gleam@time@duration:to_seconds(Duration) / 60.0).

-file("src/gleam/time/timestamp.gleam", 370).
-spec modulo(integer(), integer()) -> integer().
modulo(N, M) ->
    case gleam@int:modulo(N, M) of
        {ok, N@1} ->
            N@1;

        {error, _} ->
            0
    end.

-file("src/gleam/time/timestamp.gleam", 377).
-spec floored_div(integer(), float()) -> integer().
floored_div(Numerator, Denominator) ->
    N = case Denominator of
        +0.0 -> +0.0;
        -0.0 -> -0.0;
        Gleam@denominator -> erlang:float(Numerator) / Gleam@denominator
    end,
    erlang:round(math:floor(N)).

-file("src/gleam/time/timestamp.gleam", 383).
-spec to_civil(integer()) -> {integer(), integer(), integer()}.
to_civil(Minutes) ->
    Raw_day = floored_div(Minutes, (60.0 * 24.0)) + 719468,
    Era = case Raw_day >= 0 of
        true ->
            Raw_day div 146097;

        false ->
            (Raw_day - 146096) div 146097
    end,
    Day_of_era = Raw_day - (Era * 146097),
    Year_of_era = (((Day_of_era - (Day_of_era div 1460)) + (Day_of_era div 36524))
    - (Day_of_era div 146096))
    div 365,
    Year = Year_of_era + (Era * 400),
    Day_of_year = Day_of_era - (((365 * Year_of_era) + (Year_of_era div 4)) - (Year_of_era
    div 100)),
    Mp = ((5 * Day_of_year) + 2) div 153,
    Month = case Mp < 10 of
        true ->
            Mp + 3;

        false ->
            Mp - 9
    end,
    Day = (Day_of_year - (((153 * Mp) + 2) div 5)) + 1,
    Year@1 = case Month =< 2 of
        true ->
            Year + 1;

        false ->
            Year
    end,
    {Year@1, Month, Day}.

-file("src/gleam/time/timestamp.gleam", 312).
-spec to_calendar_from_offset(timestamp(), integer()) -> {integer(),
    integer(),
    integer(),
    integer(),
    integer(),
    integer()}.
to_calendar_from_offset(Timestamp, Offset) ->
    Total = erlang:element(2, Timestamp) + (Offset * 60),
    Seconds = modulo(Total, 60),
    Total_minutes = floored_div(Total, 60.0),
    Minutes = modulo(Total, 60 * 60) div 60,
    Hours = case (60 * 60) of
        0 -> 0;
        Gleam@denominator -> modulo(Total, (24 * 60) * 60) div Gleam@denominator
    end,
    {Year, Month, Day} = to_civil(Total_minutes),
    {Year, Month, Day, Hours, Minutes, Seconds}.

-file("src/gleam/time/timestamp.gleam", 281).
?DOC(
    " Convert a `Timestamp` to calendar time, suitable for presenting to a human\n"
    " to read.\n"
    "\n"
    " If you want a machine to use the time value then you should not use this\n"
    " function and should instead keep it as a timestamp. See the documentation\n"
    " for the `gleam/time/calendar` module for more information.\n"
    "\n"
    " # Examples\n"
    "\n"
    " ```gleam\n"
    " timestamp.from_unix_seconds(0)\n"
    " |> timestamp.to_calendar(calendar.utc_offset)\n"
    " // -> #(Date(1970, January, 1), TimeOfDay(0, 0, 0, 0))\n"
    " ```\n"
).
-spec to_calendar(timestamp(), gleam@time@duration:duration()) -> {gleam@time@calendar:date(),
    gleam@time@calendar:time_of_day()}.
to_calendar(Timestamp, Offset) ->
    Offset@1 = duration_to_minutes(Offset),
    {Year, Month, Day, Hours, Minutes, Seconds} = to_calendar_from_offset(
        Timestamp,
        Offset@1
    ),
    Month@1 = case Month of
        1 ->
            january;

        2 ->
            february;

        3 ->
            march;

        4 ->
            april;

        5 ->
            may;

        6 ->
            june;

        7 ->
            july;

        8 ->
            august;

        9 ->
            september;

        10 ->
            october;

        11 ->
            november;

        _ ->
            december
    end,
    Nanoseconds = erlang:element(3, Timestamp),
    Date = {date, Year, Month@1, Day},
    Time = {time_of_day, Hours, Minutes, Seconds, Nanoseconds},
    {Date, Time}.

-file("src/gleam/time/timestamp.gleam", 446).
-spec do_remove_trailing_zeros(list(integer())) -> list(integer()).
do_remove_trailing_zeros(Reversed_digits) ->
    case Reversed_digits of
        [] ->
            [];

        [Digit | Digits] when Digit =:= 0 ->
            do_remove_trailing_zeros(Digits);

        Reversed_digits@1 ->
            lists:reverse(Reversed_digits@1)
    end.

-file("src/gleam/time/timestamp.gleam", 440).
?DOC(" Given a list of digits, return new list with any trailing zeros removed.\n").
-spec remove_trailing_zeros(list(integer())) -> list(integer()).
remove_trailing_zeros(Digits) ->
    Reversed_digits = lists:reverse(Digits),
    do_remove_trailing_zeros(Reversed_digits).

-file("src/gleam/time/timestamp.gleam", 461).
-spec do_get_zero_padded_digits(integer(), list(integer()), integer()) -> list(integer()).
do_get_zero_padded_digits(Number, Digits, Count) ->
    case Number of
        Number@1 when (Number@1 =< 0) andalso (Count >= 9) ->
            Digits;

        Number@2 when Number@2 =< 0 ->
            do_get_zero_padded_digits(Number@2, [0 | Digits], Count + 1);

        Number@3 ->
            Digit = Number@3 rem 10,
            Number@4 = floored_div(Number@3, 10.0),
            do_get_zero_padded_digits(Number@4, [Digit | Digits], Count + 1)
    end.

-file("src/gleam/time/timestamp.gleam", 457).
?DOC(
    " Returns the list of digits of `number`.  If the number of digits is less \n"
    " than 9, the result is zero-padded at the front.\n"
).
-spec get_zero_padded_digits(integer()) -> list(integer()).
get_zero_padded_digits(Number) ->
    do_get_zero_padded_digits(Number, [], 0).

-file("src/gleam/time/timestamp.gleam", 420).
?DOC(
    " Converts nanoseconds into a `String` representation of fractional seconds.\n"
    " \n"
    " Assumes that `nanoseconds < 1_000_000_000`, which will be true for any \n"
    " normalised timestamp.\n"
).
-spec show_second_fraction(integer()) -> binary().
show_second_fraction(Nanoseconds) ->
    case gleam@int:compare(Nanoseconds, 0) of
        lt ->
            <<""/utf8>>;

        eq ->
            <<""/utf8>>;

        gt ->
            Second_fraction_part = begin
                _pipe = Nanoseconds,
                _pipe@1 = get_zero_padded_digits(_pipe),
                _pipe@2 = remove_trailing_zeros(_pipe@1),
                _pipe@3 = gleam@list:map(
                    _pipe@2,
                    fun erlang:integer_to_binary/1
                ),
                gleam@string:join(_pipe@3, <<""/utf8>>)
            end,
            <<"."/utf8, Second_fraction_part/binary>>
    end.

-file("src/gleam/time/timestamp.gleam", 240).
?DOC(
    " Convert a timestamp to a RFC 3339 formatted time string, with an offset\n"
    " supplied as an additional argument.\n"
    "\n"
    " The output of this function is also ISO 8601 compatible so long as the\n"
    " offset not negative. Offsets have at-most minute precision, so an offset\n"
    " with higher precision will be rounded to the nearest minute.\n"
    "\n"
    " If you are making an API such as a HTTP JSON API you are encouraged to use\n"
    " Unix timestamps instead of this format or ISO 8601. Unix timestamps are a\n"
    " better choice as they don't contain offset information. Consider:\n"
    "\n"
    " - UTC offsets are not time zones. This does not and cannot tell us the time\n"
    "   zone in which the date was recorded. So what are we supposed to do with\n"
    "   this information?\n"
    " - Users typically want dates formatted according to their local time zone.\n"
    "   What if the provided UTC offset is different from the current user's time\n"
    "   zone? What are we supposed to do with it then?\n"
    " - Despite it being useless (or worse, a source of bugs), the UTC offset\n"
    "   creates a larger payload to transfer.\n"
    "\n"
    " They also uses more memory than a unix timestamp. The way they are better\n"
    " than Unix timestamp is that it is easier for a human to read them, but\n"
    " this is a hinderance that tooling can remedy, and APIs are not primarily\n"
    " for humans.\n"
    "\n"
    " # Examples\n"
    "\n"
    " ```gleam\n"
    " timestamp.from_unix_seconds_and_nanoseconds(1000, 123_000_000)\n"
    " |> to_rfc3339(calendar.utc_offset)\n"
    " // -> \"1970-01-01T00:16:40.123Z\"\n"
    " ```\n"
    "\n"
    " ```gleam\n"
    " timestamp.from_unix_seconds(1000)\n"
    " |> to_rfc3339(duration.seconds(3600))\n"
    " // -> \"1970-01-01T01:16:40+01:00\"\n"
    " ```\n"
).
-spec to_rfc3339(timestamp(), gleam@time@duration:duration()) -> binary().
to_rfc3339(Timestamp, Offset) ->
    Offset@1 = duration_to_minutes(Offset),
    {Years, Months, Days, Hours, Minutes, Seconds} = to_calendar_from_offset(
        Timestamp,
        Offset@1
    ),
    Offset_minutes = modulo(Offset@1, 60),
    Offset_hours = gleam@int:absolute_value(floored_div(Offset@1, 60.0)),
    N2 = fun(_capture) -> pad_digit(_capture, 2) end,
    N4 = fun(_capture@1) -> pad_digit(_capture@1, 4) end,
    Out = <<""/utf8>>,
    Out@1 = <<<<<<<<<<Out/binary, (N4(Years))/binary>>/binary, "-"/utf8>>/binary,
                (N2(Months))/binary>>/binary,
            "-"/utf8>>/binary,
        (N2(Days))/binary>>,
    Out@2 = <<Out@1/binary, "T"/utf8>>,
    Out@3 = <<<<<<<<<<Out@2/binary, (N2(Hours))/binary>>/binary, ":"/utf8>>/binary,
                (N2(Minutes))/binary>>/binary,
            ":"/utf8>>/binary,
        (N2(Seconds))/binary>>,
    Out@4 = <<Out@3/binary,
        (show_second_fraction(erlang:element(3, Timestamp)))/binary>>,
    case gleam@int:compare(Offset@1, 0) of
        eq ->
            <<Out@4/binary, "Z"/utf8>>;

        gt ->
            <<<<<<<<Out@4/binary, "+"/utf8>>/binary, (N2(Offset_hours))/binary>>/binary,
                    ":"/utf8>>/binary,
                (N2(Offset_minutes))/binary>>;

        lt ->
            <<<<<<<<Out@4/binary, "-"/utf8>>/binary, (N2(Offset_hours))/binary>>/binary,
                    ":"/utf8>>/binary,
                (N2(Offset_minutes))/binary>>
    end.

-file("src/gleam/time/timestamp.gleam", 611).
-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/gleam/time/timestamp.gleam", 715).
-spec parse_sign(bitstring()) -> {ok, {binary(), bitstring()}} | {error, nil}.
parse_sign(Bytes) ->
    case Bytes of
        <<"+"/utf8, Remaining_bytes/binary>> ->
            {ok, {<<"+"/utf8>>, Remaining_bytes}};

        <<"-"/utf8, Remaining_bytes@1/binary>> ->
            {ok, {<<"-"/utf8>>, Remaining_bytes@1}};

        _ ->
            {error, nil}
    end.

-file("src/gleam/time/timestamp.gleam", 762).
?DOC(" Accept the given value from `bytes` and move past it if found.\n").
-spec accept_byte(bitstring(), integer()) -> {ok, bitstring()} | {error, nil}.
accept_byte(Bytes, Value) ->
    case Bytes of
        <<Byte, Remaining_bytes/binary>> when Byte =:= Value ->
            {ok, Remaining_bytes};

        _ ->
            {error, nil}
    end.

-file("src/gleam/time/timestamp.gleam", 780).
-spec accept_empty(bitstring()) -> {ok, nil} | {error, nil}.
accept_empty(Bytes) ->
    case Bytes of
        <<>> ->
            {ok, nil};

        _ ->
            {error, nil}
    end.

-file("src/gleam/time/timestamp.gleam", 840).
?DOC(
    " Note: It is the callers responsibility to ensure the inputs are valid.\n"
    " \n"
    " See https://www.tondering.dk/claus/cal/julperiod.php#formula\n"
).
-spec julian_day_from_ymd(integer(), integer(), integer()) -> integer().
julian_day_from_ymd(Year, Month, Day) ->
    Adjustment = (14 - Month) div 12,
    Adjusted_year = (Year + 4800) - Adjustment,
    Adjusted_month = (Month + (12 * Adjustment)) - 3,
    (((((Day + (((153 * Adjusted_month) + 2) div 5)) + (365 * Adjusted_year)) + (Adjusted_year
    div 4))
    - (Adjusted_year div 100))
    + (Adjusted_year div 400))
    - 32045.

-file("src/gleam/time/timestamp.gleam", 859).
?DOC(
    " Create a timestamp from a number of seconds since 00:00:00 UTC on 1 January\n"
    " 1970.\n"
).
-spec from_unix_seconds(integer()) -> timestamp().
from_unix_seconds(Seconds) ->
    {timestamp, Seconds, 0}.

-file("src/gleam/time/timestamp.gleam", 874).
?DOC(
    " Create a timestamp from a number of seconds and nanoseconds since 00:00:00\n"
    " UTC on 1 January 1970.\n"
    "\n"
    " # JavaScript int limitations\n"
    "\n"
    " Remember that JavaScript can only perfectly represent ints between positive\n"
    " and negative 9,007,199,254,740,991! If you only use the nanosecond field\n"
    " then you will almost certainly not get the date value you want due to this\n"
    " loss of precision. Always use seconds primarily and then use nanoseconds\n"
    " for the final sub-second adjustment.\n"
).
-spec from_unix_seconds_and_nanoseconds(integer(), integer()) -> timestamp().
from_unix_seconds_and_nanoseconds(Seconds, Nanoseconds) ->
    _pipe = {timestamp, Seconds, Nanoseconds},
    normalise(_pipe).

-file("src/gleam/time/timestamp.gleam", 888).
?DOC(
    " Convert the timestamp to a number of seconds since 00:00:00 UTC on 1\n"
    " January 1970.\n"
    "\n"
    " There may be some small loss of precision due to `Timestamp` being\n"
    " nanosecond accurate and `Float` not being able to represent this.\n"
).
-spec to_unix_seconds(timestamp()) -> float().
to_unix_seconds(Timestamp) ->
    Seconds = erlang:float(erlang:element(2, Timestamp)),
    Nanoseconds = erlang:float(erlang:element(3, Timestamp)),
    Seconds + (Nanoseconds / 1000000000.0).

-file("src/gleam/time/timestamp.gleam", 897).
?DOC(
    " Convert the timestamp to a number of seconds and nanoseconds since 00:00:00\n"
    " UTC on 1 January 1970. There is no loss of precision with this conversion\n"
    " on any target.\n"
).
-spec to_unix_seconds_and_nanoseconds(timestamp()) -> {integer(), integer()}.
to_unix_seconds_and_nanoseconds(Timestamp) ->
    {erlang:element(2, Timestamp), erlang:element(3, Timestamp)}.

-file("src/gleam/time/timestamp.gleam", 723).
-spec offset_to_seconds(binary(), integer(), integer()) -> integer().
offset_to_seconds(Sign, Hours, Minutes) ->
    Abs_seconds = (Hours * 3600) + (Minutes * 60),
    case Sign of
        <<"-"/utf8>> ->
            - Abs_seconds;

        _ ->
            Abs_seconds
    end.

-file("src/gleam/time/timestamp.gleam", 819).
?DOC(
    " `julian_seconds_from_parts(year, month, day, hours, minutes, seconds)` \n"
    " returns the number of Julian \n"
    " seconds represented by the given arguments.\n"
    " \n"
    " Note: It is the callers responsibility to ensure the inputs are valid.\n"
    " \n"
    " See https://www.tondering.dk/claus/cal/julperiod.php#formula\n"
).
-spec julian_seconds_from_parts(
    integer(),
    integer(),
    integer(),
    integer(),
    integer(),
    integer()
) -> integer().
julian_seconds_from_parts(Year, Month, Day, Hours, Minutes, Seconds) ->
    Julian_day_seconds = julian_day_from_ymd(Year, Month, Day) * 86400,
    ((Julian_day_seconds + (Hours * 3600)) + (Minutes * 60)) + Seconds.

-file("src/gleam/time/timestamp.gleam", 662).
-spec do_parse_second_fraction_as_nanoseconds(bitstring(), integer(), integer()) -> {ok,
        {integer(), bitstring()}} |
    {error, any()}.
do_parse_second_fraction_as_nanoseconds(Bytes, Acc, Power) ->
    Power@1 = Power div 10,
    case Bytes of
        <<Byte, Remaining_bytes/binary>> when ((16#30 =< Byte) andalso (Byte =< 16#39)) andalso (Power@1 < 1) ->
            do_parse_second_fraction_as_nanoseconds(
                Remaining_bytes,
                Acc,
                Power@1
            );

        <<Byte@1, Remaining_bytes@1/binary>> when (16#30 =< Byte@1) andalso (Byte@1 =< 16#39) ->
            Digit = Byte@1 - 16#30,
            do_parse_second_fraction_as_nanoseconds(
                Remaining_bytes@1,
                Acc + (Digit * Power@1),
                Power@1
            );

        _ ->
            {ok, {Acc, Bytes}}
    end.

-file("src/gleam/time/timestamp.gleam", 642).
-spec parse_second_fraction_as_nanoseconds(bitstring()) -> {ok,
        {integer(), bitstring()}} |
    {error, nil}.
parse_second_fraction_as_nanoseconds(Bytes) ->
    case Bytes of
        <<"."/utf8, Byte, Remaining_bytes/binary>> when (16#30 =< Byte) andalso (Byte =< 16#39) ->
            do_parse_second_fraction_as_nanoseconds(
                <<Byte, Remaining_bytes/bitstring>>,
                0,
                1000000000
            );

        <<"."/utf8, _/binary>> ->
            {error, nil};

        _ ->
            {ok, {0, Bytes}}
    end.

-file("src/gleam/time/timestamp.gleam", 741).
-spec do_parse_digits(bitstring(), integer(), integer(), integer()) -> {ok,
        {integer(), bitstring()}} |
    {error, nil}.
do_parse_digits(Bytes, Count, Acc, K) ->
    case Bytes of
        _ when K >= Count ->
            {ok, {Acc, Bytes}};

        <<Byte, Remaining_bytes/binary>> when (16#30 =< Byte) andalso (Byte =< 16#39) ->
            do_parse_digits(
                Remaining_bytes,
                Count,
                (Acc * 10) + (Byte - 16#30),
                K + 1
            );

        _ ->
            {error, nil}
    end.

-file("src/gleam/time/timestamp.gleam", 734).
?DOC(" Parse and return the given number of digits from the given bytes.\n").
-spec parse_digits(bitstring(), integer()) -> {ok, {integer(), bitstring()}} |
    {error, nil}.
parse_digits(Bytes, Count) ->
    do_parse_digits(Bytes, Count, 0, 0).

-file("src/gleam/time/timestamp.gleam", 573).
-spec parse_year(bitstring()) -> {ok, {integer(), bitstring()}} | {error, nil}.
parse_year(Bytes) ->
    parse_digits(Bytes, 4).

-file("src/gleam/time/timestamp.gleam", 577).
-spec parse_month(bitstring()) -> {ok, {integer(), bitstring()}} | {error, nil}.
parse_month(Bytes) ->
    gleam@result:'try'(
        parse_digits(Bytes, 2),
        fun(_use0) ->
            {Month, Bytes@1} = _use0,
            case (1 =< Month) andalso (Month =< 12) of
                true ->
                    {ok, {Month, Bytes@1}};

                false ->
                    {error, nil}
            end
        end
    ).

-file("src/gleam/time/timestamp.gleam", 585).
-spec parse_day(bitstring(), integer(), integer()) -> {ok,
        {integer(), bitstring()}} |
    {error, nil}.
parse_day(Bytes, Year, Month) ->
    gleam@result:'try'(
        parse_digits(Bytes, 2),
        fun(_use0) ->
            {Day, Bytes@1} = _use0,
            gleam@result:'try'(case Month of
                    1 ->
                        {ok, 31};

                    3 ->
                        {ok, 31};

                    5 ->
                        {ok, 31};

                    7 ->
                        {ok, 31};

                    8 ->
                        {ok, 31};

                    10 ->
                        {ok, 31};

                    12 ->
                        {ok, 31};

                    4 ->
                        {ok, 30};

                    6 ->
                        {ok, 30};

                    9 ->
                        {ok, 30};

                    11 ->
                        {ok, 30};

                    2 ->
                        case is_leap_year(Year) of
                            true ->
                                {ok, 29};

                            false ->
                                {ok, 28}
                        end;

                    _ ->
                        {error, nil}
                end, fun(Max_day) -> case (1 =< Day) andalso (Day =< Max_day) of
                        true ->
                            {ok, {Day, Bytes@1}};

                        false ->
                            {error, nil}
                    end end)
        end
    ).

-file("src/gleam/time/timestamp.gleam", 615).
-spec parse_hours(bitstring()) -> {ok, {integer(), bitstring()}} | {error, nil}.
parse_hours(Bytes) ->
    gleam@result:'try'(
        parse_digits(Bytes, 2),
        fun(_use0) ->
            {Hours, Bytes@1} = _use0,
            case (0 =< Hours) andalso (Hours =< 23) of
                true ->
                    {ok, {Hours, Bytes@1}};

                false ->
                    {error, nil}
            end
        end
    ).

-file("src/gleam/time/timestamp.gleam", 623).
-spec parse_minutes(bitstring()) -> {ok, {integer(), bitstring()}} |
    {error, nil}.
parse_minutes(Bytes) ->
    gleam@result:'try'(
        parse_digits(Bytes, 2),
        fun(_use0) ->
            {Minutes, Bytes@1} = _use0,
            case (0 =< Minutes) andalso (Minutes =< 59) of
                true ->
                    {ok, {Minutes, Bytes@1}};

                false ->
                    {error, nil}
            end
        end
    ).

-file("src/gleam/time/timestamp.gleam", 631).
-spec parse_seconds(bitstring()) -> {ok, {integer(), bitstring()}} |
    {error, nil}.
parse_seconds(Bytes) ->
    gleam@result:'try'(
        parse_digits(Bytes, 2),
        fun(_use0) ->
            {Seconds, Bytes@1} = _use0,
            case (0 =< Seconds) andalso (Seconds =< 60) of
                true ->
                    {ok, {Seconds, Bytes@1}};

                false ->
                    {error, nil}
            end
        end
    ).

-file("src/gleam/time/timestamp.gleam", 704).
-spec parse_numeric_offset(bitstring()) -> {ok, {integer(), bitstring()}} |
    {error, nil}.
parse_numeric_offset(Bytes) ->
    gleam@result:'try'(
        parse_sign(Bytes),
        fun(_use0) ->
            {Sign, Bytes@1} = _use0,
            gleam@result:'try'(
                parse_hours(Bytes@1),
                fun(_use0@1) ->
                    {Hours, Bytes@2} = _use0@1,
                    gleam@result:'try'(
                        accept_byte(Bytes@2, 16#3A),
                        fun(Bytes@3) ->
                            gleam@result:'try'(
                                parse_minutes(Bytes@3),
                                fun(_use0@2) ->
                                    {Minutes, Bytes@4} = _use0@2,
                                    Offset_seconds = offset_to_seconds(
                                        Sign,
                                        Hours,
                                        Minutes
                                    ),
                                    {ok, {Offset_seconds, Bytes@4}}
                                end
                            )
                        end
                    )
                end
            )
        end
    ).

-file("src/gleam/time/timestamp.gleam", 696).
-spec parse_offset(bitstring()) -> {ok, {integer(), bitstring()}} | {error, nil}.
parse_offset(Bytes) ->
    case Bytes of
        <<"Z"/utf8, Remaining_bytes/binary>> ->
            {ok, {0, Remaining_bytes}};

        <<"z"/utf8, Remaining_bytes/binary>> ->
            {ok, {0, Remaining_bytes}};

        _ ->
            parse_numeric_offset(Bytes)
    end.

-file("src/gleam/time/timestamp.gleam", 769).
-spec accept_date_time_separator(bitstring()) -> {ok, bitstring()} |
    {error, nil}.
accept_date_time_separator(Bytes) ->
    case Bytes of
        <<Byte, Remaining_bytes/binary>> when ((Byte =:= 16#54) orelse (Byte =:= 16#74)) orelse (Byte =:= 16#20) ->
            {ok, Remaining_bytes};

        _ ->
            {error, nil}
    end.

-file("src/gleam/time/timestamp.gleam", 789).
?DOC(" Note: The caller of this function must ensure that all inputs are valid.\n").
-spec from_date_time(
    integer(),
    integer(),
    integer(),
    integer(),
    integer(),
    integer(),
    integer(),
    integer()
) -> timestamp().
from_date_time(
    Year,
    Month,
    Day,
    Hours,
    Minutes,
    Seconds,
    Second_fraction_as_nanoseconds,
    Offset_seconds
) ->
    Julian_seconds = julian_seconds_from_parts(
        Year,
        Month,
        Day,
        Hours,
        Minutes,
        Seconds
    ),
    Julian_seconds_since_epoch = Julian_seconds - 210866803200,
    _pipe = {timestamp,
        Julian_seconds_since_epoch - Offset_seconds,
        Second_fraction_as_nanoseconds},
    normalise(_pipe).

-file("src/gleam/time/timestamp.gleam", 339).
?DOC(
    " Create a `Timestamp` from a human-readable calendar time.\n"
    "\n"
    " # Examples\n"
    "\n"
    " ```gleam\n"
    " timestamp.from_calendar(\n"
    "   date: calendar.Date(2024, calendar.December, 25),\n"
    "   time: calendar.TimeOfDay(12, 30, 50, 0),\n"
    "   offset: calendar.utc_offset,\n"
    " )\n"
    " |> timestamp.to_rfc3339(calendar.utc_offset)\n"
    " // -> \"2024-12-25T12:30:50Z\"\n"
    " ```\n"
).
-spec from_calendar(
    gleam@time@calendar:date(),
    gleam@time@calendar:time_of_day(),
    gleam@time@duration:duration()
) -> timestamp().
from_calendar(Date, Time, Offset) ->
    Month = case erlang:element(3, Date) of
        january ->
            1;

        february ->
            2;

        march ->
            3;

        april ->
            4;

        may ->
            5;

        june ->
            6;

        july ->
            7;

        august ->
            8;

        september ->
            9;

        october ->
            10;

        november ->
            11;

        december ->
            12
    end,
    from_date_time(
        erlang:element(2, Date),
        Month,
        erlang:element(4, Date),
        erlang:element(2, Time),
        erlang:element(3, Time),
        erlang:element(4, Time),
        erlang:element(5, Time),
        erlang:round(gleam@time@duration:to_seconds(Offset))
    ).

-file("src/gleam/time/timestamp.gleam", 533).
?DOC(
    " Parses an [RFC 3339 formatted time string][spec] into a `Timestamp`.\n"
    "\n"
    " [spec]: https://datatracker.ietf.org/doc/html/rfc3339#section-5.6\n"
    " \n"
    " # Examples\n"
    "\n"
    " ```gleam\n"
    " let assert Ok(ts) = timestamp.parse_rfc3339(\"1970-01-01T00:00:01Z\")\n"
    " timestamp.to_unix_seconds_and_nanoseconds(ts)\n"
    " // -> #(1, 0)\n"
    " ```\n"
    " \n"
    " Parsing an invalid timestamp returns an error.\n"
    " \n"
    " ```gleam\n"
    " let assert Error(Nil) = timestamp.parse_rfc3339(\"1995-10-31\")\n"
    " ```\n"
    "\n"
    " ## Time zones\n"
    "\n"
    " It may at first seem that the RFC 3339 format includes timezone\n"
    " information, as it can specify an offset such as `Z` or `+3`, so why does\n"
    " this function not return calendar time with a time zone? There are multiple\n"
    " reasons:\n"
    "\n"
    " - RFC 3339's timestamp format is based on calendar time, but it is\n"
    "   unambigous, so it can be converted into epoch time when being parsed. It\n"
    "   is always better to internally use epoch time to represent unambiguous\n"
    "   points in time, so we perform that conversion as a convenience and to\n"
    "   ensure that programmers with less time experience don't accidentally use\n"
    "   a less suitable time representation.\n"
    "\n"
    " - RFC 3339's contains _calendar time offset_ information, not time zone\n"
    "   information. This is enough to convert it to an unambiguous timestamp,\n"
    "   but it is not enough information to reliably work with calendar time.\n"
    "   Without the time zone and the time zone database it's not possible to\n"
    "   know what time period that offset is valid for, so it cannot be used\n"
    "   without risk of bugs.\n"
    "\n"
    " ## Behaviour details\n"
    " \n"
    " - Follows the grammar specified in section 5.6 Internet Date/Time Format of \n"
    "   RFC 3339 <https://datatracker.ietf.org/doc/html/rfc3339#section-5.6>.\n"
    " - The `T` and `Z` characters may alternatively be lower case `t` or `z`, \n"
    "   respectively.\n"
    " - Full dates and full times must be separated by `T` or `t`. A space is also \n"
    "   permitted.\n"
    " - Leap seconds rules are not considered.  That is, any timestamp may \n"
    "   specify digts `00` - `60` for the seconds.\n"
    " - Any part of a fractional second that cannot be represented in the \n"
    "   nanosecond precision is tructated.  That is, for the time string, \n"
    "   `\"1970-01-01T00:00:00.1234567899Z\"`, the fractional second `.1234567899` \n"
    "   will be represented as `123_456_789` in the `Timestamp`.\n"
).
-spec parse_rfc3339(binary()) -> {ok, timestamp()} | {error, nil}.
parse_rfc3339(Input) ->
    Bytes = gleam_stdlib:identity(Input),
    gleam@result:'try'(
        parse_year(Bytes),
        fun(_use0) ->
            {Year, Bytes@1} = _use0,
            gleam@result:'try'(
                accept_byte(Bytes@1, 16#2D),
                fun(Bytes@2) ->
                    gleam@result:'try'(
                        parse_month(Bytes@2),
                        fun(_use0@1) ->
                            {Month, Bytes@3} = _use0@1,
                            gleam@result:'try'(
                                accept_byte(Bytes@3, 16#2D),
                                fun(Bytes@4) ->
                                    gleam@result:'try'(
                                        parse_day(Bytes@4, Year, Month),
                                        fun(_use0@2) ->
                                            {Day, Bytes@5} = _use0@2,
                                            gleam@result:'try'(
                                                accept_date_time_separator(
                                                    Bytes@5
                                                ),
                                                fun(Bytes@6) ->
                                                    gleam@result:'try'(
                                                        parse_hours(Bytes@6),
                                                        fun(_use0@3) ->
                                                            {Hours, Bytes@7} = _use0@3,
                                                            gleam@result:'try'(
                                                                accept_byte(
                                                                    Bytes@7,
                                                                    16#3A
                                                                ),
                                                                fun(Bytes@8) ->
                                                                    gleam@result:'try'(
                                                                        parse_minutes(
                                                                            Bytes@8
                                                                        ),
                                                                        fun(
                                                                            _use0@4
                                                                        ) ->
                                                                            {Minutes,
                                                                                Bytes@9} = _use0@4,
                                                                            gleam@result:'try'(
                                                                                accept_byte(
                                                                                    Bytes@9,
                                                                                    16#3A
                                                                                ),
                                                                                fun(
                                                                                    Bytes@10
                                                                                ) ->
                                                                                    gleam@result:'try'(
                                                                                        parse_seconds(
                                                                                            Bytes@10
                                                                                        ),
                                                                                        fun(
                                                                                            _use0@5
                                                                                        ) ->
                                                                                            {Seconds,
                                                                                                Bytes@11} = _use0@5,
                                                                                            gleam@result:'try'(
                                                                                                parse_second_fraction_as_nanoseconds(
                                                                                                    Bytes@11
                                                                                                ),
                                                                                                fun(
                                                                                                    _use0@6
                                                                                                ) ->
                                                                                                    {Second_fraction_as_nanoseconds,
                                                                                                        Bytes@12} = _use0@6,
                                                                                                    gleam@result:'try'(
                                                                                                        parse_offset(
                                                                                                            Bytes@12
                                                                                                        ),
                                                                                                        fun(
                                                                                                            _use0@7
                                                                                                        ) ->
                                                                                                            {Offset_seconds,
                                                                                                                Bytes@13} = _use0@7,
                                                                                                            gleam@result:'try'(
                                                                                                                accept_empty(
                                                                                                                    Bytes@13
                                                                                                                ),
                                                                                                                fun(
                                                                                                                    _use0@8
                                                                                                                ) ->
                                                                                                                    nil = _use0@8,
                                                                                                                    {ok,
                                                                                                                        from_date_time(
                                                                                                                            Year,
                                                                                                                            Month,
                                                                                                                            Day,
                                                                                                                            Hours,
                                                                                                                            Minutes,
                                                                                                                            Seconds,
                                                                                                                            Second_fraction_as_nanoseconds,
                                                                                                                            Offset_seconds
                                                                                                                        )}
                                                                                                                end
                                                                                                            )
                                                                                                        end
                                                                                                    )
                                                                                                end
                                                                                            )
                                                                                        end
                                                                                    )
                                                                                end
                                                                            )
                                                                        end
                                                                    )
                                                                end
                                                            )
                                                        end
                                                    )
                                                end
                                            )
                                        end
                                    )
                                end
                            )
                        end
                    )
                end
            )
        end
    ).
