using System;
///
/// 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.
///
/// AKA StartDate and EndDate. Split into a separate class so you don't
/// have to put StartDate/EndDate + associated mathematics all over your
/// classes.
///
public class TimePeriod : IEquatable
{
public DateTime Start { get; protected set; }
public DateTime? End { get; protected set; }
protected TimePeriod() { } // required for NHibernate
public TimePeriod(DateTime start, DateTime? end)
{
if (end.HasValue)
{
if (end.Value < start)
throw new ArgumentOutOfRangeException("end", "Time period cannot end before it begins");
if (end.Value.Equals(DateTime.MaxValue))
throw new ArgumentException("Time ranges with no end should use null not DateTime.Max", "end");
}
this.Start = start;
this.End = end;
}
public bool IsInfinite { get { return !End.HasValue; } }
public TimeSpan Duration
{
get
{
if (!End.HasValue)
return TimeSpan.MaxValue;
return End.Value - Start;
}
}
public bool Includes(DateTime date)
{
if (date < Start)
return false;
return !End.HasValue || End.Value >= date;
}
public override bool Equals(object obj)
{
return Equals(obj as TimePeriod);
}
public bool Equals(TimePeriod other)
{
if (other == null)
return false;
if (Object.ReferenceEquals(this, other))
return true;
return this.Start.Equals(other.Start)
&& this.End.Equals(other.End);
}
public override string ToString()
{
if (End.HasValue)
return String.Format("[{0} to {1}]", Start, End.Value);
return String.Format("[{0} to infinity]", Start);
}
public bool StartsBefore(TimePeriod other)
{
if (other == null)
return false;
return this.Start < other.Start;
}
public bool StartsAfter(TimePeriod other)
{
if (other == null)
return false;
return this.Start > other.Start;
}
public bool EndsBefore(TimePeriod other)
{
if (other == null)
return false;
if (this.IsInfinite)
return false;
return other.IsInfinite || this.End < other.End;
}
public bool EndsAfter(TimePeriod other)
{
if (other == null)
return false;
if (other.IsInfinite)
return false;
return this.IsInfinite || this.End > other.End;
}
public bool ImmediatelyPrecedes(TimePeriod other)
{
return this.End.Equals(other.Start);
}
public bool ImmediatelyFollows(TimePeriod other)
{
return this.Start.Equals(other.End);
}
public bool Overlaps(TimePeriod other)
{
if (this.Start > other.Start &&
(other.End < this.Start || other.IsInfinite))
return true;
return other.Start > this.Start &&
(this.End > other.Start || this.IsInfinite);
}
///
/// Factory method to create a time period from two strings. End one
/// may be null or empty.
///
public static TimePeriod Parse(string startValue, string endValue)
{
if (String.IsNullOrEmpty(startValue))
throw new ArgumentException("Time period start cannot be null or empty.", "startValue");
DateTime start = DateTime.Parse(startValue);
DateTime? end = null;
if (!String.IsNullOrEmpty(endValue))
end = DateTime.Parse(endValue);
return new TimePeriod(start, end);
}
public TimePeriod GetRemainingSlice()
{
return GetRemainingSliceAsAt(DateTime.Now);
}
///
/// if there is any time period remaining from the specified date
/// and the end of the TimePeriod then return that. If the specified
/// time has passed (there is no time slice remaining, it is expired)
/// then return null
///
public TimePeriod GetRemainingSliceAsAt(DateTime now)
{
if (HasPassedAsAt(now))
return null;
return new TimePeriod
{
Start = now,
End = this.End
};
}
public bool HasPassed()
{
return HasPassedAsAt(DateTime.Now);
}
public bool HasPassedAsAt(DateTime now)
{
return End.Value < now;
}
public float GetPercentageElapsed()
{
return GetPercentageElapsedAsAt(DateTime.Now);
}
public float GetPercentageElapsedAsAt(DateTime now)
{
if (IsInfinite)
return 0.0f; // otherwise divide by zero
TimeSpan elapsed = now - Start;
TimeSpan total = End.Value - Start;
return (float)elapsed.Ticks / (float)total.Ticks;
}
public override int GetHashCode()
{
int x = this.Start.GetHashCode();
if (this.End.HasValue)
x ^= this.End.Value.GetHashCode();
return x;
}
}