Packages

A comprehensive calendar library for Gleam — date, time, datetime, naive datetime, duration, ISO 8601 parsing and formatting, timezone support. Port of Elixir's Calendar modules.

Current section

Files

Jump to
sidereal src calendar@date.erl
Raw

src/calendar@date.erl

-module(calendar@date).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/calendar/date.gleam").
-export([is_leap_year/1, days_in_month/2, new/4, new_simple/3, new_iso/3, to_string/1, to_iso8601/1, compare/2, add_days/2, subtract_days/2, from_timestamp/1, from_days_since_unix_epoch/2, to_timestamp/1, utc_today_with_calendar/1, utc_today/0, leap_year/1, days_in_month_for_date/1, months_in_year/1, to_iso8601_with_format/2, to_erl/1, from_erl_with_calendar/2, from_erl/1, from_gregorian_days_with_calendar/2, from_gregorian_days/1, to_gregorian_days/1, before/2, 'after'/2, convert/2, add/2, diff/2, shift/2, day_of_week_starting_on/2, day_of_week/1, beginning_of_week_starting_on/2, beginning_of_week/1, end_of_week_starting_on/2, end_of_week/1, day_of_year/1, quarter_of_year/1, year_of_era/1, day_of_era/1, beginning_of_month/1, end_of_month/1, from_iso8601_with_calendar/2, from_iso8601/1, range_with_step/3, range/2]).
-export_type([date/0, date_error/0, date_format/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
-type date() :: {date, integer(), integer(), integer(), binary()}.
-type date_error() :: invalid_date |
invalid_year |
invalid_month |
invalid_day |
invalid_format |
invalid_calendar |
incompatible_calendars.
-type date_format() :: extended | basic.
-file("src/calendar/date.gleam", 63).
?DOC(" Checks if a year is a leap year.\n").
-spec is_leap_year(integer()) -> boolean().
is_leap_year(Year) ->
case (Year rem 4) =:= 0 of
true ->
case (Year rem 100) =:= 0 of
true ->
(Year rem 400) =:= 0;
false ->
true
end;
false ->
false
end.
-file("src/calendar/date.gleam", 75).
?DOC(" Returns the number of days in a given month and year.\n").
-spec days_in_month(integer(), integer()) -> integer().
days_in_month(Year, Month) ->
case Month of
1 ->
31;
3 ->
31;
5 ->
31;
7 ->
31;
8 ->
31;
10 ->
31;
12 ->
31;
4 ->
30;
6 ->
30;
9 ->
30;
11 ->
30;
2 ->
case is_leap_year(Year) of
true ->
29;
false ->
28
end;
_ ->
0
end.
-file("src/calendar/date.gleam", 89).
?DOC(" Validates if the given date components form a valid date.\n").
-spec is_valid_date(integer(), integer(), integer()) -> boolean().
is_valid_date(Year, Month, Day) ->
(((Month >= 1) andalso (Month =< 12)) andalso (Day >= 1)) andalso (Day =< days_in_month(
Year,
Month
)).
-file("src/calendar/date.gleam", 40).
?DOC(" Creates a new Date struct.\n").
-spec new(integer(), integer(), integer(), binary()) -> {ok, date()} |
{error, date_error()}.
new(Year, Month, Day, Calendar) ->
case is_valid_date(Year, Month, Day) of
true ->
{ok, {date, Year, Month, Day, Calendar}};
false ->
{error, invalid_date}
end.
-file("src/calendar/date.gleam", 53).
?DOC(" Creates a new Date struct with ISO calendar as default.\n").
-spec new_simple(integer(), integer(), integer()) -> {ok, date()} |
{error, date_error()}.
new_simple(Year, Month, Day) ->
new(Year, Month, Day, <<"Calendar.ISO"/utf8>>).
-file("src/calendar/date.gleam", 58).
?DOC(" Creates a new Date struct with ISO calendar as default.\n").
-spec new_iso(integer(), integer(), integer()) -> {ok, date()} |
{error, date_error()}.
new_iso(Year, Month, Day) ->
new(Year, Month, Day, <<"Calendar.ISO"/utf8>>).
-file("src/calendar/date.gleam", 106).
?DOC(" Helper function to pad string with leading characters.\n").
-spec pad_left(binary(), integer(), binary()) -> binary().
pad_left(Str, Width, Pad_char) ->
Current_length = string:length(Str),
case Current_length >= Width of
true ->
Str;
false ->
<<(gleam@string:repeat(Pad_char, Width - Current_length))/binary,
Str/binary>>
end.
-file("src/calendar/date.gleam", 94).
?DOC(" Converts a Date to a string in ISO8601 format (YYYY-MM-DD).\n").
-spec to_string(date()) -> binary().
to_string(Date) ->
Year_str = case (erlang:element(2, Date) >= 0) andalso (erlang:element(
2,
Date
)
=< 9999) of
true ->
pad_left(
erlang:integer_to_binary(erlang:element(2, Date)),
4,
<<"0"/utf8>>
);
false ->
erlang:integer_to_binary(erlang:element(2, Date))
end,
Month_str = pad_left(
erlang:integer_to_binary(erlang:element(3, Date)),
2,
<<"0"/utf8>>
),
Day_str = pad_left(
erlang:integer_to_binary(erlang:element(4, Date)),
2,
<<"0"/utf8>>
),
<<<<<<<<Year_str/binary, "-"/utf8>>/binary, Month_str/binary>>/binary,
"-"/utf8>>/binary,
Day_str/binary>>.
-file("src/calendar/date.gleam", 115).
?DOC(" Converts a Date to ISO8601 format.\n").
-spec to_iso8601(date()) -> binary().
to_iso8601(Date) ->
to_string(Date).
-file("src/calendar/date.gleam", 139).
?DOC(" Convert date to days since Unix epoch (1970-01-01).\n").
-spec to_days_since_epoch(date()) -> integer().
to_days_since_epoch(Date) ->
calendar@iso:date_to_iso_days(
erlang:element(2, Date),
erlang:element(3, Date),
erlang:element(4, Date)
)
- 719528.
-file("src/calendar/date.gleam", 131).
?DOC(" Compare two dates. Returns order.Lt, order.Eq, or order.Gt.\n").
-spec compare(date(), date()) -> gleam@order:order().
compare(Date1, Date2) ->
Days1 = to_days_since_epoch(Date1),
Days2 = to_days_since_epoch(Date2),
gleam@int:compare(Days1, Days2).
-file("src/calendar/date.gleam", 144).
?DOC(" Convert days since epoch back to a date.\n").
-spec from_days_since_epoch(integer(), binary()) -> {ok, date()} |
{error, date_error()}.
from_days_since_epoch(Days, Calendar) ->
Iso_days = Days + 719528,
{Year, Month, Day} = calendar@iso:date_from_iso_days(Iso_days),
case is_valid_date(Year, Month, Day) of
true ->
{ok, {date, Year, Month, Day, Calendar}};
false ->
{error, invalid_date}
end.
-file("src/calendar/date.gleam", 120).
?DOC(" Add days to a date.\n").
-spec add_days(date(), integer()) -> {ok, date()} | {error, date_error()}.
add_days(Date, Days) ->
Total_days = to_days_since_epoch(Date) + Days,
from_days_since_epoch(Total_days, erlang:element(5, Date)).
-file("src/calendar/date.gleam", 126).
?DOC(" Subtract days from a date.\n").
-spec subtract_days(date(), integer()) -> {ok, date()} | {error, date_error()}.
subtract_days(Date, Days) ->
add_days(Date, - Days).
-file("src/calendar/date.gleam", 177).
-spec leap_day_offset(integer()) -> integer().
leap_day_offset(Year) ->
case is_leap_year(Year) of
true ->
1;
false ->
0
end.
-file("src/calendar/date.gleam", 159).
?DOC(" Convert month to approximate day of year.\n").
-spec month_to_day_of_year(integer(), integer()) -> integer().
month_to_day_of_year(Year, Month) ->
case Month of
1 ->
0;
2 ->
31;
3 ->
59 + leap_day_offset(Year);
4 ->
90 + leap_day_offset(Year);
5 ->
120 + leap_day_offset(Year);
6 ->
151 + leap_day_offset(Year);
7 ->
181 + leap_day_offset(Year);
8 ->
212 + leap_day_offset(Year);
9 ->
243 + leap_day_offset(Year);
10 ->
273 + leap_day_offset(Year);
11 ->
304 + leap_day_offset(Year);
12 ->
334 + leap_day_offset(Year);
_ ->
0
end.
-file("src/calendar/date.gleam", 185).
?DOC(" Create a date from Unix timestamp (seconds since epoch).\n").
-spec from_timestamp(integer()) -> {ok, date()} | {error, date_error()}.
from_timestamp(Timestamp) ->
from_days_since_epoch(Timestamp div 86400, <<"Calendar.ISO"/utf8>>).
-file("src/calendar/date.gleam", 190).
?DOC(" Create a date from days since Unix epoch (1970-01-01).\n").
-spec from_days_since_unix_epoch(integer(), binary()) -> {ok, date()} |
{error, date_error()}.
from_days_since_unix_epoch(Days, Calendar) ->
from_days_since_epoch(Days, Calendar).
-file("src/calendar/date.gleam", 198).
?DOC(" Convert date to Unix timestamp (approximation).\n").
-spec to_timestamp(date()) -> integer().
to_timestamp(Date) ->
to_days_since_epoch(Date) * 86400.
-file("src/calendar/date.gleam", 208).
?DOC(" Returns the current date in UTC with specified calendar.\n").
-spec utc_today_with_calendar(binary()) -> date().
utc_today_with_calendar(Calendar) ->
Timestamp = os:system_time(),
Days = Timestamp div 86400,
case from_days_since_epoch(Days, Calendar) of
{ok, Date} ->
Date;
{error, _} ->
{date, 1970, 1, 1, Calendar}
end.
-file("src/calendar/date.gleam", 203).
?DOC(" Returns the current date in UTC.\n").
-spec utc_today() -> date().
utc_today() ->
utc_today_with_calendar(<<"Calendar.ISO"/utf8>>).
-file("src/calendar/date.gleam", 218).
?DOC(" Returns `true` if the year in the given `date` is a leap year.\n").
-spec leap_year(date()) -> boolean().
leap_year(Date) ->
is_leap_year(erlang:element(2, Date)).
-file("src/calendar/date.gleam", 223).
?DOC(" Returns the number of days in the given `date` month.\n").
-spec days_in_month_for_date(date()) -> integer().
days_in_month_for_date(Date) ->
days_in_month(erlang:element(2, Date), erlang:element(3, Date)).
-file("src/calendar/date.gleam", 228).
?DOC(" Returns the number of months in the given `date` year.\n").
-spec months_in_year(date()) -> integer().
months_in_year(_) ->
12.
-file("src/calendar/date.gleam", 247).
?DOC(" Converts the given `date` to ISO 8601 with format option.\n").
-spec to_iso8601_with_format(date(), date_format()) -> binary().
to_iso8601_with_format(Date, Format) ->
case Format of
extended ->
to_string(Date);
basic ->
Year_str = case (erlang:element(2, Date) >= 0) andalso (erlang:element(
2,
Date
)
=< 9999) of
true ->
pad_left(
erlang:integer_to_binary(erlang:element(2, Date)),
4,
<<"0"/utf8>>
);
false ->
erlang:integer_to_binary(erlang:element(2, Date))
end,
Month_str = pad_left(
erlang:integer_to_binary(erlang:element(3, Date)),
2,
<<"0"/utf8>>
),
Day_str = pad_left(
erlang:integer_to_binary(erlang:element(4, Date)),
2,
<<"0"/utf8>>
),
<<<<Year_str/binary, Month_str/binary>>/binary, Day_str/binary>>
end.
-file("src/calendar/date.gleam", 263).
?DOC(" Converts the given `date` to an Erlang date tuple.\n").
-spec to_erl(date()) -> {integer(), integer(), integer()}.
to_erl(Date) ->
{erlang:element(2, Date), erlang:element(3, Date), erlang:element(4, Date)}.
-file("src/calendar/date.gleam", 273).
?DOC(" Converts an Erlang date tuple to a `Date` struct with specified calendar.\n").
-spec from_erl_with_calendar({integer(), integer(), integer()}, binary()) -> {ok,
date()} |
{error, date_error()}.
from_erl_with_calendar(Tuple, Calendar) ->
{Year, Month, Day} = Tuple,
new(Year, Month, Day, Calendar).
-file("src/calendar/date.gleam", 268).
?DOC(" Converts an Erlang date tuple to a `Date` struct.\n").
-spec from_erl({integer(), integer(), integer()}) -> {ok, date()} |
{error, date_error()}.
from_erl(Tuple) ->
from_erl_with_calendar(Tuple, <<"Calendar.ISO"/utf8>>).
-file("src/calendar/date.gleam", 287).
?DOC(" Converts a number of gregorian days to a `Date` struct with specified calendar.\n").
-spec from_gregorian_days_with_calendar(integer(), binary()) -> date().
from_gregorian_days_with_calendar(Days, Calendar) ->
case from_days_since_epoch(Days - 719163, Calendar) of
{ok, Date} ->
Date;
{error, _} ->
{date, 0, 1, 1, Calendar}
end.
-file("src/calendar/date.gleam", 282).
?DOC(" Converts a number of gregorian days to a `Date` struct.\n").
-spec from_gregorian_days(integer()) -> date().
from_gregorian_days(Days) ->
from_gregorian_days_with_calendar(Days, <<"Calendar.ISO"/utf8>>).
-file("src/calendar/date.gleam", 296).
?DOC(" Converts a `date` struct to a number of gregorian days.\n").
-spec to_gregorian_days(date()) -> integer().
to_gregorian_days(Date) ->
to_days_since_epoch(Date) + 719163.
-file("src/calendar/date.gleam", 302).
?DOC(" Returns `true` if the first date is strictly earlier than the second.\n").
-spec before(date(), date()) -> boolean().
before(Date1, Date2) ->
case compare(Date1, Date2) of
lt ->
true;
_ ->
false
end.
-file("src/calendar/date.gleam", 310).
?DOC(" Returns `true` if the first date is strictly later than the second.\n").
-spec 'after'(date(), date()) -> boolean().
'after'(Date1, Date2) ->
case compare(Date1, Date2) of
gt ->
true;
_ ->
false
end.
-file("src/calendar/date.gleam", 318).
?DOC(" Converts the given `date` from its calendar to the given `calendar`.\n").
-spec convert(date(), binary()) -> {ok, date()} | {error, date_error()}.
convert(Date, Target_calendar) ->
case erlang:element(5, Date) =:= Target_calendar of
true ->
{ok, Date};
false ->
{ok,
{date,
erlang:element(2, Date),
erlang:element(3, Date),
erlang:element(4, Date),
Target_calendar}}
end.
-file("src/calendar/date.gleam", 334).
?DOC(" Adds the number of days to the given `date`.\n").
-spec add(date(), integer()) -> date().
add(Date, Days) ->
case add_days(Date, Days) of
{ok, New_date} ->
New_date;
{error, _} ->
Date
end.
-file("src/calendar/date.gleam", 343).
?DOC(" Calculates the difference between two dates, in a full number of days.\n").
-spec diff(date(), date()) -> integer().
diff(Date1, Date2) ->
to_days_since_epoch(Date1) - to_days_since_epoch(Date2).
-file("src/calendar/date.gleam", 348).
?DOC(" Shifts given `date` by `duration` according to its calendar.\n").
-spec shift(date(), calendar@duration:duration()) -> {ok, date()} |
{error, date_error()}.
shift(Date, Duration) ->
Years = erlang:element(2, Duration),
Months = erlang:element(3, Duration),
Weeks = erlang:element(4, Duration),
Days = erlang:element(5, Duration),
New_year = erlang:element(2, Date) + Years,
New_month = erlang:element(3, Date) + Months,
Normalized_year = New_year + ((New_month - 1) div 12),
Normalized_month = ((New_month - 1) rem 12) + 1,
Max_days = days_in_month(Normalized_year, Normalized_month),
Normalized_day = case erlang:element(4, Date) > Max_days of
true ->
Max_days;
false ->
erlang:element(4, Date)
end,
case new(
Normalized_year,
Normalized_month,
Normalized_day,
erlang:element(5, Date)
) of
{ok, Intermediate_date} ->
Total_days = (Weeks * 7) + Days,
add_days(Intermediate_date, Total_days);
{error, E} ->
{error, E}
end.
-file("src/calendar/date.gleam", 391).
?DOC(" Calculates the ordinal day of the week with custom starting day.\n").
-spec day_of_week_starting_on(date(), integer()) -> integer().
day_of_week_starting_on(Date, Starting_on) ->
Year = case erlang:element(3, Date) < 3 of
true ->
erlang:element(2, Date) - 1;
false ->
erlang:element(2, Date)
end,
Month = case erlang:element(3, Date) < 3 of
true ->
erlang:element(3, Date) + 12;
false ->
erlang:element(3, Date)
end,
Day_of_week = (((((erlang:element(4, Date) + ((13 * (Month + 1)) div 5)) + Year)
+ (Year div 4))
- (Year div 100))
+ (Year div 400))
rem 7,
Adjusted = case Day_of_week of
0 ->
7;
N ->
N
end,
case (Adjusted - Starting_on) + 1 of
N@1 when N@1 =< 0 ->
N@1 + 7;
N@2 when N@2 > 7 ->
N@2 - 7;
N@3 ->
N@3
end.
-file("src/calendar/date.gleam", 385).
?DOC(" Calculates the ordinal day of the week of a given `date`.\n").
-spec day_of_week(date()) -> integer().
day_of_week(Date) ->
day_of_week_starting_on(Date, 1).
-file("src/calendar/date.gleam", 439).
?DOC(" Calculates beginning of week with custom starting day.\n").
-spec beginning_of_week_starting_on(date(), integer()) -> date().
beginning_of_week_starting_on(Date, Starting_on) ->
Current_day_of_week = day_of_week_starting_on(Date, Starting_on),
Days_to_subtract = Current_day_of_week - 1,
add(Date, - Days_to_subtract).
-file("src/calendar/date.gleam", 433).
?DOC(" Calculates a date that is the first day of the week for the given `date`.\n").
-spec beginning_of_week(date()) -> date().
beginning_of_week(Date) ->
beginning_of_week_starting_on(Date, 1).
-file("src/calendar/date.gleam", 452).
?DOC(" Calculates end of week with custom starting day.\n").
-spec end_of_week_starting_on(date(), integer()) -> date().
end_of_week_starting_on(Date, Starting_on) ->
Current_day_of_week = day_of_week_starting_on(Date, Starting_on),
Days_to_add = 7 - Current_day_of_week,
add(Date, Days_to_add).
-file("src/calendar/date.gleam", 446).
?DOC(" Calculates a date that is the last day of the week for the given `date`.\n").
-spec end_of_week(date()) -> date().
end_of_week(Date) ->
end_of_week_starting_on(Date, 1).
-file("src/calendar/date.gleam", 459).
?DOC(" Calculates the day of the year of a given `date`.\n").
-spec day_of_year(date()) -> integer().
day_of_year(Date) ->
month_to_day_of_year(erlang:element(2, Date), erlang:element(3, Date)) + erlang:element(
4,
Date
).
-file("src/calendar/date.gleam", 464).
?DOC(" Calculates the quarter of the year of a given `date`.\n").
-spec quarter_of_year(date()) -> integer().
quarter_of_year(Date) ->
case erlang:element(3, Date) of
1 ->
1;
2 ->
1;
3 ->
1;
4 ->
2;
5 ->
2;
6 ->
2;
7 ->
3;
8 ->
3;
9 ->
3;
10 ->
4;
11 ->
4;
12 ->
4;
_ ->
1
end.
-file("src/calendar/date.gleam", 475).
?DOC(" Calculates the year-of-era and era for a given calendar year.\n").
-spec year_of_era(date()) -> {integer(), integer()}.
year_of_era(Date) ->
case erlang:element(2, Date) >= 1 of
true ->
{erlang:element(2, Date), 1};
false ->
{- erlang:element(2, Date) + 1, 0}
end.
-file("src/calendar/date.gleam", 485).
?DOC(" Calculates the day-of-era and era for a given calendar `date`.\n").
-spec day_of_era(date()) -> {integer(), integer()}.
day_of_era(Date) ->
{_, Era} = year_of_era(Date),
Days = to_gregorian_days(Date),
case Era of
1 ->
{Days, 1};
0 ->
{- Days + 1, 0};
_ ->
{Days, Era}
end.
-file("src/calendar/date.gleam", 498).
?DOC(" Calculates a date that is the first day of the month for the given `date`.\n").
-spec beginning_of_month(date()) -> date().
beginning_of_month(Date) ->
{date,
erlang:element(2, Date),
erlang:element(3, Date),
1,
erlang:element(5, Date)}.
-file("src/calendar/date.gleam", 503).
?DOC(" Calculates a date that is the last day of the month for the given `date`.\n").
-spec end_of_month(date()) -> date().
end_of_month(Date) ->
Last_day = days_in_month(erlang:element(2, Date), erlang:element(3, Date)),
{date,
erlang:element(2, Date),
erlang:element(3, Date),
Last_day,
erlang:element(5, Date)}.
-file("src/calendar/date.gleam", 541).
-spec parse_iso8601_date(binary(), binary()) -> {ok, date()} |
{error, date_error()}.
parse_iso8601_date(String, Calendar) ->
case gleam@string:split(String, <<"-"/utf8>>) of
[Year_str, Month_str, Day_str] ->
case {gleam_stdlib:parse_int(Year_str),
gleam_stdlib:parse_int(Month_str),
gleam_stdlib:parse_int(Day_str)} of
{{ok, Year}, {ok, Month}, {ok, Day}} ->
new(Year, Month, Day, Calendar);
{_, _, _} ->
{error, invalid_format}
end;
_ ->
{error, invalid_format}
end.
-file("src/calendar/date.gleam", 239).
?DOC(" Parses an ISO 8601 date string with specified calendar.\n").
-spec from_iso8601_with_calendar(binary(), binary()) -> {ok, date()} |
{error, date_error()}.
from_iso8601_with_calendar(String, Calendar) ->
parse_iso8601_date(String, Calendar).
-file("src/calendar/date.gleam", 234).
?DOC(" Parses an ISO 8601 date string.\n").
-spec from_iso8601(binary()) -> {ok, date()} | {error, date_error()}.
from_iso8601(String) ->
from_iso8601_with_calendar(String, <<"Calendar.ISO"/utf8>>).
-file("src/calendar/date.gleam", 557).
-spec generate_date_range(date(), integer(), integer(), integer(), list(date())) -> {ok,
list(date())} |
{error, date_error()}.
generate_date_range(Current, Current_days, Target_days, Step, Acc) ->
case ((Step > 0) andalso (Current_days > Target_days)) orelse ((Step < 0)
andalso (Current_days < Target_days)) of
true ->
{ok, lists:reverse(Acc)};
false ->
New_acc = [Current | Acc],
case from_days_since_epoch(
Current_days + Step,
erlang:element(5, Current)
) of
{ok, Next_date} ->
generate_date_range(
Next_date,
Current_days + Step,
Target_days,
Step,
New_acc
);
{error, E} ->
{error, E}
end
end.
-file("src/calendar/date.gleam", 519).
?DOC(" Creates a date range with a step.\n").
-spec range_with_step(date(), date(), integer()) -> {ok, list(date())} |
{error, date_error()}.
range_with_step(First, Last, Step) ->
case Step =:= 0 of
true ->
{error, invalid_date};
false ->
case erlang:element(5, First) =:= erlang:element(5, Last) of
false ->
{error, incompatible_calendars};
true ->
First_days = to_days_since_epoch(First),
Last_days = to_days_since_epoch(Last),
generate_date_range(First, First_days, Last_days, Step, [])
end
end.
-file("src/calendar/date.gleam", 514).
?DOC(" Creates a date range between two dates.\n").
-spec range(date(), date()) -> {ok, list(date())} | {error, date_error()}.
range(First, Last) ->
range_with_step(First, Last, 1).