I'm writing a function that needs to parse string to a timedelta. The user must enter something like "32m" or "2h32m", or even "4:13" or "5hr34m56s"... Is there a library or something that has this sort of thing already implemented?
10 Answers
For the first format (5hr34m56s), you should parse using regular expressions
Here is re-based solution:
import re
from datetime import timedelta
regex = re.compile(r'((?P<hours>\d+?)hr)?((?P<minutes>\d+?)m)?((?P<seconds>\d+?)s)?')
def parse_time(time_str):
parts = regex.match(time_str)
if not parts:
return
parts = parts.groupdict()
time_params = {}
for name, param in parts.items():
if param:
time_params[name] = int(param)
return timedelta(**time_params)
>>> from parse_time import parse_time
>>> parse_time('12hr')
datetime.timedelta(0, 43200)
>>> parse_time('12hr5m10s')
datetime.timedelta(0, 43510)
>>> parse_time('12hr10s')
datetime.timedelta(0, 43210)
>>> parse_time('10s')
datetime.timedelta(0, 10)
>>>
To me the most elegant solution, without having to resort to external libraries such as dateutil or manually parsing the input, is to use datetime's powerful strptime string parsing method.
from datetime import datetime, timedelta
# we specify the input and the format...
t = datetime.strptime("05:20:25","%H:%M:%S")
# ...and use datetime's hour, min and sec properties to build a timedelta
delta = timedelta(hours=t.hour, minutes=t.minute, seconds=t.second)
After this you can use your timedelta object as normally, convert it to seconds to make sure we did the correct thing etc.
print(delta)
assert(5*60*60+20*60+25 == delta.total_seconds())
I had a bit of time on my hands yesterday, so I developed @virhilo's answer into a Python module, adding a few more time expression formats, including all those requested by @priestc.
Source code is on github (MIT License) for anybody that wants it. It's also on PyPI:
pip install pytimeparse
Returns the time as a number of seconds:
>>> from pytimeparse.timeparse import timeparse
>>> timeparse('32m')
1920
>>> timeparse('2h32m')
9120
>>> timeparse('4:13')
253
>>> timeparse('5hr34m56s')
20096
>>> timeparse('1.2 minutes')
72
I've modified virhilo's nice answer with a few upgrades:
- added a assertion that the string is a valid time string
- replace the "hr" hour-indicator with "h"
- allow for a "d" - days indicator
- allow non-integer times (e.g.
3m0.25sis 3 minutes, 0.25 seconds)
.
import re
from datetime import timedelta
regex = re.compile(r'^((?P<days>[\.\d]+?)d)?((?P<hours>[\.\d]+?)h)?((?P<minutes>[\.\d]+?)m)?((?P<seconds>[\.\d]+?)s)?$')
def parse_time(time_str):
"""
Parse a time string e.g. (2h13m) into a timedelta object.
Modified from virhilo's answer at https://stackoverflow.com/a/4628148/851699
:param time_str: A string identifying a duration. (eg. 2h13m)
:return datetime.timedelta: A datetime.timedelta object
"""
parts = regex.match(time_str)
assert parts is not None, "Could not parse any time information from '{}'. Examples of valid strings: '8h', '2d8h5m20s', '2m4s'".format(time_str)
time_params = {name: float(param) for name, param in parts.groupdict().items() if param}
return timedelta(**time_params)
Django comes with the utility function parse_duration(). From the documentation:
Parses a string and returns a
datetime.timedelta.Expects data in the format
"DD HH:MM:SS.uuuuuu"or as specified by ISO 8601 (e.g.P4DT1H15M20Swhich is equivalent to4 1:15:20) or PostgreSQL's day-time interval format (e.g.3 days 04:05:06).
if you want to use : as separator, I use this function:
import re
from datetime import timedelta
def timedelta_parse(value):
"""
convert input string to timedelta
"""
value = re.sub(r"[^0-9:]", "", value)
if not value:
return
return timedelta(**{key:float(val)
for val, key in zip(value.split(":")[::-1],
("seconds", "minutes", "hours", "days"))
})
Examples:
In [4]: timedelta_pars("1:0:0:1")
Out[4]: datetime.timedelta(days=1, seconds=1)
In [5]: timedelta_pars("123.5")
Out[5]: datetime.timedelta(seconds=123, microseconds=500000)
In [6]: timedelta_pars("1:6:34:9.983")
Out[6]: datetime.timedelta(days=1, seconds=23649, microseconds=983000)
In [8]: timedelta_pars("23:45:00")
Out[8]: datetime.timedelta(seconds=85500)
If you use Python 3 then here's updated version for Hari Shankar's solution, which I used:
from datetime import timedelta
import re
regex = re.compile(r'(?P<hours>\d+?)/'
r'(?P<minutes>\d+?)/'
r'(?P<seconds>\d+?)$')
def parse_time(time_str):
parts = regex.match(time_str)
if not parts:
return
parts = parts.groupdict()
print(parts)
time_params = {}
for name, param in parts.items():
if param:
time_params[name] = int(param)
return timedelta(**time_params)
Use isodate library to parse ISO 8601 duration string. For example:
isodate.parse_duration('PT1H5M26S')
Also see Is there an easy way to convert ISO 8601 duration to timedelta?
Consider trying tempora.parse_timedelta.
$ pip-run 'tempora>=4.1.1'
Collecting tempora>=4.1.1
Downloading tempora-4.1.1-py3-none-any.whl (15 kB)
Collecting jaraco.functools>=1.20
Using cached jaraco.functools-3.3.0-py3-none-any.whl (6.8 kB)
Collecting pytz
Using cached pytz-2021.1-py2.py3-none-any.whl (510 kB)
Collecting more-itertools
Using cached more_itertools-8.8.0-py3-none-any.whl (48 kB)
Installing collected packages: more-itertools, pytz, jaraco.functools, tempora
Successfully installed jaraco.functools-3.3.0 more-itertools-8.8.0 pytz-2021.1 tempora-4.1.1
Python 3.9.2 (v3.9.2:1a79785e3e, Feb 19 2021, 09:06:10)
[Clang 6.0 (clang-600.0.57)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from tempora import parse_timedelta
>>> parse_timedelta("32m")
datetime.timedelta(seconds=1920)
>>> parse_timedelta("2h32m")
datetime.timedelta(seconds=9120)
>>> parse_timedelta("4:13")
datetime.timedelta(seconds=15180)
>>> parse_timedelta("5hr34m56s")
datetime.timedelta(seconds=20096)
ddays,hhours,mminutes andsseconds using one line (after importingdatetime):datetime.timedelta(days = d, hours = h, minutes=m, seconds=s). - zthomas.nc