Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,19 @@ possible, otherwise a floating-point number)::
>>> parse('1.2 minutes')
72

For months and years, the library does not consider complications such as leap-
years and leap-seconds. Instead, it assumes "30 days for a month" and "365 days
for a year" as the basis for calculations with those units.

- ``2 mo``
- ``2 months``
- ``3y``
- ``3 years``
- ``1y2mo3w4d5h6m7s8ms``

Notes
-----

A number of seconds can be converted back into a string using the
``datetime`` module in the standard library, as noted in
`this other StackOverflow question <http://stackoverflow.com/questions/538666/python-format-timedelta-to-string>`_::
Expand Down
11 changes: 10 additions & 1 deletion pytimeparse2.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
import re

SIGN = r'(?P<sign>[+|-]|\+)?'
YEARS = r'(?P<years>[\d.]+)\s*(?:ys?|yrs?.?|years?)'
MONTHS = r'(?P<months>[\d.]+)\s*(?:mos?.?|mths?.?|months?)'
WEEKS = r'(?P<weeks>[\d.]+)\s*(?:w|wks?|weeks?)'
DAYS = r'(?P<days>[\d.]+)\s*(?:d|dys?|days?)'
HOURS = r'(?P<hours>[\d.]+)\s*(?:h|hrs?|hours?)'
Expand All @@ -50,6 +52,8 @@
r'(?P<mins>\d{2}):(?P<secs>\d{2}(?:\.\d+)?)')

MULTIPLIERS = {
'years': 60 * 60 * 24 * 365,
'months': 60 * 60 * 24 * 30,
'weeks': 60 * 60 * 24 * 7,
'days': 60 * 60 * 24,
'hours': 60 * 60,
Expand All @@ -68,11 +72,14 @@ def OPTSEP(x):


TIMEFORMATS = [
rf'{OPTSEP(YEARS)}\s*{OPTSEP(MONTHS)}\s*{OPTSEP(WEEKS)}\s*{OPTSEP(DAYS)}\s*{OPTSEP(HOURS)}\s*{OPTSEP(MINS)}\s*{OPT(SECS)}\s*{OPT(MILLIS)}',
rf'{OPTSEP(WEEKS)}\s*{OPTSEP(DAYS)}\s*{OPTSEP(HOURS)}\s*{OPTSEP(MINS)}\s*{OPT(SECS)}\s*{OPT(MILLIS)}',
rf'{MINCLOCK}',
rf'{OPTSEP(WEEKS)}\s*{OPTSEP(DAYS)}\s*{HOURCLOCK}',
rf'{DAYCLOCK}',
rf'{SECCLOCK}',
rf'{YEARS}',
rf'{MONTHS}',
]

COMPILED_SIGN = re.compile(r'\s*' + SIGN + r'\s*(?P<unsigned>.*)$')
Expand All @@ -93,7 +100,9 @@ def _interpret_as_minutes(sval, mdict):
{'hours': '1', 'mins': '24'}
"""
if sval.count(':') == 1 and '.' not in sval and (('hours' not in mdict) or (mdict['hours'] is None)) and (
('days' not in mdict) or (mdict['days'] is None)) and (('weeks' not in mdict) or (mdict['weeks'] is None)):
('days' not in mdict) or (mdict['days'] is None)) and (('weeks' not in mdict) or (mdict['weeks'] is None)) \
and (('months' not in mdict) or (mdict['months'] is None)) \
and (('years' not in mdict) or (mdict['years'] is None)):
mdict['hours'] = mdict['mins']
mdict['mins'] = mdict['secs']
mdict.pop('secs')
Expand Down
49 changes: 49 additions & 0 deletions tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ def setUp(self):

def test_mins(self):
"""Test parsing minutes."""
self.assertEqual(re.match(timeparse.MINS, '32m').groupdict(),
{'mins': '32'})
self.assertEqual(re.match(timeparse.MINS, '32min').groupdict(),
{'mins': '32'})
self.assertEqual(re.match(timeparse.MINS, '32mins').groupdict(),
Expand Down Expand Up @@ -55,6 +57,50 @@ def test_hrs(self):
self.assertEqual(re.match(timeparse.HOURS, '32 h').groupdict(),
{'hours': '32'})

def test_months(self):
"""Test parsing months."""
self.assertEqual(re.match(timeparse.MONTHS, '32mo').groupdict(),
{'months': '32'})
self.assertEqual(re.match(timeparse.MONTHS, '32mon').groupdict(),
{'months': '32'})
self.assertEqual(re.match(timeparse.MONTHS, '32month').groupdict(),
{'months': '32'})
self.assertEqual(re.match(timeparse.MONTHS, '32months').groupdict(),
{'months': '32'})
self.assertEqual(re.match(timeparse.MONTHS, '32 mo').groupdict(),
{'months': '32'})
self.assertEqual(re.match(timeparse.MONTHS, '32 months').groupdict(),
{'months': '32'})
self.assertEqual(re.match(timeparse.MONTHS, '32mos').groupdict(),
{'months': '32'})
self.assertEqual(re.match(timeparse.MONTHS, '32mths').groupdict(),
{'months': '32'})
self.assertEqual(re.match(timeparse.MONTHS, '2.3mo').groupdict(),
{'months': '2.3'})
self.assertEqual(re.match(timeparse.MONTHS, '2.5mo').groupdict(),
{'months': '2.5'})

def test_years(self):
"""Test parsing years."""
self.assertEqual(re.match(timeparse.YEARS, '32y').groupdict(),
{'years': '32'})
self.assertEqual(re.match(timeparse.YEARS, '32ys').groupdict(),
{'years': '32'})
self.assertEqual(re.match(timeparse.YEARS, '32yrs').groupdict(),
{'years': '32'})
self.assertEqual(re.match(timeparse.YEARS, '32year').groupdict(),
{'years': '32'})
self.assertEqual(re.match(timeparse.YEARS, '32years').groupdict(),
{'years': '32'})
self.assertEqual(re.match(timeparse.YEARS, '32 y').groupdict(),
{'years': '32'})
self.assertEqual(re.match(timeparse.YEARS, '32 years').groupdict(),
{'years': '32'})
self.assertEqual(re.match(timeparse.YEARS, '2.3y').groupdict(),
{'years': '2.3'})
self.assertEqual(re.match(timeparse.YEARS, '2.5y').groupdict(),
{'years': '2.5'})

def test_time(self):
"""Test parsing time expression."""
self.assertGreater(
Expand Down Expand Up @@ -377,6 +423,9 @@ def test_plain_numbers(self):
self.assertEqual(timeparse.parse('-10'), -10)
self.assertEqual(timeparse.parse('-10.1'), -10)

def test_combined(self):
self.assertEqual(timeparse.parse('1y2mo3w4d5h6m7s8ms'), 38898367.008)

def test_doctest(self):
"""Run timeparse doctests."""
self.assertTrue(doctest.testmod(timeparse, raise_on_error=True))
Expand Down