pub mod ast;
pub(crate) mod cron;
pub(crate) mod display;
pub mod error;
pub(crate) mod eval;
pub(crate) mod lexer;
pub(crate) mod parser;
pub use ast::{Schedule, ScheduleExpr};
pub use error::ScheduleError;
pub use eval::{BoundedOccurrences, Occurrences};
use jiff::Zoned;
#[cfg(feature = "serde")]
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::str::FromStr;
impl Schedule {
pub fn parse(input: &str) -> Result<Self, ScheduleError> {
parser::parse(input)
}
pub fn next_from(&self, now: &Zoned) -> Result<Option<Zoned>, ScheduleError> {
eval::next_from(self, now)
}
pub fn next_n_from(&self, now: &Zoned, n: usize) -> Result<Vec<Zoned>, ScheduleError> {
eval::next_n_from(self, now, n)
}
pub fn previous_from(&self, now: &Zoned) -> Result<Option<Zoned>, ScheduleError> {
eval::previous_from(self, now)
}
pub fn matches(&self, datetime: &Zoned) -> Result<bool, ScheduleError> {
eval::matches(self, datetime)
}
pub fn with_anchor(mut self, date: jiff::civil::Date) -> Self {
self.anchor = Some(date);
self
}
pub fn validate(input: &str) -> bool {
Self::parse(input).is_ok()
}
pub fn from_cron(cron_expr: &str) -> Result<Self, ScheduleError> {
cron::from_cron(cron_expr)
}
pub fn explain_cron(cron_expr: &str) -> Result<String, ScheduleError> {
cron::explain_cron(cron_expr)
}
pub fn to_cron(&self) -> Result<String, ScheduleError> {
cron::to_cron(self)
}
pub fn timezone(&self) -> Option<&str> {
self.timezone.as_deref()
}
pub fn expr(&self) -> &ScheduleExpr {
&self.expr
}
pub fn except(&self) -> &[ast::Exception] {
&self.except
}
pub fn until(&self) -> Option<&ast::UntilSpec> {
self.until.as_ref()
}
pub fn anchor(&self) -> Option<jiff::civil::Date> {
self.anchor
}
pub fn during(&self) -> &[ast::MonthName] {
&self.during
}
pub fn with_timezone(mut self, tz: impl Into<String>) -> Self {
self.timezone = Some(tz.into());
self
}
pub fn with_except(mut self, exceptions: Vec<ast::Exception>) -> Self {
self.except = exceptions;
self
}
pub fn with_until(mut self, until: ast::UntilSpec) -> Self {
self.until = Some(until);
self
}
pub fn with_during(mut self, months: Vec<ast::MonthName>) -> Self {
self.during = months;
self
}
pub fn occurrences(&self, from: &Zoned) -> eval::Occurrences<'_> {
eval::Occurrences::new(self, from.clone())
}
pub fn between(&self, from: &Zoned, to: &Zoned) -> eval::BoundedOccurrences<'_> {
eval::between(self, from, to)
}
}
impl FromStr for Schedule {
type Err = ScheduleError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::parse(s)
}
}
#[cfg(feature = "serde")]
impl Serialize for Schedule {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
use serde::ser::SerializeMap;
let mut map = serializer.serialize_map(None)?;
match &self.expr {
ScheduleExpr::IntervalRepeat {
interval,
unit,
from,
to,
day_filter,
} => {
map.serialize_entry("kind", "every")?;
map.serialize_entry(
"interval",
&serde_json::json!({
"value": interval,
"unit": match unit {
ast::IntervalUnit::Minutes => "minutes",
ast::IntervalUnit::Hours => "hours",
}
}),
)?;
map.serialize_entry("from", from)?;
map.serialize_entry("to", to)?;
if let Some(df) = day_filter {
map.serialize_entry("days", &day_filter_to_json(df))?;
}
}
ScheduleExpr::DayRepeat {
interval,
days,
times,
} => {
map.serialize_entry("kind", "every")?;
if *interval > 1 {
map.serialize_entry(
"interval",
&serde_json::json!({
"value": interval,
"unit": "days"
}),
)?;
}
map.serialize_entry("days", &day_filter_to_json(days))?;
map.serialize_entry("times", times)?;
}
ScheduleExpr::WeekRepeat {
interval,
days,
times,
} => {
map.serialize_entry("kind", "every")?;
map.serialize_entry(
"interval",
&serde_json::json!({
"value": interval,
"unit": "weeks"
}),
)?;
map.serialize_entry("days", days)?;
map.serialize_entry("times", times)?;
}
ScheduleExpr::MonthRepeat {
interval,
target,
times,
} => {
map.serialize_entry("kind", "every")?;
map.serialize_entry("repeat", "monthly")?;
if *interval > 1 {
map.serialize_entry(
"interval",
&serde_json::json!({
"value": interval,
"unit": "months"
}),
)?;
}
map.serialize_entry("target", target)?;
map.serialize_entry("times", times)?;
}
ScheduleExpr::SingleDate { date, times } => {
map.serialize_entry("kind", "on")?;
match date {
ast::DateSpec::Iso(d) => map.serialize_entry("date", d)?,
ast::DateSpec::Named { month, day } => {
map.serialize_entry("date", &format!("{} {}", month.as_str(), day))?;
}
}
map.serialize_entry("times", times)?;
}
ScheduleExpr::YearRepeat {
interval,
target,
times,
} => {
map.serialize_entry("kind", "every")?;
map.serialize_entry("repeat", "yearly")?;
if *interval > 1 {
map.serialize_entry(
"interval",
&serde_json::json!({
"value": interval,
"unit": "years"
}),
)?;
}
map.serialize_entry("target", target)?;
map.serialize_entry("times", times)?;
}
}
map.serialize_entry("except", &self.except)?;
map.serialize_entry("until", &self.until)?;
map.serialize_entry("starting", &self.anchor.as_ref().map(|a| a.to_string()))?;
map.serialize_entry("during", &self.during)?;
map.serialize_entry("timezone", &self.timezone)?;
map.end()
}
}
#[cfg(feature = "serde")]
fn day_filter_to_json(filter: &ast::DayFilter) -> serde_json::Value {
match filter {
ast::DayFilter::Every => serde_json::json!([
"monday",
"tuesday",
"wednesday",
"thursday",
"friday",
"saturday",
"sunday"
]),
ast::DayFilter::Weekday => {
serde_json::json!(["monday", "tuesday", "wednesday", "thursday", "friday"])
}
ast::DayFilter::Weekend => serde_json::json!(["saturday", "sunday"]),
ast::DayFilter::Days(days) => {
serde_json::json!(days.iter().map(|d| d.as_str()).collect::<Vec<_>>())
}
}
}
#[cfg(feature = "serde")]
impl<'de> Deserialize<'de> for Schedule {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let s = String::deserialize(deserializer)?;
Schedule::parse(&s).map_err(serde::de::Error::custom)
}
}