6
\$\begingroup\$

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
        return total.strftime(FMT)

    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())
\$\endgroup\$
2
  • 1
    \$\begingroup\$ I rolled back your last edits. After getting an answer you are not allowed to change your code anymore. This is to ensure that answers do not get invalidated and have to hit a moving target. If you have changed your code you can either post it as an answer (if it would constitute a code review) or ask a new question with your changed code (linking back to this one as reference). Refer to this post for more information \$\endgroup\$ Jul 16, 2019 at 16:53
  • 1
    \$\begingroup\$ Thanks for the heads up... noted :) \$\endgroup\$
    – johnashu
    Jul 16, 2019 at 17:45

1 Answer 1

2
\$\begingroup\$
  • 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.

\$\endgroup\$
1
  • \$\begingroup\$ I updated my code with your suggestions and some extra logic.. like remaining hours and the length of the working week.. \$\endgroup\$
    – johnashu
    Jul 16, 2019 at 14:22

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.