206

I am generating multi-series graphs with the date along the X-Axis.

The problem is that not all of the series in the graph have the same dates in the date range. Meaning that if I choose 1 Feb through 30 Apr that one series may have data that starts at 1 Feb but only goes through the end of March but another series may have data for the entire date range.

This skews the charts I need to create. Go, given the date range taken at the begining of the query I'd like to generate a list of dates and populate the data to be graphed, padding those series with 0's for those dates that have no data.

Kiquenet
  • 14,494
  • 35
  • 148
  • 243
Andy Evans
  • 6,997
  • 18
  • 72
  • 118

4 Answers4

450

LINQ:

Enumerable.Range(0, 1 + end.Subtract(start).Days)
          .Select(offset => start.AddDays(offset))
          .ToArray(); 

For loop:

var dates = new List<DateTime>();

for (var dt = start; dt <= end; dt = dt.AddDays(1))
{
   dates.Add(dt);
}

EDIT: As for padding values with defaults in a time-series, you could enumerate all the dates in the full date-range, and pick the value for a date directly from the series if it exists, or the default otherwise. For example:

var paddedSeries = fullDates.ToDictionary(date => date, date => timeSeries.ContainsDate(date) 
                                               ? timeSeries[date] : defaultValue);
Ani
  • 111,048
  • 26
  • 262
  • 307
  • I follow the LINQ example until the `Select` statement. What exactly is happening there? – Cody Apr 06 '12 at 16:25
  • 8
    If you want a range that goes for 5 days, for example 4/9 - 4/13, the call to Enumerable.Range would create a collection {0,1,2,3,4}. The select statement takes that collection and for each element, adds that value to the start date and returns the new date. #4/9/2012#.AddDays(0) = 4/9/2012, #4/9/2012#.AddDays(1) = 4/10/2012, #4/9/2012#.AddDays(2) = 4/11/2012, and so on, thus generating the range of dates. – Wes P Apr 10 '12 at 16:29
  • 8
    You should probably use `TotalDays` and not `Days` – yellowblood May 15 '14 at 15:27
  • 3
    @yellowblood: Why? Fractional days are not required. – Ani May 29 '14 at 04:57
  • @Matt Mitchell: I've reverted the edit you made where you claimed to be fixing a bug. Could you clarify what sort of bug you were attempting to fix? – Ani May 29 '14 at 04:59
  • 1
    My apologies, misread. Correct to retract the edit. – Matt Mitchell May 29 '14 at 05:12
  • Condition to check the created date(based on offset) is less than or equal to end date is missing – Bose_geek Dec 07 '16 at 17:28
  • how to use or call your 1st set of code @Ani – Mou Mar 01 '17 at 13:48
  • 4
    I really like this for-loop. It's not everyday you see a loop on `DateTime`. – Rachel Martin Jul 12 '19 at 15:10
  • 1
    while Linq is always nice, and often more legible than its looping counterpart, the for loop here is much more immediately legible ... thanks for including both! – heug Dec 22 '21 at 19:20
44
public static IEnumerable<DateTime> GetDateRange(DateTime startDate, DateTime endDate)
{
    if (endDate < startDate)
        throw new ArgumentException("endDate must be greater than or equal to startDate");

    while (startDate <= endDate)
    {
        yield return startDate;
        startDate = startDate.AddDays(1);
    }
}
Anthony Pegram
  • 123,721
  • 27
  • 225
  • 246
  • If you want to compare just the Date portion, modify the while condition: while (startDate.Date <= endDate.Date). Otherwise, you'll get a different range depending on how the DateTime value is constructed. – Erik K. Dec 01 '16 at 17:30
  • Since the loop should end when it equals to end date because we need dates between two start and end so end date should not include but the condition <= also return enddate. –  May 24 '17 at 12:43
  • I read the following explanations but still do not understand why you are using yield... https://stackoverflow.com/questions/39476/what-is-the-yield-keyword-used-for-in-c – petrosmm Jun 27 '18 at 19:00
  • 1
    @petrosmm this may be too late, as I believe you must have understood why, but they used yield so they could generate a list of `DateTime` as demanded by the calling method instead of creating it at once (i.e. using an empty list of date and adding values to it, then return). – Isaac Ikusika Nov 22 '22 at 03:19
41

I know this is an old post but try using an extension method:

    public static IEnumerable<DateTime> Range(this DateTime startDate, DateTime endDate)
    {
        return Enumerable.Range(0, (endDate - startDate).Days + 1).Select(d => startDate.AddDays(d));
    }

and use it like this

    var dates = new DateTime(2000, 1, 1).Range(new DateTime(2000, 1, 31));

Feel free to choose your own dates, you don't have to restrict yourself to January 2000.

Steve Adams
  • 561
  • 4
  • 8
4

Our resident maestro Jon Skeet has a great Range Class that can do this for DateTimes and other types.

Shadow The GPT Wizard
  • 66,030
  • 26
  • 140
  • 208
Dan Diplo
  • 25,076
  • 4
  • 67
  • 89