-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" " .\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 = <<<<<<<<<>/binary, "-"/utf8>>/binary, (N2(Months))/binary>>/binary, "-"/utf8>>/binary, (N2(Days))/binary>>, Out@2 = <>, Out@3 = <<<<<<<<<>/binary, ":"/utf8>>/binary, (N2(Minutes))/binary>>/binary, ":"/utf8>>/binary, (N2(Seconds))/binary>>, Out@4 = <>, case gleam@int:compare(Offset@1, 0) of eq -> <>; gt -> <<<<<<<>/binary, (N2(Offset_hours))/binary>>/binary, ":"/utf8>>/binary, (N2(Offset_minutes))/binary>>; lt -> <<<<<<<>/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 <> 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 <> when ((16#30 =< Byte) andalso (Byte =< 16#39)) andalso (Power@1 < 1) -> do_parse_second_fraction_as_nanoseconds( Remaining_bytes, Acc, Power@1 ); <> 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( <>, 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}}; <> 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 <> 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 .\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 ).