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\$ Commented Jan 4, 2024 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, 2024 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\$

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.