Click here to Skip to main content
Click here to Skip to main content

Flexible Time Schedule in C# 2.0

, 17 Jun 2011 CPOL
Rate this:
Please Sign up or sign in to vote.
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>.

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

/// <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

/// <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

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

/// <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. {Smile | :) }

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 Cron 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.

/// <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)

Share

About the Author

seeblunt

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 Pinmemberoren.shnitzer27-Feb-12 3:54 
QuestionLeap Year PinmemberMember 134478825-Jan-12 1:02 
AnswerRe: Leap Year Pinmemberseeblunt25-Jan-12 2:19 
Questionfirst wednesday of november 2011 PinmemberLee Gary27-Oct-11 0:56 
GeneralFound an error (i think) PinmemberNorbert Haberl25-Jan-11 5:42 
GeneralRe: Found an error (i think) [modified] Pinmemberseeblunt16-Jun-11 15:00 
Generaloh my god PinmemberSimone Giacomelli5-Jun-08 1:35 
QuestionHow to Fire specific Function at Listed Time. Pinmemberkyungchan ,lee2-Jun-08 21:33 
AnswerRe: How to Fire specific Function at Listed Time. Pinmemberseeblunt3-Jun-08 14:01 
Generalgreat work! Pinmemberbigtreexu25-Sep-07 5:53 
Questionweek of month? PinmemberMad20069-Jun-07 23:05 
AnswerRe: week of month? Pinmemberseeblunt11-Jun-07 5:05 
Generalthanks for a valuable article ! PinmemberBillWoodruff14-May-07 15:48 
AnswerRe: thanks for a valuable article ! Pinmemberseeblunt15-May-07 5:40 
GeneralGreat job man! [modified] PinmemberAleksey Bykov11-May-07 9:33 

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

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

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.141223.1 | Last Updated 17 Jun 2011
Article Copyright 2007 by seeblunt
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid