/* * Copyright 2004-2005 OpenSymphony * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy * of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. * */ /* * Previously Copyright (c) 2001-2004 James House */ using System; #if NET_20 using NullableDateTime = System.Nullable; #else using Nullables; #endif #if NET_35 using TimeZone = System.TimeZoneInfo; #endif using Quartz.Spi; using Quartz.Util; namespace Quartz { /// /// A concrete that is used to fire a /// at given moments in time, defined with Unix 'cron-like' definitions. /// /// ///

/// For those unfamiliar with "cron", this means being able to create a firing /// schedule such as: "At 8:00am every Monday through Friday" or "At 1:30am /// every last Friday of the month". ///

/// ///

/// The format of a "Cron-Expression" string is documented on the /// class. ///

/// ///

/// Here are some full examples:
/// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// ///
Expression Meaning
"0 0 12 * * ?"" /> Fire at 12pm (noon) every day" />
"0 15 10 ? * *"" /> Fire at 10:15am every day" />
"0 15 10 * * ?"" /> Fire at 10:15am every day" />
"0 15 10 * * ? *"" /> Fire at 10:15am every day" />
"0 15 10 * * ? 2005"" /> Fire at 10:15am every day during the year 2005" /> ///
"0 * 14 * * ?"" /> Fire every minute starting at 2pm and ending at 2:59pm, every day" /> ///
"0 0/5 14 * * ?"" /> Fire every 5 minutes starting at 2pm and ending at 2:55pm, every day" /> ///
"0 0/5 14,18 * * ?"" /> Fire every 5 minutes starting at 2pm and ending at 2:55pm, AND fire every 5 minutes starting at 6pm and ending at 6:55pm, every day" /> ///
"0 0-5 14 * * ?"" /> Fire every minute starting at 2pm and ending at 2:05pm, every day" /> ///
"0 10,44 14 ? 3 WED"" /> Fire at 2:10pm and at 2:44pm every Wednesday in the month of March." /> ///
"0 15 10 ? * MON-FRI"" /> Fire at 10:15am every Monday, Tuesday, Wednesday, Thursday and Friday" /> ///
"0 15 10 15 * ?"" /> Fire at 10:15am on the 15th day of every month" /> ///
"0 15 10 L * ?"" /> Fire at 10:15am on the last day of every month" /> ///
"0 15 10 ? * 6L"" /> Fire at 10:15am on the last Friday of every month" /> ///
"0 15 10 ? * 6L"" /> Fire at 10:15am on the last Friday of every month" /> ///
"0 15 10 ? * 6L 2002-2005"" /> Fire at 10:15am on every last friday of every month during the years 2002, 2003, 2004 and 2005" /> ///
"0 15 10 ? * 6#3"" /> Fire at 10:15am on the third Friday of every month" /> ///
///

/// ///

/// Pay attention to the effects of '?' and '*' in the day-of-week and /// day-of-month fields! ///

/// ///

/// NOTES: ///

///

