Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@d-vs
Last active April 30, 2017 08:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save d-vs/2622a0c48aa5eed824059305c7e72148 to your computer and use it in GitHub Desktop.
Save d-vs/2622a0c48aa5eed824059305c7e72148 to your computer and use it in GitHub Desktop.
A C# implementation of Time.
/// <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
}
@d-vs
Copy link
Author

d-vs commented Apr 30, 2017

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment