Skip to content

Commit ea07e9f

Browse files
committed
Merge remote-tracking branch 'origin/master'
2 parents a2fa355 + fe99639 commit ea07e9f

2 files changed

Lines changed: 28 additions & 33 deletions

File tree

structa/conversions.py

Lines changed: 27 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -68,22 +68,20 @@ def parse_bool(s, false='0', true='1'):
6868
raise ValueError('not a valid bool {!r}'.format(s))
6969

7070

71-
_SUFFIXES = [
72-
# This ordering is important; the minutes regex must be checked *before*
73-
# the months regex as one is a legitimate subset of the other
74-
('microseconds', 'm(icro)?s(ec(ond)?s?)?'),
75-
('seconds', 's(ec(ond)?s?)?'),
76-
('minutes', 'mi(n(ute)?s?)?'),
77-
('hours', 'h((ou)?rs?)?'),
78-
('days', 'd(ays?)?'),
79-
('weeks', 'w((ee)?ks?)?'),
80-
('months', 'm(on(th)?s?)?'),
81-
('years', 'y((ea)?rs?)?'),
82-
]
83-
_SPANS = [
84-
(span, re.compile(r'^(?:(?P<num>[+-]?\d+)\s*{}\b)'.format(suffix)))
85-
for span, suffix in _SUFFIXES
86-
]
71+
_SPANS = {
72+
span: re.compile(r'(?:(?P<num>[+-]?\d+)\s*{}\b)'.format(suffix))
73+
for span, suffix in [
74+
('microseconds', '(micro|u|µ)s(ec(ond)?s?)?'),
75+
('milliseconds', '(milli|m)s(ec(ond)?s?)?'),
76+
('seconds', 's(ec(ond)?s?)?'),
77+
('minutes', 'mi(n(ute)?s?)?'),
78+
('hours', 'h((ou)?rs?)?'),
79+
('days', 'd(ays?)?'),
80+
('weeks', 'w((ee)?ks?)?'),
81+
('months', 'm((on)?ths?)?'),
82+
('years', 'y((ea)?rs?)?'),
83+
]
84+
}
8785

8886

8987
def parse_duration(s):
@@ -106,13 +104,13 @@ def parse_duration(s):
106104
relativedelta(months=+1)
107105
>>> parse_duration('1 min')
108106
relativedelta(minutes=+1)
109-
>>> parse_duration('1 mon')
107+
>>> parse_duration('1 mth')
110108
relativedelta(months=+1)
111109
112110
The set of possible durations, and their recognized suffixes is as follows:
113111
114112
* *Microseconds*: microseconds, microsecond, microsec, micros, micro,
115-
mseconds, msecond, msecs, msec, ms
113+
useconds, usecond, usecs, usec, us, µseconds, µsecond, µsecs, µsec, µs
116114
117115
* *Seconds*: seconds, second, secs, sec, s
118116
@@ -130,23 +128,19 @@ def parse_duration(s):
130128
131129
If conversion fails, :exc:`ValueError` is raised.
132130
"""
133-
spans = {span: 0 for span, regex in _SPANS}
131+
spans = {span: 0 for span in _SPANS}
134132
t = s
135-
while True:
136-
t = t.lstrip(' \t\n,')
137-
if not t:
138-
return relativedelta(**spans)
139-
for span, regex in _SPANS:
140-
m = regex.search(t)
141-
if m:
142-
spans[span] += int(m.group('num'))
143-
# XXX This only truncates from the start; that in turn means
144-
# that things must be ordered year/month/day/hour/etc. Make
145-
# the algorithm order agnostic
146-
t = t[len(m.group(0)):]
133+
for span, regex in _SPANS.items():
134+
m = regex.search(t)
135+
if m:
136+
spans[span] += int(m.group('num'))
137+
t = (t[:m.start(0)] + t[m.end(0):]).strip(' \t\n,')
138+
if not t:
147139
break
148-
else:
149-
raise ValueError('invalid duration {}'.format(s))
140+
if t:
141+
raise ValueError('invalid duration {}'.format(s))
142+
spans['microseconds'] += spans.pop('milliseconds') * 1000
143+
return relativedelta(**spans)
150144

151145

152146
def parse_duration_or_timestamp(s):

tests/test_conversions.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ def test_parse_duration():
4343
assert parse_duration('1h') == relativedelta(hours=1)
4444
assert parse_duration('1hrs, 5mins') == relativedelta(hours=1, minutes=5)
4545
assert parse_duration('60 seconds') == relativedelta(minutes=1)
46+
assert parse_duration('1s-50ms') == relativedelta(seconds=1, microseconds=-50000)
4647
with pytest.raises(ValueError):
4748
parse_duration('foo')
4849

0 commit comments

Comments
 (0)