98

I'm writing a program with Python's tkinter library.

My major problem is that I don't know how to create a timer or a clock like hh:mm:ss.

I need it to update itself (that's what I don't know how to do); when I use time.sleep() in a loop the whole GUI freezes.

Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
Diego Castro
  • 3,458
  • 4
  • 35
  • 42
  • 2
    Here's [code example on how to use `root.after()` to implement a timer.](https://gist.github.com/zed/5440b9372a15d86b5c47) – jfs Mar 03 '15 at 09:17

8 Answers8

147

Tkinter root windows have a method called after which can be used to schedule a function to be called after a given period of time. If that function itself calls after you've set up an automatically recurring event.

Here is a working example:

# for python 3.x use 'tkinter' rather than 'Tkinter'
import Tkinter as tk
import time

class App():
    def __init__(self):
        self.root = tk.Tk()
        self.label = tk.Label(text="")
        self.label.pack()
        self.update_clock()
        self.root.mainloop()

    def update_clock(self):
        now = time.strftime("%H:%M:%S")
        self.label.configure(text=now)
        self.root.after(1000, self.update_clock)

app=App()

Bear in mind that after doesn't guarantee the function will run exactly on time. It only schedules the job to be run after a given amount of time. It the app is busy there may be a delay before it is called since Tkinter is single-threaded. The delay is typically measured in microseconds.

Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685
12

Python3 clock example using the frame.after() rather than the top level application. Also shows updating the label with a StringVar()

#!/usr/bin/env python3

# Display UTC.
# started with https://docs.python.org/3.4/library/tkinter.html#module-tkinter

import tkinter as tk
import time

def current_iso8601():
    """Get current date and time in ISO8601"""
    # https://en.wikipedia.org/wiki/ISO_8601
    # https://xkcd.com/1179/
    return time.strftime("%Y%m%dT%H%M%SZ", time.gmtime())

class Application(tk.Frame):
    def __init__(self, master=None):
        tk.Frame.__init__(self, master)
        self.pack()
        self.createWidgets()

    def createWidgets(self):
        self.now = tk.StringVar()
        self.time = tk.Label(self, font=('Helvetica', 24))
        self.time.pack(side="top")
        self.time["textvariable"] = self.now

        self.QUIT = tk.Button(self, text="QUIT", fg="red",
                                            command=root.destroy)
        self.QUIT.pack(side="bottom")

        # initial time display
        self.onUpdate()

    def onUpdate(self):
        # update displayed time
        self.now.set(current_iso8601())
        # schedule timer to call myself after 1 second
        self.after(1000, self.onUpdate)

root = tk.Tk()
app = Application(master=root)
root.mainloop()
David Poole
  • 3,432
  • 5
  • 34
  • 34
  • 1
    This is a good answer, with one important thing - the time displayed is really the system time, and not some accumulated error time (if you wait "about 1000 ms" 60 times, you get "about a minute" not 60 senconds, and the error grows with time). However - your clock can skip seconds on display - you can accumulate sub-second errors, and then e.g. skip 2 s forward. I would suggest: `self.after(1000 - int(1000 * (time.time() - int(time.time()))) or 1000, self.onUpdate)`. Probably better to save `time.time()` to a variable before this expression. – Tomasz Gandor Jun 12 '17 at 18:19
  • 3
    I aspire to be awesome enough to embed xkcd's into my comments :) – bitsmack Jun 21 '17 at 14:53
  • 2
    What is the benefit of using frame.after() instead of root.after()? – Kai Wang Sep 03 '19 at 21:55
6
from tkinter import *
import time
tk=Tk()
def clock():
    t=time.strftime('%I:%M:%S',time.localtime())
    if t!='':
        label1.config(text=t,font='times 25')
    tk.after(100,clock)
