summaryrefslogtreecommitdiff
path: root/src/iter
diff options
context:
space:
mode:
Diffstat (limited to 'src/iter')
-rw-r--r--src/iter/iterinfo.rs255
-rw-r--r--src/iter/mod.rs339
-rw-r--r--src/iter/monthinfo.rs70
-rw-r--r--src/iter/poslist.rs63
-rw-r--r--src/iter/yearinfo.rs217
5 files changed, 944 insertions, 0 deletions
diff --git a/src/iter/iterinfo.rs b/src/iter/iterinfo.rs
new file mode 100644
index 0000000..09090f1
--- /dev/null
+++ b/src/iter/iterinfo.rs
@@ -0,0 +1,255 @@
+use crate::datetime::*;
+use crate::easter::*;
+use crate::iter::monthinfo::{MonthInfo, rebuild_month};
+use crate::iter::yearinfo::{YearInfo, rebuild_year};
+use crate::options::*;
+use chrono::prelude::*;
+
+pub struct IterInfo<'a> {
+ pub yearinfo: Option<YearInfo>,
+ pub monthinfo: Option<MonthInfo>,
+ pub eastermask: Option<Vec<isize>>,
+ options: &'a ParsedOptions,
+}
+
+impl<'a> IterInfo<'a> {
+ pub fn new(options: &'a ParsedOptions) -> Self {
+ Self {
+ options,
+ yearinfo: None,
+ monthinfo: None,
+ eastermask: None,
+ }
+ }
+
+ pub fn rebuild(&mut self, year: isize, month: usize) {
+ if self.monthinfo.is_none() || year != self.monthinfo.as_ref().unwrap().lastyear {
+ self.yearinfo = Some(rebuild_year(year as i32, self.options));
+ }
+
+ if !self.options.bynweekday.is_empty()
+ && ((self.monthinfo.is_none() || month != self.monthinfo.as_ref().unwrap().lastmonth)
+ || (self.monthinfo.is_none() || year != self.monthinfo.as_ref().unwrap().lastyear))
+ {
+ if let Some(yearinfo) = &self.yearinfo {
+ self.monthinfo = Some(rebuild_month(
+ year,
+ month,
+ yearinfo.yearlen,
+ &yearinfo.mrange,
+ &yearinfo.wdaymask,
+ self.options,
+ ));
+ }
+ }
+
+ if let Some(byeaster) = self.options.byeaster {
+ self.eastermask = Some(easter(year, byeaster));
+ }
+ }
+
+ pub fn lastyear(&self) -> Option<isize> {
+ match &self.monthinfo {
+ Some(info) => Some(info.lastyear),
+ None => None,
+ }
+ }
+ pub fn lastmonth(&self) -> Option<usize> {
+ match &self.monthinfo {
+ Some(info) => Some(info.lastmonth),
+ None => None,
+ }
+ }
+ pub fn yearlen(&self) -> Option<usize> {
+ match &self.yearinfo {
+ Some(info) => Some(info.yearlen),
+ None => None,
+ }
+ }
+ pub fn yearordinal(&self) -> Option<isize> {
+ match &self.yearinfo {
+ Some(info) => Some(info.yearordinal),
+ None => None,
+ }
+ }
+ pub fn mrange(&self) -> Option<&Vec<usize>> {
+ match &self.yearinfo {
+ Some(info) => Some(&info.mrange),
+ None => None,
+ }
+ }
+
+ pub fn eastermask(&self) -> Option<&Vec<isize>> {
+ match &self.eastermask {
+ Some(mask) => Some(&mask),
+ None => None,
+ }
+ }
+ pub fn wdaymask(&self) -> Option<&Vec<usize>> {
+ match &self.yearinfo {
+ Some(info) => Some(&info.wdaymask),
+ None => None,
+ }
+ }
+
+ pub fn mmask(&self) -> Option<&Vec<usize>> {
+ match &self.yearinfo {
+ Some(info) => Some(&info.mmask),
+ None => None,
+ }
+ }
+
+ pub fn wnomask(&self) -> Option<&Vec<usize>> {
+ match &self.yearinfo {
+ Some(info) => match &info.wnomask {
+ Some(mask) => Some(mask),
+ None => None,
+ },
+ None => None,
+ }
+ }
+ pub fn nwdaymask(&self) -> Option<&Vec<isize>> {
+ match &self.monthinfo {
+ Some(info) => Some(&info.nwdaymask),
+ None => None,
+ }
+ }
+ pub fn nextyearlen(&self) -> Option<usize> {
+ match &self.yearinfo {
+ Some(info) => Some(info.nextyearlen),
+ None => None,
+ }
+ }
+ pub fn mdaymask(&self) -> Option<&Vec<isize>> {
+ match &self.yearinfo {
+ Some(info) => Some(&info.mdaymask),
+ None => None,
+ }
+ }
+ pub fn nmdaymask(&self) -> Option<&Vec<isize>> {
+ match &self.yearinfo {
+ Some(info) => Some(&info.nmdaymask),
+ None => None,
+ }
+ }
+
+ pub fn ydayset(&self) -> (Vec<usize>, usize, usize) {
+ let yearlen = self.yearlen().unwrap();
+ let mut v = Vec::with_capacity(yearlen);
+ for i in 0..yearlen {
+ v.push(i);
+ }
+ (v, 0, yearlen)
+ }
+
+ pub fn mdayset(&self, month: usize) -> (Vec<usize>, usize, usize) {
+ let mrange = self.mrange().unwrap();
+ let start = mrange[month - 1];
+ let end = mrange[month];
+ let mut set = vec![0; self.yearlen().unwrap()];
+ for i in start..end {
+ set[i] = i;
+ }
+ (set, start, end)
+ }
+
+ pub fn wdayset(&self, year: isize, month: usize, day: usize) -> (Vec<usize>, usize, usize) {
+ let set_len = self.yearlen().unwrap() + 7;
+ let mut set = vec![0; set_len];
+
+ let mut i = (to_ordinal(
+ &Utc.ymd(year as i32, month as u32, day as u32)
+ .and_hms(0, 0, 0),
+ ) - self.yearordinal().unwrap()) as usize;
+
+ let start = i;
+ for _ in 0..7 {
+ if i >= set_len {
+ break;
+ }
+ set[i] = i;
+ i += 1;
+ if self.wdaymask().unwrap()[i] == self.options.wkst {
+ break;
+ }
+ }
+ (set, start, i)
+ }
+
+ pub fn ddayset(&self, year: isize, month: usize, day: usize) -> (Vec<usize>, usize, usize) {
+ let mut set = vec![0; self.yearlen().unwrap()];
+
+ let i = (to_ordinal(
+ &Utc.ymd(year as i32, month as u32, day as u32)
+ .and_hms(0, 0, 0),
+ ) - self.yearordinal().unwrap()) as usize;
+
+ set[i] = i;
+ (set, i, i + 1)
+ }
+
+ pub fn htimeset(&self, hour: usize, _: usize, second: usize, millisecond: usize) -> Vec<Time> {
+ let mut set = self
+ .options
+ .byminute
+ .iter()
+ .map(|minute| self.mtimeset(hour, *minute, second, millisecond))
+ .flatten()
+ .collect::<Vec<Time>>();
+ set.sort_by_key(|a| a.time());
+ set
+ }
+
+ pub fn mtimeset(&self, hour: usize, minute: usize, _: usize, millisecond: usize) -> Vec<Time> {
+ let mut set = self
+ .options
+ .bysecond
+ .iter()
+ .map(|second| Time::new(hour, minute, *second, millisecond))
+ .collect::<Vec<Time>>();
+ set.sort_by_key(|a| a.time());
+ set
+ }
+
+ pub fn stimeset(
+ &self,
+ hour: usize,
+ minute: usize,
+ second: usize,
+ millisecond: usize,
+ ) -> Vec<Time> {
+ vec![Time::new(hour, minute, second, millisecond)]
+ }
+
+ pub fn getdayset(
+ &self,
+ freq: &Frequenzy,
+ year: isize,
+ month: usize,
+ day: usize,
+ ) -> (Vec<usize>, usize, usize) {
+ match freq {
+ Frequenzy::Yearly => self.ydayset(),
+ Frequenzy::Monthly => self.mdayset(month),
+ Frequenzy::Weekly => self.wdayset(year, month, day),
+ Frequenzy::Daily => self.ddayset(year, month, day),
+ _ => self.ddayset(year, month, day),
+ }
+ }
+
+ pub fn gettimeset(
+ &self,
+ freq: &Frequenzy,
+ hour: usize,
+ minute: usize,
+ second: usize,
+ millisecond: usize,
+ ) -> Vec<Time> {
+ match freq {
+ Frequenzy::Hourly => self.htimeset(hour, minute, second, millisecond),
+ Frequenzy::Minutely => self.mtimeset(hour, minute, second, millisecond),
+ Frequenzy::Secondly => self.stimeset(hour, minute, second, millisecond),
+ _ => panic!("Invalid freq"),
+ }
+ }
+}
diff --git a/src/iter/mod.rs b/src/iter/mod.rs
new file mode 100644
index 0000000..d426a98
--- /dev/null
+++ b/src/iter/mod.rs
@@ -0,0 +1,339 @@
+mod yearinfo;
+use yearinfo::get_weekday_val;
+
+mod monthinfo;
+
+mod iterinfo;
+use iterinfo::IterInfo;
+
+mod poslist;
+use poslist::build_poslist;
+
+use crate::options::*;
+use crate::datetime::{Time, from_ordinal};
+use chrono::offset::TimeZone;
+use chrono::prelude::*;
+use chrono::{DateTime, Duration};
+use chrono_tz::*;
+
+pub trait IterResult {
+ fn accept(&mut self, date: DateTime<Tz>) -> bool;
+ fn get_value(&self) -> Vec<DateTime<Tz>>;
+}
+
+pub fn iter<T: IterResult>(
+ iter_result: &mut T,
+ options: &mut ParsedOptions,
+) -> Vec<DateTime<Tz>> {
+
+ if (options.count.is_some() && options.count.unwrap() == 0) || options.interval == 0 {
+ return iter_result.get_value();
+ }
+
+ let mut counter_date = options.dtstart;
+ let mut ii = IterInfo::new(options);
+ ii.rebuild(counter_date.year() as isize, counter_date.month() as usize);
+
+ let mut timeset = make_timeset(&ii, &counter_date, options);
+ let mut count = match options.count {
+ Some(count) => count,
+ _ => 0,
+ };
+
+ loop {
+ let (dayset, start, end) = ii.getdayset(
+ &options.freq,
+ counter_date.year() as isize,
+ counter_date.month() as usize,
+ counter_date.day() as usize,
+ );
+
+ let mut dayset = dayset
+ .into_iter()
+ .map(|s| Some(s as isize))
+ .collect::<Vec<Option<isize>>>();
+
+ let filtered = remove_filtered_days(&mut dayset, start, end, &ii, options);
+
+ if not_empty(&options.bysetpos) {
+ let poslist = build_poslist(&options.bysetpos, &timeset, start, end, &ii, &dayset, &options.tzid);
+
+ for j in 0..poslist.len() {
+ let res = poslist[j];
+ if options.until.is_some() && res > options.until.unwrap() {
+ return iter_result.get_value();
+ }
+
+ if res >= options.dtstart {
+ //let rezoned_date = rezone_if_needed(&res, &options);
+ // let rezoned_date = UTC.timestamp(res.timestamp(), 0);
+
+ if !iter_result.accept(res) {
+ return iter_result.get_value();
+ }
+
+ if count > 0 {
+ count -= 1;
+ if count == 0 {
+ return iter_result.get_value();
+ }
+ }
+ }
+ }
+ } else {
+ for j in start..end {
+ let current_day = dayset[j];
+ if current_day.is_none() {
+ continue;
+ }
+
+ let current_day = current_day.unwrap();
+ let date = from_ordinal(ii.yearordinal().unwrap() + current_day);
+ for k in 0..timeset.len() {
+ let res = options.tzid.ymd(date.year(), date.month(), date.day()).and_hms(
+ timeset[k].hour as u32,
+ timeset[k].minute as u32,
+ timeset[k].second as u32,
+ );
+ if options.until.is_some() && res > options.until.unwrap() {
+ return iter_result.get_value();
+ }
+ if res >= options.dtstart {
+ //let rezoned_date = rezone_if_needed(&res, &options);
+ // let rezoned_date = UTC.timestamp(res.timestamp(), 0);
+ // let rezoned_date = tzid.from_utc_datetime(&res.naive_utc());
+ // let rezoned_date = res.with_timezone(&options.tzid);
+
+ if !iter_result.accept(res) {
+ return iter_result.get_value();
+ }
+ if count > 0 {
+ count -= 1;
+ if count == 0 {
+ return iter_result.get_value();
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if options.interval == 0 {
+ return iter_result.get_value();
+ }
+
+ // Handle frequency and interval
+ counter_date = increment_counter_date(counter_date, options, filtered);
+
+ if counter_date.year() > 2200 {
+ return iter_result.get_value();
+ }
+
+ if options.freq == Frequenzy::Hourly
+ || options.freq == Frequenzy::Minutely
+ || options.freq == Frequenzy::Secondly
+ {
+ timeset = ii.gettimeset(
+ &options.freq,
+ counter_date.hour() as usize,
+ counter_date.minute() as usize,
+ counter_date.second() as usize,
+ 0,
+ );
+ }
+
+ ii.rebuild(counter_date.year() as isize, counter_date.month() as usize);
+ }
+}
+
+
+pub fn increment_counter_date(
+ counter_date: DateTime<Utc>,
+ options: &ParsedOptions,
+ filtered: bool,
+) -> DateTime<Utc> {
+ match options.freq {
+ Frequenzy::Yearly => counter_date
+ .with_year(counter_date.year() + options.interval as i32)
+ .unwrap(),
+ Frequenzy::Monthly => {
+ let new_month = counter_date.month() + options.interval as u32;
+ if new_month > 12 {
+ let mut year_div = new_month / 12;
+ let mut new_month = new_month % 12;
+ if new_month == 0 {
+ new_month = 12;
+ year_div -= 1;
+ }
+ let new_year = counter_date.year() + year_div as i32;
+ counter_date
+ .with_month(new_month)
+ .unwrap()
+ .with_year(new_year)
+ .unwrap()
+ } else {
+ counter_date.with_month(new_month).unwrap()
+ }
+ }
+ Frequenzy::Weekly => {
+ let mut day_delta = 0;
+ let weekday = get_weekday_val(&counter_date.weekday());
+ if options.wkst > weekday {
+ day_delta += -((weekday + 1 + (6 - options.wkst)) as isize)
+ + (options.interval as isize) * 7;
+ } else {
+ day_delta += -((weekday - options.wkst) as isize) + (options.interval as isize) * 7;
+ }
+ counter_date + Duration::days(day_delta as i64)
+ }
+ Frequenzy::Daily => counter_date + Duration::days(options.interval as i64),
+ Frequenzy::Hourly => {
+ let mut new_hours = counter_date.hour() as usize;
+ if filtered {
+ new_hours += ((23 - new_hours) as f32 / options.interval as f32).floor() as usize
+ * options.interval;
+ }
+
+ loop {
+ new_hours += options.interval;
+ if options.byhour.is_empty()
+ || options
+ .byhour
+ .iter()
+ .any(|bh| *bh == (new_hours % 24) as usize)
+ {
+ break;
+ }
+ }
+ counter_date.with_hour(0).unwrap() + Duration::hours(new_hours as i64)
+ }
+ Frequenzy::Minutely => counter_date + Duration::minutes(options.interval as i64),
+ Frequenzy::Secondly => counter_date + Duration::seconds(options.interval as i64),
+ }
+}
+
+pub fn includes<T>(v: &Vec<T>, el: &T) -> bool
+where
+ T: PartialEq,
+{
+ v.iter().any(|ve| ve == el)
+}
+
+pub fn not_empty<T>(v: &Vec<T>) -> bool {
+ !v.is_empty()
+}
+
+pub fn is_filtered(ii: &IterInfo, current_day: usize, options: &ParsedOptions) -> bool {
+ return (not_empty(&options.bymonth)
+ && !includes(&options.bymonth, &ii.mmask().unwrap()[current_day]))
+ || (not_empty(&options.byweekno) && (ii.wnomask().unwrap()[current_day]) == 0)
+ || (not_empty(&options.byweekday)
+ && !includes(&options.byweekday, &ii.wdaymask().unwrap()[current_day]))
+ || (ii.nwdaymask().is_some()
+ && not_empty(ii.nwdaymask().unwrap())
+ && (ii.nwdaymask().unwrap()[current_day]) == 0)
+ || (options.byeaster.is_some()
+ && !(includes(ii.eastermask().unwrap(), &(current_day as isize))))
+ || ((not_empty(&options.bymonthday) || not_empty(&options.bynmonthday))
+ && !includes(&options.bymonthday, &ii.mdaymask().unwrap()[current_day])
+ && !includes(&options.bynmonthday, &ii.nmdaymask().unwrap()[current_day]))
+ || (not_empty(&options.byyearday)
+ && ((current_day < ii.yearlen().unwrap()
+ && !includes(&options.byyearday, &(current_day as isize + 1))
+ && !includes(
+ &options.byyearday.iter().map(|v| *v as isize).collect(),
+ &(-(ii.yearlen().unwrap() as isize) + current_day as isize),
+ ))
+ || (current_day >= ii.yearlen().unwrap()
+ && !includes(
+ &options.byyearday,
+ &((current_day + 1 - ii.yearlen().unwrap()) as isize),
+ )
+ && !includes(
+ &options.byyearday.iter().map(|v| *v as isize).collect(),
+ &(-(ii.nextyearlen().unwrap() as isize) + current_day as isize
+ - ii.yearlen().unwrap() as isize),
+ ))));
+}
+
+pub fn remove_filtered_days(
+ dayset: &mut Vec<Option<isize>>,
+ start: usize,
+ end: usize,
+ ii: &IterInfo,
+ options: &ParsedOptions,
+) -> bool {
+ let mut filtered = false;
+
+ for daycounter in start..end {
+ let current_day = dayset[daycounter];
+ if current_day.is_none() {
+ continue;
+ }
+
+ filtered = is_filtered(ii, current_day.unwrap() as usize, options);
+ if filtered {
+ dayset[daycounter] = None;
+ }
+ }
+ filtered
+}
+
+pub fn build_timeset(options: &ParsedOptions) -> Vec<Time> {
+ let millisecond_mod = (options.dtstart.timestamp_millis() & 1000) as usize;
+
+ if options.freq > Frequenzy::Daily {
+ return vec![];
+ }
+
+ let mut timeset = vec![];
+ for hour in &options.byhour {
+ for minute in &options.byminute {
+ for second in &options.bysecond {
+ timeset.push(Time::new(*hour, *minute, *second, millisecond_mod));
+ }
+ }
+ }
+
+ timeset
+}
+
+pub fn make_timeset(
+ ii: &IterInfo,
+ counter_date: &DateTime<Utc>,
+ options: &ParsedOptions,
+) -> Vec<Time> {
+ if options.freq < Frequenzy::Hourly {
+ return build_timeset(options);
+ }
+
+ if (options.freq >= Frequenzy::Hourly
+ && !options.byhour.is_empty()
+ && !options
+ .byhour
+ .iter()
+ .any(|&h| h == counter_date.hour() as usize))
+ || (options.freq >= Frequenzy::Minutely
+ && !options.byminute.is_empty()
+ && !options
+ .byminute
+ .iter()
+ .any(|&m| m == counter_date.minute() as usize))
+ || (options.freq >= Frequenzy::Secondly
+ && !options.bysecond.is_empty()
+ && !options
+ .bysecond
+ .iter()
+ .any(|&s| s == counter_date.second() as usize))
+ {
+ return vec![];
+ }
+
+ ii.gettimeset(
+ &options.freq,
+ counter_date.hour() as usize,
+ counter_date.minute() as usize,
+ counter_date.second() as usize,
+ counter_date.timestamp_subsec_millis() as usize,
+ )
+}
diff --git a/src/iter/monthinfo.rs b/src/iter/monthinfo.rs
new file mode 100644
index 0000000..c89bfc3
--- /dev/null
+++ b/src/iter/monthinfo.rs
@@ -0,0 +1,70 @@
+use crate::options::*;
+use crate::iter::yearinfo::pymod;
+
+#[derive(Debug)]
+pub struct MonthInfo {
+ pub lastyear: isize,
+ pub lastmonth: usize,
+ pub nwdaymask: Vec<isize>,
+}
+
+pub fn rebuild_month(
+ year: isize,
+ month: usize,
+ yearlen: usize,
+ mrange: &Vec<usize>,
+ wdaymask: &Vec<usize>,
+ options: &ParsedOptions,
+) -> MonthInfo {
+ let mut result = MonthInfo {
+ lastyear: year,
+ lastmonth: month,
+ nwdaymask: vec![],
+ };
+
+ let mut ranges: Vec<(isize, isize)> = vec![];
+ if options.freq == Frequenzy::Yearly {
+ if options.bymonth.is_empty() {
+ ranges = vec![(0, yearlen as isize)];
+ } else {
+ for j in 0..options.bymonth.len() {
+ let m = options.bymonth[j];
+ ranges.push((mrange[m - 1] as isize, mrange[m] as isize))
+ }
+ }
+ } else if options.freq == Frequenzy::Monthly {
+ ranges.push((mrange[month - 1] as isize, mrange[month] as isize));
+ }
+
+ if ranges.is_empty() {
+ return result;
+ }
+
+ // Weekly frequency won't get here, so we may not
+ // care about cross-year weekly periods.
+ result.nwdaymask = vec![0; yearlen];
+
+ for j in 0..ranges.len() {
+ let rang = ranges[j];
+ let first = rang.0;
+ let last = rang.1 - 1;
+
+ for k in 0..options.bynweekday.len() {
+ let mut i: isize;
+ let wday = options.bynweekday[k][0];
+ let n = options.bynweekday[k][1];
+ if n < 0 {
+ i = last + (n + 1) * 7;
+ i -= pymod(wdaymask[i as usize] as isize - wday, 7);
+ } else {
+ i = first + (n - 1) * 7;
+ i += pymod(7 - wdaymask[i as usize] as isize + wday, 7);
+ }
+ if first <= i && i <= last {
+ result.nwdaymask[i as usize] = 1;
+ }
+ }
+ }
+
+ result
+}
diff --git a/src/iter/poslist.rs b/src/iter/poslist.rs
new file mode 100644
index 0000000..fa9fbd8
--- /dev/null
+++ b/src/iter/poslist.rs
@@ -0,0 +1,63 @@
+use crate::datetime::*;
+use crate::iter::iterinfo::IterInfo;
+use crate::iter::yearinfo::pymod;
+use crate::datetime::from_ordinal;
+use chrono::prelude::*;
+use chrono_tz::Tz;
+
+pub fn build_poslist(
+ bysetpost: &Vec<isize>,
+ timeset: &Vec<Time>,
+ start: usize,
+ end: usize,
+ ii: &IterInfo,
+ dayset: &Vec<Option<isize>>,
+ tz: &Tz
+) -> Vec<DateTime<Tz>> {
+ let mut poslist = vec![];
+
+ for j in 0..bysetpost.len() {
+ let daypos;
+ let timepos;
+ let pos = bysetpost[j];
+ if pos < 0 {
+ daypos = (pos as f32 / timeset.len() as f32).floor() as isize;
+ timepos = pymod(pos as isize, timeset.len() as isize);
+ } else {
+ daypos = ((pos - 1) as f32 / timeset.len() as f32) as isize;
+ timepos = pymod(pos as isize - 1, timeset.len() as isize);
+ }
+
+ let mut tmp = vec![];
+ for k in start..end {
+ let val = dayset[k];
+ if val.is_some() {
+ tmp.push(val.unwrap());
+ }
+ }
+
+ let i;
+ if daypos < 0 {
+ let index = tmp.len() as isize + daypos;
+ i = &tmp[index as usize];
+ } else {
+ i = &tmp[daypos as usize];
+ }
+
+ let date = from_ordinal(ii.yearordinal().unwrap() + i);
+ let res = tz.ymd(date.year(), date.month(), date.day()).and_hms(
+ timeset[timepos as usize].hour as u32,
+ timeset[timepos as usize].minute as u32,
+ timeset[timepos as usize].second as u32,
+ );
+ // XXX: can this ever be in the array?
+ // - compare the actual date instead?
+ if !poslist.iter().any(|&p| p == res) {
+ poslist.push(res);
+ }
+ }
+
+ poslist.sort_by_key(|a| a.timestamp());
+
+ poslist
+}
diff --git a/src/iter/yearinfo.rs b/src/iter/yearinfo.rs
new file mode 100644
index 0000000..8a944f7
--- /dev/null
+++ b/src/iter/yearinfo.rs
@@ -0,0 +1,217 @@
+use crate::masks::MASKS;
+use crate::options::*;
+use chrono::prelude::*;
+use crate::datetime::to_ordinal;
+
+
+#[derive(Debug)]
+pub struct YearInfo {
+ pub yearlen: usize,
+ pub nextyearlen: usize,
+ pub yearordinal: isize,
+ pub yearweekday: usize,
+ pub mmask: Vec<usize>,
+ pub mrange: Vec<usize>,
+ pub mdaymask: Vec<isize>,
+ pub nmdaymask: Vec<isize>,
+ pub wdaymask: Vec<usize>,
+ pub wnomask: Option<Vec<usize>>,
+}
+
+fn is_leap_year(year: i32) -> bool {
+ year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)
+}
+
+fn get_year_len(year: i32) -> usize {
+ if is_leap_year(year) {
+ return 366;
+ }
+ 365
+}
+
+pub fn get_weekday_val(wk: &Weekday) -> usize {
+ match wk {
+ Weekday::Mon => 0,
+ Weekday::Tue => 1,
+ Weekday::Wed => 2,
+ Weekday::Thu => 3,
+ Weekday::Fri => 4,
+ Weekday::Sat => 5,
+ Weekday::Sun => 6,
+ }
+}
+
+pub struct BaseMasks {
+ mmask: Vec<usize>,
+ mdaymask: Vec<isize>,
+ nmdaymask: Vec<isize>,
+ wdaymask: Vec<usize>,
+ mrange: Vec<usize>,
+}
+
+fn base_year_masks(year: i32) -> BaseMasks {
+ let masks = MASKS.clone();
+ let firstyday = Utc.ymd(year, 1, 1).and_hms_milli(0, 0, 0, 0);
+ let yearlen = get_year_len(year);
+ let wday = get_weekday_val(&firstyday.weekday()) as usize;
+
+ if yearlen == 365 {
+ return BaseMasks {
+ mmask: masks.m365,
+ mdaymask: masks.mday365,
+ nmdaymask: masks.nmday365,
+ mrange: masks.m365range,
+ wdaymask: Vec::from(&masks.wday[wday..]),
+ };
+ }
+
+ BaseMasks {
+ mmask: masks.m366,
+ mdaymask: masks.mday366,
+ nmdaymask: masks.nmday366,
+ mrange: masks.m366range,
+ wdaymask: Vec::from(&masks.wday[wday..]),
+ }
+}
+
+pub fn pymod(a: isize, b: isize) -> isize {
+ let r = a % b;
+ // If r and b differ in sign, add b to wrap the result to the correct sign.
+ if (r > 0 && b < 0) || (r < 0 && b > 0) {
+ return r + b;
+ }
+ r
+}
+
+pub fn rebuild_year(year: i32, options: &ParsedOptions) -> YearInfo {
+ let firstyday = Utc.ymd(year, 1, 1).and_hms_milli(0, 0, 0, 0);
+
+ let yearlen = get_year_len(year);
+ let nextyearlen = get_year_len(year + 1);
+ let yearordinal = to_ordinal(&firstyday);
+ let yearweekday = get_weekday_val(&firstyday.weekday());
+
+ let base_masks = base_year_masks(year);
+
+ let mut result = YearInfo {
+ yearlen,
+ nextyearlen,
+ yearordinal,
+ yearweekday,
+ wnomask: None,
+ mmask: base_masks.mmask,
+ mdaymask: base_masks.mdaymask,
+ nmdaymask: base_masks.nmdaymask,
+ mrange: base_masks.mrange,
+ wdaymask: base_masks.wdaymask,
+ };
+
+ if options.byweekno.is_empty() {
+ return result;
+ }
+
+ let mut wnomask = vec![0; yearlen + 7];
+ let wyearlen;
+ let mut no1wkst = pymod((7 - yearweekday + options.wkst) as isize, 7);
+ let firstwkst = no1wkst;
+ if no1wkst >= 4 {
+ no1wkst = 0;
+ // Number of days in the year, plus the days we got
+ // from last year.
+ wyearlen = result.yearlen as isize + pymod(yearweekday as isize - options.wkst as isize, 7);
+ } else {
+ // Number of days in the year, minus the days we
+ // left in last year.
+ wyearlen = yearlen as isize - no1wkst;
+ }
+
+ let div = (wyearlen as f32 / 7.).floor() as isize;
+ let year_mod = pymod(wyearlen, 7);
+ //const numweeks = Math.floor(div + mod / 4)
+ let numweeks = div + (year_mod / 4);
+
+ for j in 0..options.byweekno.len() {
+ let mut n = options.byweekno[j];
+ if n < 0 {
+ n += (numweeks + 1) as isize;
+ }
+ if !(n > 0 && n <= numweeks) {
+ continue;
+ }
+ let mut i;
+ if n > 1 {
+ i = no1wkst + (n - 1) * 7;
+ if no1wkst != firstwkst {
+ i -= 7 - firstwkst;
+ }
+ } else {
+ i = no1wkst;
+ }
+
+ for _ in 0..7 {
+ wnomask[i as usize] = 1;
+ i += 1;
+ if result.wdaymask[i as usize] as usize == options.wkst {
+ break;
+ }
+ }
+ }
+
+ if options.byweekno.iter().any(|&wkno| wkno == 1) {
+ // Check week number 1 of next year as well
+ // orig-TODO : Check -numweeks for next year.
+ let mut i = no1wkst + numweeks * 7;
+ if no1wkst != firstwkst {
+ i -= 7 - firstwkst;
+ }
+ if i < yearlen as isize {
+ // If week starts in next year, we
+ // don't care about it.
+ for _ in 0..7 {
+ wnomask[i as usize] = 1;
+ i += 1;
+ if result.wdaymask[i as usize] as usize == options.wkst {
+ break;
+ }
+ }
+ }
+ }
+
+ if no1wkst > 0 {
+ // Check last week number of last year as
+ // well. If no1wkst is 0, either the year
+ // started on week start, or week number 1
+ // got days from last year, so there are no
+ // days from last year's last week number in
+ // this year.
+ let lnumweeks;
+ if !options.byweekno.iter().any(|&weekno| weekno == -1) {
+ let lyearweekday = get_weekday_val(&Utc.ymd(year - 1, 1, 1).weekday());
+
+ let lno1wkst = pymod((7 - lyearweekday + options.wkst) as isize, 7);
+
+ let lyearlen = get_year_len(year - 1);
+ let weekst;
+ if lno1wkst >= 4 {
+ //lno1wkst = 0;
+ weekst = lyearlen as isize + pymod((lyearweekday - options.wkst) as isize, 7);
+ } else {
+ weekst = yearlen as isize - no1wkst;
+ }
+
+ lnumweeks = 52 + (pymod(weekst, 7) / 4) as isize;
+ } else {
+ lnumweeks = -1 as isize;
+ }
+
+ if options.byweekno.iter().any(|&weekno| weekno == lnumweeks) {
+ for i in 0..no1wkst {
+ wnomask[i as usize] = 1;
+ }
+ }
+ }
+
+ result.wnomask = Some(wnomask);
+
+ result
+}