/*
* 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:
///
/// - Support for specifying both a day-of-week and a day-of-month value is
/// not complete (you'll need to use the '?' character in on of these fields).
///
/// - Be careful when setting fire times between mid-night and 1:00 AM -
/// "daylight savings" can cause a skip or a repeat depending on whether the
/// time moves back or jumps forward.
///
///
///
///
///
///
/// 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);
}
}
}