Skip to content

Commit c8f251a

Browse files
Implement full Go duration specification support
- Add support for all Go time units: ns, us, µs, ms, s, m, h - Add fractional number support: 1.5h, 30.918s - Add comprehensive pytest test suite with 22 test cases - All Go specification examples now supported - TDD approach: tests first, implementation second 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 0d4f9ed commit c8f251a

File tree

4 files changed

+68
-10
lines changed

4 files changed

+68
-10
lines changed

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
.PHONY: test
22

33
test:
4+
python -m pytest test_parser.py -v
45
python -c 'import doctest, re; f = lambda: None; f.__doc__ = re.search(r"```python\n(.*?)\n```", open("README.md").read(), re.DOTALL).group(1); doctest.run_docstring_examples(f, globals(), verbose=True)'

goduration/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
from .parser import parse
2+
3+
__version__ = "0.1.0"
4+
__all__ = ["parse"]

goduration.py renamed to goduration/parser.py

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,29 +17,43 @@ def parse(duration_str: str) -> timedelta:
1717
i = 0
1818

1919
while i < len(s):
20-
# Parse number
20+
# Parse number (including fractional)
2121
num_start = i
22-
while i < len(s) and s[i].isdigit():
22+
while i < len(s) and (s[i].isdigit() or s[i] == '.'):
2323
i += 1
2424

2525
if i == num_start:
2626
raise ValueError(f"Expected number at position {i}")
2727

28-
num = int(s[num_start:i])
28+
num = float(s[num_start:i])
2929

30-
# Parse unit
30+
# Parse unit (1-2 chars)
3131
if i >= len(s):
3232
raise ValueError(f"Missing unit after number {num}")
3333

34-
unit = s[i]
35-
if unit == 'h':
36-
total_minutes += num * 60
34+
unit_start = i
35+
if i < len(s) - 1 and s[i:i+2] in ['ns', 'us', 'µs', 'μs', 'ms']:
36+
unit = s[i:i+2]
37+
i += 2
38+
else:
39+
unit = s[i]
40+
i += 1
41+
42+
# Convert to minutes
43+
if unit == 'ns':
44+
total_minutes += num / (1000 * 1000 * 1000 * 60)
45+
elif unit in ['us', 'µs', 'μs']:
46+
total_minutes += num / (1000 * 1000 * 60)
47+
elif unit == 'ms':
48+
total_minutes += num / (1000 * 60)
49+
elif unit == 's':
50+
total_minutes += num / 60
3751
elif unit == 'm':
3852
total_minutes += num
53+
elif unit == 'h':
54+
total_minutes += num * 60
3955
else:
40-
raise ValueError(f"Invalid unit '{unit}', expected 'h' or 'm'")
41-
42-
i += 1
56+
raise ValueError(f"Invalid unit '{unit}', expected ns, us, ms, s, m, or h")
4357

4458
if is_negative:
4559
total_minutes = -total_minutes

test_parser.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import pytest
2+
from datetime import timedelta
3+
from goduration import parse
4+
5+
6+
@pytest.mark.parametrize("input_str,expected", [
7+
("1m", timedelta(minutes=1)),
8+
("2h", timedelta(hours=2)),
9+
("2h30m", timedelta(hours=2, minutes=30)),
10+
("-8h", timedelta(hours=-8)),
11+
("1s", timedelta(seconds=1)),
12+
("30s", timedelta(seconds=30)),
13+
("1m30s", timedelta(minutes=1, seconds=30)),
14+
("300ms", timedelta(milliseconds=300)),
15+
("1s500ms", timedelta(seconds=1, milliseconds=500)),
16+
("500us", timedelta(microseconds=500)),
17+
("500µs", timedelta(microseconds=500)),
18+
("1000ns", timedelta(microseconds=1)),
19+
("1.5h", timedelta(hours=1, minutes=30)),
20+
("2.5m", timedelta(minutes=2, seconds=30)),
21+
("1.25s", timedelta(seconds=1, milliseconds=250)),
22+
("1h15m30.918s", timedelta(hours=1, minutes=15, seconds=30, milliseconds=918)),
23+
])
24+
def test_parse_duration_go_spec(input_str, expected):
25+
result = parse(input_str)
26+
assert result == expected, f"parse('{input_str}') = {result}, expected {expected}"
27+
28+
29+
@pytest.mark.parametrize("invalid_input", [
30+
"",
31+
"1",
32+
"1x",
33+
"1m4z",
34+
"h1",
35+
"1.2.3m",
36+
])
37+
def test_parse_duration_invalid(invalid_input):
38+
with pytest.raises((ValueError, TypeError)):
39+
parse(invalid_input)

0 commit comments

Comments
 (0)