Coder Perfect

What is the best way to iterate over a date range?

Problem

I’m not sure how to do this without resorting to some dreadful loop/counter solution. Here’s the issue:

I’ve been given two dates, a start date and an end date, and I need to take action at a predetermined interval. For example, I need to make a List entry for every day between 3/10/2009 and 3/26/2009 on every third day. So here are my suggestions:

DateTime StartDate = "3/10/2009";
DateTime EndDate = "3/26/2009";
int DayInterval = 3;

and I’d get a list with the following dates as output:

3/13/2009 3/16/2009 3/19/2009 3/22/2009 3/25/2009

So, how would I go about doing anything like this? I considered using a for loop to run through each day in the range with a distinct counter, as follows:

int count = 0;

for(int i = 0; i < n; i++)
{
     count++;
     if(count >= DayInterval)
     {
          //take action
          count = 0;
     }

}

However, it appears that there may be a better option.

Asked by Paul Mignard

Solution #1

So, one way or another, you’ll have to loop over them. I prefer to use the following syntax to define a method:

public IEnumerable<DateTime> EachDay(DateTime from, DateTime thru)
{
    for(var day = from.Date; day.Date <= thru.Date; day = day.AddDays(1))
        yield return day;
}

After that, you can put it to use as follows:

foreach (DateTime day in EachDay(StartDate, EndDate))
    // print it or whatever

You may do this by hitting every other day, every third day, only weekdays, and so on. For example, instead of calling AddDays(3) in the loop, you might simply call AddDays(3) in the loop to return every third day beginning with the “start” date (1).

Answered by mqp

Solution #2

In MiscUtil, I have a Range class that you might find useful. You could perform the following with the various extension methods:

foreach (DateTime date in StartDate.To(EndDate).ExcludeEnd()
                                   .Step(DayInterval.Days())
{
    // Do something with the date
}

(You may or may not want to leave off the end; I simply wanted to give you an example.)

This is essentially mquander’s answer in a ready-rolled (and more general-purpose) version.

Answered by Jon Skeet

Solution #3

You can use this as a model.

DateTime StartDate = new DateTime(2009, 3, 10);
DateTime EndDate = new DateTime(2009, 3, 26);
int DayInterval = 3;

List<DateTime> dateList = new List<DateTime>();
while (StartDate.AddDays(DayInterval) <= EndDate)
{
   StartDate = StartDate.AddDays(DayInterval);
   dateList.Add(StartDate);
}

Answered by Adriaan Stander

Solution #4

In extensions, code from @mquander and @Yogurt The Wise was used:

public static IEnumerable<DateTime> EachDay(DateTime from, DateTime thru)
{
    for (var day = from.Date; day.Date <= thru.Date; day = day.AddDays(1))
        yield return day;
}

public static IEnumerable<DateTime> EachMonth(DateTime from, DateTime thru)
{
    for (var month = from.Date; month.Date <= thru.Date || month.Month == thru.Month; month = month.AddMonths(1))
        yield return month;
}

public static IEnumerable<DateTime> EachDayTo(this DateTime dateFrom, DateTime dateTo)
{
    return EachDay(dateFrom, dateTo);
}

public static IEnumerable<DateTime> EachMonthTo(this DateTime dateFrom, DateTime dateTo)
{
    return EachMonth(dateFrom, dateTo);
}

Answered by Jacob Sobus

Solution #5

May it still be useful a year later.

To make it more versatile, this version contains a predicate.

var today = DateTime.UtcNow;
var birthday = new DateTime(2018, 01, 01);
var toBirthday = today.RangeTo(birthday);  
var toBirthday = today.RangeTo(birthday, x => x.AddMonths(2));
var toBirthday = today.RangeTo(birthday, x => x.AddYears(1));
// same result
var fromToday = birthday.RangeFrom(today);
var toBirthday = today.RangeTo(birthday);
public static class DateTimeExtensions 
{

    public static IEnumerable<DateTime> RangeTo(this DateTime from, DateTime to, Func<DateTime, DateTime> step = null)
    {
        if (step == null)
        {
            step = x => x.AddDays(1);
        }

        while (from < to)
        {
            yield return from;
            from = step(from);
        }
    }

    public static IEnumerable<DateTime> RangeFrom(this DateTime to, DateTime from, Func<DateTime, DateTime> step = null)
    {
        return from.RangeTo(to, step);
    }
}

If fromDate > toDate, you could issue an exception, but I prefer to return an empty range instead []

Answered by amd

Post is based on https://stackoverflow.com/questions/1847580/how-do-i-loop-through-a-date-range