.Net (C#) Date Range implementation using generics

Many objects have a start date and end date. With generics, we now have the ability to use nullable types that were previously not natively nullable.
 
In this sample, I create a DateRange class able to handle all ranges attainable with the DateTime structure. The start date must precede the end date. The Start and End dates can be null (infinite). When start date is null, it is equivalent to negative infinity. When end date is null, it is equivalent to positive infinity. Given two date ranges, it is able to calculate whether the ranges intersect and what the range of intersection is between the two ranges. Unlike the TimeSpan structure, this class retains the start and end date components of the range (hence the choice for the term "Range" instead of "Span").
 
 

public class DateRange : IEquatable<DateRange> {

    Nullable<DateTime> startDate, endDate;
    public DateRange() : this(new Nullable<DateTime>(), new Nullable<DateTime>()) { }
    public DateRange(Nullable<DateTime> startDate, Nullable<DateTime> endDate) {
        AssertStartDateFollowsEndDate(startDate, endDate);
        this.startDate = startDate;
        this.endDate = endDate;
    }
    public Nullable<TimeSpan> TimeSpan {
        getreturn endDate – startDate; }
    }
    public Nullable<DateTime> StartDate {
        get { return startDate; }
        set {
            AssertStartDateFollowsEndDate(value, this.endDate);
            startDate = value;
        }
    }
    public Nullable<DateTime> EndDate {
        get { return endDate; }
        set {
            AssertStartDateFollowsEndDate(this.startDate, value);
            endDate = value;
        }
    }
    private void AssertStartDateFollowsEndDate(Nullable<DateTime> startDate,
        Nullable<DateTime> endDate) {
            if ((startDate.HasValue && endDate.HasValue) &&
                (endDate.Value < startDate.Value))
                throw new InvalidOperationException("Start Date must be less than or equal to End Date");
    }
    public DateRange GetIntersection(DateRange other) {
        if (!Intersects(other)) throw new InvalidOperationException("DateRanges do not intersect");
            return new DateRange(GetLaterStartDate(other.StartDate), GetEarlierEndDate(other.EndDate));
    }
    private Nullable<DateTime> GetLaterStartDate(Nullable<DateTime> other) {
        return Nullable.Compare<DateTime>(startDate, other) >= 0 ? startDate : other;
    }
    private Nullable<DateTime> GetEarlierEndDate(Nullable<DateTime> other) {
        //!endDate.HasValue == +infinity, not negative infinity
        //as is the case with !startDate.HasValue
        if (Nullable.Compare<DateTime>(endDate, other) == 0) return other;
        if (endDate.HasValue && !other.HasValue) return endDate;
        if (!endDate.HasValue && other.HasValue) return other;
        return (Nullable.Compare<DateTime>(endDate, other) >= 0) ? other : endDate;
    }
    public bool Intersects(DateRange other) {
        if((this.startDate.HasValue && other.EndDate.HasValue &&
            other.EndDate.Value < this.startDate.Value) ||
            (this.endDate.HasValue && other.StartDate.HasValue &&
            other.StartDate.Value > this.endDate.Value) ||
            (other.StartDate.HasValue && this.endDate.HasValue &&
            this.endDate.Value < other.StartDate.Value) ||
            (other.EndDate.HasValue && this.startDate.HasValue &&
            this.startDate.Value > other.EndDate.Value)) {
                return false;
        }
        return true;
    }
    public bool Equals(DateRange other) {
        if (object.ReferenceEquals(other, null)) return false;
        return ((startDate == other.StartDate) && (endDate == other.EndDate));
    }
}

 

One thing to note is that it may be nice to have a collection of DateRange objects sort. The problem is that there is no clear item to sort on; StartDate?, EndDate?, TimeSpan? So, it is recommended practice to implement an external comparer for the public properties such as the following.
 

public class DateRangeComparerByStartDate : System.Collections.IComparer,
    System.Collections.Generic.IComparer<DateRange> {
    public int Compare(object x, object y) {
        if (!(x is DateRange) || !(y is DateRange))
            throw new System.ArgumentException("Value not a DateRange");
        return Compare((DateRange)x, (DateRange)y);
    }
    public int Compare(DateRange x, DateRange y) {
        if (x.StartDate < y.StartDate) { return -1; }
        if (x.StartDate > y.StartDate) { return 1; }
        return 0;
    }
}

 
I thought that a nice extension to this class would be to allow it to take in multiple ranges and find out whether they all intersect and be able to find the overlapping range(s) for them. I may think to implement this functionality later on.
Advertisements

One thought on “.Net (C#) Date Range implementation using generics

  1. tks you a lot. 

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s