Hours and Minutes math in a work week calculator I have written a small bit of code to tell me how many hours I need to work to finish up my 40 hours a week each day.

I can add hours per day and it will calculate total hours left in the work week.

My main issue with it is the way my math is written. It just seems so complicated and unnecessary but I was unable to find a better solution and was hoping someone could point me in the right direct for a better formula.

If you check the hours_left_update(self) method you will see that I am using floats, the Decimal method with str and quantize as well as split. All to try and get an output to a label that shows my results in hours and minutes.

Keep in mind when you put in hours worked the time clock system I get my information from uses a decimal system to display work hours. So if I worked 7 and 1/2 hours one day it would show me 7.50 hours worked. This is the kind of format I am putting into the program and it automatically convert that number to display as 7 hours 30 min.

from decimal import Decimal, ROUND_HALF_UP
from time import strftime
import tkinter as tk
import datetime


class TimeClockApp(tk.Frame):
    def __init__(self, master, *args, **kwargs):
        tk.Frame.__init__(self, master, *args, **kwargs)
        self.master = master
        self.hours_worked = 0.00
        self.dtlbl = tk.Label(self.master, text="Current Date-Time: {} ~ {}".format(datetime.date.today(),strftime("%H:%M:%S")))
        self.dtlbl.grid(row=0, column=0, columnspan=2)

        self.lbl1 = tk.Label(self.master, text="Hours worked").grid(row=1, column=0, columnspan=2)

        weekdays = ["Monday: ", "Tuesday: ", "Wednesday: ", "Thursday: ", "Friday: "]
        for i in range(5):
            tk.Label(self.master, text=weekdays[i], anchor = "e").grid(row=i+2, column=0, sticky="e")

        self.lbl7 = tk.Label(self.master, text="Total Hours worked:").grid(row=7, column=0, sticky="e")
        self.lbl8 = tk.Label(self.master, text="00h 0m")
        self.lbl8.grid(row=7, column=1, sticky="w")

        self.lbl9 = tk.Label(self.master, text="Hours left to 40:").grid(row=8, column=0, sticky="e")
        self.lbl10 = tk.Label(self.master, text="40h 0m")
        self.lbl10.grid(row=8, column=1, sticky="w")

        self.entry_list = []

        for i in range(5):
            self.entry_list.append(tk.Entry(self.master,width=7))
            self.entry_list[i].bind("&lt;Key&gt;", lambda x: self.master.after(500, self.hours_left_update))
            self.entry_list[i].grid(row = i+2, column = 1, sticky = "w")

        self.status_clock()

    def status_clock(self):
        self.dtlbl.config(text ="Current Date-Time: {} ~ {}".format(datetime.date.today(),strftime("%H:%M:%S")))
        self.dtlbl.after(1000, lambda: self.status_clock())

    def hours_left_update(self):
        for entry in self.entry_list:
            if entry.get() != "":
                try:
                    #print(Decimal(entry.get()).quantize(Decimal("0.01"), ROUND_HALF_UP))
                    self.hours_worked += float(entry.get())
                except:
                    pass

        x = str(Decimal(str(self.hours_worked)).quantize(Decimal("0.01"), ROUND_HALF_UP)).split('.')
        m60 = "00"

        if int(x[1]) != 0:
            m60 = Decimal(str(60 * float("0.{}".format(x[1])))).quantize(Decimal("1"))

        self.lbl8.config(text="{}h {}m".format(x[0], m60))

        var = 40 - int(x[0])
        if int(x[1]) != 0:
            var = 39 - int(x[0])

        x2 = "00"

        if Decimal(str(60 * float("0.{}".format(x[1])))).quantize(Decimal("1")) != 0:
            x2 = (60 - Decimal(str(60 * float("0.{}".format(x[1])))).quantize(Decimal("1")))

        self.lbl10.config(text="{}h {}m".format(var,x2))

        self.hours_worked = 0.00


if __name__ == "__main__":
    root = tk.Tk()
    app = TimeClockApp(root)
    root.mainloop()
It's a nice little program. Intuitively I'd prefer to input the times as 1:30 instead of 1.5, but that's just a personal preference.

The self.hours_worked field is redundant. It should simply be a local variable in the hours_left_update method. It's kind of weird that you reset it at the end of the method instead of initializing it at the beginning of the method. Anyway, just make it a local variable.

I played around a bit and came up with this code:

def hours_left_update(self):
    minutes_worked = 0
    for entry in self.entry_list:
        if entry.get() != "":
            try:
                minutes_worked += 60 * float(entry.get())
            except:
                pass

    minutes_worked = round(minutes_worked)   # just in case
    minutes_left = 40 * 60 - minutes_worked

    self.lbl8.config(text="{:02d}h {:02d}m".format(minutes_worked // 60, minutes_worked % 60))
    self.lbl10.config(text="{:02d}h {:02d}m".format(minutes_left // 60, minutes_left % 60))


Is there any reason for waiting 500 ms before updating the total times after each key press? I generally don't expect programs to have unnecessary delays.

I changed the output format for the two computed fields. As a result, the format changes after I press the first key because the labels are initialized with the old format. To make things consistent, I'd create the labels without any text and call self.hours_left_update() at the end of the constructor.



After cleaning up the rest of the code, it looks like this:

from time import strftime
import tkinter as tk
import datetime


class TimeClockApp(tk.Frame):
    def __init__(self, master, *args, **kwargs):
        tk.Frame.__init__(self, master, *args, **kwargs)

        self.now_lbl = tk.Label(master)
        self.now_lbl.grid(row=0, column=0, columnspan=2)

        tk.Label(master, text="Hours worked").grid(row=1, column=0, columnspan=2)

        self.entries = []
        weekdays = ["Monday: ", "Tuesday: ", "Wednesday: ", "Thursday: ", "Friday: "]
        for i in range(5):
            tk.Label(master, text=weekdays[i], anchor="e").grid(row=i+2, column=0, sticky="e")
            entry = tk.Entry(master, width=7)
            entry.grid(row=i+2, column=1, sticky="w")
            entry.bind("&lt;Key&gt;", lambda event: master.after(0, self.update_totals))
            self.entries.append(entry)

        tk.Label(master, text="Total Hours worked:").grid(row=7, column=0, sticky="e")
        self.time_worked_lbl = tk.Label(master)
        self.time_worked_lbl.grid(row=7, column=1, sticky="w")

        tk.Label(master, text="Hours left to 40:").grid(row=8, column=0, sticky="e")
        self.time_left_lbl = tk.Label(master)
        self.time_left_lbl.grid(row=8, column=1, sticky="w")

        self.update_clock()
        self.update_totals()

    def update_clock(self):
        now = "Current Date-Time: {} ~ {}".format(datetime.date.today(), strftime("%H:%M:%S"))
        self.now_lbl.config(text=now)
        self.now_lbl.after(200, self.update_clock)

    def update_totals(self):
        minutes_worked = 0
        for entry in self.entries:
            try:
                minutes_worked += 60 * float(entry.get())
            except:
                pass

        minutes_worked = round(minutes_worked)
        minutes_left = 40 * 60 - minutes_worked

        self.time_worked_lbl.config(text="{:02d}h {:02d}m".format(minutes_worked // 60, minutes_worked % 60))
        self.time_left_lbl.config(text="{:02d}h {:02d}m".format(minutes_left // 60, minutes_left % 60))

if __name__ == "__main__":
    root = tk.Tk()
    app = TimeClockApp(root)
    root.mainloop()


I did at least the following:


Start the method names with a verb.
Rename lbl8 to time_worked_lbl, and similar ones.
Only store those labels in self that are needed by the other methods.
Replace self.master with a simple master.
Made the formatting consisten, especially the spacing around named arguments.
Removed unnecessary lambdas.
Merge the two for loops into one.
Build the UI elements strictly from top to bottom.


Too bad that the grid method doesn't return self. That would have saved some more lines. :) By the way, this means that your code did something unexpected. It stored None into self.lbl1.