///
/// /// /// /// Sharada Jambula /// James House /// Contributions from Mads Henderson [Serializable] public class CronTrigger : Trigger { private const int YearToGiveupSchedulingAt = 2299; private CronExpression cronEx = null; private DateTime startTimeUtc = DateTime.MinValue; private NullableDateTime endTimeUtc = null; private NullableDateTime nextFireTimeUtc = null; private NullableDateTime previousFireTimeUtc = null; [NonSerialized] private TimeZone timeZone = null; /// /// Create a with no settings. /// /// /// The start-time will also be set to the current time, and the time zone /// will be set the the system's default time zone. /// public CronTrigger() { StartTimeUtc = DateTime.UtcNow; #if !NET_35 TimeZone = TimeZone.CurrentTimeZone; #else TimeZone = TimeZoneInfo.Local; #endif } /// /// Create a with the given name and group. /// /// /// The start-time will also be set to the current time, and the time zone /// will be set the the system's default time zone. /// /// The name. /// The group. public CronTrigger(string name, string group) : base(name, group) { StartTimeUtc = DateTime.UtcNow; #if !NET_35 TimeZone = TimeZone.CurrentTimeZone; #else TimeZone = TimeZoneInfo.Local; #endif } /// /// Create a with the given name, group and /// expression. /// /// /// The start-time will also be set to the current time, and the time zone /// will be set the the system's default time zone. /// /// The name. /// The group. /// The cron expression. public CronTrigger(string name, string group, string cronExpression) : base(name, group) { CronExpressionString = cronExpression; StartTimeUtc = DateTime.UtcNow; #if !NET_35 TimeZone = TimeZone.CurrentTimeZone; #else TimeZone = TimeZoneInfo.Local; #endif } /// /// Create a with the given name and group, and /// associated with the identified . /// /// /// The start-time will also be set to the current time, and the time zone /// will be set the the system's default time zone. /// /// The name. /// The group. /// Name of the job. /// The job group. public CronTrigger(String name, string group, string jobName, string jobGroup) : base(name, group, jobName, jobGroup) { StartTimeUtc = DateTime.UtcNow; #if !NET_35 TimeZone = TimeZone.CurrentTimeZone; #else TimeZone = TimeZoneInfo.Local; #endif } /// /// Create a with the given name and group, /// associated with the identified , /// and with the given "cron" expression. /// /// /// The start-time will also be set to the current time, and the time zone /// will be set the the system's default time zone. /// /// The name. /// The group. /// Name of the job. /// The job group. /// The cron expression. public CronTrigger(string name, string group, string jobName, string jobGroup, string cronExpression) #if !NET_35 : this(name, group, jobName, jobGroup, DateTime.UtcNow, null, cronExpression, TimeZone.CurrentTimeZone) #else : this(name, group, jobName, jobGroup, DateTime.UtcNow, null, cronExpression, TimeZoneInfo.Local) #endif { } /// /// Create a with the given name and group, /// associated with the identified , /// and with the given "cron" expression resolved with respect to the . /// /// The name. /// The group. /// Name of the job. /// The job group. /// The cron expression. /// The time zone. public CronTrigger(string name, string group, string jobName, string jobGroup, string cronExpression, TimeZone timeZone) : this(name, group, jobName, jobGroup, DateTime.UtcNow, null, cronExpression, timeZone) { } /// /// Create a that will occur at the given time, /// until the given end time. ///

/// If null, the start-time will also be set to the current time, the time /// zone will be set the the system's default. ///

