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?
13 Answers
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())
8 Comments
datetime.strptime("32:20:25","%H:%M:%S") doesn't work), and you have to know the exact input format.timedelta parameter pretty annoying, but the best I can come up with for avoiding this is: delta = t - datetime.combine(t.date(), time.min), which is...horrible.dateutil is an unnecessary distraction. dateutil.parse.parse doesn't support timedelta objects.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
3 Comments
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)
>>>
4 Comments
dateutil.parser.parse won't parse timedelta objects. It returns a datetime, and it would trigger an exception for strings like '28:32:11.10'.If Pandas is already in your dependencies, it does this pretty well:
>>> import pandas as pd
>>> pd.Timedelta('5hr34m56s')
Timedelta('0 days 05:34:56')
>>> pd.Timedelta('2h32m')
Timedelta('0 days 02:32:00')
>>> pd.Timedelta('5hr34m56s')
Timedelta('0 days 05:34:56')
>>> # It is pretty forgiving:
>>> pd.Timedelta('2 days 24:30:00 10 sec')
Timedelta('3 days 00:30:10')
To convert to datetime.timedelta if you prefer that type:
>>> pd.Timedelta('1 days').to_pytimedelta()
datetime.timedelta(1)
Unfortunately this does not work though:
>>> pd.Timedelta('4:13')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "pandas\_libs\tslibs\timedeltas.pyx", line 1217, in
pandas._libs.tslibs.timedeltas.Timedelta.__new__
File "pandas\_libs\tslibs\timedeltas.pyx", line 454, in
pandas._libs.tslibs.timedeltas.parse_timedelta_string
ValueError: expected hh:mm:ss format
Pandas actually has pretty extensive date and time tools even though that is not its main purpose.
To install Pandas:
# If you use pip
pip install pandas
# If you use conda
conda install pandas
1 Comment
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)
7 Comments
I wanted to input just a time and then add it to various dates so this worked for me:
from datetime import datetime as dtt
time_only = dtt.strptime('15:30', "%H:%M") - dtt.strptime("00:00", "%H:%M")
4 Comments
dtt.strptime(myduration, "%H:%M:%S") - dtt(1900, 1, 1) also works...duration = datetime.strptime(value, "%H:%M:%S.%f") - datetime.strptime("0", "%S"). This is how Python saves these timedeltas to JSON (for durations less than one day, so typically anything like how a long a request took to process, etc).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?
Comments
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).
1 Comment
parse_duration() function uses regex match under the hood.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_parse("1:0:0:1")
Out[4]: datetime.timedelta(days=1, seconds=1)
In [5]: timedelta_parse("123.5")
Out[5]: datetime.timedelta(seconds=123, microseconds=500000)
In [6]: timedelta_parse("1:6:34:9.983")
Out[6]: datetime.timedelta(days=1, seconds=23649, microseconds=983000)
In [8]: timedelta_parse("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)
Comments
Inspired by a few answers here, this is a version that fully covers the constructor of timedelta():
import re
from datetime import timedelta
TIMEDELTA_PATTERN = re.compile(
r"""
^
\s*
((?P<weeks>\d+(\.\d+)?)(w|\s*wks?|\s*weeks?))?
\s*
((?P<days>\d+(\.\d+)?)(d|\s*days?))?
\s*
((?P<hours>\d+(\.\d+)?)(h|\s*hrs?|\s*hours?))?
\s*
((?P<minutes>\d+(\.\d+)?)(m|\s*mins?|\s*minutes?))?
\s*
((?P<seconds>\d+(\.\d+)?)(s|\s*secs?|\s*seconds?))?
\s*
((?P<milliseconds>\d+(\.\d+)?)(ms|\s*millis?|\s*milliseconds?))?
\s*
((?P<microseconds>\d+(\.\d+)?)(us|\s*micros?|\s*microseconds?))?
\s*
$
""",
flags=re.IGNORECASE | re.VERBOSE,
)
def parse_timedelta(value: str) -> timedelta:
match = TIMEDELTA_PATTERN.match(value)
if not match:
raise ValueError(f"Invalid timedelta: {value!r}")
params = {u: float(v) for u, v in match.groupdict().items() if v}
return timedelta(**params)
parse_timedelta('6d 6ms888.9us') # 6 days, 0:00:00.006889
parse_timedelta('8 weeks 1 day 1001us') # 57 days, 0:00:00.001001
parse_timedelta('1w 1d 1h 1m 1s 1ms 1us') # 8 days, 1:01:01.001001
parse_timedelta('1d \n 1h') # 1 day, 1:00:00
parse_timedelta('5 mins') # 0:05:00
Comments
Consider trying tempora.parse_timedelta (from tempora).
$ pip-run 'tempora>=4.1.1' -- -q
>>> 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)
2 Comments
pip-run 'tempora>=4.1.1', but I realize many wouldn't know pip-run or know that it accepts the same syntax as pip install. I also filed jaraco/skeleton#77 to consider addressing the general deficiency (docs don't link easily to the project).It doesn't cover all your examples, but a straightforward way to convert a time string in the format HH:MM:SS to a timedelta object is:
from datetime import time, timedelta
t = time.fromisoformat('00:28:00') # Or any other time in the HH:MM:SS format.
dt = timedelta(
hours=t.hour,
minutes=t.minute,
seconds=t.second,
microseconds=t.microsecond
)
ddays,hhours,mminutes andsseconds using one line (after importingdatetime):datetime.timedelta(days = d, hours = h, minutes=m, seconds=s).