C# | .NET : Smart Duration Class

C# | .NET : Smart Duration Class

[Originally posted on 11 October 13]

Sometimes you need to have start and end dates, validate whether these dates define valid duration, or determine overlapping duration/timespan in two given duration(s)? I wrote an MVVM ready, equitable, duration class which does all the above and a little more. :-). I call this class TimeDuration. Let’s go through the code of the class.

TimeDuration implements two interfaces,  IEquatable and INotifyPropertyChanged. In later parts we will see the implementation of methods for IEquatable, let’s see INotifyPropertyChanged implementation first.  It has System.ComponentModel and a PropertyChangedEventHandler type public event PropertyChanged. It has 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));
            }
        }
    }
}

The class encapsulate two basic information elements – start time and end time. To expose this information class has two public DateTime type properties; Start and End. To notify views when the basic information changes in VM the class calls onPropertyChanged method from inside settters of both these properties. The implementation code looks like this:

        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");
                }
            }
        }

The class initializes DateTime structured in the constructor so that object based on this class is ready for use on initialization. Constructor looks like so:

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

(Fields could have been initialized at the time of defining the field and not have the constructor at all.)
Duration represents a time span, so TimeDuration class has a TimeSpan type public property named Duration. This property returns the difference of start and end times. Code is like this:

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

This part makes the class a smart duration class. TimeDuration is capable of telling intersecting duration/timespan between two given durations. Following figure illustrates intersecting durations:
DurationFigure

In the figure above AB, CD, EF, and GH represent durations. Class returns information regarding CB in the context of AB and CD, where starting part of CD overlaps with end part of AB. The method returns 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. Following is the code to return appropriate type, TimeSpan or TimeDuration, after checking the intersection.

        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();
        }

IEquatable interface implementation:

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

The entire SmartDuration class:

/*
 * 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.

 

Nifty Extension Methods For DateTime in C#

Aztec

These are some easy and quick date extensions which will make your life easier with dates. You can copy these extension methods in a CS file in your project and Intellisense will be more than happy to join these methods in the armory of its DateTime methods.

Intellisense

Let’s get to coding.

Have a CS file in your project containing code like so:

using System;
using System.Threading;
using MyProject.Extensions;

namespace MyProject.Extensions
{
    public static class DateExtensions
    {
    }
}

We will write all our static extension methods inside this DateExtensions class.

Start Date of The Week

You wish to get start date of the week to which the date belongs. You also want that the date returned should adhere to the culture of the device/thread because different cultures have different first day of week. In some cultures Monday is the first day of the week, in some Sunday, and in some Friday. Usage scenario where you might want to use this method is – Sub-total of some value in the duration of the week of the give date or current date.  Here is the method:

        public static DateTime StartDateOfTheWeek(this DateTime dt)
        {
            DateTime _returnDateTime = dt.AddDays(-((dt.DayOfWeek - Thread.CurrentThread.CurrentCulture.DateTimeFormat.FirstDayOfWeek)));
            return _returnDateTime;
        }

Call this method like so:

            DateTime _dt = new DateTime();
            DateTime _weekStart = _dt.StartDateOfTheWeek();

End Date of The Week

This is going to be simple. We will get first date of the week, from the method above, and add 6 days.

        public static DateTime EndDateOfTheWeek(this DateTime dt)
        {
            return dt.StartDateOfTheWeek().AddDays(6);
        }

Calling this method is simple:

            DateTime _dt = new DateTime();
            DateTime _weekEnd = _dt.EndDateOfTheWeek();

Start Date of The Month

Getting start date of the month is simple:

        public static DateTime StartDateOfTheMonth(this DateTime dt)
        {
            return new DateTime(dt.Year, dt.Month, 1);
        }

End Date of The Month

Getting end date of the month requires a little tweaking. We will add number of days in the month to the start date and then subtract one day to find the end date of the month.

        public static DateTime EndDateOfCurrentMonth(this DateTime dt)
        {
            return DateTime.Now.StartDateOfTheMonth().AddDays(DateTime.DaysInMonth(dt.Year, dt.Month) - 1);
        }

Following is the complete listing of the code. You can copy/paste this code in a CS file in your project, compile the code, and have your DateTime object call these methods.

using System;
using System.Threading;
using MyProject.Extensions;

namespace MyProject.Extensions
{
    public static class DateExtensions
    {
        public static DateTime StartDateOfTheWeek(this DateTime dt)
        {
            DateTime _returnDateTime = dt.AddDays(-((dt.DayOfWeek - Thread.CurrentThread.CurrentCulture.DateTimeFormat.FirstDayOfWeek)));
            return _returnDateTime;
        }
        public static DateTime EndDateOfCurrentWeek(this DateTime dt)
        {
            return dt.StartDateOfTheWeek().AddDays(6);
        }
        public static DateTime StartDateOfTheMonth(this DateTime dt)
        {
            return new DateTime(dt.Year, dt.Month, 1);
        }
        public static DateTime EndDateOfCurrentMonth(this DateTime dt)
        {
            return DateTime.Now.StartDateOfTheMonth().AddDays(DateTime.DaysInMonth(dt.Year, dt.Month) - 1);
        }
    }
}

Happy Date-ing! 😀