///
/// The name. /// The group. /// Name of the job. /// The job group. /// The start time. /// The end time. /// The cron expression. public CronTrigger(string name, string group, string jobName, string jobGroup, DateTime startTimeUtc, NullableDateTime endTime, string cronExpression) : base(name, group, jobName, jobGroup) { CronExpressionString = cronExpression; if (startTimeUtc == DateTime.MinValue) { startTimeUtc = DateTime.UtcNow; } StartTimeUtc = startTimeUtc; if (endTime.HasValue) { EndTimeUtc = endTime; } #if !NET_35 TimeZone = TimeZone.CurrentTimeZone; #else TimeZone = TimeZoneInfo.Local; #endif } /// /// Create a with fire time dictated by the /// resolved with respect to the specified /// occuring from the until /// the given . /// /// The name. /// The group. /// Name of the job. /// The job group. /// The start time. /// The end time. public CronTrigger(string name, string group, string jobName, string jobGroup, DateTime startTimeUtc, NullableDateTime endTime, string cronExpression, TimeZone timeZone) : base(name, group, jobName, jobGroup) { CronExpressionString = cronExpression; if (startTimeUtc == DateTime.MinValue) { startTimeUtc = DateTime.UtcNow; } StartTimeUtc = startTimeUtc; if (endTime.HasValue) { EndTimeUtc = endTime; } if (timeZone == null) { #if !NET_35 TimeZone = TimeZone.CurrentTimeZone; #else timeZone = TimeZoneInfo.Local; #endif } else { TimeZone = timeZone; } } /// /// Clones this instance. /// /// public override object Clone() { CronTrigger copy = (CronTrigger) MemberwiseClone(); if (cronEx != null) { copy.CronExpression = (CronExpression) cronEx.Clone(); } return copy; } /// /// Gets or sets the cron expression string. /// /// The cron expression string. public string CronExpressionString { set { TimeZone orginalTimeZone = TimeZone; cronEx = new CronExpression(value); cronEx.TimeZone = orginalTimeZone; } get { return cronEx == null ? null : cronEx.CronExpressionString; } } /// /// Set the CronExpression to the given one. The TimeZone on the passed-in /// CronExpression over-rides any that was already set on the Trigger. /// /// The cron expression. public CronExpression CronExpression { set { cronEx = value; timeZone = value.TimeZone; } } /// /// Returns the date/time on which the trigger may begin firing. This /// defines the initial boundary for trigger firings the trigger /// will not fire prior to this date and time. /// /// public override DateTime StartTimeUtc { get { return startTimeUtc; } set { NullableDateTime eTime = EndTimeUtc; if (eTime.HasValue && eTime.Value < value) { throw new ArgumentException("End time cannot be before start time"); } // round off millisecond... DateTime dt = new DateTime(value.Year, value.Month, value.Day, value.Hour, value.Minute, value.Second); DateTimeUtil.AssumeUniversalTime(dt); startTimeUtc = dt; } } /// /// Get or sets the time at which the CronTrigger should quit /// repeating - even if repeastCount isn't yet satisfied. /// public override NullableDateTime EndTimeUtc { get { return endTimeUtc; } set { DateTime sTime = StartTimeUtc; if (value.HasValue && sTime > value.Value) { throw new ArgumentException("End time cannot be before start time"); } endTimeUtc = DateTimeUtil.AssumeUniversalTime(value); } } /// /// Returns the next time at which the is scheduled to fire. If /// the trigger will not fire again, will be returned. Note that /// the time returned can possibly be in the past, if the time that was computed /// for the trigger to next fire has already arrived, but the scheduler has not yet /// been able to fire the trigger (which would likely be due to lack of resources /// e.g. threads). /// /// /// The value returned is not guaranteed to be valid until after the /// has been added to the scheduler. /// /// /// public override NullableDateTime GetNextFireTimeUtc() { return nextFireTimeUtc; } /// /// Returns the previous time at which the fired. /// If the trigger has not yet fired, will be returned. /// /// public override NullableDateTime GetPreviousFireTimeUtc() { return previousFireTimeUtc; } /// /// Sets the next fire time. ///

/// This method should not be invoked by client code. ///

///
/// The fire time. public void SetNextFireTime(NullableDateTime fireTime) { nextFireTimeUtc = DateTimeUtil.AssumeUniversalTime(fireTime); } /// /// Sets the previous fire time. ///

/// This method should not be invoked by client code. ///

///
/// The fire time. public void SetPreviousFireTime(NullableDateTime fireTime) { previousFireTimeUtc = DateTimeUtil.AssumeUniversalTime(fireTime); } /// /// Sets the time zone for which the of this /// will be resolved. /// /// /// If is set after this /// property, the TimeZone setting on the CronExpression will "win". However /// if is set after this property, the /// time zone applied by this method will remain in effect, since the /// string cron expression does not carry a time zone! /// /// The time zone. public TimeZone TimeZone { get { if (cronEx != null) { return cronEx.TimeZone; } if (timeZone == null) { #if !NET_35 timeZone = TimeZone.CurrentTimeZone; #else timeZone = TimeZoneInfo.Local; #endif } return timeZone; } set { if (cronEx != null) { cronEx.TimeZone = value; } timeZone = value; } } /// /// Returns the next time at which the will fire, /// after the given time. If the trigger will not fire after the given time, /// will be returned. /// /// /// public override NullableDateTime GetFireTimeAfter(NullableDateTime afterTimeUtc) { if (!afterTimeUtc.HasValue) { afterTimeUtc = DateTime.UtcNow; } if (StartTimeUtc > afterTimeUtc.Value) { afterTimeUtc = DateTimeUtil.AssumeUniversalTime(startTimeUtc).AddSeconds(-1); } if (EndTimeUtc.HasValue && (afterTimeUtc.Value.CompareTo(EndTimeUtc.Value) >= 0)) { return null; } NullableDateTime pot = GetTimeAfter(afterTimeUtc.Value); if (EndTimeUtc.HasValue && pot.HasValue && pot.Value > EndTimeUtc.Value) { return null; } return pot; } /// /// Returns the last UTC time at which the will fire, if /// the Trigger will repeat indefinitely, null will be returned. ///

