stellar_prune/build/dev/javascript/gleam_time/gleam@time@timestamp.erl
2025-11-30 15:44:22 +01:00

1188 lines
45 KiB
Erlang

-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
).