Reputation: 143
I made a countdown timer using Tkinter in python, but my only problem is that one second in the timer is a bit longer than a real second.
I used the after() function to, every millisecond, remove one millisecond (0.001 second) from the clock.
I don't know if it's doing that because the code of the clock takes some extra time to execute, if that's the case how could I make a clock with the exact same UI that takes less time to execute?
Here's a Video showing the problem
The program:
from tkinter import *
class root(Tk):
def __init__(self):
super(root, self).__init__()
self.title("Timer")
self.buttonplay = Button(self, text = "Play", fg= 'green', command = self.play)
self.buttonplay.pack()
self.buttonpause = Button(self, text = "Pause", fg = "red", command=self.pause)
self.buttonpause.pack()
self.createTimers()
def play(self):
self.timeit=True
self.timer1.configure(bg='#1C953D')
self.doTimer()
def pause(self):
self.timeit=False
self.timer1.configure(bg='#454545')
def reset(self):
self.timer1.destroy()
self.createTimers()
def createTimers(self):
self.minute = 0
self.second = 5
self.ms = 0
self.total = self.second + self.minute *60 + self.ms*0.001
self.time1 = StringVar()
self.time1.set(str(self.minute).rjust(2, '0') + ':' + str(self.second).rjust(2, '0') +'.'+ str(self.ms).rjust(3, '0'))
self.timer1 = Label(self, textvariable=self.time1, bg='#454545', fg='white', font ="Gadugi 40 bold")
self.timer1.pack()
self.timer1.configure(bg='#454545')
def doTimer(self):
self.time = self.second + self.minute *60 + self.ms*0.001
if self.time !=0: #Checks if the timer ended
if self.timeit:
self.timer1.configure(bg='#1C953D')
self.ms = self.ms -1
if self.ms <0:
self.second = self.second -1
self.ms = 999
if self.second == -1:
self.minute = self.minute -1
self.second = 59
self.time1.set(str(self.minute).rjust(2, '0') + ':' + str(self.second).rjust(2, '0') +'.'+ str(self.ms).rjust(3, '0'))
if self.timeit:
self.after(1, self.doTimer)
else:
self.ended = 1
self.timer1.configure(bg='#FF0000')
self.after(3000, self.reset)
root = root()
root.mainloop()
Upvotes: 3
Views: 387
Reputation: 151
I don't know if it's doing that because the code of the clock takes some extra time to execute
In this case you are right, the tempo of your timer is dependent on the runtime of your code. So making this script require more resources from your computer would also slow down the timer, and vice versa.
"Don't reinvent the wheel." - Programming proverb.
Use the time
module to get a more accurate time in your timer. More specifically time.time()
and format it to make it readable.
Here's an example to show your problem.
import time
time_zone = 0 # UTC
while True:
current_time = time.time()
ms = (current_time * 1000) % 1000
s = (current_time % 60)
m = (current_time % (60*60))//60
h = (current_time % (60*60*24))//(60*60)
print(f"h:{int(h+time_zone)} - m:{int(m)} - s:{int(s)} - ms:{int(ms)}")
time.sleep(1)
In theory this script should print the time exactly every second. However a couple of milliseconds are added due to the code runtime.
Removing time.sleep(1)
should get you pretty close to a live clock.
So if you want something close to a timer or stopwatch, you get a timestamp for the current epoch time (time.time()
) update your timer about every x-amount of ticks per second to get the effect of counting ms, when the button is pressed again you get a new timestamp, and then you compare it to the first timestamp and subtract to get the exact time between each button press.
I've made a template for you to tinker a bit around with:
import tkinter as tk
import time
time_zone = 0 # UTC
# Convert epoch time to readable format
def readable_epoch(time_epoch):
ms = (time_epoch * 1000) % 1000
s = (time_epoch % 60)
m = (time_epoch % (60*60))//60
h = (time_epoch % (60*60*24))//(60*60)
return (h, m, s, ms)
class App(tk.Tk):
def __init__(self):
super().__init__()
self.time1 = 0
self.time2 = 0
self.geometry("300x200")
self.button1 = tk.Button(
self, text="Click me first", command=self.button1, bg="teal")
self.button1.pack(ipadx=10, ipady=10, expand=True, side=tk.LEFT)
self.button2 = tk.Button(
self, text="Click me second", command=self.button2, bg="pink")
self.button2.pack(ipadx=10, ipady=10, expand=True, side=tk.LEFT)
# Get current time
def button1(self):
self.current_time = time.time()
h, m, s, ms = readable_epoch(self.current_time)
print(f"h:{int(h+time_zone)}, m:{int(m)}, s:{int(s)}, ms:{int(ms)}")
# Elapsed time since button 1 was pressed:
def button2(self):
new_time = time.time()
new_time = new_time - self.current_time
h, m, s, ms = readable_epoch(new_time)
print(f"h:{int(h+time_zone)}, m:{int(m)}, s:{int(s)}, ms:{int(ms)}")
if __name__ == "__main__":
app = App()
app.mainloop()
Upvotes: 2