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