Click here to Skip to main content
15,880,796 members
Articles / Programming Languages / C#

Flexible Time Schedule in C# 2.0

Rate me:
Please Sign up or sign in to vote.
4.94/5 (27 votes)
17 Jun 2011CPOL3 min read 133.1K   4.7K   144   15
A simple way to iterate through a timeline using a flexible predicate system

Introduction

This very small project was inspired by a small project released on The Code Project by fellow CPian Aleksey Bykov.

Unfortunately, I was not satisfied with the flexibility provided by the offered library.

Recently having dealt heavily with predicates and Iterators and having dabbled with Orcas and the new LINQ syntax (can't wait for LINQ), I felt this was the sort of project that could benefit from such knowledge.

Until LINQ becomes mainstream, it will at least benefit you to get familiarized with predicates, actions, etc. (Predicate<T>, Action<T>). The enclosed Schedule class has been kept light weight by intention and makes simple use of Predicate<DateTime>.

C#
public delegate bool Predicate<T>(T obj);
public delegate void Action<T>(T obj);   

Background

The aim of the project was to be able to iterate through a timeline. The iteration process is based on your schedule defined in code (in the form of predicates) applied to a very simple Iterator.

Iterators can be chained in a hierarchical fashion. This allows us to apply predicates to a daily iterator that iterates over the output from a predicated monthly iterator, etc.

This extremely simple syntax provides great flexibility. Once you understand how to define predicates for filtering DateTime, it allows you to build up even the most complex schedules.

There is elegance in lightweight design. I'm a fond believer that design is king. If the design is right, code is compact, flowing, intuitive and easy to maintain.

Learn to use predicates, actions, iterator patterns, anonymous methods, etc. New doors will open and lights will shine through your windows (LOL).

Using the Code

Bin Days Example

C#
/// <summary>
/// Thursdays in third week of Month else Fridays
/// Days we put the bin out for garbage collection.
/// </summary>
[TestAttribute]
public void BinDays()
{
    DateTime start = new DateTime(2007, 01, 01)
    DateTime  end = new DateTime(2007, 06, 30);
    Schedule schedule = new Schedule(ScheduleStep.Days , start, end);
    schedule.Predicate = new Predicate<DateTime>(delegate(DateTime date)
    {
        return (date.Day / 7 == 3)?(date.DayOfWeek == DayOfWeek.Thursday)
            :(date.DayOfWeek == DayOfWeek.Friday);
    }
    );
    int count = 0;
    foreach (DateTime date in schedule)
    {
        Console.WriteLine(date.ToString("f"));
        Assert.IsTrue(schedule.Predicate(date));
        count++;
    }
    Assert.AreEqual(26, count);
}

A Slightly More Complex Example

C#
/// <summary>
/// every leap year, odd month, Tues and Thurs of third week of month
/// </summary>
[TestAttribute]
public void BasicWalk_Leap_OddMonth_TueThurOfWk3()
{
    DateTime start = new DateTime(1990, 01, 01);
    DateTime end = new DateTime(2007, 01, 01);
    Schedule schedule = new Schedule(ScheduleStep.Days, start, end);
    schedule.Predicate = new Predicate<DateTime>(delegate(DateTime date)
    {
        return (date.Year % 4 == 0
            && date.Month % 2 != 0 &&
            ((date.DayOfWeek == DayOfWeek.Tuesday ||
            date.DayOfWeek == DayOfWeek.Thursday) && (date.Day / 7 == 3)));
    }
    );
    foreach (DateTime date in schedule)
    {
        Console.WriteLine(date.ToString("f"));
        Assert.IsTrue(schedule.Predicate(date));
    }
} 

Example Syntax - Calling A Chain

C#
foreach (DateTime date in schedule4.GetEnumerator
    (schedule3.GetEnumerator(schedule2.GetEnumerator(schedule1))))
{
Console.WriteLine(date.ToString("f"));
} 

Schedule chaining syntax is just a way to optimize. For example, you want every 10th minute, of the 20th hour, of the fourth Tuesday of the 6th month of odd years since 1900. You could iterate all minutes since 1900, applying a single predicate, or you could chain a hierarchical series of iterators.

Chained Iteration Example

C#
/// <summary>
/// every leap year, odd month, Tues and Thurs of third week of month
/// Same as for BasicWalk_Leap_OddMonth_TueThurOfWk3.
/// </summary>
[TestAttribute]
public void BasicChain_Leap_OddMonth_TueThurOfWk3()
{
    DateTime start = new DateTime(1990, 01, 01);
    DateTime end = new DateTime(2007, 01, 01);
    //every leap year
    Schedule schedule = new Schedule(ScheduleStep.Years , start, end);
    schedule.Predicate = new Predicate<DateTime>(delegate(DateTime date)
      {
          return (date.Year % 4 == 0);
      }
      );
    // odd month
    Schedule schedule2 = new Schedule(ScheduleWalkTo.MonthsInYear);
    schedule2.Predicate = new Predicate<DateTime>(delegate(DateTime date)
       {
           return (date.Month % 2 != 0);
       }
       );
    //Tues and Thurs of third week of month
    Schedule schedule3 = new Schedule(ScheduleWalkTo.DaysInMonth );
    schedule3.Predicate = new Predicate<DateTime>(delegate(DateTime date)
       {
           return ((date.DayOfWeek == DayOfWeek.Tuesday || 
               date.DayOfWeek == DayOfWeek.Thursday) && (date.Day / 7 == 3));
       }
       );
    // UnitTest Stuff
    Schedule schedulebasic = new Schedule(ScheduleStep.Days, start, end);
    schedulebasic.Predicate = new Predicate<DateTime>(delegate(DateTime date)
       {
           return (date.Year % 4 == 0
               && date.Month % 2 != 0 &&
               ((date.DayOfWeek == DayOfWeek.Tuesday ||
               date.DayOfWeek == DayOfWeek.Thursday) && (date.Day / 7 == 3)));
       }
       );
    IEnumerator<DateTime> ie = schedulebasic.GetEnumerator();
    foreach (DateTime date in 
        schedule3.GetEnumerator(schedule2.GetEnumerator( schedule)))
    {
        ie.MoveNext();
        Assert.IsTrue(schedule.Predicate(date));
        Assert.IsTrue(schedule2.Predicate(date));
        Assert.IsTrue(schedule3.Predicate(date));
        Assert.IsTrue(schedulebasic.Predicate(date));
        Assert.AreEqual (ie.Current ,date);
        Console.WriteLine(date.ToString("f"));
    }
}

Points of Interest

This is all probably easier to fathom in code which remains extremely lightweight. (I've spent more time writing this article and getting it online than in writing the class). This is really just to illustrate the flexibility of predicates and iteration by design. Compare this to Aleksey's library Idaligo.Time, which was designed to simulate Unix Cron.

I hope you appreciate the elegance of this design and understand its flexibility.

History

Quick and dirty, I know. Just the way I like it. {:-)}

2005/05/17: An updated version has been posted that includes a single class Cron.

Cron maintains flexible efficient design. Cron is also efficient in the way it iterates dates. It outputs all dates in the range using Unix Cr<code>on syntax. Cron calling chain syntax allows attachment of Predicates using Where functions (similar to LINQ) anywhere in the call chain. Also Actions can be attached in the call chain.

C#
/// <summary>
/// This example says it all.
/// Iterates 2007-2008, months 1-6 , days 1-14 where is even day and not weekend
/// </summary>
[TestAttribute]
public void Y07_08_M01_12_D1_31()
{
   
    foreach (DateTime date in Cron.Years(2007, 2008).Action(ActionYear).Month(1, 6).
        Action(ActionMonth).Day(1, 14).Action(ActionDay).
            Where(BusinessDay).Where(EvenDay))
    {
        Assert.GreaterOrEqual(date.Year, 2007);
        Assert.LessOrEqual(date.Year, 2008);
        Assert.GreaterOrEqual(date.Month, 1);
        Assert.LessOrEqual(date.Month, 6);
        Assert.GreaterOrEqual(date.Day, 1);
        Assert.LessOrEqual(date.Day, 14);
        Assert.IsTrue(EvenDay(date));
        Assert.IsTrue(BusinessDay(date));
    }
}
 /// <summary>
///  Prints out a work hours timetable
/// </summary>
[TestAttribute]
public void WORKTimeTable()
{

    foreach (DateTime date in Cron.Years(2007, 2008).Action(ActionYear).Month(1, 6).
        Action(ActionMonth).Day(1, 14).Action(ActionDay).Where(BusinessDay).
                       Where(EvenDay).Hour(9,17).Action(ActionHour ))
    {
        Assert.GreaterOrEqual(date.Year, 2007);
        Assert.LessOrEqual(date.Year, 2008);
        Assert.GreaterOrEqual(date.Month, 1);
        Assert.LessOrEqual(date.Month, 6);
        Assert.GreaterOrEqual(date.Day, 1);
        Assert.LessOrEqual(date.Day, 14);
        Assert.IsTrue(EvenDay(date));
        Assert.IsTrue(BusinessDay(date));
    }
}

//Demonstrating offloading Predicate and Action delegate to functions
// Can build library of Actions and or Predicates in this manner
private void ActionYear(DateTime date)
{
    Console.WriteLine("YEAR    {0}", date.Year);
}
private void ActionMonth(DateTime date)
{
    Console.WriteLine("\t\tMONTH    {0:MMMM}", date  );
}
private void ActionDay(DateTime date)
{
    Console.WriteLine("\t\t\tDAY   {0}   {1}", date.DayOfWeek, date.Day );
}
private void ActionHour(DateTime date)
{
    Console.WriteLine("{0:h:m tt}\t----", date);
}
private bool BusinessDay(DateTime day)
{
    return day.DayOfWeek!= DayOfWeek.Saturday && day.DayOfWeek!= DayOfWeek.Sunday ;
}
private bool EvenDay(DateTime day)
{
    return day.Day % 2 == 0;
} 

The background reading for custom iterators can be found here.

2011/06/16: Fixed a bug with iterating months in the zip file only

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Australia Australia
Interested in financial math and programming theory in general. Working on medical applications in spare time. Happy to get feedback.

Comments and Discussions

 
GeneralMy vote of 5 Pin
oren.shnitzer27-Feb-12 2:54
oren.shnitzer27-Feb-12 2:54 
QuestionLeap Year Pin
Member 134478825-Jan-12 0:02
Member 134478825-Jan-12 0:02 
I'm surprised no one has yet commented on
(date.Year % 4 == 0);
being a flawed test for leap years.
AnswerRe: Leap Year Pin
seeblunt25-Jan-12 1:19
seeblunt25-Jan-12 1:19 
Questionfirst wednesday of november 2011 Pin
Lee Gary26-Oct-11 23:56
Lee Gary26-Oct-11 23:56 
GeneralFound an error (i think) Pin
Norbert Haberl25-Jan-11 4:42
Norbert Haberl25-Jan-11 4:42 
GeneralRe: Found an error (i think) [modified] Pin
seeblunt16-Jun-11 14:00
seeblunt16-Jun-11 14:00 
Generaloh my god Pin
Simone Giacomelli5-Jun-08 0:35
Simone Giacomelli5-Jun-08 0:35 
QuestionHow to Fire specific Function at Listed Time. Pin
kyungchan ,lee2-Jun-08 20:33
kyungchan ,lee2-Jun-08 20:33 
AnswerRe: How to Fire specific Function at Listed Time. Pin
seeblunt3-Jun-08 13:01
seeblunt3-Jun-08 13:01 
Generalgreat work! Pin
bigtreexu25-Sep-07 4:53
bigtreexu25-Sep-07 4:53 
Questionweek of month? Pin
Mad20069-Jun-07 22:05
Mad20069-Jun-07 22:05 
AnswerRe: week of month? Pin
seeblunt11-Jun-07 4:05
seeblunt11-Jun-07 4:05 
Generalthanks for a valuable article ! Pin
BillWoodruff14-May-07 14:48
professionalBillWoodruff14-May-07 14:48 
AnswerRe: thanks for a valuable article ! Pin
seeblunt15-May-07 4:40
seeblunt15-May-07 4:40 
GeneralGreat job man! [modified] Pin
Aleksey Bykov11-May-07 8:33
Aleksey Bykov11-May-07 8:33 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.