/// Note that the return time *may* be in the past. ///

///
public override NullableDateTime FinalFireTimeUtc { get { NullableDateTime resultTime; if (EndTimeUtc.HasValue) { resultTime = GetTimeBefore(EndTimeUtc.Value.AddSeconds(1)); } else { resultTime = (cronEx == null) ? null : cronEx.GetFinalFireTime(); } if (resultTime.HasValue && resultTime.Value < StartTimeUtc) { return null; } return resultTime; } } /// /// Tells whether this Trigger instance can handle events /// in millisecond precision. /// /// public override bool HasMillisecondPrecision { get { return false; } } /// /// Used by the to determine whether or not /// it is possible for this to fire again. ///

/// If the returned value is then the /// may remove the from the . ///

///
/// public override bool GetMayFireAgain() { return GetNextFireTimeUtc().HasValue; } /// /// Validates the misfire instruction. /// /// The misfire instruction. /// protected override bool ValidateMisfireInstruction(int misfireInstruction) { return (misfireInstruction == Quartz.MisfireInstruction.CronTrigger.DoNothing) || (misfireInstruction == Quartz.MisfireInstruction.CronTrigger.FireOnceNow) || (misfireInstruction == Quartz.MisfireInstruction.SmartPolicy); } /// /// This method should not be used by the Quartz client. ///

/// To be implemented by the concrete classes that extend this class. ///

///

/// The implementation should update the 's state /// based on the MISFIRE_INSTRUCTION_XXX that was selected when the /// was created. ///

///
/// public override void UpdateAfterMisfire(ICalendar cal) { int instr = MisfireInstruction; if (instr == Quartz.MisfireInstruction.SmartPolicy) { instr = Quartz.MisfireInstruction.CronTrigger.FireOnceNow; } if (instr == Quartz.MisfireInstruction.CronTrigger.DoNothing) { NullableDateTime newFireTime = GetFireTimeAfter(DateTime.UtcNow); while (newFireTime.HasValue && cal != null && !cal.IsTimeIncluded(newFireTime.Value)) { newFireTime = GetFireTimeAfter(newFireTime); } SetNextFireTime(newFireTime); } else if (instr == Quartz.MisfireInstruction.CronTrigger.FireOnceNow) { SetNextFireTime(DateTime.UtcNow); } } /// ///

/// Determines whether the date and (optionally) time of the given Calendar /// instance falls on a scheduled fire-time of this trigger. ///

/// ///

/// Equivalent to calling . ///

///
/// The date to compare. /// public bool WillFireOn(DateTime test) { return WillFireOn(test, false); } /// /// Determines whether the date and (optionally) time of the given Calendar /// instance falls on a scheduled fire-time of this trigger. ///

/// Note that the value returned is NOT validated against the related /// ICalendar (if any). ///

