# Calculate working hours using Python

This is a simple boilerplate / template for a bigger program (I hope), either linked to a DB or including a GUI frontend using PYQT or TKinter.

This is just a little tool I wrote to keep track of my own hours.

from datetime import datetime as dt

FMT = "%H:%M"
PARSE = (f"00:00", FMT)

class WorkingTime:
def __init__(self, worker, company):
self.worker = worker
self.company = company
# This can be a JSON file or a SQL DB
self.days_dict = {
"Monday": {"start": "", "end": ""},
"Tuesday": {"start": "", "end": ""},
"Wednesday": {"start": "", "end": ""},
"Thursday": {"start": "", "end": ""},
"Friday": {"start": "", "end": ""},
"Saturday": {"start": "", "end": ""},
"Sunday": {"start": "", "end": ""},
}

def set_start_hour(self, day, hour):
self.days_dict[day.title()]["start"] = hour

def set_end_hour(self, day, hour):
self.days_dict[day.title()]["end"] = hour

def set_start_and_end(self, day, start, end):
self.days_dict[day.title()]["start"] = start
self.days_dict[day.title()]["end"] = end

def get_start_hour(self, day):
return self.days_dict[day.title()]["start"]

def get_end_hour(self, day):
return self.days_dict[day.title()]["end"]

def get_start_and_end(self, day):
return self.days_dict[day.title()]["start"], self.days_dict[day.title()]["end"]

def calculate_day_hours(self, hours):
""" takes in a tuple and returns the number of hours"""
start, end = hours
try:
start_time_obj = dt.strptime(f"{start[:2]}:{start[2:4]}", FMT)
end_time_obj = dt.strptime(f"{end[:2]}:{end[2:4]}", FMT)
return end_time_obj - start_time_obj
except ValueError:
return dt.strptime(*PARSE).strftime(FMT)

def calculate_week_hours(self):
""" returns the sum of the week """
total = dt.strptime(*PARSE)
for k, v in self.days_dict.items():
if v.get("start") and v.get("end"):
hours = self.calculate_day_hours((v.get("start"), v.get("end")))
total += hours

def display_hours_day(self, day, hours):
""" Returns a string of the hours for that day """
day_hours = self.get_start_and_end(day)
rtn_hours = self.calculate_day_hours(day_hours)
space = " " * (11 - len(day))
return f"\n\t{day.title()}{space}::  {rtn_hours} Hours\n"

def display_hours_week(self):
""" Returns a string of the hours for the week """
rtn_str = "\n"
rtn_str += f"\n\tName       ::  {self.worker}\n\tCompany    ::  {self.company}"
line = "-" * 50
total_line = '-'*26
rtn_str += f"\n{line}"
for k, v in self.days_dict.items():
if v:
hours = self.calculate_day_hours((v.get("start"), v.get("end")))
rtn_str += self.display_hours_day(k, hours)
total = self.calculate_week_hours()
rtn_str += f"\t{total_line}"
rtn_str += f"\n\tTotal      ::  {total} Hours\n"
rtn_str += f"\n{line}\n\n"
return rtn_str

wt = WorkingTime('Maffaz', 'IT Circle Consult')

# Set hours
wt.set_start_hour("monday", "0730")
wt.set_end_hour("monday", "1800")
wt.set_start_and_end("TUESDAY", "", "")
wt.set_start_and_end("wednesday", "", "")
wt.set_start_and_end("THUrsday", "", "")
wt.set_start_and_end("FrIDAY", "", "")

# # get hours
print(wt.get_start_hour("Monday"))
print(wt.get_end_hour("Wednesday"))
print(wt.get_start_and_end("Friday"))

# calculate day hours
print(wt.calculate_day_hours(wt.get_start_and_end("monday")))

# calculate week hours
week = wt.calculate_week_hours()
print(week)

# print day hours
tues_hours = wt.get_start_and_end("tuesday")
print(wt.display_hours_day("Tuesday", tues_hours))

# print week hours total
print(wt.display_hours_week())

• Thanks for the heads up... noted :) – johnashu Jul 16 '19 at 17:45

• Extract day.title() to a separate method. In this case, if you wanted to change how you store the days of the weeks (e.g. Monday, Tuesday ... -> monday, tuesday...), you would have to change day.title() to day.lower() only in one place*.

• Implement set_start_and_end() by calling set_start() and set_end(). Identically for getters. The rationale is similar to the previous point: if you decided to change the data structure where you store the time slots, you would have to change less code*.

• Magic numbers. e.g. 11. First of all extract such number into a constant with a meaningful name. Btw, is this value really a constant? Will you still want to use this value if the names of the days of the week change (e.g. you decide to use Mon, Tue etc., or names in another language)

• Do not do formatting in calculate_*** methods. You have display_** methods responsible for that. Let all the formatting be done there. Btw, maybe format_*** would be a better name?

• Another question is how you initialize your days_dict. Maybe you would want to pass a dictionary with some already existing values as an argument to the constructor. Possibly make it optional, and if we don't pass anything initialize it with default values as you do now:

def __init__(self, worker, company, days_dict=None):
self.worker = worker
self.company = company
# This can be a JSON file or a SQL DB
self.days_dict = days_dict or DEFAULT_DAYS_DICT


Also, maybe it would be nicer to have default values like "Monday": {"start": "0000", "end": "0000"}?

• How about having a fluent API?

wt = WorkingTime('Maffaz', 'IT Circle Consult')
.set_start_hour("monday", "0730")
.set_end_hour("monday", "1800")
...
.set_start_and_end("FrIDAY", "", "")


For that you would need to return self object from every setter/getter:

def set_start_hour(self, day, hour):
...
return self


* I wrote about situations where you would have to change a lot of code. The problem is actually not with changing it. The problem is that in some situations you might forget to do it and your program will start working incorrectly.

• I updated my code with your suggestions and some extra logic.. like remaining hours and the length of the working week.. – johnashu Jul 16 '19 at 14:22