label1=Label(tk,justify='center')
label1.pack()
clock()
tk.mainloop()
Ravikiran D
  • 329
  • 3
  • 8
  • 5
    It would be helpful if you could add some description. Just copy/pasting code is rarely useful ;-) – Martin Tournoij Sep 26 '17 at 13:21
  • 3
    this code gives the the exact time of the locality.it also serves as a timer. – Ravikiran D Sep 26 '17 at 16:27
  • It seems to me, it would be better to use "%H" instead of "%I", because "%I" shows only the hours from 0 till 12 and doesn't show whether the time is AM or PM. Or another way is to use both "%I" and "%p" ("%p" indicates AM/PM). – Demian Wolf Feb 19 '20 at 00:18
3

You should call .after_idle(callback) before the mainloop and .after(ms, callback) at the end of the callback function.

Example:

import tkinter as tk
import time


def refresh_clock():
    clock_label.config(
        text=time.strftime("%H:%M:%S", time.localtime())
    )
    root.after(1000, refresh_clock)  # <--


root = tk.Tk()

clock_label = tk.Label(root, font="Times 25", justify="center")
clock_label.pack()

root.after_idle(refresh_clock)  # <--
root.mainloop()
Demian Wolf
  • 1,698
  • 2
  • 14
  • 34
  • 2
    ... just a side note, `after` is a [universal widget method](https://web.archive.org/web/20190426100646id_/http://infohost.nmt.edu/tcc/help/pubs/tkinter/web/universal.html), so it could be called on `timer_label` as well. – Wolf May 04 '21 at 09:49
2

I have a simple answer to this problem. I created a thread to update the time. In the thread i run a while loop which gets the time and update it. Check the below code and do not forget to mark it as right answer.

from tkinter import *
from tkinter import *
import _thread
import time


def update():
    while True:
      t=time.strftime('%I:%M:%S',time.localtime())
      time_label['text'] = t



win = Tk()
win.geometry('200x200')

time_label = Label(win, text='0:0:0', font=('',15))
time_label.pack()


_thread.start_new_thread(update,())

win.mainloop()
Hardeep Singh
  • 55
  • 2
  • 7
  • This code has multitude of problems. The while loop in the update() function is a busy loop. To access the global variable time_label from multiple threads is not great. – Kenji Noguchi Sep 30 '19 at 21:23
  • but i feel , this is the best way to do it. because this do not reduce the performance of the application. – Hardeep Singh Oct 12 '19 at 06:34
1

I just created a simple timer using the MVP pattern (however it may be overkill for that simple project). It has quit, start/pause and a stop button. Time is displayed in HH:MM:SS format. Time counting is implemented using a thread that is running several times a second and the difference between the time the timer has started and the current time.

Source code on github

0
from tkinter import *

from tkinter import messagebox

root = Tk()

root.geometry("400x400")

root.resizable(0, 0)

root.title("Timer")

seconds = 21

def timer():

    global seconds
    if seconds > 0:
        seconds = seconds - 1
        mins = seconds // 60
        m = str(mins)

        if mins < 10:
            m = '0' + str(mins)
        se = seconds - (mins * 60)
        s = str(se)

        if se < 10:
            s = '0' + str(se)
        time.set(m + ':' + s)
        timer_display.config(textvariable=time)
        # call this function again in 1,000 milliseconds
        root.after(1000, timer)

    elif seconds == 0:
        messagebox.showinfo('Message', 'Time is completed')
        root.quit()


frames = Frame(root, width=500, height=500)

frames.pack()

time = StringVar()

timer_display = Label(root, font=('Trebuchet MS', 30, 'bold'))

timer_display.place(x=145, y=100)

timer()  # start the timer

root.mainloop()
Meruemu
  • 611
  • 1
  • 8
  • 28
0

You can emulate time.sleep with tksleep and call the function after a given amount of time. This may adds readability to your code, but has its limitations:

def tick():
    while True:
        clock.configure(text=time.strftime("%H:%M:%S"))
        tksleep(0.25) #sleep for 0.25 seconds
    

root = tk.Tk()
clock = tk.Label(root,text='5')
clock.pack(fill=tk.BOTH,expand=True)
tick()
root.mainloop()
Thingamabobs
  • 7,274
  • 5
  • 21
  • 54