use super::{
masks::MASKS,
utils::{days_since_unix_epoch, get_year_len, pymod},
};
use crate::RRule;
use chrono::{Datelike, TimeZone, Utc};
#[derive(Debug)]
pub(crate) struct BaseMasks {
month_mask: &'static [u8],
month_day_mask: &'static [i8],
neg_month_day_mask: &'static [i8],
month_range: &'static [u16],
weekday_mask: &'static [u32],
}
fn base_year_masks(year_weekday: u16, year_len: u16) -> BaseMasks {
if year_len == 365 {
BaseMasks {
month_mask: &MASKS.month_365,
month_day_mask: &MASKS.month_day_365,
neg_month_day_mask: &MASKS.neg_month_day_365,
month_range: &MASKS.month_365_range,
weekday_mask: &MASKS.weekday[usize::from(year_weekday)..],
}
} else {
BaseMasks {
month_mask: &MASKS.month_366,
month_day_mask: &MASKS.month_day_366,
neg_month_day_mask: &MASKS.neg_month_day_366,
month_range: &MASKS.month_366_range,
weekday_mask: &MASKS.weekday[usize::from(year_weekday)..],
}
}
}
#[derive(Debug, Clone)]
pub(crate) struct YearInfo {
pub year: i32,
pub year_len: u16,
pub next_year_len: u16,
pub year_ordinal: i64,
pub month_mask: &'static [u8],
pub month_day_mask: &'static [i8],
pub neg_month_day_mask: &'static [i8],
pub month_range: &'static [u16],
pub weekday_mask: &'static [u32],
pub week_no_mask: Option<Vec<u8>>,
}
impl YearInfo {
pub fn new(year: i32, rrule: &RRule) -> Self {
let first_year_day = Utc.with_ymd_and_hms(year, 1, 1, 0, 0, 0).unwrap();
let year_len = get_year_len(year);
let next_year_len = get_year_len(year + 1);
let year_ordinal = days_since_unix_epoch(&first_year_day);
let year_start_weekday = u16::try_from(first_year_day.weekday().num_days_from_monday())
.expect("num_days_from_monday is between 0 and 6 which is covered by u16");
let base_masks = base_year_masks(year_start_weekday, year_len);
let mut result = Self {
year,
year_len,
next_year_len,
year_ordinal,
week_no_mask: None,
month_mask: base_masks.month_mask,
month_day_mask: base_masks.month_day_mask,
neg_month_day_mask: base_masks.neg_month_day_mask,
month_range: base_masks.month_range,
weekday_mask: base_masks.weekday_mask,
};
if rrule.by_week_no.is_empty() {
return result;
}
let mut week_no_mask = vec![0; usize::from(year_len) + 7];
let rrule_week_start = u16::try_from(rrule.week_start.num_days_from_monday())
.expect("num_days_from_monday is between 0 and 6 which is covered by u16");
let mut no1_week_start = pymod(7 - year_start_weekday + rrule_week_start, 7);
let first_week_start = no1_week_start;
let year_len_ext = if no1_week_start >= 4 {
no1_week_start = 0;
let diff = i32::from(year_start_weekday) - i32::from(rrule_week_start);
result.year_len
+ u16::try_from(pymod(diff, 7)).expect("to be positive because 7 is the modulus")
} else {
year_len - no1_week_start
};
let div = year_len_ext / 7;
let year_mod = pymod(year_len_ext, 7);
let num_weeks = div + (year_mod / 4);
for &(mut n) in &rrule.by_week_no {
let num_weeks =
i8::try_from(num_weeks).expect("num_weeks is 52-54 which is covered by i8::MAX");
if n < 0 {
n += num_weeks + 1;
}
if !(n > 0 && n <= num_weeks) {
continue;
}
let i = if n > 1 {
let n =
u16::try_from(n).expect("We know that 1 < n < i8::MAX which is covered by u16");
let mut i = no1_week_start + ((n - 1) * 7);
if no1_week_start != first_week_start {
i -= 7 - first_week_start;
}
i
} else {
no1_week_start
};
for j in i..i + 7 {
let j = usize::from(j);
week_no_mask[j] = 1;
if result.weekday_mask[j + 1] == rrule.week_start.num_days_from_monday() {
break;
}
}
}
if rrule.by_week_no.contains(&1) {
let mut i = no1_week_start + num_weeks * 7;
if no1_week_start != first_week_start {
i -= 7 - first_week_start;
}
if i < year_len {
for j in i..i + 7 {
let j = usize::from(j);
week_no_mask[j] = 1;
if result.weekday_mask[j + 1] == rrule.week_start.num_days_from_monday() {
break;
}
}
}
}
if no1_week_start > 0 {
let l_num_weeks = if rrule.by_week_no.contains(&-1) {
-1
} else {
let l_year_weekday = u16::try_from(
Utc.with_ymd_and_hms(year - 1, 1, 1, 0, 0, 0)
.unwrap()
.weekday()
.num_days_from_monday(),
)
.expect("num_days_from_monday is between 0 and 6 which is covered by u16");
let rrule_week_start = u16::try_from(rrule.week_start.num_days_from_monday())
.expect("num_days_from_monday is between 0 and 6 which is covered by u16");
let ln_no1_week_start = pymod(7 - l_year_weekday + rrule_week_start, 7);
let l_year_len = get_year_len(year - 1);
let week_start = if ln_no1_week_start >= 4 {
l_year_len
+ u16::try_from(pymod(
i32::from(l_year_weekday) - i32::from(rrule_week_start),
7,
))
.expect("7 is the modulo, so the range is 0-6, and u16 covers that range")
} else {
year_len - no1_week_start
};
52 + i8::try_from(pymod(week_start, 7) / 4)
.expect("7 is the modulo, so the range is 0-6, and i8 covers that range")
};
if rrule.by_week_no.contains(&l_num_weeks) {
for i in 0..no1_week_start {
let i = usize::from(i);
week_no_mask[i] = 1;
}
}
}
result.week_no_mask = Some(week_no_mask);
result
}
}