///
/// The date to compare /// If set to true, the method will only determine if the /// trigger will fire during the day represented by the given Calendar /// (hours, minutes and seconds will be ignored). /// public bool WillFireOn(DateTime test, bool dayOnly) { test = new DateTime(test.Year, test.Month, test.Day, test.Hour, test.Minute, test.Second); if (dayOnly) { test = new DateTime(test.Year, test.Month, test.Day, 0, 0, 0); } DateTimeUtil.AssumeUniversalTime(test); NullableDateTime fta = GetFireTimeAfter(test.AddMilliseconds(-1 * 1000)); #if !NET_35 DateTime p = TimeZone.ToLocalTime(fta.Value); #else DateTime p = TimeZoneInfo.ConvertTimeFromUtc(fta.Value, TimeZone); #endif if (dayOnly) { return (p.Year == test.Year && p.Month == test.Month && p.Day == test.Day); } while (fta.Value < test) { fta = GetFireTimeAfter(fta); } if (fta.Equals(test)) { return true; } return false; } /// /// Called when the has decided to 'fire' /// the trigger (Execute the associated ), in order to /// give the a chance to update itself for its next /// triggering (if any). /// /// /// public override void Triggered(ICalendar cal) { previousFireTimeUtc = nextFireTimeUtc; nextFireTimeUtc = GetFireTimeAfter(nextFireTimeUtc); while (nextFireTimeUtc.HasValue && cal != null && !cal.IsTimeIncluded(nextFireTimeUtc.Value)) { nextFireTimeUtc = GetFireTimeAfter(nextFireTimeUtc); } } /// /// Updates the trigger with new calendar. /// /// The calendar to update with. /// The misfire threshold. public override void UpdateWithNewCalendar(ICalendar calendar, TimeSpan misfireThreshold) { nextFireTimeUtc = GetFireTimeAfter(previousFireTimeUtc); if (!nextFireTimeUtc.HasValue || calendar == null) { return; } DateTime now = DateTime.UtcNow; while (nextFireTimeUtc.HasValue && !calendar.IsTimeIncluded(nextFireTimeUtc.Value)) { nextFireTimeUtc = GetFireTimeAfter(nextFireTimeUtc); if (!nextFireTimeUtc.HasValue) { break; } // avoid infinite loop if (nextFireTimeUtc.Value.Year > YearToGiveupSchedulingAt) { nextFireTimeUtc = null; } if (nextFireTimeUtc.HasValue && nextFireTimeUtc.Value < (now)) { TimeSpan diff = now - nextFireTimeUtc.Value; if (diff >= misfireThreshold) { nextFireTimeUtc = GetFireTimeAfter(nextFireTimeUtc); continue; } } } } /// /// Called by the scheduler at the time a is first /// added to the scheduler, in order to have the /// compute its first fire time, based on any associated calendar. ///

/// After this method has been called, /// should return a valid answer. ///

///
/// /// /// the first time at which the will be fired /// by the scheduler, which is also the same value /// will return (until after the first firing of the ). /// public override NullableDateTime ComputeFirstFireTimeUtc(ICalendar cal) { nextFireTimeUtc = GetFireTimeAfter(startTimeUtc.AddSeconds(-1)); while (nextFireTimeUtc.HasValue && cal != null && !cal.IsTimeIncluded(nextFireTimeUtc.Value)) { nextFireTimeUtc = GetFireTimeAfter(nextFireTimeUtc); } return nextFireTimeUtc; } /// /// Gets the expression summary. /// /// public string GetExpressionSummary() { return cronEx == null ? null : cronEx.GetExpressionSummary(); } //////////////////////////////////////////////////////////////////////////// // // Computation Functions // //////////////////////////////////////////////////////////////////////////// /// /// Gets the next time to fire after the given time. /// /// The time to compute from. /// protected NullableDateTime GetTimeAfter(DateTime afterTime) { if (cronEx != null) { return cronEx.GetTimeAfter(afterTime); } else { return null; } } /// /// NOT YET IMPLEMENTED: Returns the time before the given time /// that this will fire. /// /// The date. /// protected NullableDateTime GetTimeBefore(NullableDateTime date) { return (cronEx == null) ? null : cronEx.GetTimeBefore(endTimeUtc); } } }