Last active
September 30, 2024 22:40
-
-
Save santiagobasulto/698f0ff660968200f873a2f9d1c4113c to your computer and use it in GitHub Desktop.
A simple script to parse human readable time deltas into Python datetime.timedeltas
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import re | |
TIMEDELTA_REGEX = (r'((?P<days>-?\d+)d)?' | |
r'((?P<hours>-?\d+)h)?' | |
r'((?P<minutes>-?\d+)m)?') | |
TIMEDELTA_PATTERN = re.compile(TIMEDELTA_REGEX, re.IGNORECASE) | |
def parse_delta(delta): | |
""" Parses a human readable timedelta (3d5h19m) into a datetime.timedelta. | |
Delta includes: | |
* Xd days | |
* Xh hours | |
* Xm minutes | |
Values can be negative following timedelta's rules. Eg: -5h-30m | |
""" | |
match = TIMEDELTA_PATTERN.match(delta) | |
if match: | |
parts = {k: int(v) for k, v in match.groupdict().items() if v} | |
return timedelta(**parts) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
def test_day_deltas(): | |
assert parse_delta('3d') == timedelta(days=3) | |
assert parse_delta('-3d') == timedelta(days=-3) | |
assert parse_delta('-37D') == timedelta(days=-37) | |
def test_hours_deltas(): | |
assert parse_delta('18h') == timedelta(hours=18) | |
assert parse_delta('-5h') == timedelta(hours=-5) | |
assert parse_delta('11H') == timedelta(hours=11) | |
def test_minute_deltas(): | |
assert parse_delta('129m') == timedelta(minutes=129) | |
assert parse_delta('-68m') == timedelta(minutes=-68) | |
assert parse_delta('12M') == timedelta(minutes=12) | |
def test_combined_deltas(): | |
assert parse_delta('3d5h') == timedelta(days=3, hours=5) | |
assert parse_delta('-3d-5h') == timedelta(days=-3, hours=-5) | |
assert parse_delta('13d4h19m') == timedelta(days=13, hours=4, minutes=19) | |
assert parse_delta('13d19m') == timedelta(days=13, minutes=19) | |
assert parse_delta('-13d-19m') == timedelta(days=-13, minutes=-19) | |
assert parse_delta('-13d19m') == timedelta(days=-13, minutes=19) | |
assert parse_delta('13d-19m') == timedelta(days=13, minutes=-19) | |
assert parse_delta('4h19m') == timedelta(hours=4, minutes=19) | |
assert parse_delta('-4h-19m') == timedelta(hours=-4, minutes=-19) | |
assert parse_delta('-4h19m') == timedelta(hours=-4, minutes=19) | |
assert parse_delta('4h-19m') == timedelta(hours=4, minutes=-19) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Nice!
The tests show the desired functionality. For bogus input, mainly the empty string and other, non-matching material, the
if match
condition does not work. All three capture groups are optional, so at the end of the day, that regex matches everything we throw at it.match
will never beNone
, but always be anre.Match
object, which always evaluates toTrue
. This is the exact same issue as here.There, one answer suggests negative lookahead, anchored to the start of the string:
^(?!$)
. This works so the regex no longer matches the empty string. However, it still matches any other invalid string:I don't really know how to circumvent this. It is probably easiest to check for
if match.group()
: without arguments, thegroup
method returns the entire match.