90

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
  • 257,635
  • 38
  • 339
  • 400
Diego Castro
  • 3,358
  • 4
  • 34
  • 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

7 Answers7

139

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
  • 341,422
  • 46
  • 489
  • 636
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,262
  • 4
  • 38
  • 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
  • 1
    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
  • 319
  • 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

The root.after(ms, func) is the method you need to use. Just call it once before the mainloop starts and reschedule it inside the bound function every time it is called. Here is an example:

from tkinter import *
import time
 

def update_clock():
    timer_label.config(text=time.strftime('%H:%M:%S',time.localtime()),
                  font='Times 25')  # change the text of the time_label according to the current time
    root.after(100, update_clock)  # reschedule update_clock function to update time_label every 100 ms

root = Tk()  # create the root window
timer_label = Label(root, justify='center')  # create the label for timer
timer_label.pack()  # show the timer_label using pack geometry manager
root.after(0, update_clock)  # schedule update_clock function first call
root.mainloop()  # start the root window mainloop
Wolf
  • 9,246
  • 7
  • 59
  • 101
Demian Wolf
  • 1,488
  • 2
  • 11
  • 28
  • 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
  • 1
  • 6
  • 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
  • 561
  • 1
  • 7
  • 27