///|
fn DateRule::matches_year_fields(
self : DateRule,
value : DateTime,
period_year : Int32,
) -> Bool {
if self.byyearday is Some(days) {
let length = if is_leap_year(value.year) { 366 } else { 365 }
let day = days_from_civil(value.year, value.month, value.day) -
days_from_civil(value.year, 1, 1) +
1
if !days.contains(day) && !days.contains(day - length - 1) {
return false
}
}
if self.byweekno is Some(weeks) &&
!recurrence_matches_weekno(value, period_year, self.wkst, weeks) {
return false
}
if self.byeaster is Some(offsets) {
let day = days_from_civil(value.year, value.month, value.day) -
days_from_civil(period_year, 1, 1) +
1
if !offsets.contains(day - recurrence_easter_day(period_year)) {
return false
}
}
true
}
///|
// Gregorian Western Easter, expressed as a one-based day of year. The
// integer algorithm also works at both ends of our supported year range.
fn recurrence_easter_day(year : Int32) -> Int32 {
let a = year % 19
let b = year / 100
let c = year % 100
let d = b / 4
let e = b % 4
let f = (b + 8) / 25
let g = (b - f + 1) / 3
let h = (19 * a + b - d - g + 15) % 30
let i = c / 4
let k = c % 4
let l = (32 + 2 * e + 2 * i - h - k) % 7
let m = (a + 11 * h + 22 * l) / 451
let month = (h + l - 7 * m + 114) / 31
let day = (h + l - 7 * m + 114) % 31 + 1
days_from_civil(year, month, day) - days_from_civil(year, 1, 1) + 1
}
///|
// A weekly period may run into January of the following year. Resolve week
// numbers using the period's year, as dateutil._iterinfo.rebuild does, rather
// than rebuilding the mask midway through that period.
fn recurrence_matches_weekno(
value : DateTime,
year : Int32,
wkst : Int32,
weeks : Array[Int32],
) -> Bool {
let length = if is_leap_year(year) { 366 } else { 365 }
let index = days_from_civil(value.year, value.month, value.day) -
days_from_civil(year, 1, 1)
let weekday = weekday_from_civil(year, 1, 1)
let first_start = (7 - weekday + wkst) % 7
let first = if first_start >= 4 { 0 } else { first_start }
let week_year_length = if first_start >= 4 {
length + (weekday - wkst + 7) % 7
} else {
length - first
}
let count = week_year_length / 7 + week_year_length % 7 / 4
for requested in weeks {
let week = if requested < 0 { requested + count + 1 } else { requested }
if week < 1 || week > count {
continue
}
let start = if week == 1 {
first
} else {
first +
(week - 1) * 7 -
(if first != first_start { 7 - first_start } else { 0 })
}
if recurrence_in_week(index, start, weekday, wkst) {
return true
}
}
// Only an explicit +1 includes the next year's first week. Keep dateutil's
// distinction between +1 and a negative number normalized to +1.
if weeks.contains(1) {
let start = first +
count * 7 -
(if first != first_start { 7 - first_start } else { 0 })
if start < length && recurrence_in_week(index, start, weekday, wkst) {
return true
}
}
if first != 0 && index >= 0 && index < first {
if weeks.contains(-1) {
return true
}
let previous_weekday = weekday_from_civil(year - 1, 1, 1)
let previous_start = (7 - previous_weekday + wkst) % 7
let previous_length = if is_leap_year(year - 1) { 366 } else { 365 }
// This expression intentionally follows dateutil's year-mask semantics.
let previous_count = if previous_start >= 4 {
52 + (previous_length + (previous_weekday - wkst + 7) % 7) % 7 / 4
} else {
52 + (length - first) % 7 / 4
}
return weeks.contains(previous_count)
}
false
}
///|
// January 1 may be partway through a numbered week. Stop at the next WKST,
// rather than treating every numbered interval as seven days from January 1.
fn recurrence_in_week(
index : Int32,
start : Int32,
year_weekday : Int32,
wkst : Int32,
) -> Bool {
let distance = (wkst - (year_weekday + start) % 7 + 7) % 7
let length = if distance == 0 { 7 } else { distance }
index >= start && index < start + length
}