One of the applications I work on is a planning system, used for managing the operations of the business over the next week, month and financial year.
Almost every entity in this application has a fixed ‘applicable period’ — a lifetime that begins and ends at certain dates. For example:
- An employee’s applicable period lasts as long as they are employed
- A business unit’s lifetime lasts from the day it’s formed, to the day it disbands
- A policy lasts from the day it comes into effect to the day it ends
- A shift starts at 8am and finishes at 6pm
Previous incarnations of the application simply added StartDate and EndDate properties to every object, and evaluated them ad-hoc as required. This resulted in a lot of code duplication — date and time logic around overlaps, contiguous blocks etc were repeated all over the place.
As we’ve been carving off bounded contexts and reimplementing them using DDD, I’m proud to say this concept has been identified and separated out into an explicit value type with encapsulated behaviour. We call it a Time Period:
It’s sort of like a .NET TimeSpan but represents a specific period of time, e.g. seven days starting from yesterday morning — not seven days in general.
Here’s the behaviour we’ve implemented so far, taking care of things like comparisons and overlapping periods:
/// <summary>
/// A value type to represent a period of time with known end points (as
/// opposed to just a period like a timespan that could happen anytime).
/// The end point of a TimeRange can be infinity.
/// </summary>
public class TimePeriod : IEquatable<TimePeriod>
{
public DateTime Start { get; }
public DateTime? End { get; }
public bool IsInfinite { get; }
public TimeSpan Duration { get; }
public bool Includes(DateTime date);
public bool StartsBefore(TimePeriod other);
public bool StartsAfter(TimePeriod other);
public bool EndsBefore(TimePeriod other);
public bool EndsAfter(TimePeriod other);
public bool ImmediatelyPrecedes(TimePeriod other);
public bool ImmediatelyFollows(TimePeriod other);
public bool Overlaps(TimePeriod other);
public TimePeriod GetRemainingSlice();
public TimePeriod GetRemainingSliceAsAt(DateTime when);
public bool HasPassed();
public bool HasPassedAsAt(DateTime when);
public float GetPercentageElapsed();
public float GetPercentageElapsedAsAt(DateTime when);
}
Encapsulating logic all in one place means we can get rid of all that ugly duplication (DRY), and it still maps cleanly to StartDate/EndDate columns in the database as an NHibernate component or IValueType.
You can grab our initial implementation here:
November 25th, 2009 | 7 Comments



November 25th, 2009 at 1:32 pm
Very nice stuff. This class looks remarkably similar to CalendarInterval, a class I wrote for my Java applications.
November 26th, 2009 at 9:34 pm
It makes me nervous that you represent semi-infinite intervals going forwards but not going backwards (“before next Tuesday” as opposed to “from now on”). I suppose you could use the beginning of the epoch, but it seems ugly.
Then again, I can’t see much use for (\infty, n] intervals in a business context.
November 26th, 2009 at 9:40 pm
@pozorvlak: you could have a null start for “since the start of time until X” but we haven’t needed that yet.
February 12th, 2010 at 2:11 pm
What do you use to make your diagram? It’s very nice.
February 12th, 2010 at 2:14 pm
Thanks, they’re generated live from http://yuml.me
March 13th, 2010 at 9:50 pm
Very nice.
Something alike should be included in .net.
I’ve added 4 more methods, what do you think of them?
///
/// Splits TimePeriod
///
/// Any Time (within or out of TimePeriod)
/// 1 or 2 new Elements
public IEnumerable Split(DateTime splitTime)
{
if (splitTime > Start && (!End.HasValue || End.Value > splitTime))
return new TimePeriod[] { new TimePeriod(Start, splitTime), new TimePeriod(splitTime, End) };
else
return new TimePeriod[] { (TimePeriod)this.MemberwiseClone() };
}
///
/// Removes/Substracts onther Timeperiod
///
/// To be substracted
/// returns 0, 1 or 2 new Elements
public IEnumerable Remove(TimePeriod other)
{
List result = new List();
// everything left over
if (!Overlaps(other))
result.Add(new TimePeriod(this.Start, this.End));
// left side or right side or both sides or nothing left over
else
{
// left ok
if (this.Start other.End))
result.Add(new TimePeriod(other.End.Value, this.End));
}
return result;
}
///
/// Compares 2 timelines and returns overlaps
///
/// Timeline1
/// Timeline2
/// new timeline of overlaps
public static IEnumerable GetOverlaps(IEnumerable timePeriods1, IEnumerable timePeriods2)
{
List result = new List();
foreach (TimePeriod t1 in timePeriods1)
{
foreach (TimePeriod t2 in timePeriods2)
{
// t1 eats t2
if (t1.Start = t2.End))
result.Add(new TimePeriod(t2.Start, t2.End));
// t2 eats t1
else if (t1.Start >= t2.Start && (t2.IsInfinite || t1.End <= t2.End))
result.Add(new TimePeriod(t1.Start, t1.End));
// t1 touches t2 on t1's right side
else if (t1.Start t2.Start)
result.Add(new TimePeriod(t2.Start, t1.End));
// t1 touches t2 on t1′s left side
else if (t1.Start > t2.Start && t1.Start < t2.End)
result.Add(new TimePeriod(t1.Start, t2.End));
}
}
return result;
}
///
/// Removes TimePeriods from a timeline
///
/// Timeline
/// TimePeriods that (might) overlap and shall be removed from timeline
/// new timeline without overlaps
public static IEnumerable RemoveOverlaps(IEnumerable timePeriods, IEnumerable overlaps)
{
List result = timePeriods.ToList();
for (int i = 0; i < result.Count; i++)
{
foreach (TimePeriod overlap in overlaps)
{
if (result[i].Overlaps(overlap))
{
// Add rest of clash (if exists) to end to be processed as well
result.AddRange(result[i].Remove(overlap));
// Remove overlapping TimePeriod
result.RemoveAt(i);
}
}
}
return result;
}
March 29th, 2010 at 9:16 am
Daniel : Love it. I think we needed a Split method before, good idea.