7
\$\begingroup\$

I have two lap time strings I need to subtract, the format is minutes, seconds, milliseconds. This is to compare a new world record time to the previous holder's time. The result will always be positive.

Example input:

x = "1:09.201"
y = "0:57.199"
# 12.002

I have two working solutions, but I'm not sure I'm content. Am I able to make this better, shorter, cleaner, or more readable?

1st solution:

from datetime import datetime, time, timedelta

def getTimeDifference(t1, t2):
    wrTime = convertTimeString(t1)
    wrTime = datetime(2000, 1, 1,
        minute=wrTime["minutes"],
        second=wrTime["seconds"],
        microsecond=wrTime["milliseconds"]*1000
    )

    previousWrTime = convertTimeString(t2)
    previousWrTime = datetime(2000, 1, 1,
        minute=previousWrTime["minutes"],
        second=previousWrTime["seconds"],
        microsecond=previousWrTime["milliseconds"]*1000
    )

    current, prev = wrTime.timestamp(), previousWrTime.timestamp()
    difference = round(prev - current, 3)

    return difference


def convertTimeString(time):
    time = time.replace(":", " ").replace(".", " ").split()
    try:
        converted = {
            "minutes": int(time[0]),
            "seconds": int(time[1]),
            "milliseconds": int(time[2])
        }
    except IndexError:
        print("Index error occured when formatting time from scraped data")

    return converted 


x = "1:09.201"
y = "0:57.199"
print(getTimeDifference(y, x))

2nd solution:

from datetime import datetime, time, timedelta

# Takes two m:ss.fff time strings
# Example: 1:
def getTimeDifference(t1, t2):
    wrTime = convertTimeString(t1)
    time1 = timedelta(
        minutes=wrTime["minutes"],
        seconds=wrTime["seconds"],
        milliseconds=wrTime["milliseconds"])
    
    previousWrTime = convertTimeString(t2)
    time2 = timedelta(
        minutes=previousWrTime["minutes"],
        seconds=previousWrTime["seconds"],
        milliseconds=previousWrTime["milliseconds"])
    
    diff = time2 - time1
    formatted = f"{diff.seconds}.{int(diff.microseconds/1000):>03}"
    return formatted # 0.000 seconds


def convertTimeString(time):
    time = time.replace(":", " ").replace(".", " ").split()
    try:
        converted = {
            "minutes": int(time[0]),
            "seconds": int(time[1]),
            "milliseconds": int(time[2])
        }
    except IndexError:
        print("Index error occured when formatting time from scraped data")

    return converted


x = "1:09.201"
y = "0:57.199"
print(getTimeDifference(y, x))
```
\$\endgroup\$
1
  • \$\begingroup\$ Can timestamps use less than three digits for the milliseconds? If so, your algorithm to extract the milliseconds fails. \$\endgroup\$
    – uli
    Commented Jan 4 at 17:46

2 Answers 2

11
\$\begingroup\$

Figure out what your requirements are; your two variations actually perform different operations: the first one returns the difference as a float of seconds, the second one as a formatted string. I'd argue the best format for a time difference is a timedelta instance, and factor out the formatting code.

Use the standard library when you can. In this case, you can easily parse any string into a datetime object using datetime.strptime as long as you know the string format. This is likely more robust than your own implementation, more concise, and easier to understand when reading code.

You can subtract two datetime objects directly to get the resulting timedelta, no need to manually build timedelta's before performing arithmetic.

Include type hints to your function's signature, to make it clear that it expects strings and returns a float/string/timedelta (depending on implementation). You could also document it with a docstring, but I feel like it might not be necessary with type hints.

There is no built-in method for formatting timedelta, so your formatting code is about as good as it gets.

You don't use datetime.time, so you should remove that import.

With this in mind, you can achieve your requirements with very few lines of code:

from datetime import datetime, timedelta


TIME_FORMAT = '%M:%S.%f'


def get_time_difference(t1: str, t2:str) -> timedelta:
    return (datetime.strptime(t1, TIME_FORMAT)
            - datetime.strptime(t2, TIME_FORMAT))


def format_difference(difference: timedelta) -> str:
    sign = '-' if difference.days < 0 else ''
    if difference.days < 0:
        difference = - difference
    return f'{sign}{difference.seconds}.{difference.microseconds//1000:03}'



if __name__ == '__main__':
    print(format_difference(get_time_difference('1:09.201', '0:57.199')))

Edit: as pointed out in the comments, formatting can be simplified a lot by using the total_seconds method of the timedelta class:

def format_difference(difference: timedelta) -> str:
    return f'{difference.total_seconds():.3f}'

Now that all functionality is implemented with simple calls to standard library methods, the relevance of wrapping them in helper functions such as these is questionable, I'll let you decide depending on your actual use case.

\$\endgroup\$
1
  • 3
    \$\begingroup\$ I know the use case is more a matter of a few seconds and this won’t matter much, but since you made your format_difference generic enough to check on days being negative, you should probably return f'{sign}{difference.total_seconds():.3f}' instead. Heck, using total_seconds also properly handles negative deltas so you don't even need the first three lines and the sign variable at all. \$\endgroup\$ Commented Jan 4 at 16:05
1
\$\begingroup\$

Here is a solution that only uses string parsing:

import re
import sys

'''
Input time one at a time, in the format:
    m:ss.sss

Convert to seconds.
Record the best time.
'''

def parse_time_str(time_str: str):
    regex = re.match(r"(\d+):(\d\d.\d\d\d)", time_str)
    if not regex:
        print(f"Invalid time string: {time_str}", file=sys.stderr)
        return float('inf')
    match regex.groups():
        case (minutes, seconds):
            return float(minutes) * 60 + float(seconds)
        case fail:
            print(f"Invalid parsed time: {fail}", file=sys.stderr)
            return float('inf')

def format_time(time: float):
    positive = time >= 0
    minutes, seconds = divmod(abs(time), 60)
    return f"{'-' if not positive else ''}{int(minutes)}:{seconds:06.3f}"

class TimeTracker:
    
    def __init__(self, best_time: float = float('inf')):
        self.best_time = best_time
    
    def check_time(self, time_str: str):
        parsed_time = parse_time_str(time_str)
        if parsed_time == float('inf'):
            return
        if self.best_time == float('inf'):
            print(f"No previous best time. New best time: {time_str}")
            self.best_time = parsed_time
            return
        print(f"Previous best time: {format_time(self.best_time)}")
        print(f"Time delta: {format_time(parsed_time - self.best_time)}")
        if parsed_time < self.best_time:
            print(f"New best time: {time_str}")
            self.best_time = parsed_time
            
if __name__ == "__main__":
    tracker = TimeTracker()
    while True:
        time_str = input("Time: ")
        tracker.check_time(time_str)

For input times, we simply follow the specified format, then parse the time and convert to a number for storage and comparison. When accessed we can simply reproduce the formatting.

\$\endgroup\$

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