Built a time calculator Project

Hello!
I finished the second certified project and I feel quite good because it took me less effort than the first one. However, I think my code could be improved, I feel like I am using ‘if’ loops all the time! It does work, but I was wondering if someone has suggestions to make it more efficient/readible and to try to find a different way of thinking about these problems. Thank you all in advance!

My code:

def add_time(start, duration, day = None):
    # separate in variables start hours, minutes and AM/PM
    s_time = start.split(' ')[0]
    s_hours = int(s_time.split(':')[0])
    s_min = int(s_time.split(':')[1])
    s_period = start.split(' ')[1]

    # transform into a 24:00 format
    if s_period == 'PM':
        s_hours += 12
    d_hours = int(duration.split(':')[0])
    d_min = int(duration.split(':')[1])

    # calculate total minutes
    extra_hours = 0
    if s_min + d_min > 60:
        extra_hours = (s_min + d_min) // 60
        final_min = (s_min + d_min) % 60
    else:
        final_min = s_min + d_min
    # final_min does need to have two digits
    if final_min < 10:
        final_min = '0' + str(final_min)

    # calculate total hours and extra days
    extra_days = 0
    if s_hours + d_hours + extra_hours > 24:
        extra_days = (s_hours + d_hours + extra_hours) // 24
        final_hours = (s_hours + d_hours + extra_hours) % 24
    else:
        final_hours = (s_hours + d_hours + extra_hours)
    
    # encode AM/PM for the new time
    if final_hours >= 12:
        final_period = 'PM'
        final_hours -= 12
        if final_hours == 0:
            final_hours = 12
    else:
        final_period = 'AM'
        if final_hours == 0:
            final_hours = 12

    
    # print week day
    if day:
        day = day.lower().capitalize()
        week =[
            'Monday',
            'Tuesday',
            'Wednesday',
            'Thursday',
            'Friday',
            'Saturday',
            'Sunday'
            ]
            
        if extra_days:
            if extra_days == 1:
                for i in range(len(week)):
                    if week[i] == day:
                        new_day = i + 1
                        if new_day > 7:
                            new_day = new_day % 7
                new_time = f"{final_hours}:{final_min} {final_period}, {week[new_day]} (next day)"
            elif extra_days > 1:
                for i in range(len(week)):
                    if week[i] == day:
                        new_day = i + extra_days
                        if new_day > 7:
                            new_day = new_day % 7
                new_time = f"{final_hours}:{final_min} {final_period}, {week[new_day]} ({extra_days} days later)"
        else:
            new_time = f"{final_hours}:{final_min} {final_period}, {day}"
    else:
    # print relative day
        if extra_days:
            if extra_days == 1:
                new_time = f"{final_hours}:{final_min} {final_period} (next day)"
            elif extra_days > 1:
                new_time = f"{final_hours}:{final_min} {final_period} ({extra_days} days later)"
        else:
            new_time = f"{final_hours}:{final_min} {final_period}"

    return new_time

Unless you have specific idea what to improve first, focus on making things simpler. Make sure to do it in small steps, checking after each one, if everything is still working.

Take a look at what happens in the last part of the code, the big if/else block is doing the following:

new_time = f"{final_hours}:{final_min} {final_period}, {week[new_day]} (next day)"
new_time = f"{final_hours}:{final_min} {final_period}, {week[new_day]} ({extra_days} days later)"
new_time = f"{final_hours}:{final_min} {final_period}, {day}"
new_time = f"{final_hours}:{final_min} {final_period} (next day)"
new_time = f"{final_hours}:{final_min} {final_period} ({extra_days} days later)"
new_time = f"{final_hours}:{final_min} {final_period}"

This shows how big part is not changing, just moving the constant part outside of if/else will make it easier to follow.


A bit similar pattern is when else could be really removed, for example:

    if s_min + d_min > 60:
        extra_hours = (s_min + d_min) // 60
        final_min = (s_min + d_min) % 60
    else:
        final_min = s_min + d_min

The final_min has similar parts in both cases. Extracting it before if would make it a kind of default case, then if some condition is met, something more can happen to it.


Another thing is assignments with indices, in example start.split(' ')[0]. Python has ability to unpack to multiple variables at the same time, for example:

coordinates = [0, 2]
x, y = coordinates
1 Like

Two suguessions to save some if/else blocks:

  1. Is this simple two lines
    extra_hours = (s_min + d_min) // 60
    final_min = (s_min + d_min) % 60

doing the same thing of the following code?

    extra_hours = 0
    if s_min + d_min > 60:
        extra_hours = (s_min + d_min) // 60
        final_min = (s_min + d_min) % 60
    else:
        final_min = s_min + d_min

If the sum of s_min and d_min is less than 60, the result of (s_min + d_min) // 60 is already 0.

  1. The issue of padding zero to single digit integer to make it two digits can be done by a little trick of string formating, by adding “:02” just after the variable within curly braces of f-string, which means filling ‘0’ to make that part of the output string have a width of 2. For example:
>>> n = 4
>>> print(f'{n:02}')
04
>>> print(f'{n:03}') 
004

So it can save the the following if statement:

# final_min does need to have two digits
    if final_min < 10:
        final_min = '0' + str(final_min)

This tricks is part of the " Format Specification Mini-Language", which have many other features. The details can be checked from Python documentation.

1 Like