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