Current section

Files

Jump to
gtempo src tempo@date.erl
Raw

src/tempo@date.erl

-module(tempo@date).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]).
-export([get_year/1, get_month/1, get_day/1, to_string/1, from_tuple/1, new/3, from_string/1, literal/1, to_tuple/1, from_dynamic_string/1, from_unix_utc/1, current_local/0, current_utc/0, to_unix_utc/1, from_unix_milli_utc/1, to_unix_milli_utc/1, from_unix_micro_utc/1, to_unix_micro_utc/1, compare/2, is_earlier/2, is_earlier_or_equal/2, is_equal/2, is_later/2, is_later_or_equal/2, difference/2, as_period/2, add/2, subtract/2, to_weekday/1, is_weekend/1, first_of_month/1, last_of_month/1]).
-export_type([day_of_week/0]).
-type day_of_week() :: sun | mon | tue | wed | thu | fri | sat.
-spec get_year(tempo:date()) -> integer().
get_year(Date) ->
erlang:element(2, Date).
-spec get_month(tempo:date()) -> tempo:month().
get_month(Date) ->
erlang:element(3, Date).
-spec get_day(tempo:date()) -> integer().
get_day(Date) ->
erlang:element(4, Date).
-spec split_int_tuple(binary(), binary()) -> {ok,
{integer(), integer(), integer()}} |
{error, tempo:error()}.
split_int_tuple(Date, Delim) ->
_pipe = gleam@string:split(Date, Delim),
_pipe@1 = gleam@list:map(_pipe, fun gleam@int:parse/1),
_pipe@2 = gleam@result:all(_pipe@1),
_pipe@3 = gleam@result:'try'(_pipe@2, fun(Date@1) -> case Date@1 of
[Year, Month, Day] ->
{ok, {Year, Month, Day}};
_ ->
{error, nil}
end end),
gleam@result:replace_error(_pipe@3, date_invalid_format).
-spec to_string(tempo:date()) -> binary().
to_string(Date) ->
_pipe@3 = gleam@string_builder:from_strings(
[gleam@int:to_string(erlang:element(2, Date)),
<<"-"/utf8>>,
begin
_pipe = tempo@month:to_int(erlang:element(3, Date)),
_pipe@1 = gleam@int:to_string(_pipe),
gleam@string:pad_left(_pipe@1, 2, <<"0"/utf8>>)
end,
<<"-"/utf8>>,
begin
_pipe@2 = gleam@int:to_string(erlang:element(4, Date)),
gleam@string:pad_left(_pipe@2, 2, <<"0"/utf8>>)
end]
),
gleam@string_builder:to_string(_pipe@3).
-spec from_tuple({integer(), integer(), integer()}) -> {ok, tempo:date()} |
{error, tempo:error()}.
from_tuple(Date) ->
Year = erlang:element(1, Date),
Month = erlang:element(2, Date),
Day = erlang:element(3, Date),
gleam@result:'try'(
tempo@month:from_int(Month),
fun(Month@1) -> case (Year >= 1000) andalso (Year =< 9999) of
true ->
case (Day >= 1) andalso (Day =< tempo@month:days(
Month@1,
Year
)) of
true ->
{ok, {date, Year, Month@1, Day}};
false ->
{error, date_out_of_bounds}
end;
false ->
{error, date_out_of_bounds}
end end
).
-spec new(integer(), integer(), integer()) -> {ok, tempo:date()} |
{error, tempo:error()}.
new(Year, Month, Day) ->
from_tuple({Year, Month, Day}).
-spec from_string(binary()) -> {ok, tempo:date()} | {error, tempo:error()}.
from_string(Date) ->
_pipe = split_int_tuple(Date, <<"-"/utf8>>),
_pipe@1 = gleam@result:try_recover(
_pipe,
fun(_) -> split_int_tuple(Date, <<"/"/utf8>>) end
),
_pipe@2 = gleam@result:try_recover(
_pipe@1,
fun(_) -> split_int_tuple(Date, <<"."/utf8>>) end
),
_pipe@3 = gleam@result:try_recover(
_pipe@2,
fun(_) -> split_int_tuple(Date, <<"_"/utf8>>) end
),
_pipe@4 = gleam@result:try_recover(
_pipe@3,
fun(_) -> split_int_tuple(Date, <<" "/utf8>>) end
),
_pipe@8 = gleam@result:try_recover(
_pipe@4,
fun(_) ->
Year = begin
_pipe@5 = gleam@string:slice(Date, 0, 4),
gleam@int:parse(_pipe@5)
end,
Month = begin
_pipe@6 = gleam@string:slice(Date, 4, 2),
gleam@int:parse(_pipe@6)
end,
Day = begin
_pipe@7 = gleam@string:slice(Date, 6, 2),
gleam@int:parse(_pipe@7)
end,
case {Year, Month, Day} of
{{ok, Year@1}, {ok, Month@1}, {ok, Day@1}} ->
{ok, {Year@1, Month@1, Day@1}};
{_, _, _} ->
{error, date_invalid_format}
end
end
),
gleam@result:'try'(_pipe@8, fun from_tuple/1).
-spec literal(binary()) -> tempo:date().
literal(Date) ->
case from_string(Date) of
{ok, Date@1} ->
Date@1;
{error, date_invalid_format} ->
erlang:error(#{gleam_error => panic,
message => <<"Invalid date literal format"/utf8>>,
module => <<"tempo/date"/utf8>>,
function => <<"literal"/utf8>>,
line => 74});
{error, date_out_of_bounds} ->
erlang:error(#{gleam_error => panic,
message => <<"Invalid date literal value"/utf8>>,
module => <<"tempo/date"/utf8>>,
function => <<"literal"/utf8>>,
line => 75});
{error, _} ->
erlang:error(#{gleam_error => panic,
message => <<"Invalid date literal"/utf8>>,
module => <<"tempo/date"/utf8>>,
function => <<"literal"/utf8>>,
line => 76})
end.
-spec to_tuple(tempo:date()) -> {integer(), integer(), integer()}.
to_tuple(Date) ->
{erlang:element(2, Date),
tempo@month:to_int(erlang:element(3, Date)),
erlang:element(4, Date)}.
-spec from_dynamic_string(gleam@dynamic:dynamic_()) -> {ok, tempo:date()} |
{error, list(gleam@dynamic:decode_error())}.
from_dynamic_string(Dynamic_string) ->
gleam@result:'try'(
gleam@dynamic:string(Dynamic_string),
fun(Dt) -> case from_string(Dt) of
{ok, Date} ->
{ok, Date};
{error, Tempo_error} ->
{error,
[{decode_error,
<<"tempo.Date"/utf8>>,
<<(case Tempo_error of
date_invalid_format ->
<<"Invalid format: "/utf8>>;
date_out_of_bounds ->
<<"Date out of bounds: "/utf8>>;
month_out_of_bounds ->
<<"Month out of bounds: "/utf8>>;
_ ->
<<""/utf8>>
end)/binary, Dt/binary>>,
[]}]}
end end
).
-spec from_unix_utc(integer()) -> tempo:date().
from_unix_utc(Unix_ts) ->
Z = (Unix_ts div 86400) + 719468,
Era = case Z >= 0 of
true ->
Z;
false ->
Z - 146096
end div 146097,
Doe = Z - (Era * 146097),
Yoe = (((Doe - (Doe div 1460)) + (Doe div 36524)) - (Doe div 146096)) div 365,
Y = Yoe + (Era * 400),
Doy = Doe - (((365 * Yoe) + (Yoe div 4)) - (Yoe div 100)),
Mp = ((5 * Doy) + 2) div 153,
D = (Doy - (((153 * Mp) + 2) div 5)) + 1,
M = Mp + case Mp < 10 of
true ->
3;
false ->
-9
end,
Y@1 = Y + gleam@bool:to_int(M =< 2),
_assert_subject = tempo@month:from_int(M),
{ok, Month} = case _assert_subject of
{ok, _} -> _assert_subject;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Assertion pattern match failed"/utf8>>,
value => _assert_fail,
module => <<"tempo/date"/utf8>>,
function => <<"from_unix_utc"/utf8>>,
line => 357})
end,
{date, Y@1, Month, D}.
-spec current_local() -> tempo:date().
current_local() ->
_pipe = (tempo_ffi:now() + tempo@offset:local_nano()) div 1000000000,
from_unix_utc(_pipe).
-spec current_utc() -> tempo:date().
current_utc() ->
_pipe = tempo_ffi:now() div 1000000000,
from_unix_utc(_pipe).
-spec to_unix_utc(tempo:date()) -> integer().
to_unix_utc(Date) ->
Full_years_since_epoch = erlang:element(2, Date) - 1970,
Full_elapsed_leap_years_since_epoch = (Full_years_since_epoch + 1) div 4,
Full_elapsed_non_leap_years_since_epoch = Full_years_since_epoch - Full_elapsed_leap_years_since_epoch,
Year_sec = (Full_elapsed_non_leap_years_since_epoch * 31536000) + (Full_elapsed_leap_years_since_epoch
* 31622400),
Feb_milli = case tempo@year:is_leap_year(erlang:element(2, Date)) of
true ->
2505600;
false ->
2419200
end,
Month_sec = case erlang:element(3, Date) of
jan ->
0;
feb ->
2678400;
mar ->
2678400 + Feb_milli;
apr ->
5356800 + Feb_milli;
may ->
7948800 + Feb_milli;
jun ->
10627200 + Feb_milli;
jul ->
13219200 + Feb_milli;
aug ->
15897600 + Feb_milli;
sep ->
18576000 + Feb_milli;
oct ->
21168000 + Feb_milli;
nov ->
23846400 + Feb_milli;
dec ->
26438400 + Feb_milli
end,
Day_sec = (erlang:element(4, Date) - 1) * 86400,
(Year_sec + Month_sec) + Day_sec.
-spec from_unix_milli_utc(integer()) -> tempo:date().
from_unix_milli_utc(Unix_ts) ->
from_unix_utc(Unix_ts div 1000).
-spec to_unix_milli_utc(tempo:date()) -> integer().
to_unix_milli_utc(Date) ->
to_unix_utc(Date) * 1000.
-spec from_unix_micro_utc(integer()) -> tempo:date().
from_unix_micro_utc(Unix_ts) ->
from_unix_utc(Unix_ts div 1000000).
-spec to_unix_micro_utc(tempo:date()) -> integer().
to_unix_micro_utc(Date) ->
to_unix_utc(Date) * 1000000.
-spec compare(tempo:date(), tempo:date()) -> gleam@order:order().
compare(A, B) ->
case erlang:element(2, A) =:= erlang:element(2, B) of
true ->
case erlang:element(3, A) =:= erlang:element(3, B) of
true ->
case erlang:element(4, A) =:= erlang:element(4, B) of
true ->
eq;
false ->
gleam@int:compare(
erlang:element(4, A),
erlang:element(4, B)
)
end;
false ->
gleam@int:compare(
tempo@month:to_int(erlang:element(3, A)),
tempo@month:to_int(erlang:element(3, B))
)
end;
false ->
gleam@int:compare(erlang:element(2, A), erlang:element(2, B))
end.
-spec is_earlier(tempo:date(), tempo:date()) -> boolean().
is_earlier(A, B) ->
compare(A, B) =:= lt.
-spec is_earlier_or_equal(tempo:date(), tempo:date()) -> boolean().
is_earlier_or_equal(A, B) ->
(compare(A, B) =:= lt) orelse (compare(A, B) =:= eq).
-spec is_equal(tempo:date(), tempo:date()) -> boolean().
is_equal(A, B) ->
compare(A, B) =:= eq.
-spec is_later(tempo:date(), tempo:date()) -> boolean().
is_later(A, B) ->
compare(A, B) =:= gt.
-spec is_later_or_equal(tempo:date(), tempo:date()) -> boolean().
is_later_or_equal(A, B) ->
(compare(A, B) =:= gt) orelse (compare(A, B) =:= eq).
-spec difference(tempo:date(), tempo:date()) -> tempo:period().
difference(A, B) ->
{Start, End} = case begin
_pipe = A,
is_earlier_or_equal(_pipe, B)
end of
true ->
{A, B};
false ->
{B, A}
end,
{naive_period,
{naive_date_time, Start, {time, 0, 0, 0, 0}},
{naive_date_time, End, {time, 0, 0, 0, 0}}}.
-spec as_period(tempo:date(), tempo:date()) -> tempo:period().
as_period(Start, End) ->
{Start@1, End@1} = case begin
_pipe = Start,
is_earlier_or_equal(_pipe, End)
end of
true ->
{Start, End};
false ->
{End, Start}
end,
{naive_period,
{naive_date_time, Start@1, {time, 0, 0, 0, 0}},
{naive_date_time, End@1, {time, 24, 0, 0, 0}}}.
-spec add(tempo:date(), integer()) -> tempo:date().
add(Date, Days) ->
Days_left_this_month = tempo@month:days(
erlang:element(3, Date),
erlang:element(2, Date)
)
- erlang:element(4, Date),
case Days =< Days_left_this_month of
true ->
{date,
erlang:element(2, Date),
erlang:element(3, Date),
erlang:element(4, Date) + Days};
false ->
Next_month = tempo@month:next(erlang:element(3, Date)),
Year = case Next_month =:= jan of
true ->
erlang:element(2, Date) + 1;
false ->
erlang:element(2, Date)
end,
add({date, Year, Next_month, 1}, (Days - Days_left_this_month) - 1)
end.
-spec subtract(tempo:date(), integer()) -> tempo:date().
subtract(Date, Days) ->
case Days < erlang:element(4, Date) of
true ->
{date,
erlang:element(2, Date),
erlang:element(3, Date),
erlang:element(4, Date) - Days};
false ->
Prior_month = tempo@month:prior(erlang:element(3, Date)),
Year = case Prior_month =:= dec of
true ->
erlang:element(2, Date) - 1;
false ->
erlang:element(2, Date)
end,
subtract(
{date, Year, Prior_month, tempo@month:days(Prior_month, Year)},
Days - erlang:element(4, Date)
)
end.
-spec to_weekday(tempo:date()) -> day_of_week().
to_weekday(Date) ->
Year_code = begin
_pipe = erlang:element(2, Date) rem 100,
(fun(Short_year) -> (Short_year + (Short_year div 4)) rem 7 end)(_pipe)
end,
Month_code = case erlang:element(3, Date) of
jan ->
0;
feb ->
3;
mar ->
3;
apr ->
6;
may ->
1;
jun ->
4;
jul ->
6;
aug ->
2;
sep ->
5;
oct ->
0;
nov ->
3;
dec ->
5
end,
Century_code = case erlang:element(2, Date) of
Year when Year < 1752 ->
0;
Year@1 when Year@1 < 1800 ->
4;
Year@2 when Year@2 < 1900 ->
2;
Year@3 when Year@3 < 2000 ->
0;
Year@4 when Year@4 < 2100 ->
6;
Year@5 when Year@5 < 2200 ->
4;
Year@6 when Year@6 < 2300 ->
2;
Year@7 when Year@7 < 2400 ->
4;
_ ->
0
end,
Leap_year_code = case tempo@year:is_leap_year(erlang:element(2, Date)) of
true ->
case erlang:element(3, Date) of
jan ->
1;
feb ->
1;
_ ->
0
end;
false ->
0
end,
Day_of_week = ((((Year_code + Month_code) + Century_code) + erlang:element(
4,
Date
))
- Leap_year_code)
rem 7,
case Day_of_week of
0 ->
sun;
1 ->
mon;
2 ->
tue;
3 ->
wed;
4 ->
thu;
5 ->
fri;
6 ->
sat;
_ ->
erlang:error(#{gleam_error => panic,
message => <<"Invalid day of week found after modulo by 7"/utf8>>,
module => <<"tempo/date"/utf8>>,
function => <<"to_weekday"/utf8>>,
line => 812})
end.
-spec is_weekend(tempo:date()) -> boolean().
is_weekend(Date) ->
case to_weekday(Date) of
sat ->
true;
sun ->
true;
_ ->
false
end.
-spec first_of_month(tempo:date()) -> tempo:date().
first_of_month(Date) ->
{date, erlang:element(2, Date), erlang:element(3, Date), 1}.
-spec last_of_month(tempo:date()) -> tempo:date().
last_of_month(Date) ->
{date,
erlang:element(2, Date),
erlang:element(3, Date),
tempo@month:days(erlang:element(3, Date), erlang:element(2, Date))}.