Have you ever come across a scenario where you require to have start and end dates, validate whether these dates define valid duration, or determine overlapping duration/timespan in two given duration(s)?

If yes, here I present to you a beautiful MVVM ready, equitable, duration class which does all the above and a little more. 🙂

We will call this class TimeDuration.

TimeDuration implements two interfaces, IEquatable and INotifyPropertyChanged. In later parts we will see the implementation of methods for IEquatable, let’s have INotifyPropertyChanged implementation first. Include System.ComponentModel to your cs file and define a PropertyChangedEventHandler type public event PropertyChanged. Also create a private method onPropertyChanged with a return type void. The complete INotifyPropertyChanged implementation looks like this:

using System; using System.ComponentModel; namespace Demo.DateExtentions { public class TimeDuration : IEquatable, INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; private void onPropertyChanged(string name) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(name)); } } } }

Basic information encapsulation this class represent consists of start time and end time. So we will have two public DateTime type properties; Start and End in this class. We wish to notify our View when the basic information changes in VM so we will call onPropertyChanged method from inside settters of both these properties. Let’s implement the properties and define their supporting fields, like so :

DateTime start; DateTime end; public DateTime Start { get { return start; } set { if (start != value) { start = value; onPropertyChanged("Start"); } } } public DateTime End { get { return end; } set { if (end != value) { end = value; onPropertyChanged("End"); } } }

We need to have our DateTime structured initialized in the constructor so that our object based on this class is ready for use on initialization. Define constructor like so:

public TimeDuration() { start = new DateTime(); end = new DateTime(); }

(We could have initialized these fields at the time of defining the field and not have the constructor at all.)

Duration represents a time span, so our TimeDuration class will have a TimeSpan type public property named Duration. This property will return the difference of start and end times. Code is like this:

public TimeSpan Duration { get { return end - start; } }

Now this is the part which makes this class a smart duration class. We will make TimeDuration capable of telling intersecting duration/timespan between two given durations. Let’s see what I mean by intersecting durations. Refer to the following figure:



In the figure above AB, CD, EF, and GH represent durations. We want to know CB in the context of AB and CD, where starting part of CD overlaps with end part of AB. Our method should also be able to give us the overlap duration if one duration completely falls inside of another duration, as in AB and EF, where EF completely lies inside AB. The third scenario is where tail part of one duration overlaps with head part of another duration as in AB and GH where overlap is occurring at AH. So, let’s implement conditions and calculations. We will make it convenient for the caller of the method to receive either TimeSpan or our TimeDuration type in return.

public TimeSpan IntersectingSpan(TimeDuration other) { return getIntersection(other).Duration; } public TimeDuration IntersectingDuration(TimeDuration other) { return getIntersection(other); } private TimeDuration getIntersection(TimeDuration other) { if (this.Equals(other)) return this; DateTime iStart = this.Start < other.Start ? other.Start : this.Start; DateTime iEnd = this.End < other.End ? this.End : other.End; return iStart < iEnd ? new TimeDuration(iStart, iEnd) : new TimeDuration(); }

Last, we will implement IEquatable interface:

public bool Equals(TimeDuration compareWith) { return CompareWith.Start == this.Start && CompareWith.End == this.End; } public override int GetHashCode() { return _start.GetHashCode() ^ _end.GetHashCode(); }

The complete listing of the class looks like this:

/* * Disclaimer * Unless otherwise noted, code snippets in this repository are licensed under a Creative Commons Attribution 4.0 International license (http://creativecommons.org/licenses/by/4.0/) * Please do not forget to credit if you choose to use code in any which way. You can credit in any way you please as below: By Sanjay (https://sharpsnippets.wordpress.com/) By Sanjay (http://www.twitter.com/SanjayAtPilcrow) * Blog post about following code: http://wp.me/p2iWZr-4T * General Notes * - This is working code, but not production code. * - Code follows universal C# code convention but might not follow your company's internal convention. * - Code is more of POC and thus does not have full exception handling and parameter checking. * - If you choose to use the code in production, do re-code to make it production ready as per your org's engineering policy. */ using System; using System.ComponentModel; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace POCs.Sanjay.SharpSnippets.Dates { public class TimeDuration : IEquatable, INotifyPropertyChanged { DateTime start; DateTime end; public DateTime Start { get { return start; } set { if (start != value) { start = value; onPropertyChanged("Start"); } } } public DateTime End { get { return end; } set { if (end != value) { end = value; onPropertyChanged("End"); } } } public event PropertyChangedEventHandler PropertyChanged; public TimeDuration() { start = new DateTime(); end = new DateTime(); } public TimeDuration(DateTime start, DateTime end) { start = start; end = end; } public bool IsValidDuration { get { return _start <= _end; } } public TimeSpan Duration { get { return end - start; } } public TimeSpan IntersectingSpan(TimeDuration other) { return getIntersection(other).Duration; } public TimeDuration IntersectingDuration(TimeDuration other) { return getIntersection(other); } private TimeDuration getIntersection(TimeDuration other) { if (this.Equals(other)) return this; DateTime iStart = this.Start < other.Start ? other.Start : this.Start; DateTime iEnd = this.End < other.End ? this.End : other.End; return iStart < iEnd ? new TimeDuration(iStart, iEnd) : new TimeDuration(); } #region Equatable public bool Equals(TimeDuration compareWith) { return CompareWith.Start == this.Start && CompareWith.End == this.End; } public override int GetHashCode() { return _start.GetHashCode() ^ _end.GetHashCode(); } #endregion //Equatable #region notify property changed private void onPropertyChanged(string name) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(name)); } } #endregion } }

Find code on my GitHub repository.