Search code examples
c#datetimecollectionsrecurrence

Looking for C# algorithm to grab recurring items from a collection of DateTime objects (factoring in both date and time)?


I have a collection of DateTime objects. I need to "parse" out subcollections from this original list to grab related items based on recurrence. So I need to take this single original collection:

var collectionOfDateTime = GetDateTimeCollection();

and translate that into a list of DateTime collections where each collection includes a set of dates from the first list that following a specific recurrence pattern.

In my examples below I am not including time but in the real requirement, these items have both Date and Time elements to them. So for example the Dates need to be 7 days apart but also be the same time (one time on Feb 3rd at 11AM doesn't match feb 10th at 3PM but it does match with Feb 10th at 11AM)

For example, lets say my recurrence pattern is "Weekly" (in other cases it might be "Monthly") and my date collection looks like this:

 var date1 = DateTime.Today.AddHours(8);
 var date2 = DateTime.Today.AddWeeks(1).AddHours(8);
 var date3 = DateTime.Today.AddDays(3);
 var date4 = DateTime.Today.AddWeeks(8).AddHours(6);

 var collectionOfDateTime = new List<DateTime>() { date1, date2, date3, date4 };

I would want a function (lets call it StripOutSubCollections()) to pass in the collectionOfDateTime and the fact that its "Weekly" and return one collection that includes date1, date2 (as they are all part of the same weekly slot). Note date3 doesn't not fit and date4 also doesn't fit because the hours do not match with the others

For another example to help prove out the requirement, if the input to the original collection was this:

 var date1 = DateTime.Today;
 var date2 = DateTime.Today.AddWeeks(1);
 var date3 = DateTime.Today.AddDays(3);
 var date4 = DateTime.Today.AddWeeks(8);
 var date5 = DateTime.Today.AddDays(3).AddWeeks(2);

 var collectionOfDateTime = new List<DateTime>() { date1, date2, date3, date4, date5 };

I would want this function to return 2 lists (one list with date1, date2, and date4) and another list with date3 and date5.

Let me know if I need more examples to articulate the requirements? Note that is possible that one of the dates might fall into multiple output lists which is fine.

I can convert weekly into the number 7 and do a loop through each items starting with first item to the end, then the second item to the end, then the third item, etc. but wanted to see if there was a more elegant way


Solution

  • If I understand your question properly, you are trying to "chunk" the DateTime values by Day of the Week?

    If so, something like this should do it:

    var byDayOfWeek = collectionOfDateTime.GroupBy(dt => dt.DayOfWeek);
    // Optionally convert to a dictionary by day of week
    var asDict = byDayOfWeek.ToDictionary(grp => grp.Key, grp => grp.ToList());
    
    foreach(var kvp in asDict)
    {
        Console.WriteLine("Day:" + kvp.Key);
        foreach (var value in kvp.Value)
        {
            Console.WriteLine(value);
        }
    }
    

    Output:

    Day:Thursday
    2/21/2013 12:00:00 AM
    2/28/2013 12:00:00 AM
    4/18/2013 12:00:00 AM
    Day:Sunday
    2/24/2013 12:00:00 AM
    

    EDIT: For multiple "grouping by" rules:

    public enum ChunkType
    {
        Weekly,
        Monthly,
        Yearly
    }
    
    public IEnumerable<IEnumerable<DateTime>> ChunkDates(IEnumerable<DateTime> collection, ChunkType chunkBy)
    {
        switch(chunkBy)
        {
            case ChunkType.Weekly:
                // roughly equals by day of week
                return collection.GroupBy(dt => dt.DayOfWeek).Select(grp => grp.ToList());
            case ChunkType.Monthly:
                // Trickier - assume by ordinal day of month?
                return collection.GroupBy(dt => dt.Day).Select(grp => grp.ToList());
            case ChunkType.Yearly:
                // Trickier - assume by ordinal day of year?
                return collection.GroupBy(dt => dt.DayOfYear).Select(grp => grp.ToList());        
        }
        return new[]{ collection };
    }
    
    var date1 = DateTime.Today;
    var date2 = DateTime.Today.AddDays(7);
    var date3 = DateTime.Today.AddDays(3);
    var date4 = DateTime.Today.AddDays(8*7);
    
    var collectionOfDateTime = new List<DateTime>() { date1, date2, date3, date4};
    
    foreach(var type in new [] { ChunkType.Weekly, ChunkType.Monthly, ChunkType.Yearly })
    {
        Console.WriteLine("Now grouping by:" + type);
        var grouped = ChunkDates(collectionOfDateTime, type);
        foreach(var groupOfDates in grouped)
        {
            Console.WriteLine("New group!");
            foreach (var value in groupOfDates)
            {
                Console.WriteLine(value);
            }
        }
    }
    

    Output:

    Now grouping by:Weekly
    New group!
    2/21/2013 12:00:00 AM
    2/28/2013 12:00:00 AM
    4/18/2013 12:00:00 AM
    New group!
    2/24/2013 12:00:00 AM
    
    Now grouping by:Monthly
    New group!
    2/21/2013 12:00:00 AM
    New group!
    2/28/2013 12:00:00 AM
    New group!
    2/24/2013 12:00:00 AM
    New group!
    4/18/2013 12:00:00 AM
    
    Now grouping by:Yearly
    New group!
    2/21/2013 12:00:00 AM
    New group!
    2/28/2013 12:00:00 AM
    New group!
    2/24/2013 12:00:00 AM
    New group!
    4/18/2013 12:00:00 AM