HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpMinor

Method to return date ranges of 1 year

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
yearmethodreturndateranges

Problem

I made a method that takes 2 DateTimes as a parameter, a startDate and a stopDate.

The routine should return a list of DateTime ranges by checking if the range in the parameters is above one year TimeSpan. If it is, it should separate the range into smaller ranges of one year, starting form startDate and adding a year until reaching the limit, and then adjusting the last range accordingly (as it might only contain a few months).

Can this method be made better or even recursive?

// startDate = 2016-01-01        
// endDate = 2020-06-01

List> list = GetYearsBetweenDates(startDate, endDate);

//list = 2016-01-01 2016-12-31 
//       2017-01-01 2017-12-31 
//       2018-01-01 2018-12-31 
//       2019-01-01 2019-12-31 
//       2020-01-01 2020-06-01

private List> GetYearsBetweenDates(DateTime startDate, DateTime stopDate)
{
    var list = new List>();
    var oneYear = TimeSpan.FromDays(365);
    var tempStopDate = startDate + oneYear;
    var tempStartDate = startDate;
    bool first = true;

    if (stopDate - startDate (startDate, stopDate));
        return list;
    }

    while (tempStopDate != stopDate)
    {
        list.Add(new Tuple(tempStartDate, tempStopDate));
        if (first)
        {
            tempStartDate += oneYear.Add(TimeSpan.FromDays(1)); // We should not have the same date twice
            first = false;
        }
        else
            tempStartDate += oneYear;

        tempStopDate += oneYear;

        if (tempStopDate > stopDate)
        {
            tempStopDate = stopDate;
            list.Add(new Tuple(tempStartDate, stopDate));
        }
    }

    return list;
}

Solution

If you perform all calcuclations dynamically your method becomes a simple few lines long loop:

private IEnumerable GetYearsBetweenDates(DateTime startDate, DateTime stopDate)
{
    for (int i = startDate.Year; i <= stopDate.Year; i++)
    {
        yield return new DateTimeRange
        (
            start: new DateTime(i, startDate.Month, startDate.Day),
            end:
             i == stopDate.Year 
             ? new DateTime(i, stopDate.Month, stopDate.Day).AddYears(1) 
             : new DateTime(i, startDate.Month, startDate.Day).AddYears(1)
        );
    }
}


So what I have I changed?

The method returns an IEnumerable and uses the yield return which makes it deferred (this means it is executed only if enumerated).

The T is a new struct that is easier to understand than a Tuple:

struct DateTimeRange
{
    public DateTimeRange(DateTime start, DateTime end)
    {
        Start = start;
        End = end;
    }
    public DateTime Start { get; }
    public DateTime End { get; }
}


To get all dates you call it with ToList or ToArray

var dates = GetYearsBetweenDates(startDate, endDate).ToList();


or you put it in a foreach loop

foreach(var range in GetYearsBetweenDates(startDate, endDate))
{
    // do something
}


You can also exagerate and make it pure LINQ method without any manual loop but personaly I think the for loop looks better:

private IEnumerable GetYearsBetweenDates3(DateTime startDate, DateTime stopDate)
{
    return Enumerable.Range(startDate.Year, stopDate.Year - startDate.Year + 1)
        .Select(x => new DateTimeRange
        (
            start: new DateTime(x, startDate.Month, startDate.Day),
            end:
             x == stopDate.Year 
             ? new DateTime(x, stopDate.Month, stopDate.Day).AddYears(1) 
             : new DateTime(x, startDate.Month, startDate.Day).AddYears(1)
        ));
}


What else?

var list = new List>();


You should always choose meaningful names for all variables. You did it well for the others. This could have been results or dates.

var oneYear = TimeSpan.FromDays(365);


It's safer to use the AddYear(1) method rather then hardcoding the number. Especially if you don't want to care about leapyears.

Code Snippets

private IEnumerable<DateTimeRange> GetYearsBetweenDates(DateTime startDate, DateTime stopDate)
{
    for (int i = startDate.Year; i <= stopDate.Year; i++)
    {
        yield return new DateTimeRange
        (
            start: new DateTime(i, startDate.Month, startDate.Day),
            end:
             i == stopDate.Year 
             ? new DateTime(i, stopDate.Month, stopDate.Day).AddYears(1) 
             : new DateTime(i, startDate.Month, startDate.Day).AddYears(1)
        );
    }
}
struct DateTimeRange
{
    public DateTimeRange(DateTime start, DateTime end)
    {
        Start = start;
        End = end;
    }
    public DateTime Start { get; }
    public DateTime End { get; }
}
var dates = GetYearsBetweenDates(startDate, endDate).ToList();
foreach(var range in GetYearsBetweenDates(startDate, endDate))
{
    // do something
}
private IEnumerable<DateTimeRange> GetYearsBetweenDates3(DateTime startDate, DateTime stopDate)
{
    return Enumerable.Range(startDate.Year, stopDate.Year - startDate.Year + 1)
        .Select(x => new DateTimeRange
        (
            start: new DateTime(x, startDate.Month, startDate.Day),
            end:
             x == stopDate.Year 
             ? new DateTime(x, stopDate.Month, stopDate.Day).AddYears(1) 
             : new DateTime(x, startDate.Month, startDate.Day).AddYears(1)
        ));
}

Context

StackExchange Code Review Q#146422, answer score: 4

Revisions (0)

No revisions yet.