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?
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)
>>>
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'
. –
Sharlasharleen 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())
datetime.strptime("32:20:25","%H:%M:%S")
doesn't work), and you have to know the exact input format. –
Blocking delta = target_date - start_date
could be used to specify it. –
Anastigmatic 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. –
Oui dateutil
is an unnecessary distraction. dateutil.parse.parse
doesn't support timedelta objects. –
Consubstantiate 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
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)
>>>
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'
. –
Sharlasharleen 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
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")
dtt.strptime(myduration, "%H:%M:%S") - dtt(1900, 1, 1)
also works... –
Baalman 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). –
Adachi 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.25s
is 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://mcmap.net/q/143920/-how-to-construct-a-timedelta-object-from-a-simple-string
: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)
'-'
. –
Juba [\.\d]+
will match multiple .
. In fact, it will match a string with only .
. –
Furtek 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.P4DT1H15M20S
which is equivalent to4 1:15:20
) or PostgreSQL's day-time interval format (e.g.3 days 04:05:06
).
parse_duration()
function uses regex match under the hood. –
Chincapin 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)
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?
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)
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)
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). –
Consubstantiate import re
from datetime import timedelta
class InvalidTimeString(Exception):
"""Exception raised when the input string is not a valid time string."""
_TIME_REGEX = re.compile(r'((?P<hours>\d+?)hr)|((?P<minutes>\d+?)m)|((?P<seconds>\d+?)s)')
def parse_time(time_str: str) -> timedelta | None:
"""
Parse a time string into a timedelta object.
Args:
time_str (str): The time string to parse. This can include hours ("Xhr"), minutes ("Ym"), and seconds ("Zs").
Each component is optional and can appear in any order, but they should be separated by non-numeric characters.
Returns:
timedelta: A timedelta object representing the time in the input string.
Raises:
InvalidTimeString: If the input string is not a valid time string.
Usage:
>>> parse_time('12hr5m10s')
datetime.timedelta(seconds=43510)
>>> parse_time('12hr')
datetime.timedelta(seconds=43200)
>>> parse_time('12hr10s')
datetime.timedelta(seconds=43210)
>>> parse_time('12hr5m10s')
datetime.timedelta(seconds=43510)
>>> parse_time('5m10s12hr')
datetime.timedelta(seconds=43510)
"""
time_params = {"hours": 0, "minutes": 0, "seconds": 0}
matches = _TIME_REGEX.finditer(time_str)
if not matches:
raise InvalidTimeString(f"'{time_str}' is not a valid time string")
for match in matches:
match_dict = match.groupdict()
for name, param in match_dict.items():
if param:
time_params[name] = int(param)
return timedelta(**time_params)
The parse_time
function is designed to parse a time string into a Python timedelta object. The input time string can include hours ("Xhr"), minutes ("Ym"), and seconds ("Zs") in any order. Each component is optional and can be separated by non-numeric characters.
The function begins by defining a regular expression that is capable of matching these time string components. This regular expression includes named groups for hours, minutes, and seconds, which makes it easier to extract these values later.
Next, the function uses the finditer method of the regular expression to find all matches in the input string. This method returns an iterator yielding match objects for every non-overlapping match of the regular expression pattern in the string.
The function then iterates over each match, converting the match object into a dictionary using the groupdict method. This method returns a dictionary containing all the named groups found in the match, with the group names as the keys and the matched strings as the values.
For each named group in the dictionary, the function checks if the group has a value. If it does, the function converts this value into an integer and stores it in the time_params dictionary under the appropriate key (hours, minutes, or seconds).
Finally, the function creates a timedelta
object from the time_params dictionary and returns it.
There are several advantages to using this approach:
- Flexibility: The function can handle time strings in a variety of formats, making it more useful in different situations.
- Robustness: The function includes error checking to ensure that the input string is a valid time string. If it is not, the function raises a custom exception to indicate this.
- Readability: The use of regular expressions and named groups makes the code more readable and easier to understand.
- Efficiency: By using a dictionary to store the time components and a timedelta object to represent the final result, the function can handle the time calculations more efficiently.
© 2022 - 2024 — McMap. All rights reserved.
d
days,h
hours,m
minutes ands
seconds using one line (after importingdatetime
):datetime.timedelta(days = d, hours = h, minutes=m, seconds=s)
. – Mangrum