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?
- 4,303
- 4
- 29
- 51
- 29,908
- 21
- 78
- 111
-
For people just looking to construct a timedelta object of `d` days, `h` hours, `m` minutes and `s` seconds using one line (after importing `datetime`): `datetime.timedelta(days = d, hours = h, minutes=m, seconds=s)`. – zthomas.nc Apr 18 '17 at 22:09
11 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())
- 19,326
- 12
- 84
- 95
-
42Note this approach only works if the timespan is less than 24 hours (`datetime.strptime("32:20:25","%H:%M:%S")` doesn't work), and you have to know the exact input format. – verdesmarald Oct 02 '12 at 01:27
-
This also only part answers the OP's question. If the function needs to deal with multiple formats - you still need additional format inspection (1 colon or 2?). – Danny Staple Oct 29 '12 at 13:15
-
In case days, months or years have to be used, it wouldn't be hard to expand on the format to e.g. "%Y-%m-%d_%H:%M:%S". Details on the format options available in the [library docs](http://docs.python.org/2/library/datetime.html#strftime-and-strptime-behavior). In this case, though, it isn't quite clear to me what time delta is desired - seconds since O A.D.? Anyway, something in the manner of `delta = target_date - start_date` could be used to specify it. – metakermit Oct 29 '12 at 21:51
-
3@verdesmarald So, as of python 3.5, is there an elegant solution without using external libraries and without assuming timespan is less than 24 hours? – max Apr 22 '16 at 18:57
-
1I find the need to manually specify the named parameters for the `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. – Kyle Strand Jul 26 '16 at 19:56
-
3A serious problem with this approach is that if you include days then sending %d into strptime, will not enable you to input day 0, as only days of >=1 are valid for a date. – user1581390 Jun 24 '18 at 02:58
-
@metakermit Better not to use %Y (years) and %m (months) here, since they don't represent deterministic days. – John Lin Jan 10 '19 at 02:18
-
1Mention of `dateutil` is an unnecessary distraction. `dateutil.parse.parse` doesn't support timedelta objects. – Jason R. Coombs Jun 20 '21 at 14:10
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
- 1
- 1
- 4,509
- 1
- 17
- 23
-
-
1@luca.giovagnoli In Scala you can use Duration class. Duration can be constructed from strings like '15 seconds', '4 minutes' etc. – Konrad Malik Aug 13 '20 at 12:41
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,712
- 3
- 36
- 37
- 6,026
- 1
- 27
- 26
-
5I was thinking of some kind of function that could take anything you throw at it and still be able to handle converting to timedelta. – priestc Jan 07 '11 at 17:15
-
2
-
4I don't see how dateutil.parser.parse can parse durations, seems like it always returns a datetime. What am I missing? – Nickolay Jan 12 '14 at 13:36
-
11`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'`. – Pietro Saccardi Aug 18 '16 at 14:34
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")
- 2,533
- 18
- 14
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)
- 10,579
- 9
- 63
- 75
-
1Great! I added " *" between the elements to also allow "1d 3h 5m" – Marcel Waldvogel Apr 01 '19 at 05:19
-
@MarcelWaldvogel nice, if you copy the text of the new regex I'll add your answer in – Peter Nov 04 '19 at 16:32
-
@virhilo and Peter: My slight evolution on your code is here: https://github.com/zeitgitter/zeitgitterd/blob/master/zeitgitter/deltat.py . I presume it is OK to use your code. Do you have any preferences for the license? MIT, Apache, GPL, …? – Marcel Waldvogel Jan 28 '20 at 15:51
-
1Marcel, can you send me your address so I can sue? JK go ahead any license is fine. – Peter Jan 28 '20 at 17:12
-
Here is the new Regex; the difference is the " *"s: regex = re.compile(r'^((?P
[\.\d]+?)d)? *' r'((?P – Marcel Waldvogel Mar 30 '20 at 15:49[\.\d]+?)h)? *' r'((?P [\.\d]+?)m)? *' r'((?P [\.\d]+?)s)?$') -
In my copy of this function I also added the ability to make a negative time by starting the string with `'-'`. – Neil C. Obremski Mar 02 '21 at 14:14
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
- 1
- 47,683
- 25
- 191
- 275
-
For further information: Django's `parse_duration()` function uses regex match under the hood. – Eido95 Sep 14 '20 at 17:33
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)
- 670
- 1
- 5
- 17
-
-
You were right: re.sub should leave dots in the string. I corrected the function, it should work now. thanks! – n4321d Apr 07 '22 at 04:17
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
- 516
- 6
- 10
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?
- 39
- 3
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)
- 636
- 6
- 12
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)
- 39,015
- 10
- 79
- 86