@@ -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
8987def 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
152146def parse_duration_or_timestamp (s ):
0 commit comments