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

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

推荐.NET配套的通用数据层ORM框架:CYQ.Data 通用数据层框架
新浪微博粉丝精灵,刷粉丝、刷评论、刷转发、企业商家微博营销必备工具"