views:

225

answers:

4

Hi. First question ever.

A few of my domain objects contain date ranges as a pair of start and end date properties:

public class Period {
  public DateTime EffectiveDate { get; set; }
  public DateTime ThroughDate { get; set; }
}

public class Timeline {
  public DateTime StartDate { get; set; }
  public DateTime EndDate { get; set; }
}

And I find myself with a lot of this:

abstract public int Foo(DateTime startDate, DateTime endDate);
abstract public decimal Bar(DateTime startDate, DateTime endDate);
abstract public ICollection<C5.Rec<DateTime, DateTime>> FooBar(DateTime startDate, DateTime endDate);

The last one made me wonder ... Should I implement a DateRange class? I'm not aware of one in the BCL.

In my experience, making the object hierarchy deeper often complicates things. These objects do get sent to RDLC reports displayed by the ReportViewer control, but that's secondary. I'll bend the view to the model rather than vice versa. We aren't tied to the property names, though, and would be willing to compromise with something like:

public class DateRange {
  public DateTime StartDate { get; set; }
  public DateTime EndDate { get; set; }
}

Period p = new Period();
DateTime t = p.EffectiveDateRange.StartDate;

A benefit of a DateRange class would be centralized validation of the end date coming after the start date, and it will simplify my method signatures:

abstract public int Foo(DateRange dateRange);
abstract public decimal Bar(DateRange dateRange);
abstract public ICollection<DateRange> FooBar(DateRange dateRange);

I'm just not sure that a DateRange class won't get me into more trouble than its worth. Opinions?

Side question: Did I miss a generic general-purpose tuple class in the BCL somewhere? I know there's some very specific ones floating around in various namespaces. Polluting my public domain method signatures with C5 types feels very, very dirty.

+1  A: 

If you do a lot of work with dates, yes - a range can be handy. This is actually one of those oh-so-rare cases where you should probably write it as a struct (immutable). Note, however, that "Noda Time" will probably give you all of this and more (when it is complete). I've done scheduling software before; I had a couple of such structs (for slightly different jobs).

Note, there isn't a handy BCL construct for this.

Also - think of all the wonderful methods (and possibly operators) that you can centralise when you have a range; "contains" (of a datetime? of another range? including/excluding limits?), "intersects", offset-by (a timespan), etc. A definite case for having a type to handle it. Note that at the ORM level, this is easier if your ORM supports composite values - I believe NHibernate does, and possibly EF 4.0.

Marc Gravell
Oh yes, Noda. Waiting for it....
o.k.w
Given that this is a refactoring to better handle date and range manipulation, I suppose it does make sense to give it a class.I only wish I was using NHibernate on this project. All those hours lost writing a quite nice if I say so myself, but pale in comparison, DAL. Next project, though. I'm playing with it right now to get the hang of it.
qstarin
A: 

I do not know of any native .NET class of the DateRange nature. The closest is probably DateTime+TimeSpan or DateTime/DateTime combination.

I think what you want is fairly sound.

o.k.w
+1  A: 

No, you didn't miss a general purpose class.

I have a Range type in MiscUtil which you may be interested in - and it certainly makes for simple DateTime manipulation. Referring to Marc's answer, I can't remember whether this is a struct or a class - you'd be welcome to change it of course.

It's nice and easy to step through, due to Marc's generics shenanigans (assuming you're using .NET 3.5, at least - it's feasible with 2.0 but not supported at the moment);

Range<DateTime> range = 19.June(1976).To(DateTime.Today);

foreach (DateTime date in range.Step(1.Days())
{
    // I was alive in this day
}

(That's also using a bunch of extension methods - more useful for test than production.)

To address the other point in Marc's answer, Noda Time will certainly be able to express the concept of a date more appropriately than the .NET API, but we don't have anything like a range at the moment... It's a nice idea though - I've added a feature request.

Jon Skeet
Sorry if I mis-spoke about Noda Time - it seems an excellent fit for people interested in time, though.
Marc Gravell
No, it's good - if you hadn't mentioned it, I'm not sure whether I'd have thought of adding a feature request to the project :) I think it's a pretty common requirement - the tricky bit will be working out what's needed to accommodate the various tweaks people will want!
Jon Skeet
Interesting syntax.
qstarin
I changed my accepted answer to yours. After reading through the Range<T> code I have decided to use the DateTime parameterized version of that for the core of my DateRange domain class. It gets me 80% of the way. Thank you for the contribution.
qstarin
Easy come, easy go... ;-)
Marc Gravell
A: 

As Mark and Jon already mentionned, I would create this as a value-type, which is immutable. I would opt to implement it as a struct, and implement the IEquatable and IComparable interfaces.

When using an ORM like NHibernate, you will be able to store the value type inside a table which represents an entity.

Frederik Gheysels
So it shouldn't be difficult to have a DateRange sub-object but keep the flat table structure with Start and End date columns using NHibernate (Fluent)? I wouldn't think so, but it's good to know ahead of time.
qstarin
Frederik Gheysels