use super::ranged::{Days, Hours, Minutes, Months, Seconds, Weekdays, Years};
use crate::{DateTimeRange, Error, Pattern, private};
use core::ops::RangeBounds;
use jiff::ToSpan;
use jiff::civil::{DateTime, Weekday};
#[derive(Debug, Clone, Default)]
pub struct Cron {
years: Years,
months: Months,
weekdays: Weekdays,
days: Days,
hours: Hours,
minutes: Minutes,
seconds: Seconds,
}
impl Cron {
pub fn new() -> Cron {
Cron::default()
}
}
impl Cron {
#[must_use]
pub fn year(self, year: i16) -> Cron {
self.try_year(year)
.expect("value for year is out of bounds")
}
#[must_use]
pub fn year_step_by(self, start: i16, step: usize) -> Cron {
self.try_year_step_by(start, step)
.expect("value for year step is out of bounds")
}
#[must_use]
pub fn years<I: IntoIterator<Item = i16>>(self, years: I) -> Cron {
self.try_years(years)
.expect("value for years is out of bounds")
}
#[must_use]
pub fn month(self, month: i8) -> Cron {
self.try_month(month)
.expect("value for month is out of bounds")
}
#[must_use]
pub fn month_step_by(self, start: i8, step: usize) -> Cron {
self.try_month_step_by(start, step)
.expect("value for month step is out of bounds")
}
#[must_use]
pub fn months<I: IntoIterator<Item = i8>>(self, months: I) -> Cron {
self.try_months(months)
.expect("value for months is out of bounds")
}
#[must_use]
pub fn weekday(self, weekday: Weekday) -> Cron {
self.weekday_i8(weekday.to_monday_one_offset())
}
#[inline]
fn weekday_i8(mut self, weekday: i8) -> Cron {
self.weekdays
.try_insert(weekday)
.expect("weekday is out of bounds, please file a bug");
self
}
#[must_use]
pub fn weekday_step_by(self, start: Weekday, step: usize) -> Cron {
(start.to_monday_one_offset()..=Weekdays::MAX)
.step_by(step)
.fold(self, Cron::weekday_i8)
}
#[must_use]
pub fn weekdays<I: IntoIterator<Item = Weekday>>(self, weekdays: I) -> Cron {
weekdays.into_iter().fold(self, Cron::weekday)
}
#[must_use]
pub fn day(self, day: i8) -> Cron {
self.try_day(day).expect("value for day is out of bounds")
}
#[must_use]
pub fn day_step_by(self, start: i8, step: usize) -> Cron {
self.try_day_step_by(start, step)
.expect("value for day step is out of bounds")
}
#[must_use]
pub fn days<I: IntoIterator<Item = i8>>(self, days: I) -> Cron {
self.try_days(days)
.expect("value for days is out of bounds")
}
#[must_use]
pub fn hour(self, hour: i8) -> Cron {
self.try_hour(hour)
.expect("value for hour is out of bounds")
}
#[must_use]
pub fn hour_step_by(self, start: i8, step: usize) -> Cron {
self.try_hour_step_by(start, step)
.expect("value for hour step is out of bounds")
}
#[must_use]
pub fn hours<I: IntoIterator<Item = i8>>(self, hours: I) -> Cron {
self.try_hours(hours)
.expect("value for hours is out of bounds")
}
#[must_use]
pub fn minute(self, minute: i8) -> Cron {
self.try_minute(minute)
.expect("value for minute is out of bounds")
}
#[must_use]
pub fn minute_step_by(self, start: i8, step: usize) -> Cron {
self.try_minute_step_by(start, step)
.expect("value for minute step is out of bounds")
}
#[must_use]
pub fn minutes<I: IntoIterator<Item = i8>>(self, minutes: I) -> Cron {
self.try_minutes(minutes)
.expect("value for minutes is out of bounds")
}
#[must_use]
pub fn second(self, second: i8) -> Cron {
self.try_second(second)
.expect("value for second is out of bounds")
}
#[must_use]
pub fn second_step_by(self, start: i8, step: usize) -> Cron {
self.try_second_step_by(start, step)
.expect("value for seconds step is out of bounds")
}
#[must_use]
pub fn seconds<I: IntoIterator<Item = i8>>(self, seconds: I) -> Cron {
self.try_seconds(seconds)
.expect("value for seconds is out of bounds")
}
}
impl Cron {
pub fn try_year(mut self, year: i16) -> Result<Cron, Error> {
self.years.try_insert(year)?;
Ok(self)
}
pub fn try_year_step_by(self, start: i16, step: usize) -> Result<Cron, Error> {
(start..=Years::MAX)
.step_by(step)
.try_fold(self, Cron::try_year)
}
pub fn try_years<I: IntoIterator<Item = i16>>(self, years: I) -> Result<Cron, Error> {
years.into_iter().try_fold(self, Cron::try_year)
}
pub fn try_month(mut self, month: i8) -> Result<Cron, Error> {
self.months.try_insert(month)?;
Ok(self)
}
pub fn try_month_step_by(self, start: i8, step: usize) -> Result<Cron, Error> {
(start..=Months::MAX)
.step_by(step)
.try_fold(self, Cron::try_month)
}
pub fn try_months<I: IntoIterator<Item = i8>>(self, months: I) -> Result<Cron, Error> {
months.into_iter().try_fold(self, Cron::try_month)
}
pub fn try_day(mut self, day: i8) -> Result<Cron, Error> {
self.days.try_insert(day)?;
Ok(self)
}
pub fn try_day_step_by(self, start: i8, step: usize) -> Result<Cron, Error> {
(start..=Days::MAX)
.step_by(step)
.try_fold(self, Cron::try_day)
}
pub fn try_days<I: IntoIterator<Item = i8>>(self, days: I) -> Result<Cron, Error> {
days.into_iter().try_fold(self, Cron::try_day)
}
pub fn try_hour(mut self, hour: i8) -> Result<Cron, Error> {
self.hours.try_insert(hour)?;
Ok(self)
}
pub fn try_hour_step_by(self, start: i8, step: usize) -> Result<Cron, Error> {
(start..=Hours::MAX)
.step_by(step)
.try_fold(self, Cron::try_hour)
}
pub fn try_hours<I: IntoIterator<Item = i8>>(self, hours: I) -> Result<Cron, Error> {
hours.into_iter().try_fold(self, Cron::try_hour)
}
pub fn try_minute(mut self, minute: i8) -> Result<Cron, Error> {
self.minutes.try_insert(minute)?;
Ok(self)
}
pub fn try_minute_step_by(self, start: i8, step: usize) -> Result<Cron, Error> {
(start..=Minutes::MAX)
.step_by(step)
.try_fold(self, Cron::try_minute)
}
pub fn try_minutes<I: IntoIterator<Item = i8>>(self, minutes: I) -> Result<Cron, Error> {
minutes.into_iter().try_fold(self, Cron::try_minute)
}
pub fn try_second(mut self, second: i8) -> Result<Cron, Error> {
self.seconds.try_insert(second)?;
Ok(self)
}
pub fn try_second_step_by(self, start: i8, step: usize) -> Result<Cron, Error> {
(start..=Seconds::MAX)
.step_by(step)
.try_fold(self, Cron::try_second)
}
pub fn try_seconds<I: IntoIterator<Item = i8>>(self, seconds: I) -> Result<Cron, Error> {
seconds.into_iter().try_fold(self, Cron::try_second)
}
}
impl Cron {
fn next_after_or_current(&self, instant: DateTime, range: DateTimeRange) -> Option<DateTime> {
let mut clamp = DateTimeClamp::new(instant);
for year in self.years.range(clamp.year..=Years::MAX) {
if year > instant.year() {
clamp.months_to_min();
}
let month_start = clamp.month;
if !self.months.contains(month_start) {
clamp.months_to_min();
}
for month in self.months.range(month_start..=Months::MAX) {
let day_start = clamp.day;
if !self.days.contains(day_start) {
clamp.days_to_min();
}
let day_end = days_in_month(month, year);
let day_start = day_start.min(day_end);
'day_loop: for day in self.days.range(day_start..=day_end) {
let hour_start = clamp.hour;
if !self.hours.contains(clamp.hour) {
clamp.hours_to_min();
}
for hour in self.hours.range(hour_start..=Hours::MAX) {
let minute_start = clamp.minute;
if !self.minutes.contains(minute_start) {
clamp.minutes_to_min();
}
for minute in self.minutes.range(minute_start..=Minutes::MAX) {
let second_start = clamp.second;
if !self.seconds.contains(second_start) {
clamp.seconds_to_min();
}
for second in self.seconds.range(second_start..=Seconds::MAX) {
let Ok(date) =
DateTime::new(year, month, day, hour, minute, second, 0)
else {
continue;
};
if self
.weekdays
.contains(date.weekday().to_monday_one_offset())
{
if range.contains(&date) {
return Some(date);
}
return None;
}
continue 'day_loop;
}
clamp.minutes_to_min();
}
clamp.hours_to_min();
}
clamp.days_to_min();
}
clamp.months_to_min();
}
}
None
}
fn previous_before_or_current(
&self,
instant: DateTime,
range: DateTimeRange,
) -> Option<DateTime> {
let mut clamp = DateTimeClamp::new(instant);
for year in self.years.range(Years::MIN..=clamp.year).rev() {
let month_end = clamp.month;
if !self.months.contains(month_end) {
clamp.months_to_max();
}
for month in self.months.range(Months::MIN..=month_end).rev() {
let day_end = clamp.day;
if !self.days.contains(day_end) {
clamp.days_to_max();
}
let day_end = days_in_month(month, year).min(day_end);
'day_loop: for day in self.days.range(Days::MIN..=day_end).rev() {
let hour_end = clamp.hour;
if !self.hours.contains(clamp.hour) {
clamp.hours_to_max();
}
for hour in self.hours.range(Hours::MIN..=hour_end).rev() {
let minute_end = clamp.minute;
if !self.minutes.contains(minute_end) {
clamp.minutes_to_max();
}
for minute in self.minutes.range(Minutes::MIN..=minute_end).rev() {
let second_end = clamp.second;
if !self.seconds.contains(second_end) {
clamp.seconds_to_max();
}
for second in self.seconds.range(Seconds::MIN..=second_end).rev() {
let Ok(date) =
DateTime::new(year, month, day, hour, minute, second, 0)
else {
continue;
};
if self
.weekdays
.contains(date.weekday().to_monday_one_offset())
{
if range.contains(&date) {
return Some(date);
}
return None;
}
continue 'day_loop;
}
clamp.minutes_to_max();
}
clamp.hours_to_max();
}
clamp.days_to_max();
}
clamp.months_to_max();
}
}
None
}
}
impl Pattern for Cron {
fn next_after(&self, instant: DateTime, range: DateTimeRange) -> Option<DateTime> {
let instant = instant.checked_add(1.second()).ok()?;
self.next_after_or_current(instant, range)
}
fn previous_before(&self, instant: DateTime, range: DateTimeRange) -> Option<DateTime> {
let instant = if instant.subsec_nanosecond() > 0 {
instant
} else {
instant.checked_sub(1.second()).ok()?
};
self.previous_before_or_current(instant, range)
}
fn closest_to(&self, instant: DateTime, range: DateTimeRange) -> Option<DateTime> {
let instant = instant.max(range.start).min(range.end);
let Some(next) = self.next_after_or_current(instant, range) else {
return self.previous_before(instant, range);
};
if next == instant {
return Some(next);
}
let Some(previous) = self.previous_before(instant, range) else {
return Some(next);
};
if instant.duration_since(previous) >= instant.duration_until(next) {
Some(next)
} else {
Some(previous)
}
}
}
impl private::Sealed for Cron {}
struct DateTimeClamp {
year: i16,
month: i8,
day: i8,
hour: i8,
minute: i8,
second: i8,
}
impl DateTimeClamp {
fn new(date: DateTime) -> DateTimeClamp {
DateTimeClamp {
year: date.year(),
month: date.month(),
day: date.day(),
hour: date.hour(),
minute: date.minute(),
second: date.second(),
}
}
fn months_to_max(&mut self) {
self.month = Months::MAX;
self.days_to_max();
}
fn months_to_min(&mut self) {
self.month = Months::MIN;
self.days_to_min();
}
fn days_to_max(&mut self) {
self.day = Days::MAX;
self.hours_to_max();
}
fn days_to_min(&mut self) {
self.day = Days::MIN;
self.hours_to_min();
}
fn hours_to_max(&mut self) {
self.hour = Hours::MAX;
self.minutes_to_max();
}
fn hours_to_min(&mut self) {
self.hour = Hours::MIN;
self.minutes_to_min();
}
fn minutes_to_max(&mut self) {
self.minute = Minutes::MAX;
self.seconds_to_max();
}
fn minutes_to_min(&mut self) {
self.minute = Minutes::MIN;
self.seconds_to_min();
}
fn seconds_to_max(&mut self) {
self.second = Seconds::MAX;
}
fn seconds_to_min(&mut self) {
self.second = Seconds::MIN;
}
}
fn is_leap_year(year: i16) -> bool {
let by_four = year % 4 == 0;
let by_hundred = year % 100 == 0;
let by_four_hundred = year % 400 == 0;
by_four && ((!by_hundred) || by_four_hundred)
}
fn days_in_month(month: i8, year: i16) -> i8 {
match month {
9 | 4 | 6 | 11 => 30,
2 if is_leap_year(year) => 29,
2 => 28,
_ => 31,
}
}