Extensibility: Creating a custom Schedule

Thursday, September 13, 2007

DemiCode Scheduler can be extended with additional schedules, either provided by third parties or created by you. This article describes the basics of how to create a custom schedule.

Defining your schedule

Before you set out to implement a new schedule you probably have some idea of how the schedule should behave. For this walktrough let us imagine that we have the need to alert our employees that it is lunch time. Our schedule must be able to get the time of day for lunch and to differentiate between workdays and weekends.

Implementing the basics

Every schedule must implement the ISchedule interface. To save you from having to write the basic implementation to support this interface, we have placed some common functionality in the ScheduleBase class. To get started quickly we will go ahead and subclass ScheduleBase, and at the same time add a property to store our lunch time.

public class LunchSchedule : ScheduleBase
{
    private readonly TimeSpan _lunchTimeOfDay;

    public TimeSpan LunchTimeOfDay
    {
        get { return _lunchTimeOfDay; }
    }
}

Calculating events

The next thing is to override the key method that keeps a schedule “moving”: ISchedule.MoveToFirstFrom. The responsibility of this method is: given an arbitrary point in time T, calculate the future point in time, closest to T, at which the schedule should alarm. The method must also return true if there are any future points ahead; false if there are none.

The implementation handles the first requirement, calculating a point in time based on the lunch time. By using time.Date we’re effectively removing the arbitrary timespan. We can then add our LunchTimeOfDay timespan to get a combined date and lunch time.

public override bool MoveToFirstFrom(DateTime time)
{
    if (time.TimeOfDay > LunchTimeOfDay)
    {
        //If we're over lunch time, move to next workday
        time = time.Date.AddDays(1);
    }

    while ((time.DayOfWeek == DayOfWeek.Saturday) || time.DayOfWeek == DayOfWeek.Sunday)
    {
        //If we're on a saturday or a sunday, we move to the following monday
        time.date.AddDays(1);
    }

    //Set our lunch time as exact time of day
     CurrentOccurrence = time.Date.Add(LunchTimeOfDay);

    return true;
}

Constructors

Finally, we need a way to instantiate an instance of our class. here we create two constructors for our schedule. One for constructing the schedule in code and one for configuring the schedule from XML.

The code-based constructor can have any signature you like, e.g. like this:

public LunchSchedule(TimeSpan lunchTimeOfDay)
{
    _lunchTimeOfDay = lunchTimeOfDay;
}

The xml constructor must have the following signature, taking in a single XmlNode parameter. It is also necessary to call the base ScheduleBase(XmlNode) constructor in order to properly initialize the schedule name.

public LunchSchedule(XmlNode config) : base(config)
{
    _lunchTimeOfDay = TimeSpan.Parse(config["lunchTimeOfDay"].InnerText);
}

Using the new schedule

To use the schedule, you need to create an instance of it and . You can do this by adding it to the Scheduler programatically:

Scheduler s = new Scheduler();
LunchSchedule lunch = new LunchSchedule();
s.Add(lunch);

Or you can add it to the configuration file under the <scheduler> element:

<schedule name="myLunchSchedule" type="LunchSchedule, MyAssembly">
    <lunchTimeOfDay>11:30:00</lunchTimeOfDay>
</schedule>

Where you replace the type attribute of the <schedule> element with the fully qualified class name and the name of the assembly holding our custom scheduler.