Introduction
I was putting together a program to keep track of CDs. I needed a way to store the length of each track, and I tried using a DateTime
variable to hold the values. Needless to say, the DateTime
data type just wasn't cutting it. I did a search for a Class
I could use to hold the values, but came up empty. There were a lot of people recommending using TimeSpan
, but it was wholly useless for what I needed. So I started small, creating a Time
data type that would hold hours, minutes and seconds. Then I realized I might have negative amounts when subtracting values, so I added a Sign
. Then, things just spiraled out of control...
Background
I'm putting together a training course for Visual Basic and Visual C#. I need multiple example programs, so I've been writing them continuously for the last two months or so. Well, the over-arching project is a CD tracking program that will be expanded and enhanced as each section of the training is finished.
In Part 1: Fundamentals, I have the Console program using a Structure
that holds the Artist Name and Album Title, and it saves the data to a text file.
In Part 2: Object Oriented Programming, I upgrade the program to use classes and XML files. This is where this code comes in. Part 2 stores not only Artist and Album, but also Song Titles and Lengths, year of Release and computes Total Length.
In Part 3: GUI Programming, I will convert the program to a GUI interface and add album covers and lyrics.
Finally, in Part 4: Database Programming, I will upgrade again, and convert the whole thing to use a database.
Using the Code
Add the Time
class to your project and it exposes the following:
Properties
Hours
- An Integer value holding the hours portion Minutes
- An Integer value holding the minutes portion. Range: 0 (zero) to 59 (fifty-nine) Seconds
- An Integer value holding the seconds portion. Range: 0 (zero) to 59 (fifty-nine) Sign
- An Integer value holding the sign value of the time. Range: -1 for negative numbers, 0 for zero, +1 for positive numbers
Constructors
New()
- Parameterless constructor. Sets a default value of +00:00:00. New(seconds)
- Parameterized constructor. Will convert the seconds to hours, minutes and seconds. New(minutes, seconds)
- Parameterized constructor. Will create a value of the form S00:MM:SS where the first S is the sign of the number. New(hours, minutes, seconds)
- Parameterized constructor. Will create a value of the form SHH:MM:SS where the first S is the Sign of the number. New(sign, hours, minutes, seconds)
- Parameterized constructor. Like previous constructor, but you can explicitly set the Sign. New(string)
- Parameterized constructor. Will parse the String, using the colon (:) as a delimiter. Will fill the value from right to left if parameters are missing from the String. New(string, delimeter)
- Parameterized constructor. Like previous constructor, but you can give an explicit delimiter to use for parsing the String.
Relational Operators
The relational operators you can use are:
CompareTo(time)
- Implements System.IComparable(Of Time).CompareTo
Compare(time, time)
- Implements System.Collections.Generic.IComparer(Of Time).Compare
Equals1(time)
- Implements System.IEquatable(Of Time).Equals
GetHashCode()
- Because we have an Equals()
method < (less than) > (greater than) <= (less than or equal) >= (greater than or equal) = (equal) <> (not equal)
- All the relational operators that make sense are included
Overloaded Operators
The overloaded mathematical operators you can use are:
- (Negate)
- returns the negative value of positive time, positive value of negative time + (Addition) - (Subtraction) * (Multiplication) / (Division)
- The whole reason I wrote the class in the first place ToShort ToUShort ToInt16 ToUInt16 ToInteger ToUInteger ToInt32 ToUInt32 ToLong ToUlong ToInt64 ToUint64
- Standard conversion methods ToSingle ToDouble ToDecimal
- The whole part of these numbers are converted to hours and minutes, while the seconds are from the decimal part (i.e. 30 seconds = .5) ToTime() and CType()
- Will convert between pretty much everything else it can
Miscellaneous Methods
The other mathematical operations you can use are:
Abs()
- Absolute value Min()
and Max()
- Minimum and Maximum of two values Ceiling()
, Floor()
and Round()
- These work like the standard rounding methods, and round to the minute ToString()
- Outputs either #0:00:00
or -#0:00:00
On the possible narrowing conversions, I used some range checking and throw an OverflowException
if it is out of range. I used standard exceptions so you could easily incorporate the code into your programs.
Public Shared Function ToShort(ByVal time As Time) As Short
Dim temp As Integer = time.Sign * ((time.Hours * 3600) + (time.Minutes * 60) + time.Seconds)
If temp < -32768 Or temp > 32767 Then
Throw New OverflowException
End If
Return CShort(temp)
End Function
I realized that trying to compare two Time
values would be tedious and overly verbose, so in the Compare()
methods, I convert the Time
to Integer
and then compare them as integers:
Public Function CompareTo(other As Time) As Integer _
Implements System.IComparable(Of Time).CompareTo
Dim time1, time2 As Integer
time1 = Me.Sign * ((Me.Hours * 3600) + (Me.Minutes * 60) + Me.Seconds)
time2 = other.Sign * ((other.Hours * 3600) + (other.Minutes * 60) + other.Seconds)
Return time1.CompareTo(time2)
End Function
And in the Addition/Subtraction/Multiplication methods, I convert them to Integers, do the math, then convert them back to Time
:
Public Shared Operator +(ByVal time1 As Time, ByVal time2 As Time) As Time
Dim temp As Integer
temp = ToInteger(time1) + ToInteger(time2)
Return ToTime(temp)
End Operator
And the ToTime
method:
Public Shared Function ToTime(ByVal value As Integer) As Time
Dim hours, minutes, seconds As Integer
hours = value \ 3600
seconds = value Mod 3600
minutes = seconds \ 60
seconds = seconds Mod 60
Return New Time(Math.Sign(value), hours, minutes, seconds)
End Function
But in the Division
method, I convert them to Decimal first, then do the division:
Public Shared Operator /(ByVal time1 As Time, ByVal time2 As Time) As Time
Dim temp As Decimal
temp = ToDecimal(time1) / ToDecimal(time2)
Return ToTime(temp)
End Operator
It would be an odd result otherwise.
A Quick Test
I tested this with the small program below. It passed for the limited tests I've done, but extensive testing might (and probably will) reveal flaws. If you find one, please let me know!
Module TestTimeClass
Sub Main()
Dim i As Time
Dim j As New Time(275)
Dim k As New Time("4:15")
i = j + k
Console.WriteLine(i.ToString())
i = k - j
Console.WriteLine(i.ToString())
Console.WriteLine(Time.ToDecimal(i))
If k < j Then
Console.WriteLine("k is smaller than j.")
Else
Console.WriteLine("j is smaller than k.")
End If
Console.ReadLine()
End Sub
End Module
Points of Interest
I tried to squeeze the code to as small as I could get it without driving myself mad. That's why so many places call other places, mainly because I not only hate typing, but because I also know if code works for one routine, re-using it for another I'm assured it will work there, too. I started small: just wanting to add and subtract time values. Then, as I added one little thing to make the code work just a little bit better, eventually I found I had a huge Time Class
that would handle just about anything you'd need to do with Time
values.
I'm posting it here so you can hopefully use it in your own programs, but also to see how I could improve it, expand it, and/or optimize it.
Any questions are very welcome!
History
- 2013-07-04 - Version 0.0.0.1 (pre-pre-pre-alpha)
- 2013-07-07 - Added the C# version of the
Time
Class