use crate::config::ScheduleError;
use crate::schedule::{BusinessCalendar, HolidayCalendar, Schedule};
use crate::task::ScheduledTask;
use chrono::{DateTime, Datelike, Duration, Timelike, Utc};
#[cfg(feature = "cron")]
use chrono::{Offset, TimeZone};
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};
use std::sync::Arc;
#[derive(Debug, Clone, Default)]
pub struct ScheduleIndex {
by_type: HashMap<String, HashSet<String>>,
by_next_run: Vec<(String, DateTime<Utc>)>,
dirty: bool,
}
impl ScheduleIndex {
pub fn new() -> Self {
Self {
by_type: HashMap::new(),
by_next_run: Vec::new(),
dirty: false,
}
}
pub fn add_task(&mut self, task: &ScheduledTask) {
let type_key = Self::schedule_type_key(&task.schedule);
self.by_type
.entry(type_key)
.or_default()
.insert(task.name.clone());
if let Ok(next_run) = task.next_run_time() {
self.by_next_run.push((task.name.clone(), next_run));
self.dirty = true;
}
}
pub fn remove_task(&mut self, task: &ScheduledTask) {
let type_key = Self::schedule_type_key(&task.schedule);
if let Some(set) = self.by_type.get_mut(&type_key) {
set.remove(&task.name);
}
self.by_next_run.retain(|(name, _)| name != &task.name);
self.dirty = true;
}
pub fn update_task(&mut self, old_task: &ScheduledTask, new_task: &ScheduledTask) {
self.remove_task(old_task);
self.add_task(new_task);
}
pub fn rebuild(&mut self, tasks: &HashMap<String, ScheduledTask>) {
self.by_type.clear();
self.by_next_run.clear();
for task in tasks.values() {
if task.enabled {
self.add_task(task);
}
}
self.sort_by_next_run();
self.dirty = false;
}
fn sort_by_next_run(&mut self) {
if self.dirty {
self.by_next_run.sort_by_key(|(_, time)| *time);
self.dirty = false;
}
}
pub fn get_by_type(&self, schedule_type: &str) -> Option<&HashSet<String>> {
self.by_type.get(schedule_type)
}
pub fn get_next_due(&mut self, limit: usize, now: DateTime<Utc>) -> Vec<String> {
self.sort_by_next_run();
self.by_next_run
.iter()
.filter(|(_, time)| *time <= now)
.take(limit)
.map(|(name, _)| name.clone())
.collect()
}
pub fn earliest_next_run(&mut self) -> Option<DateTime<Utc>> {
self.sort_by_next_run();
self.by_next_run.first().map(|(_, time)| *time)
}
fn schedule_type_key(schedule: &Schedule) -> String {
match schedule {
Schedule::Interval { .. } => "interval".to_string(),
#[cfg(feature = "cron")]
Schedule::Crontab { .. } => "crontab".to_string(),
#[cfg(feature = "solar")]
Schedule::Solar { .. } => "solar".to_string(),
Schedule::OneTime { .. } => "onetime".to_string(),
}
}
pub fn is_dirty(&self) -> bool {
self.dirty
}
pub fn mark_dirty(&mut self) {
self.dirty = true;
}
pub fn count_by_type(&self, schedule_type: &str) -> usize {
self.by_type
.get(schedule_type)
.map(|set| set.len())
.unwrap_or(0)
}
pub fn total_count(&self) -> usize {
self.by_next_run.len()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BlackoutPeriod {
pub name: String,
pub start: DateTime<Utc>,
pub end: DateTime<Utc>,
#[serde(skip_serializing_if = "Option::is_none")]
pub recurring: Option<BlackoutRecurrence>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum BlackoutRecurrence {
Daily,
Weekly,
Monthly,
}
impl BlackoutPeriod {
pub fn new(name: impl Into<String>, start: DateTime<Utc>, end: DateTime<Utc>) -> Self {
Self {
name: name.into(),
start,
end,
recurring: None,
}
}
pub fn with_recurrence(mut self, recurrence: BlackoutRecurrence) -> Self {
self.recurring = Some(recurrence);
self
}
pub fn is_blackout(&self, time: DateTime<Utc>) -> bool {
if time >= self.start && time <= self.end {
return true;
}
if let Some(ref recurrence) = self.recurring {
match recurrence {
BlackoutRecurrence::Daily => {
let start_time = (self.start.hour(), self.start.minute());
let end_time = (self.end.hour(), self.end.minute());
let current_time = (time.hour(), time.minute());
current_time >= start_time && current_time <= end_time
}
BlackoutRecurrence::Weekly => {
if time.weekday() == self.start.weekday() {
let start_time = (self.start.hour(), self.start.minute());
let end_time = (self.end.hour(), self.end.minute());
let current_time = (time.hour(), time.minute());
current_time >= start_time && current_time <= end_time
} else {
false
}
}
BlackoutRecurrence::Monthly => {
if time.day() == self.start.day() {
let start_time = (self.start.hour(), self.start.minute());
let end_time = (self.end.hour(), self.end.minute());
let current_time = (time.hour(), time.minute());
current_time >= start_time && current_time <= end_time
} else {
false
}
}
}
} else {
false
}
}
pub fn next_available_time(&self, time: DateTime<Utc>) -> DateTime<Utc> {
if !self.is_blackout(time) {
return time;
}
let mut current = self.end;
if let Some(ref recurrence) = self.recurring {
while self.is_blackout(current) {
current = match recurrence {
BlackoutRecurrence::Daily => current + Duration::days(1),
BlackoutRecurrence::Weekly => current + Duration::weeks(1),
BlackoutRecurrence::Monthly => {
if current.month() == 12 {
current
.with_year(current.year() + 1)
.unwrap()
.with_month(1)
.unwrap()
} else {
current.with_month(current.month() + 1).unwrap()
}
}
};
}
}
current
}
}
#[derive(Debug, Clone, Default)]
pub struct CalendarWithBlackout {
pub holiday_calendar: HolidayCalendar,
pub business_calendar: Option<BusinessCalendar>,
pub blackout_periods: Vec<BlackoutPeriod>,
pub holidays_only: bool,
}
impl CalendarWithBlackout {
pub fn new() -> Self {
Self {
holiday_calendar: HolidayCalendar::new(),
business_calendar: None,
blackout_periods: Vec::new(),
holidays_only: false,
}
}
pub fn add_blackout(&mut self, blackout: BlackoutPeriod) {
self.blackout_periods.push(blackout);
}
pub fn set_holidays_only(&mut self, enabled: bool) {
self.holidays_only = enabled;
}
pub fn set_business_calendar(&mut self, calendar: BusinessCalendar) {
self.business_calendar = Some(calendar);
}
pub fn is_valid_time(&self, time: DateTime<Utc>) -> bool {
for blackout in &self.blackout_periods {
if blackout.is_blackout(time) {
return false;
}
}
if self.holidays_only && !self.holiday_calendar.is_holiday(&time) {
return false;
}
if let Some(ref business) = self.business_calendar {
if !business.is_business_time(&time) {
return false;
}
}
true
}
pub fn next_valid_time(&self, mut time: DateTime<Utc>) -> DateTime<Utc> {
for _ in 0..365 {
let mut in_blackout = false;
for blackout in &self.blackout_periods {
if blackout.is_blackout(time) {
time = blackout.next_available_time(time);
in_blackout = true;
break;
}
}
if in_blackout {
continue;
}
if self.holidays_only && !self.holiday_calendar.is_holiday(&time) {
time += Duration::days(1);
continue;
}
if let Some(ref business) = self.business_calendar {
if !business.is_business_time(&time) {
time = business.next_business_time(time);
continue;
}
}
return time;
}
time
}
}
#[derive(Debug, Clone)]
pub struct CompositeSchedule {
schedules: Vec<Schedule>,
mode: CompositeMode,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum CompositeMode {
And,
Or,
}
impl CompositeSchedule {
pub fn and(schedules: Vec<Schedule>) -> Self {
Self {
schedules,
mode: CompositeMode::And,
}
}
pub fn or(schedules: Vec<Schedule>) -> Self {
Self {
schedules,
mode: CompositeMode::Or,
}
}
pub fn next_run(
&self,
last_run: Option<DateTime<Utc>>,
) -> Result<DateTime<Utc>, ScheduleError> {
if self.schedules.is_empty() {
return Err(ScheduleError::Invalid(
"Composite schedule has no sub-schedules".to_string(),
));
}
let mut next_runs = Vec::new();
for schedule in &self.schedules {
match schedule.next_run(last_run) {
Ok(next) => next_runs.push(next),
Err(e) => {
if self.mode == CompositeMode::And {
return Err(e);
}
}
}
}
if next_runs.is_empty() {
return Err(ScheduleError::Invalid(
"No valid next run time from any sub-schedule".to_string(),
));
}
match self.mode {
CompositeMode::And => {
Ok(*next_runs.iter().max().unwrap())
}
CompositeMode::Or => {
Ok(*next_runs.iter().min().unwrap())
}
}
}
pub fn is_due(&self, last_run: Option<DateTime<Utc>>) -> Result<bool, ScheduleError> {
let next_run = self.next_run(last_run)?;
Ok(Utc::now() >= next_run)
}
pub fn schedule_count(&self) -> usize {
self.schedules.len()
}
pub fn mode(&self) -> CompositeMode {
self.mode
}
pub fn schedules(&self) -> &[Schedule] {
&self.schedules
}
}
impl std::fmt::Display for CompositeSchedule {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mode_str = match self.mode {
CompositeMode::And => "AND",
CompositeMode::Or => "OR",
};
write!(f, "Composite[{}](", mode_str)?;
for (i, schedule) in self.schedules.iter().enumerate() {
if i > 0 {
write!(f, " {} ", mode_str)?;
}
write!(f, "{}", schedule)?;
}
write!(f, ")")
}
}
type CustomScheduleFn =
Arc<dyn Fn(Option<DateTime<Utc>>) -> Result<DateTime<Utc>, ScheduleError> + Send + Sync>;
pub struct CustomSchedule {
pub name: String,
next_run_fn: CustomScheduleFn,
}
impl CustomSchedule {
pub fn new<F>(name: impl Into<String>, next_run_fn: F) -> Self
where
F: Fn(Option<DateTime<Utc>>) -> Result<DateTime<Utc>, ScheduleError>
+ Send
+ Sync
+ 'static,
{
Self {
name: name.into(),
next_run_fn: Arc::new(next_run_fn),
}
}
pub fn next_run(
&self,
last_run: Option<DateTime<Utc>>,
) -> Result<DateTime<Utc>, ScheduleError> {
(self.next_run_fn)(last_run)
}
pub fn is_due(&self, last_run: Option<DateTime<Utc>>) -> Result<bool, ScheduleError> {
let next_run = self.next_run(last_run)?;
Ok(Utc::now() >= next_run)
}
}
impl std::fmt::Debug for CustomSchedule {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CustomSchedule")
.field("name", &self.name)
.finish()
}
}
impl std::fmt::Display for CustomSchedule {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Custom[{}]", self.name)
}
}
pub struct TimezoneUtils;
impl TimezoneUtils {
#[cfg(feature = "cron")]
pub fn format_in_timezone(utc_time: DateTime<Utc>, timezone: &str) -> String {
use chrono_tz::Tz;
if let Ok(tz) = timezone.parse::<Tz>() {
let local_time = utc_time.with_timezone(&tz);
format!("{} {}", local_time.format("%Y-%m-%d %H:%M:%S"), tz.name())
} else {
format!(
"{} (invalid timezone: {})",
utc_time.format("%Y-%m-%d %H:%M:%S UTC"),
timezone
)
}
}
#[cfg(feature = "cron")]
pub fn current_time_in_zones(timezones: &[&str]) -> HashMap<String, String> {
let now = Utc::now();
timezones
.iter()
.map(|tz| {
let formatted = Self::format_in_timezone(now, tz);
(tz.to_string(), formatted)
})
.collect()
}
#[cfg(feature = "cron")]
pub fn time_until_next_occurrence(
target_hour: u32,
target_minute: u32,
timezone: &str,
) -> Result<chrono::Duration, String> {
use chrono_tz::Tz;
let tz: Tz = timezone
.parse()
.map_err(|_| format!("Invalid timezone: {}", timezone))?;
let now_utc = Utc::now();
let now_local = now_utc.with_timezone(&tz);
let target_today = now_local
.date_naive()
.and_hms_opt(target_hour, target_minute, 0)
.ok_or("Invalid time")?
.and_local_timezone(tz)
.single()
.ok_or("Ambiguous or invalid time due to DST")?;
let target_utc = target_today.with_timezone(&Utc);
let final_target = if target_utc <= now_utc {
let tomorrow = now_local.date_naive() + chrono::Days::new(1);
let target_tomorrow = tomorrow
.and_hms_opt(target_hour, target_minute, 0)
.ok_or("Invalid time")?
.and_local_timezone(tz)
.single()
.ok_or("Ambiguous or invalid time due to DST")?;
target_tomorrow.with_timezone(&Utc)
} else {
target_utc
};
Ok(final_target - now_utc)
}
#[cfg(feature = "cron")]
pub fn detect_system_timezone() -> String {
if let Ok(tz) = std::env::var("TZ") {
if Self::is_valid_timezone(&tz) {
return tz;
}
}
#[cfg(target_os = "linux")]
{
if let Ok(tz) = std::fs::read_to_string("/etc/timezone") {
let tz = tz.trim().to_string();
if Self::is_valid_timezone(&tz) {
return tz;
}
}
}
#[cfg(any(target_os = "linux", target_os = "macos"))]
{
if let Ok(link) = std::fs::read_link("/etc/localtime") {
if let Some(tz_path) = link.to_str() {
if let Some(tz_start) = tz_path.find("zoneinfo/") {
let tz = &tz_path[tz_start + 9..];
if Self::is_valid_timezone(tz) {
return tz.to_string();
}
}
}
}
}
"UTC".to_string()
}
#[cfg(feature = "cron")]
pub fn is_valid_timezone(timezone: &str) -> bool {
use chrono_tz::Tz;
timezone.parse::<Tz>().is_ok()
}
#[cfg(feature = "cron")]
pub fn list_all_timezones() -> Vec<String> {
use chrono_tz::TZ_VARIANTS;
TZ_VARIANTS.iter().map(|tz| tz.name().to_string()).collect()
}
#[cfg(feature = "cron")]
pub fn search_timezones(pattern: &str) -> Vec<String> {
let pattern_lower = pattern.to_lowercase();
Self::list_all_timezones()
.into_iter()
.filter(|tz| tz.to_lowercase().contains(&pattern_lower))
.collect()
}
#[cfg(feature = "cron")]
pub fn is_dst_active(timezone: &str, at_time: Option<DateTime<Utc>>) -> Result<bool, String> {
use chrono_tz::Tz;
let tz: Tz = timezone
.parse()
.map_err(|_| format!("Invalid timezone: {}", timezone))?;
let time = at_time.unwrap_or_else(Utc::now);
let local_time = time.with_timezone(&tz);
let offset = local_time.offset().fix();
let std_offset = tz.offset_from_utc_datetime(&local_time.naive_utc()).fix();
Ok(offset != std_offset)
}
#[cfg(feature = "cron")]
pub fn get_utc_offset(timezone: &str, at_time: Option<DateTime<Utc>>) -> Result<i32, String> {
use chrono_tz::Tz;
let tz: Tz = timezone
.parse()
.map_err(|_| format!("Invalid timezone: {}", timezone))?;
let time = at_time.unwrap_or_else(Utc::now);
let local_time = time.with_timezone(&tz);
Ok(local_time.offset().fix().local_minus_utc())
}
#[cfg(feature = "cron")]
pub fn get_timezone_info(
timezone: &str,
at_time: Option<DateTime<Utc>>,
) -> Result<TimezoneInfo, String> {
use chrono_tz::Tz;
let tz: Tz = timezone
.parse()
.map_err(|_| format!("Invalid timezone: {}", timezone))?;
let time = at_time.unwrap_or_else(Utc::now);
let local_time = time.with_timezone(&tz);
let offset_seconds = local_time.offset().fix().local_minus_utc();
let is_dst = Self::is_dst_active(timezone, Some(time))?;
Ok(TimezoneInfo {
name: timezone.to_string(),
utc_offset_seconds: offset_seconds,
utc_offset_hours: offset_seconds as f32 / 3600.0,
is_dst,
current_time: local_time.format("%Y-%m-%d %H:%M:%S %Z").to_string(),
abbreviation: format!("{}", local_time.format("%Z")),
})
}
#[cfg(feature = "cron")]
pub fn convert_between_timezones(
time: DateTime<Utc>,
from_tz: &str,
to_tz: &str,
) -> Result<String, String> {
use chrono_tz::Tz;
let _source_tz: Tz = from_tz
.parse()
.map_err(|_| format!("Invalid source timezone: {}", from_tz))?;
let target_tz: Tz = to_tz
.parse()
.map_err(|_| format!("Invalid target timezone: {}", to_tz))?;
let target_time = time.with_timezone(&target_tz);
Ok(format!(
"{} {}",
target_time.format("%Y-%m-%d %H:%M:%S"),
target_tz.name()
))
}
#[cfg(feature = "cron")]
pub fn get_common_timezone_abbreviations() -> HashMap<String, String> {
let mut abbrevs = HashMap::new();
abbrevs.insert("EST".to_string(), "America/New_York".to_string());
abbrevs.insert("EDT".to_string(), "America/New_York".to_string());
abbrevs.insert("CST".to_string(), "America/Chicago".to_string());
abbrevs.insert("CDT".to_string(), "America/Chicago".to_string());
abbrevs.insert("MST".to_string(), "America/Denver".to_string());
abbrevs.insert("MDT".to_string(), "America/Denver".to_string());
abbrevs.insert("PST".to_string(), "America/Los_Angeles".to_string());
abbrevs.insert("PDT".to_string(), "America/Los_Angeles".to_string());
abbrevs.insert("AKST".to_string(), "America/Anchorage".to_string());
abbrevs.insert("AKDT".to_string(), "America/Anchorage".to_string());
abbrevs.insert("HST".to_string(), "Pacific/Honolulu".to_string());
abbrevs.insert("GMT".to_string(), "Europe/London".to_string());
abbrevs.insert("BST".to_string(), "Europe/London".to_string());
abbrevs.insert("CET".to_string(), "Europe/Paris".to_string());
abbrevs.insert("CEST".to_string(), "Europe/Paris".to_string());
abbrevs.insert("EET".to_string(), "Europe/Athens".to_string());
abbrevs.insert("EEST".to_string(), "Europe/Athens".to_string());
abbrevs.insert("JST".to_string(), "Asia/Tokyo".to_string());
abbrevs.insert("KST".to_string(), "Asia/Seoul".to_string());
abbrevs.insert("CST_CHINA".to_string(), "Asia/Shanghai".to_string());
abbrevs.insert("IST".to_string(), "Asia/Kolkata".to_string());
abbrevs.insert("SGT".to_string(), "Asia/Singapore".to_string());
abbrevs.insert("HKT".to_string(), "Asia/Hong_Kong".to_string());
abbrevs.insert("AEST".to_string(), "Australia/Sydney".to_string());
abbrevs.insert("AEDT".to_string(), "Australia/Sydney".to_string());
abbrevs.insert("ACST".to_string(), "Australia/Adelaide".to_string());
abbrevs.insert("ACDT".to_string(), "Australia/Adelaide".to_string());
abbrevs.insert("AWST".to_string(), "Australia/Perth".to_string());
abbrevs
}
}
#[cfg(feature = "cron")]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TimezoneInfo {
pub name: String,
pub utc_offset_seconds: i32,
pub utc_offset_hours: f32,
pub is_dst: bool,
pub current_time: String,
pub abbreviation: String,
}
#[cfg(feature = "cron")]
impl std::fmt::Display for TimezoneInfo {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{} (UTC{:+.1}h, {}, DST: {}): {}",
self.name,
self.utc_offset_hours,
self.abbreviation,
if self.is_dst { "Yes" } else { "No" },
self.current_time
)
}
}
#[derive(Debug, Clone)]
pub struct ScheduleBuilder {
interval_seconds: Option<u64>,
#[cfg(feature = "cron")]
timezone: Option<String>,
#[cfg(feature = "cron")]
business_hours: bool,
#[cfg(feature = "cron")]
weekends: bool,
#[cfg(feature = "cron")]
weekdays: bool,
}
impl ScheduleBuilder {
pub fn new() -> Self {
Self {
interval_seconds: None,
#[cfg(feature = "cron")]
timezone: None,
#[cfg(feature = "cron")]
business_hours: false,
#[cfg(feature = "cron")]
weekends: false,
#[cfg(feature = "cron")]
weekdays: false,
}
}
pub fn every_n_seconds(mut self, seconds: u64) -> Self {
self.interval_seconds = Some(seconds);
self
}
pub fn every_n_minutes(mut self, minutes: u64) -> Self {
self.interval_seconds = Some(minutes * 60);
self
}
pub fn every_n_hours(mut self, hours: u64) -> Self {
self.interval_seconds = Some(hours * 3600);
self
}
pub fn every_n_days(mut self, days: u64) -> Self {
self.interval_seconds = Some(days * 86400);
self
}
#[cfg(feature = "cron")]
pub fn business_hours_only(mut self) -> Self {
self.business_hours = true;
self.weekdays = true;
self
}
#[cfg(feature = "cron")]
pub fn weekends_only(mut self) -> Self {
self.weekends = true;
self
}
#[cfg(feature = "cron")]
pub fn weekdays_only(mut self) -> Self {
self.weekdays = true;
self
}
#[cfg(feature = "cron")]
pub fn in_timezone(mut self, timezone: &str) -> Self {
self.timezone = Some(timezone.to_string());
self
}
pub fn build(self) -> Schedule {
#[cfg(feature = "cron")]
{
if self.business_hours || self.weekends || self.weekdays || self.timezone.is_some() {
let interval_minutes = self.interval_seconds.unwrap_or(3600) / 60;
let minute_expr = if interval_minutes < 60 {
format!("*/{}", interval_minutes)
} else {
"0".to_string()
};
let hour_expr = if self.business_hours {
"9-17".to_string()
} else {
"*".to_string()
};
let dow_expr = if self.weekends {
"0,6".to_string() } else if self.weekdays || self.business_hours {
"1-5".to_string() } else {
"*".to_string()
};
if let Some(tz) = self.timezone {
return Schedule::crontab_tz(
&minute_expr,
&hour_expr,
&dow_expr,
"*",
"*",
&tz,
);
} else {
return Schedule::crontab(&minute_expr, &hour_expr, &dow_expr, "*", "*");
}
}
}
Schedule::interval(self.interval_seconds.unwrap_or(3600))
}
}
impl Default for ScheduleBuilder {
fn default() -> Self {
Self::new()
}
}
pub struct ScheduleTemplates;
impl ScheduleTemplates {
pub fn every_minute() -> Schedule {
Schedule::interval(60)
}
pub fn every_5_minutes() -> Schedule {
Schedule::interval(300)
}
pub fn every_15_minutes() -> Schedule {
Schedule::interval(900)
}
pub fn every_30_minutes() -> Schedule {
Schedule::interval(1800)
}
pub fn hourly() -> Schedule {
Schedule::interval(3600)
}
#[cfg(feature = "cron")]
pub fn daily_at_midnight() -> Schedule {
Schedule::crontab("0", "0", "*", "*", "*")
}
#[cfg(feature = "cron")]
pub fn daily_at_hour(hour: u32) -> Schedule {
Schedule::crontab("0", &hour.to_string(), "*", "*", "*")
}
#[cfg(feature = "cron")]
pub fn weekdays_at(hour: u32, minute: u32) -> Schedule {
Schedule::crontab(&minute.to_string(), &hour.to_string(), "1-5", "*", "*")
}
#[cfg(feature = "cron")]
pub fn weekly_on_monday(hour: u32, minute: u32) -> Schedule {
Schedule::crontab(&minute.to_string(), &hour.to_string(), "1", "*", "*")
}
#[cfg(feature = "cron")]
pub fn monthly_first_day() -> Schedule {
Schedule::crontab("0", "0", "*", "1", "*")
}
#[cfg(feature = "cron")]
pub fn monthly_last_day() -> Schedule {
Schedule::crontab("0", "0", "*", "28-31", "*")
}
#[cfg(feature = "cron")]
pub fn business_hours_hourly() -> Schedule {
Schedule::crontab("0", "9-17", "1-5", "*", "*")
}
#[cfg(feature = "cron")]
pub fn business_hours_every_15_minutes() -> Schedule {
Schedule::crontab("*/15", "9-17", "1-5", "*", "*")
}
#[cfg(feature = "cron")]
pub fn weekend_mornings() -> Schedule {
Schedule::crontab("0", "8", "0,6", "*", "*")
}
#[cfg(feature = "cron")]
pub fn quarterly() -> Schedule {
Schedule::crontab("0", "0", "*", "1", "1,4,7,10")
}
pub fn every_2_hours() -> Schedule {
Schedule::interval(7200)
}
pub fn every_6_hours() -> Schedule {
Schedule::interval(21600)
}
pub fn every_12_hours() -> Schedule {
Schedule::interval(43200)
}
}