Last active
April 30, 2017 08:08
-
-
Save d-vs/2622a0c48aa5eed824059305c7e72148 to your computer and use it in GitHub Desktop.
A C# implementation of Time.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// <summary> | |
/// Represents an instant in time, expressed as a time of day. | |
/// </summary> | |
[Serializable] | |
public struct Time : IEquatable<Time>, IComparable<Time>, IComparable, ISerializable, IFormattable | |
{ | |
#region Fields | |
static readonly string[] _dateFormats = new string[] { "HH:mm", "h:mm tt", "HH:mm:ss.FFF", "h:mm:ss.FFF tt", "HH", "h tt" }; | |
const string _standardFormat = "HH:mm:ss.FFF"; | |
/// <summary> | |
/// Represents the maximum value of a Time instance, which is the moment before midnight 23:59:59.999. | |
/// </summary> | |
public static readonly Time MaxValue = new Time(23, 59, 59, 999); | |
/// <summary> | |
/// Represents the minimum value of a Time instance, which is midnight 00:00:00.000. | |
/// </summary> | |
public static readonly Time MinValue = new Time(0, 0, 0, 0); | |
/// <summary> | |
/// Represents the maximum number of ticks that a Time instance can represent. | |
/// </summary> | |
static readonly long _maxTicks = Time.MaxValue.Ticks; | |
#endregion | |
#region Ctor | |
/// <summary> | |
/// Initializes a new instance of the Time structure to the specified number of ticks. | |
/// </summary> | |
/// <param name="ticks"> | |
/// The number of 100-nanosecond intervals that have elapsed since 00:00:00.000 midnight. Valid values are between | |
/// 0 and Time.MaxValue.Ticks. If a negative value is passed, the absolute value will be used. | |
/// </param> | |
/// <exception cref="System.ArgumentOutOfRangeException"> | |
/// Thrown if the number of ticks exceeds Time.MaxValue.Ticks. | |
/// </exception> | |
public Time(long ticks) | |
{ | |
long absTicks = Math.Abs(ticks); | |
if (absTicks > Time.MaxValue.Ticks) | |
{ | |
throw new ArgumentOutOfRangeException("ticks", "The specified number of ticks exceeds Time.MaxValue."); | |
} | |
TimeSpan timeSpan = TimeSpan.FromTicks(absTicks); | |
_hour = timeSpan.Hours; | |
_minute = timeSpan.Minutes; | |
_second = timeSpan.Seconds; | |
_millisecond = timeSpan.Milliseconds; | |
_ticks = timeSpan.Ticks; | |
} | |
/// <summary> | |
/// Initializes a new instance of the Time structure to the specified hour. | |
/// </summary> | |
/// <param name="hour">The hour (0 to 23).</param> | |
public Time(int hour) | |
: this(hour, 0, 0, 0) | |
{ | |
} | |
/// <summary> | |
/// Initializes a new instance of the Time structure to the specified hour, and minute. | |
/// </summary> | |
/// <param name="hour">The hour (0 to 23).</param> | |
/// <param name="minute">The minute (0 to 59).</param> | |
public Time(int hour, int minute) | |
: this(hour, minute, 0, 0) | |
{ | |
} | |
/// <summary> | |
/// Initializes a new instance of the Time structure to the specified hour, minute, and second. | |
/// </summary> | |
/// <param name="hour">The hour (0 to 23).</param> | |
/// <param name="minute">The minute (0 to 59).</param> | |
/// <param name="second">The second (0 to 59).</param> | |
public Time(int hour, int minute, int second) | |
: this(hour, minute, second, 0) | |
{ | |
} | |
/// <summary> | |
/// Initializes a new instance of the Time structure to the specified hour, minute, second, and millisecond. | |
/// </summary> | |
/// <param name="hour">The hour (0 to 23).</param> | |
/// <param name="minute">The minute (0 to 59).</param> | |
/// <param name="second">The second (0 to 59).</param> | |
/// <param name="millisecond">The millisecond (0 to 999).</param> | |
public Time(int hour, int minute, int second, int millisecond) | |
{ | |
if (!Time.IsHourValid(hour)) | |
{ | |
throw new ArgumentOutOfRangeException("hour", "Hour must be a value between 0 and 23"); | |
} | |
if (!Time.IsMinuteValid(minute)) | |
{ | |
throw new ArgumentOutOfRangeException("minute", "Minute must be a value between 0 and 59"); | |
} | |
if (!Time.IsSecondValid(second)) | |
{ | |
throw new ArgumentOutOfRangeException("second", "Second must be a value between 0 and 59"); | |
} | |
if (!Time.IsMillisecondValid(millisecond)) | |
{ | |
throw new ArgumentOutOfRangeException("millisecond", "Millisecond must be a value between 0 and 999"); | |
} | |
_hour = hour; | |
_minute = minute; | |
_second = second; | |
_millisecond = millisecond; | |
_ticks = TimeSpan.FromHours(_hour).Ticks + | |
TimeSpan.FromMinutes(_minute).Ticks + | |
TimeSpan.FromSeconds(_second).Ticks + | |
TimeSpan.FromMilliseconds(_millisecond).Ticks; | |
} | |
//Constructor for deserialization | |
private Time(SerializationInfo info, StreamingContext context) | |
{ | |
if (info == null) | |
{ | |
throw new ArgumentNullException("info"); | |
} | |
string timeString = info.GetString("Time"); | |
Time time = Time.MinValue; | |
if (!string.IsNullOrWhiteSpace(timeString)) | |
{ | |
Time.TryParse(timeString, out time); | |
} | |
_hour = time.Hour; | |
_minute = time.Minute; | |
_second = time.Second; | |
_millisecond = time.Millisecond; | |
_ticks = time.Ticks; | |
} | |
#endregion | |
#region Property Hour | |
private int _hour; | |
/// <summary> | |
/// Gets the hour component of the time represented by this instance, which is a value between 0 and 23. | |
/// </summary> | |
public int Hour | |
{ | |
get | |
{ | |
return _hour; | |
} | |
} | |
#endregion | |
#region Property Minute | |
private int _minute; | |
/// <summary> | |
/// Gets the minute component of the time represented by this instance, which is a value between 0 and 59. | |
/// </summary> | |
public int Minute | |
{ | |
get | |
{ | |
return _minute; | |
} | |
} | |
#endregion | |
#region Property Second | |
private int _second; | |
/// <summary> | |
/// Gets the second component of the time represented by this instance, which is a value between 0 and 59. | |
/// </summary> | |
public int Second | |
{ | |
get | |
{ | |
return _second; | |
} | |
} | |
#endregion | |
#region Property Millisecond | |
private int _millisecond; | |
/// <summary> | |
/// Gets the millisecond component of the time represented by this instance, which is a value between 0 and 999. | |
/// </summary> | |
public int Millisecond | |
{ | |
get | |
{ | |
return _millisecond; | |
} | |
} | |
#endregion | |
#region Property Ticks | |
private long _ticks; | |
/// <summary> | |
/// Gets the number of ticks that represent the time of this instance, which is the number of 100-nanosecond intervals that have elapsed since 00:00:00.000 midnight. | |
/// </summary> | |
public long Ticks | |
{ | |
get | |
{ | |
return _ticks; | |
} | |
} | |
#endregion | |
#region Method TryParse | |
/// <summary> | |
/// Converts the specified string representation of a time to its Time equivalent and returns a value that indicates whether the conversion succeeded. | |
/// </summary> | |
/// <param name="s"> | |
/// A string containing a time to convert. The time must be represented in one of these formats: | |
/// HH:mm:ss.FFF, h:mm:ss.FFF tt, HH:mm, h:mm tt, HH, h t | |
/// </param> | |
/// <param name="result"> | |
/// When this method returns, contains the Time value equivalent to the time contained in s, if the conversion succeeded, or Time.MinValue | |
/// if the conversion failed. The conversion fails if the s parameter is null, is an empty string (""), or does not contain a valid | |
/// string representation of a time. | |
/// </param> | |
public static bool TryParse(string s, out Time result) | |
{ | |
return TryParseExact(s, _dateFormats, out result); | |
} | |
#endregion | |
#region Method TryParseExact | |
/// <summary> | |
/// Converts the specified string representation of a time to its Time equivalent and returns a value that indicates whether the conversion succeeded. | |
/// </summary> | |
/// <param name="s">A string containing a time to convert.</param> | |
/// <param name="format">The required format of s.</param> | |
/// <param name="result"> | |
/// When this method returns, contains the Time value equivalent to the time contained in s, if the conversion succeeded, or Time.MinValue | |
/// if the conversion failed. The conversion fails if the s parameter is null, is an empty string (""), or does not contain a valid | |
/// string representation of a time. | |
/// </param> | |
public static bool TryParseExact(string s, string format, out Time result) | |
{ | |
return TryParseExact(s, new string[] { format }, out result); | |
} | |
/// <summary> | |
/// Converts the specified string representation of a time to its Time equivalent and returns a value indicating whether the conversion succeeded. | |
/// If the conversion fails, returns Time.MinValue. | |
/// </summary> | |
/// <param name="s">A string containing a time to convert.</param> | |
/// <param name="format">The required format of s.</param> | |
/// <param name="result"> | |
/// When this method returns, contains the Time value equivalent to the time contained in s, if the conversion succeeded, or Time.MinValue | |
/// if the conversion failed. The conversion fails if the s parameter is null, is an empty string (""), or does not contain a valid | |
/// string representation of a time. | |
/// </param> | |
public static bool TryParseExact(string s, string[] formats, out Time result) | |
{ | |
Time time = Time.MinValue; | |
DateTime dateTime; | |
bool success = DateTime.TryParseExact(s, formats, CultureInfo.InvariantCulture.DateTimeFormat, DateTimeStyles.NoCurrentDateDefault, out dateTime); | |
if (success) | |
{ | |
time = new Time(dateTime.Hour, dateTime.Minute, dateTime.Second, dateTime.Millisecond); | |
} | |
result = time; | |
return success; | |
} | |
#endregion | |
#region Method TryCreate | |
/// <summary> | |
/// Tries to convert the specified string representation of a time to its Time equivalent and returns the result. If the conversion fails, | |
/// returns null. | |
/// </summary> | |
/// <param name="s">A string containing a time to convert.</param> | |
public static Time? TryCreate(string s) | |
{ | |
Time time; | |
bool success = TryParse(s, out time); | |
return success ? time : (Time?)null; | |
} | |
#endregion | |
#region Method ToString | |
/// <summary> | |
/// Converts the value of the current Time instance to its equivalent string representation using the specified format and | |
/// culture-invariant format information. | |
/// </summary> | |
/// <param name="format">A standard or custom time format string.</param> | |
public string ToString(string format) | |
{ | |
return this.ToString(format, CultureInfo.InvariantCulture.DateTimeFormat); | |
} | |
/// <summary> | |
/// Converts the value of the current Time instance to its equivalent string representation using the specified format and culture-specific format information. | |
/// </summary> | |
/// <param name="format">A standard or custom time format string. If null or string.Empty, the standard time format HH:mm:ss.FFF will be used.</param> | |
/// <param name="provider">An object that supplies culture-specific formatting information.</param> | |
public string ToString(string format, IFormatProvider provider) | |
{ | |
string f = string.IsNullOrEmpty(format) ? _standardFormat : format; | |
string result = string.Empty; | |
try | |
{ | |
result = new DateTime(1, 1, 1, this.Hour, this.Minute, this.Second, this.Millisecond).ToString(f, provider); | |
} | |
catch (FormatException) | |
{ | |
throw new FormatException(string.Format("The specified time format '{0}' does not contain a valid format specifier or custom time format pattern.", format ?? string.Empty)); | |
} | |
catch (ArgumentOutOfRangeException) | |
{ | |
throw new ArgumentOutOfRangeException("provider", string.Format("The time is outside the range supported by the calendar used by provider '{0}'", provider)); | |
} | |
return result; | |
} | |
#endregion | |
#region Method ToDateTime | |
/// <summary> | |
/// Returns a new DateTime instance with the year, month, and day of the given DateTime, and the current time's | |
/// hour, minute, second, and millisecond values. | |
/// </summary> | |
/// <param name="date">The date of the new DateTime.</param> | |
public DateTime ToDateTime(DateTime date) | |
{ | |
return this.ToDateTime(date.Year, date.Month, date.Day); | |
} | |
/// <summary> | |
/// Returns a new DateTime instance with the given year, month, and day, and the current time's | |
/// hour, minute, second, and millisecond values. | |
/// </summary> | |
/// <param name="year">The year of the new DateTime.</param> | |
/// <param name="month">The month of the new DateTime.</param> | |
/// <param name="day">The day of the new DateTime.</param> | |
public DateTime ToDateTime(int year, int month, int day) | |
{ | |
return new DateTime(year, month, day, this.Hour, this.Minute, this.Second, this.Millisecond); | |
} | |
#endregion | |
#region IEquatable<Time> Implementation | |
/// <summary> | |
/// Returns a value indicating whether the specified Time instance is equal to the current Time instance by value comparison. | |
/// </summary> | |
/// <param name="other">The Time instance to compare.</param> | |
public bool Equals(Time other) | |
{ | |
return this.Ticks == other.Ticks; | |
} | |
#endregion | |
#region IComparable<Time> Implementation | |
/// <summary> | |
/// Compares the current instance with another Time instance and returns an integer that indicates whether the current instance precedes, follows, | |
/// or occurs in the same position in the sort order as the other object. | |
/// <param name="obj">The Time instance to which to compare the current instance.</param> | |
public int CompareTo(Time other) | |
{ | |
return this == other ? 0 : (this > other ? 1 : -1); | |
} | |
/// <summary> | |
/// Compares the current instance with another object instance and returns an integer that indicates whether the current instance precedes, follows, | |
/// or occurs in the same position in the sort order as the other object. | |
/// </summary> | |
/// <param name="obj">The object instance to which to compare the current instance.</param> | |
public int CompareTo(object obj) | |
{ | |
return obj == null || !(obj is Time) ? 1 : CompareTo((Time)obj); | |
} | |
#endregion | |
#region ISerializable Implementation | |
[SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)] | |
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) | |
{ | |
if (info == null) | |
{ | |
throw new ArgumentNullException("info"); | |
} | |
info.AddValue("Time", this.ToString()); | |
} | |
#endregion | |
#region Overrides | |
/// <summary> | |
/// Returns a value indicating whether the specified object is equal to the current Time by value comparison. | |
/// </summary> | |
/// <param name="obj">The object to compare.</param> | |
public override bool Equals(object obj) | |
{ | |
return obj != null && | |
(obj is Time) && | |
this.Equals((Time)obj); | |
} | |
/// <summary> | |
/// Converts the value of the current Time instance to its equivalent string representation using a standard format and invariant culture format information. | |
/// The format returned is HH:mm:ss.FFF | |
/// </summary> | |
public override string ToString() | |
{ | |
return this.ToString(_standardFormat, CultureInfo.InvariantCulture.DateTimeFormat); | |
} | |
public override int GetHashCode() | |
{ | |
return string.Concat("Time", this.Hour, this.Minute, this.Second, this.Millisecond).GetHashCode(); | |
} | |
#endregion | |
#region Operators | |
/// <summary> | |
/// Returns a value indicating whether the two times are equal to each other. | |
/// </summary> | |
/// <param name="a">The first time to compare.</param> | |
/// <param name="b">The second time to compare.</param> | |
public static bool operator ==(Time a, Time b) | |
{ | |
return a.Equals(b); | |
} | |
/// <summary> | |
/// Returns a value indicating whether the two times are not equal to each other. | |
/// </summary> | |
/// <param name="a">The first time to compare.</param> | |
/// <param name="b">The second time to compare.</param> | |
public static bool operator !=(Time a, Time b) | |
{ | |
return !(a == b); | |
} | |
/// <summary> | |
/// Returns a value indicating whether the first time is later than the second time. | |
/// </summary> | |
/// <param name="a">The first time to compare.</param> | |
/// <param name="b">The second time to compare.</param> | |
public static bool operator >(Time a, Time b) | |
{ | |
return a.Ticks > b.Ticks; | |
} | |
/// <summary> | |
/// Returns a value indicating whether the first time is earlier than the second time. | |
/// </summary> | |
/// <param name="a">The first time to compare.</param> | |
/// <param name="b">The second time to compare.</param> | |
public static bool operator <(Time a, Time b) | |
{ | |
return a.Ticks < b.Ticks; | |
} | |
/// <summary> | |
/// Returns a value indicating whether the first time is later than or equal to the second time. | |
/// </summary> | |
/// <param name="a">The first time to compare.</param> | |
/// <param name="b">The second time to compare.</param> | |
public static bool operator >=(Time a, Time b) | |
{ | |
return a == b || a > b; | |
} | |
/// <summary> | |
/// Returns a value indicating whether the first time is earlier than or equal to the second time. | |
/// </summary> | |
/// <param name="a">The first time to compare.</param> | |
/// <param name="b">The second time to compare.</param> | |
public static bool operator <=(Time a, Time b) | |
{ | |
return a == b || a < b; | |
} | |
/// <summary> | |
/// Subtracts the second time from first time and returns a new time. | |
/// </summary> | |
/// <param name="a">The time from which to subtract the second time.</param> | |
/// <param name="b">The time to subtract from the first time.</param> | |
public static TimeSpan operator -(Time a, Time b) | |
{ | |
return TimeSpan.FromTicks(a.Ticks - b.Ticks); | |
} | |
/// <summary> | |
/// Subtracts the absolute value of the ticks of the specified TimeSpan from the specified time, yielding a new time. | |
/// </summary> | |
/// <param name="a">The time from which to subtract the interval.</param> | |
/// <param name="t">The interval to subtract.</param> | |
public static Time operator -(Time a, TimeSpan t) | |
{ | |
long ticks = Math.Abs(t.Ticks) * -1; | |
return a + TimeSpan.FromTicks(ticks); | |
} | |
/// <summary> | |
/// Adds a specified time interval to the specified time, yielding a new time. | |
/// </summary> | |
/// <param name="a">The time to which to add the interval.</param> | |
/// <param name="t">The interval to add.</param> | |
public static Time operator +(Time a, TimeSpan t) | |
{ | |
int hour = a.Hour + t.Hours; | |
int minute = a.Minute + t.Minutes; | |
int second = a.Second + t.Seconds; | |
int millisecond = a.Millisecond + t.Milliseconds; | |
long ticks = TimeSpan.FromHours(hour).Ticks + TimeSpan.FromMinutes(minute).Ticks + TimeSpan.FromSeconds(second).Ticks + TimeSpan.FromMilliseconds(millisecond).Ticks; | |
if (ticks > Time.MaxValue.Ticks) | |
{ | |
throw new ArgumentOutOfRangeException("t", string.Format("Unable to add timespan {0} to time {1} as it would exceed Time.MaxValue.", t, a)); | |
} | |
if (ticks < Time.MinValue.Ticks) | |
{ | |
throw new ArgumentOutOfRangeException("t", string.Format("Unable to add timespan {0} to time {1} as it would be less than Time.MinValue.", t, a)); | |
} | |
return new Time(hour, minute, second, millisecond); | |
} | |
#endregion | |
#region Helpers | |
private static bool IsHourValid(int hour) | |
{ | |
return hour.Between(0, 23); | |
} | |
private static bool IsMinuteValid(int minute) | |
{ | |
return minute.Between(0, 59); | |
} | |
private static bool IsSecondValid(int second) | |
{ | |
return second.Between(0, 59); | |
} | |
private static bool IsMillisecondValid(int millisecond) | |
{ | |
return millisecond.Between(0, 999); | |
} | |
#endregion | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
To me this plugs a gap in the .Net framework that is useful when date has no meaning, such as when dealing with hotel check-in times. Fully unit tested.