public struct DateTimeSpan : IEquatable<DateTimeSpan>, IComparable, IComparable<DateTimeSpan> { #region Fields //______________________________________________________________________ private Int32 _Years; public Int32 Years { get { return _Years; } set { if (!value.Between(0, 9999)) throw new ArgumentOutOfRangeException("Years", "between 0 and 9999"); _Years = value ; } } //______________________________________________________________________ private byte _Months; public Int32 Months { get { return _Months; } set { if (!value.Between(0, 11)) throw new ArgumentOutOfRangeException("Months", "between 0 and 11"); _Months = unchecked((byte)value); } } //______________________________________________________________________ private byte _Days; public Int32 Days { get { return _Days; } set { if (!value.Between(0, 30)) throw new ArgumentOutOfRangeException("Days", "between 0 and 30"); _Days = unchecked((byte)value); } } //______________________________________________________________________ private byte _Hours; public Int32 Hours { get { return _Hours; } set { if (!value.Between(0, 23)) throw new ArgumentOutOfRangeException("Hours", "between 0 and 23"); _Hours = unchecked((byte)value); } } //______________________________________________________________________ private byte _Minutes; public Int32 Minutes { get { return _Minutes; } set { if (!value.Between(0, 59)) throw new ArgumentOutOfRangeException("Hours", "between 0 and 59"); _Minutes = unchecked((byte)value); } } //______________________________________________________________________ private byte _Seconds; public Int32 Seconds { get { return _Seconds; } set { if (!value.Between(0, 59)) throw new ArgumentOutOfRangeException("Seconds", "between 0 and 59"); _Seconds = unchecked((byte)value); } } //______________________________________________________________________ private ushort _MilliSeconds; public Int32 MilliSeconds { get { return _MilliSeconds; } set { if (!value.Between(0, 999)) throw new ArgumentOutOfRangeException("MilliSeconds", "between 0 and 999"); _MilliSeconds = unchecked((ushort)value); } } //______________________________________________________________________ //______________________________________________________________________ #endregion Fields #region Constructors //______________________________________________________________________ public DateTimeSpan(int years, int months, int days) : this(years, months, days, 0,0,0, 0) { } //______________________________________________________________________ public DateTimeSpan( int years, int months, int days, int hours, int minutes, int seconds ) : this(years, months, days, hours, minutes, seconds, 0) { } //______________________________________________________________________ public DateTimeSpan( int years, int months, int days, int hours, int minutes, int seconds, int milliSeconds ) { HashCode = _Years = _MilliSeconds = _Months = _Days = _Hours = _Minutes = _Seconds = 0; HasCodePresent = false; Years = years; Months = months; Days = days; Hours = hours; Minutes = minutes; Seconds = seconds; MilliSeconds = milliSeconds; } //______________________________________________________________________ /* * start: 2012/03/14 * end: 2022/04/15 * years: 2022 - 2012, * months: 04-03 * days 2022/04/14 >> 2022/04/15 = 1 * * start: 2012/03/14 * end: 2022/02/13 * years: 2022 - 2012 + 1 * months: 02 - 03 + 12 * days: 2022/01/14 >> 2022/02/13 = 30 */ public DateTimeSpan(DateTime start, DateTime end) { if (start > end) throw new ArgumentException("startDate <= endDate"); HashCode = _Years = _MilliSeconds = _Months = _Days = _Hours = _Minutes = _Seconds = 0; HasCodePresent = false; Years = end.Year - start.Year; int months = end.Month - start.Month; if (months < 0) { months += 12; Years--; } TimeSpan tsStart = new TimeSpan(start.Day, start.Hour, start.Minute, start.Second, start.Millisecond); TimeSpan tsEnd = new TimeSpan(end.Day, end.Hour, end.Minute, end.Second, end.Millisecond); if (tsStart > tsEnd) { if (--months < 0) { months += 12; Years--; } } Months = months; DateTime between = start.AddYears(Years).AddMonths(months); TimeSpan delta = end - between; Days = delta.Days; Hours = delta.Hours; Minutes = delta.Minutes; Seconds = delta.Seconds; MilliSeconds = delta.Milliseconds; } //______________________________________________________________________ #endregion Constructors //______________________________________________________________________ #region ToString //______________________________________________________________________ public override string ToString() { return string.Format("Years:{0} Months:{1} Days:{2} Hours:{3} Minutes:{4} Seconds:{5}", Years, Months, Days, Hours, Minutes, Seconds); } //______________________________________________________________________ public string ToString(bool comparable) { if (!comparable) return ToString(); return String.Format("{0:0000}{1:00}{2:00}{3:00}{4:00}{5:00}{6:000}", Years,Months, Days, Hours, Minutes, Seconds, MilliSeconds); } //______________________________________________________________________ #endregion ToString #region IComparable //______________________________________________________________________ public int CompareTo(object other) { if (other == null) return 1; if (!(other is DateTimeSpan)) throw new ArgumentException("not a DateTimeSpan", "other"); return CompareTo((DateTimeSpan)other); } //______________________________________________________________________ #endregion IComparable #region IComparable<T> //______________________________________________________________________ public int CompareTo(DateTimeSpan other) { return String.CompareOrdinal(ToString(true), other.ToString(true)); } //______________________________________________________________________ #endregion IComparable<T> #region Equals //______________________________________________________________________ public override bool Equals(object other) { return other is DateTimeSpan ? Equals((DateTimeSpan)other) : false; } //______________________________________________________________________ #endregion Equals #region IEquatable<T> //______________________________________________________________________ public bool Equals(DateTimeSpan other) { return CompareTo(other) == 0; } //______________________________________________________________________ #endregion IEquatable<T> #region GetHashCode //______________________________________________________________________ private bool HasCodePresent; private int HashCode; public override int GetHashCode() { if (!HasCodePresent) { using (RandomNumberGenerator rnd = RandomNumberGenerator.Create()) { byte[] bytes = new byte[ByteSize.Int32]; rnd.GetBytes(bytes); HashCode = BitConverter.ToInt32(bytes, 0); } } return HashCode; } //______________________________________________________________________ #endregion GetHashCode #region Operators //______________________________________________________________________ public static bool operator ==(DateTimeSpan dts1, DateTimeSpan dts2) { return dts1.Equals(dts2); } //______________________________________________________________________ public static bool operator !=(DateTimeSpan dts1, DateTimeSpan dts2) { return !(dts1 == dts2); } //______________________________________________________________________ public static bool operator <(DateTimeSpan dts1, DateTimeSpan dts2) { return dts1.CompareTo(dts1) < 0; } //______________________________________________________________________ public static bool operator >=(DateTimeSpan dts1, DateTimeSpan dts2) { return !(dts1 < dts2); } //______________________________________________________________________ public static bool operator >(DateTimeSpan dts1, DateTimeSpan dts2) { return dts1.CompareTo(dts1) > 0; } //______________________________________________________________________ public static bool operator <=(DateTimeSpan dts1, DateTimeSpan dts2) { return !(dts1 < dts2); } //______________________________________________________________________ #endregion Operators }Extension Method Between()
public static partial class ExtensionMethods_Comparison { //______________________________________________________________________ /// <summary> /// tells whether s between s1 and s2 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="s"></param> /// <param name="s1"></param> /// <param name="s2"></param> /// <returns>true or false</returns> // // IComparable<T> // - By definition, any object compares greater than a null reference // - two null references compare equal to each other public static bool Between<T>(this T s, T s1, T s2) where T : IComparable<T> { if (s != null) return (s.CompareTo(s1) >= 0) && (s.CompareTo(s2) <= 0); // s == null here /* * s1 s s2 s1<=s s<=s2 final * !=null null !null false true false * null null !null true true true * * !null null null false true false * null null null true true true * */ /* * conclusion * It does not matter whether s2 is null or not * s < non-null-s2 * s == null-s2 * hence always: s <= s2 * * the only thing that matters is s1 * non_null_s1 < s * null_s1 == s */ return (s1 == null); } //______________________________________________________________________ }Constants
public class ByteSize { public const int Byte = 1; public const int SByte = 1; public const int Char = 2; public const int Int16 = 2; public const int UInt16 = 2; public const int Int32 = 4; public const int UInt32 = 4; public const int Int64 = 8; public const int UInt64 = 8; }test program
class Class1 { static void Main(string[] args) { Test(); Console.ReadLine(); } static DateTimeSpanTest[] TestData = { new DateTimeSpanTest( new DateTime(2011, 04, 15, 0, 0, 0), new DateTime(2012, 03, 14, 0, 0, 0), new DateTimeSpan(0, 10, 28) ) ,new DateTimeSpanTest( new DateTime(2011, 04, 15, 17, 0, 0), new DateTime(2012, 04, 15, 16, 59, 59), new DateTimeSpan(0, 11, 30, 23, 59, 59) ) ,new DateTimeSpanTest( new DateTime(2011, 04, 15, 17, 0, 0), new DateTime(2013, 04, 15, 16, 59, 59), new DateTimeSpan(1, 11, 30, 23, 59, 59) ) ,new DateTimeSpanTest( new DateTime(2011, 04, 15, 17, 0, 0), new DateTime(2011, 04, 16, 16, 59, 59), new DateTimeSpan(0, 0, 0, 23, 59, 59) ) ,new DateTimeSpanTest( new DateTime(2012, 02, 28, 17, 0, 0), new DateTime(2012, 03, 01, 16, 59, 59), new DateTimeSpan(0, 0, 1, 23, 59, 59) ) ,new DateTimeSpanTest( new DateTime(2011, 02, 28, 17, 0, 0), new DateTime(2011, 03, 01, 16, 59, 59), new DateTimeSpan(0, 0, 0, 23, 59, 59) ) ,new DateTimeSpanTest( new DateTime(2011, 02, 28, 17, 0, 0), new DateTime(2011, 03, 01, 18, 01, 02), new DateTimeSpan(0, 0, 1, 01, 01, 02) ) }; static bool Test() { bool success = true; bool caseResult; foreach (DateTimeSpanTest testcase in TestData) { DateTimeSpan result = new DateTimeSpan(testcase.Start, testcase.End); caseResult = result == testcase.Delta; Console.WriteLine("{0} - {1} : \n{2} expected\n{3} got\n{4}", testcase.Start, testcase.End, testcase.Delta, result, caseResult ? "OK" : "fail" ); if (!caseResult) { success = false; break; } } return success; } } class DateTimeSpanTest { public DateTime Start; public DateTime End; public DateTimeSpan Delta; public DateTimeSpanTest(DateTime start, DateTime end, DateTimeSpan delta) { Start = start; End = end; Delta = delta; } }