efthimio
efthimio

Reputation: 928

Convert string that was originally a `timedelta` back into a `timedelta` object in Python

I have strings which were originally produced from timedelta objects like so: print(f'{my_delta}').

I have many of these statements logged (e.g. "12:21:00", "1 day, 0:53:00", "2 days, 9:28:00") and I simply want to parse these logged statements to convert back to timedelta objects. Is this possible with the datetime library?

The strings were literally produced from just printing timedelta objects, but I cannot seem to convert back by using timedelta(my_string). Wondering if there is a standard way of doing this that I am missing.

Upvotes: 0

Views: 112

Answers (2)

efthimio
efthimio

Reputation: 928

As others have noted emphatically, the datetime library does not seem to support this functionality.

Here's a one-liner that uses regex:

>>> from datetime import timedelta
>>> import re
>>> 
>>> str_to_dlt = lambda t: timedelta(days=int((m:=re.match(r'((\d+)\sdays?,\s*)?(\d{1,2}):(\d{2}):(\d{2})', t))[2] or 0), hours=int(m[3]), minutes=int(m[4]), seconds=int(m[5]))

Printing the timedelta returns the original string it was supplied:

>>> print(str_to_dlt('5:11:13'))
5:11:13
>>> 
>>> print(str_to_dlt('1 day, 5:11:13'))
1 day, 5:11:13
>>> 
>>> print(str_to_dlt('2 days, 5:11:13'))
2 days, 5:11:13

Upvotes: 1

juanpa.arrivillaga
juanpa.arrivillaga

Reputation: 96267

This should be amenable to regex, since it seems very regular. It may have days specified at the beginning, or microseconds at the end. But its very... regular:

import re

pattern = r'''
    ((?P<days>-?\d{1,})\ days?,\ )?  # maybe there will be day(s)
    (?P<hours>\d{1,2})  # hours will be one or 2 digits
    :(?P<minutes>\d{2})  # separated by a colon, minutes will be 2 digits
    :(?P<seconds>\d{2})  # separated by a colon, seconds will be 2 digits
    (\.(?P<microseconds>\d{6}))?  # maybe, separated by a period, microseconds will be 6 digits
'''

parse_timedelta_regex = re.compile(pattern, flags=re.VERBOSE)

Note, take care using the re.VERBOSE flag with spaces, they are ignored outside of specific cases unless you escape them. But I do think it is more readable this way.

Examples of how to use this:

>> parse_timedelta_regex.search("1000 days, 0:00:00").groupdict()
{'days': '1000', 'hours': '0', 'minutes': '00', 'seconds': '00', 'microseconds': None}
>>> parse_timedelta_regex.search("1 day, 11:00:13.000130").groupdict()
{'days': '1', 'hours': '11', 'minutes': '00', 'seconds': '13', 'microseconds': '000130'}

You could also just write the function to convert the string to timedelta object like so:

>>> def string_to_timedelta(s):
...     groups = parse_timedelta_regex.search(s).groupdict()
...     args = {k:int(v) for k,v in groups.items() if v}
...     return datetime.timedelta(**args)
...
>>> string_to_timedelta(str(datetime.timedelta(seconds=1337)))
datetime.timedelta(seconds=1337)

Here's a more rigorous test (still not very rigorous):

>> def test_parse(n):
...     for _ in range(n):
...         seconds = random.random()*100_000_000
...         td = datetime.timedelta(seconds=seconds)
...         parsed = string_to_timedelta(str(td))
...         assert td == parsed
...
>>> test_parse(1000)
>>> test_parse(10_000)
>>> test_parse(100_000)

Upvotes: 1

Related Questions