A common exercise is to write a program that counts down to 0 from some arbitrary starting time. The important feature is that the count down is done in hour, minutes and seconds, but the time is decremented one second at a time.
Let’s assume that the starting time is 10000 seconds. Then the number of hours is 1000 modulo 3600 or 2, with 2800 seconds remainder. Then we can get the minutes and seconds from that remainder.
The easiest way to get both these numbers is to use the divmod function that returns a tuple of the quotient and remainder:
h = divmod(10000, 3600)
The value of h is (2, 2800) so
hours = h[0] # and
secs = h[1]
Of course, we also need to calculate the minutes and remaining seconds by
m = divmod(secs, 60)
minutes = m[0] # and
seconds = m[1]
And this is the trick we use to make our countdown clock. For any given number of seconds, we calculate the hours, minutes and seconds and format it to display as output.
Starting with hours, minutes and seconds
But, of course, we would probably not start with 10,000 seconds: it’s just too unwieldy to grasp. So, a better approach would be to start with hours, minutes and seconds and convert them to all seconds. This is incredibly simple, of course since
totalSeconds = hours*3600 + minutes*60 + seconds
The only other thing to do, is to ask for the three values separated by commas and convert them to integers.
entry = input("Enter hours, minutes, seconds:")
tarray = entry.split(',')
hours = int(tarray[0])
minutes = int(tarray[1])
seconds = int(tarray[2])
Putting it all in a class
Now, to make this easy to use and manage for both a console and a GUI version, we want to encapsulate all of this into an easy to use class. Here is the first version of that class in a module called HmsSimple.
class Hms:
# split comma separated entry into hours, mins, secs
def __init__(self, entry):
tarray = entry.split(',')
hours = int(tarray[0])
minutes = int(tarray[1])
seconds = int(tarray[2])
self.error = False
self.totalSeconds = hours * 3600 \
+ minutes * 60 + seconds
# calculate hours, mins, secs from totalSeconds
# and return as tuple
# and decrement the totalSeconds
def makeHms(self):
# compute hours, with seconds as remainder
h = divmod(self.totalSeconds, 3600)
self.hrs = h[0]
# compute minutes with seconds as remainder
s = divmod(h[1], 60)
self.secs = s[1] # seconds
self.mins = s[0] # minutes
self.totalSeconds -= 1 # decrease count here
# return h,m,s tuple
return self.hrs, self.mins, self.secs
This class has two methods: the initializer method that takes a comma-separated string and converts it into totalSeconds, and the makeHms method that returns the computed hours, minutes and seconds for the current number of totalSeconds. Then it subtracts one from the value for next time.
The Simple Application
With all this put into the Hms class, the application that prints out the countdown is extremely straightforward:
import time
from HmsSimple import Hms
"""Simple program to count down hours, minutes, seconds
Makes use of the Hms class to compute them from seconds
"""
def main():
# get the keyboard input
entry = input("Enter hours, minutes, seconds:")
hms = Hms(entry) # convert to seconds inside HMS
while hms.totalSeconds > 0 :
h,m,s = hms.makeHms()
# get the current values and print them out each second
print(f'{h:02}:{m:02}:{s:02}')
time.sleep(1)
if __name__ == '__main__':
main()
The executing code looks like this:
Enter hours, minutes, seconds:0,1,27
00:01:27
00:01:26
00:01:25
00:01:24
00:01:23
00:01:22
…and so forth.
Error Checking
One last thing before we close this class: we really need to check for errors. This means checking that 3 numbers were entered, and that they are all numbers. This is easily done by catching an exceptions and saving the error message.
def __init__(self, entry):
tarray = entry.split(',')
self.errorMessage = ""
try:
hours = int(tarray[0]) # get the 3 arguments
minutes = int(tarray[1])
seconds = int(tarray[2])
self.error = False
self.totalSeconds = hours * 3600 + minutes * 60 + seconds
except Exception as inst:
self.error = True # set the error flag
self.errorMessage = inst.args # save the error message
Then, you can check the error flag and print the message from the calling program.
entry = input("Enter hours, minutes, seconds:")
hms = Hms(entry)
if hms.error:
print (hms.errorMessage)
else:
while hms.totalSeconds > 0 :
h,m,s = hms.makeHms()
print(f'{h:02}:{m:02}:{s:02}')
time.sleep(1)
So, now we have a complete Hms class with error checking that we can use in any countdown program. The final step in this article is to make the countdown visual.
Visual countdown
Here is a simple visual version of our countdown program using tkinter:
Our visual countdown will use the same Hms class we’ve already written and the same time.sleep method as above. We lay out the visual widgets in a Grid layout having 4 rows and 3 columns.
As we have done before in many articles, we use the DButton class we’ve derived from the Button class, which has a comd method already in it, and use a Mediator class to read the entry fields and the Start button, and then write into the label fields that make up the counting windows.
# the Start button launches the count down
class StartButton(DButton):
def __init__(self, root, med):
super().__init__(root, text='Start')
self.med = med
def comd(self):
self.med.startCount()
We also derive two kinds of labels, a BlueLabel for the top labels, and CountLabel for the 3 boxes where the countdown occurs.
# derived blue label
class BlueLabel(Label):
def __init__(self,root, lbtext, **kwargs):
super().__init__(root,text=lbtext, justify=CENTER,
foreground='blue' )
# count labels have a larger font
class CountLabel(Label):
def __init__(self, root, tkFont=None, **kwargs):
super().__init__(root, text='00', justify=LEFT,
borderwidth=2, relief="ridge")
self.config(font=('Helvetica bold', 26))
Here is how we lay out the labels and entry fields in the grid and pass the entry fields to the Mediator:
def build(self):
root = tk.Tk()
self.root = root
self.root.geometry("250x150")
# top row are labels for the entry fields
hrLabel = BlueLabel(root, "Hours").grid(row=0, column=0)
minLabel = BlueLabel(root, "Minutes").grid(row=0, column=1)
secLabel = BlueLabel(root, "Seconds").grid(row=0, column=2)
# second row contains the 3 entry fields
hrEntry = Entry(root, width = 8, justify=CENTER )
hrEntry.grid(row=1, column=0, padx=10)
minEntry = Entry(root, width = 8, justify=CENTER)
minEntry.grid(row=1, column=1, padx=10)
secEntry = Entry(root, width = 8, justify=CENTER)
secEntry.grid(row=1, column=2, padx=10 )
med = Mediator(root)
med.setEntries(hrEntry, minEntry, secEntry)
Starting the visual counter
The Start button calls the startCount method in the Mediator, and this is where all the work is done. As you can see, it is much like the simple non-visual example above.
def build(self):
root = tk.Tk()
self.root = root
self.root.geometry("250x150")
# top row are labels for the entry fields
hrLabel = BlueLabel(root, "Hours").grid(row=0, column=0)
minLabel = BlueLabel(root, "Minutes").grid(row=0, column=1)
secLabel = BlueLabel(root, "Seconds").grid(row=0, column=2)
# second row contains the 3 entry fields
hrEntry = Entry(root, width = 8, justify=CENTER )
hrEntry.grid(row=1, column=0, padx=10)
minEntry = Entry(root, width = 8, justify=CENTER)
minEntry.grid(row=1, column=1, padx=10)
secEntry = Entry(root, width = 8, justify=CENTER)
secEntry.grid(row=1, column=2, padx=10 )
med = Mediator(root)
med.setEntries(hrEntry, minEntry, secEntry)
The only real difference is the we update the screen with self.root.update() before calling the sleep method.
And this is the basic for this nice visual countdown program. However, we will discover that there are still some more issues to solve: it is hard to stop the program, since most of the time is spent in the sleep method that ignores you, and if you do stop it, you will get a Python error message. We’ll take up the solutions to these problems in next week’s article.
All of the source code can be found in GitHub under jameswcooper/newsletter/Countdown
Code includes:
HmsSimple.py-- Hms class without error checkin
CountDownSimple.py – Simple countdown without error checking
Hms.py – complete Hms class with error checking
Countdown.py – complete countdown with error checking
CountDownTk.py – Visual countdown program