///|
/// Gregorian date algorithms adapted from RUI calendar.mbt at b1291945.
/// MIT; see the module THIRD_PARTY_NOTICES.md.
pub struct CalendarDate {
year : Int
month : Int
day : Int
} derive(Eq, Debug)
///|
pub fn days_in_month(year : Int, month : Int) -> Int {
match month {
2 =>
if year % 400 == 0 || (year % 4 == 0 && year % 100 != 0) {
29
} else {
28
}
4 | 6 | 9 | 11 => 30
_ => 31
}
}
///|
pub fn calendar_date(year~ : Int, month~ : Int, day~ : Int) -> CalendarDate {
let year = year.clamp(min=1, max=9999)
let month = month.clamp(min=1, max=12)
{ year, month, day: day.clamp(min=1, max=days_in_month(year, month)), }
}
///|
pub fn CalendarDate::ordinal(self : CalendarDate) -> Int {
let year = self.year - 1
let mut days = 365 * year + year / 4 - year / 100 + year / 400 + self.day
for month = 1; month < self.month; month = month + 1 {
days = days + days_in_month(self.year, month)
}
days
}
///|
pub fn CalendarDate::weekday(self : CalendarDate) -> Int {
self.ordinal() % 7
}
///|
pub fn CalendarDate::add_months(
self : CalendarDate,
delta : Int,
) -> CalendarDate {
let index = ((self.year - 1) * 12 + self.month - 1 + delta).clamp(
min=0,
max=119987,
)
calendar_date(year=index / 12 + 1, month=index % 12 + 1, day=self.day)
}
///|
pub fn CalendarDate::add_days(self : CalendarDate, delta : Int) -> CalendarDate {
let target = (self.ordinal() + delta).clamp(min=1, max=3652059)
let mut low = 1
let mut high = 9999
while low < high {
let middle = (low + high + 1) / 2
if calendar_date(year=middle, month=1, day=1).ordinal() <= target {
low = middle
} else {
high = middle - 1
}
}
let mut remaining = target -
calendar_date(year=low, month=1, day=1).ordinal() +
1
let mut month = 1
while remaining > days_in_month(low, month) {
remaining = remaining - days_in_month(low, month)
month = month + 1
}
calendar_date(year=low, month~, day=remaining)
}
///|
pub fn CalendarDate::iso(self : CalendarDate) -> String {
let y = self.year.to_string()
let m = self.month.to_string()
let d = self.day.to_string()
"0".repeat(4 - y.length()) +
y +
"-" +
"0".repeat(2 - m.length()) +
m +
"-" +
"0".repeat(2 - d.length()) +
d
}
///|
pub(all) enum DateSelection {
Single(CalendarDate?)
Multiple(Array[CalendarDate])
Range(CalendarDate?, CalendarDate?)
} derive(Eq, Debug)
///|
pub fn DateSelection::choose(
self : DateSelection,
date : CalendarDate,
) -> DateSelection {
match self {
Single(_) => Single(Some(date))
Multiple(values) =>
if values.contains(date) {
Multiple(values.filter(value => value != date))
} else {
let next = values.copy()
next.push(date)
Multiple(next)
}
Range(Some(start), None) =>
if date.ordinal() < start.ordinal() {
Range(Some(date), Some(start))
} else {
Range(Some(start), Some(date))
}
Range(_, _) => Range(Some(date), None)
}
}
///|
pub fn DateSelection::contains(
self : DateSelection,
date : CalendarDate,
) -> Bool {
match self {
Single(value) => value == Some(date)
Multiple(values) => values.contains(date)
Range(Some(start), Some(end)) =>
date.ordinal() >= start.ordinal() && date.ordinal() <= end.ordinal()
Range(start, _) => start == Some(date)
}
}