├── pytimeparse ├── VERSION ├── tests │ ├── __init__.py │ └── testtimeparse.py ├── __init__.py └── timeparse.py ├── MANIFEST.in ├── .gitignore ├── setup.cfg ├── .travis.yml ├── LICENSE.rst ├── README.rst └── setup.py /pytimeparse/VERSION: -------------------------------------------------------------------------------- 1 | 1.1.8 2 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE.rst 2 | include README.rst 3 | include pytimeparse/VERSION 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *~ 3 | .DS_Store 4 | /.coverage 5 | /MANIFEST 6 | /build/ 7 | /dist/ 8 | /pytimeparse.egg-info/ 9 | -------------------------------------------------------------------------------- /pytimeparse/tests/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | __init__.py 6 | (c) Will Roberts 18 February, 2015 7 | 8 | pytimeparse.tests module 9 | ''' 10 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | # This flag says that the code is written to work on both Python 2 and Python 3 | # 3. If at all possible, it is good practice to do this. If you cannot, you 4 | # will need to generate wheels for each Python version that you support. 5 | universal=1 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.7" 4 | - "3.4" 5 | - "3.5" 6 | - "3.6" 7 | - "3.7" 8 | # command to install dependencies 9 | install: "pip install . 'coverage<4' coveralls" 10 | # command to run tests 11 | script: 12 | coverage run --source=pytimeparse setup.py test 13 | after_success: 14 | coveralls 15 | -------------------------------------------------------------------------------- /pytimeparse/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | __init__.py 6 | (c) Will Roberts 1 February, 2014 7 | 8 | `timeparse` module. 9 | ''' 10 | 11 | from __future__ import absolute_import 12 | from codecs import open 13 | from os import path 14 | 15 | # Version. For each new release, the version number should be updated 16 | # in the file VERSION. 17 | try: 18 | # If a VERSION file exists, use it! 19 | with open(path.join(path.dirname(__file__), 'VERSION'), 20 | encoding='utf-8') as infile: 21 | __version__ = infile.read().strip() 22 | except NameError: 23 | __version__ = 'unknown (running code interactively?)' 24 | except IOError as ex: 25 | __version__ = "unknown (%s)" % ex 26 | 27 | # import top-level functionality 28 | from .timeparse import timeparse as parse 29 | -------------------------------------------------------------------------------- /LICENSE.rst: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Will Roberts 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ===================================== 2 | pytimeparse: time expression parser 3 | ===================================== 4 | 5 | .. image:: https://travis-ci.org/wroberts/pytimeparse.svg?branch=master 6 | :target: https://travis-ci.org/wroberts/pytimeparse 7 | :alt: Travis CI build status 8 | 9 | .. image:: https://coveralls.io/repos/wroberts/pytimeparse/badge.svg 10 | :target: https://coveralls.io/r/wroberts/pytimeparse 11 | :alt: Test code coverage 12 | 13 | .. image:: https://img.shields.io/pypi/v/pytimeparse.svg 14 | :target: https://pypi.python.org/pypi/pytimeparse/ 15 | :alt: Latest Version 16 | 17 | Copyright (c) 2014 Will Roberts 18 | 19 | Licensed under the MIT License (see source file ``timeparse.py`` for 20 | details). 21 | 22 | A small Python library to parse various kinds of time expressions, 23 | inspired by 24 | `this StackOverflow question `_. 25 | 26 | The single function ``pytimeparse.timeparse.timeparse`` defined in the 27 | library (also available as ``pytimeparse.parse``) parses time 28 | expressions like the following: 29 | 30 | - ``32m`` 31 | - ``2h32m`` 32 | - ``3d2h32m`` 33 | - ``1w3d2h32m`` 34 | - ``1w 3d 2h 32m`` 35 | - ``1 w 3 d 2 h 32 m`` 36 | - ``4:13`` 37 | - ``4:13:02`` 38 | - ``4:13:02.266`` 39 | - ``2:04:13:02.266`` 40 | - ``2 days, 4:13:02`` (``uptime`` format) 41 | - ``2 days, 4:13:02.266`` 42 | - ``5hr34m56s`` 43 | - ``5 hours, 34 minutes, 56 seconds`` 44 | - ``5 hrs, 34 mins, 56 secs`` 45 | - ``2 days, 5 hours, 34 minutes, 56 seconds`` 46 | - ``1.2 m`` 47 | - ``1.2 min`` 48 | - ``1.2 mins`` 49 | - ``1.2 minute`` 50 | - ``1.2 minutes`` 51 | - ``172 hours`` 52 | - ``172 hr`` 53 | - ``172 h`` 54 | - ``172 hrs`` 55 | - ``172 hour`` 56 | - ``1.24 days`` 57 | - ``5 d`` 58 | - ``5 day`` 59 | - ``5 days`` 60 | - ``5.6 wk`` 61 | - ``5.6 week`` 62 | - ``5.6 weeks`` 63 | 64 | It returns the time as a number of seconds (an integer value if 65 | possible, otherwise a floating-point number):: 66 | 67 | >>> from pytimeparse import parse 68 | >>> parse('1.2 minutes') 69 | 72 70 | 71 | A number of seconds can be converted back into a string using the 72 | ``datetime`` module in the standard library, as noted in 73 | `this other StackOverflow question `_:: 74 | 75 | >>> from pytimeparse import parse 76 | >>> import datetime 77 | >>> parse('1 day, 14:20:16') 78 | 138016 79 | >>> str(datetime.timedelta(seconds=138016)) 80 | '1 day, 14:20:16' 81 | 82 | Future work 83 | ----------- 84 | 85 | 1. Give the user more flexibility over which characters to use as 86 | separators between fields in a time expression (e.g., ``+`` might 87 | be useful). 88 | 2. Internationalisation? 89 | 3. Wow, https://github.com/bear/parsedatetime . 90 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | setup.py 6 | (c) Will Roberts 14 April, 2014 7 | 8 | distutils setup script for pytimeparse. 9 | ''' 10 | 11 | from setuptools import setup, find_packages # Always prefer setuptools over distutils 12 | from codecs import open # To use a consistent encoding 13 | from os import path 14 | 15 | HERE = path.abspath(path.dirname(__file__)) 16 | 17 | with open(path.join(HERE, 'pytimeparse', 'VERSION'), encoding='utf-8') as f: 18 | VERSION = f.read().strip() 19 | 20 | # Get the long description from the relevant file 21 | with open(path.join(HERE, 'README.rst'), encoding='utf-8') as f: 22 | LONG_DESCRIPTION = f.read() 23 | 24 | setup( 25 | name='pytimeparse', 26 | 27 | # Versions should comply with PEP440. For a discussion on single-sourcing 28 | # the version across setup.py and the project code, see 29 | # https://packaging.python.org/en/latest/single_source_version.html 30 | version=VERSION, 31 | 32 | description='Time expression parser', 33 | long_description=LONG_DESCRIPTION, 34 | 35 | # The project's main homepage. 36 | url='https://github.com/wroberts/pytimeparse', 37 | 38 | # Author details 39 | author='Will Roberts', 40 | author_email='wildwilhelm@gmail.com', 41 | 42 | # Choose your license 43 | license='License :: OSI Approved :: MIT License', 44 | 45 | # See https://pypi.python.org/pypi?%3Aaction=list_classifiers 46 | classifiers=[ 47 | # How mature is this project? Common values are 48 | # 3 - Alpha 49 | # 4 - Beta 50 | # 5 - Production/Stable 51 | 'Development Status :: 4 - Beta', 52 | 53 | # Indicate who your project is intended for 54 | 'Intended Audience :: Developers', 55 | 'Topic :: Text Processing', 56 | 'Natural Language :: English', 57 | 58 | # Pick your license as you wish (should match "license" above) 59 | 'License :: OSI Approved :: MIT License', 60 | 61 | # Specify the Python versions you support here. In particular, ensure 62 | # that you indicate whether you support Python 2, Python 3 or both. 63 | 'Programming Language :: Python :: 2', 64 | 'Programming Language :: Python :: 2.7', 65 | 'Programming Language :: Python :: 3', 66 | 'Programming Language :: Python :: 3.2', 67 | 'Programming Language :: Python :: 3.3', 68 | 'Programming Language :: Python :: 3.4', 69 | 'Programming Language :: Python :: 3.5', 70 | 'Programming Language :: Python :: 3.6', 71 | ], 72 | 73 | # What does your project relate to? 74 | keywords='time parsing parser', 75 | 76 | # You can just specify the packages manually here if your project is 77 | # simple. Or you can use find_packages(). 78 | packages=find_packages(exclude=[]), 79 | 80 | # List additional groups of dependencies here (e.g. development dependencies). 81 | # You can install these using the following syntax, for example: 82 | # $ pip install -e .[dev,test] 83 | #extras_require = { 84 | # 'dev': ['check-manifest'], 85 | # 'test': ['coverage'], 86 | #}, 87 | 88 | # If there are data files included in your packages that need to be 89 | # installed, specify them here. If using Python 2.6 or less, then these 90 | # have to be included in MANIFEST.in as well. 91 | package_data={ 92 | 'pytimeparse': ['VERSION'], 93 | }, 94 | 95 | # Although 'package_data' is the preferred approach, in some case you may 96 | # need to place data files outside of your packages. 97 | # see http://docs.python.org/3.4/distutils/setupscript.html#installing-additional-files 98 | # In this case, 'data_file' will be installed into '/my_data' 99 | #data_files=[('my_data', ['data/data_file'])], 100 | 101 | # To provide executable scripts, use entry points in preference to the 102 | # "scripts" keyword. Entry points provide cross-platform support and allow 103 | # pip to create the appropriate form of executable for the target platform. 104 | #entry_points={ 105 | # 'console_scripts': [ 106 | # 'sample=sample:main', 107 | # ], 108 | #}, 109 | test_suite='nose.collector', 110 | tests_require=['nose'], 111 | ) 112 | -------------------------------------------------------------------------------- /pytimeparse/timeparse.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | timeparse.py 6 | (c) Will Roberts 1 February, 2014 7 | 8 | Implements a single function, `timeparse`, which can parse various 9 | kinds of time expressions. 10 | ''' 11 | 12 | # MIT LICENSE 13 | # 14 | # Permission is hereby granted, free of charge, to any person 15 | # obtaining a copy of this software and associated documentation files 16 | # (the "Software"), to deal in the Software without restriction, 17 | # including without limitation the rights to use, copy, modify, merge, 18 | # publish, distribute, sublicense, and/or sell copies of the Software, 19 | # and to permit persons to whom the Software is furnished to do so, 20 | # subject to the following conditions: 21 | # 22 | # The above copyright notice and this permission notice shall be 23 | # included in all copies or substantial portions of the Software. 24 | # 25 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 26 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 27 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 28 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 29 | # BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 30 | # ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 31 | # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 32 | # SOFTWARE. 33 | 34 | import re 35 | 36 | SIGN = r'(?P[+|-])?' 37 | #YEARS = r'(?P\d+)\s*(?:ys?|yrs?.?|years?)' 38 | #MONTHS = r'(?P\d+)\s*(?:mos?.?|mths?.?|months?)' 39 | WEEKS = r'(?P[\d.]+)\s*(?:w|wks?|weeks?)' 40 | DAYS = r'(?P[\d.]+)\s*(?:d|dys?|days?)' 41 | HOURS = r'(?P[\d.]+)\s*(?:h|hrs?|hours?)' 42 | MINS = r'(?P[\d.]+)\s*(?:m|(mins?)|(minutes?))' 43 | SECS = r'(?P[\d.]+)\s*(?:s|secs?|seconds?)' 44 | SEPARATORS = r'[,/]' 45 | SECCLOCK = r':(?P\d{2}(?:\.\d+)?)' 46 | MINCLOCK = r'(?P\d{1,2}):(?P\d{2}(?:\.\d+)?)' 47 | HOURCLOCK = r'(?P\d+):(?P\d{2}):(?P\d{2}(?:\.\d+)?)' 48 | DAYCLOCK = (r'(?P\d+):(?P\d{2}):' 49 | r'(?P\d{2}):(?P\d{2}(?:\.\d+)?)') 50 | 51 | OPT = lambda x: r'(?:{x})?'.format(x=x, SEPARATORS=SEPARATORS) 52 | OPTSEP = lambda x: r'(?:{x}\s*(?:{SEPARATORS}\s*)?)?'.format( 53 | x=x, SEPARATORS=SEPARATORS) 54 | 55 | TIMEFORMATS = [ 56 | r'{WEEKS}\s*{DAYS}\s*{HOURS}\s*{MINS}\s*{SECS}'.format( 57 | #YEARS=OPTSEP(YEARS), 58 | #MONTHS=OPTSEP(MONTHS), 59 | WEEKS=OPTSEP(WEEKS), 60 | DAYS=OPTSEP(DAYS), 61 | HOURS=OPTSEP(HOURS), 62 | MINS=OPTSEP(MINS), 63 | SECS=OPT(SECS)), 64 | r'{MINCLOCK}'.format( 65 | MINCLOCK=MINCLOCK), 66 | r'{WEEKS}\s*{DAYS}\s*{HOURCLOCK}'.format( 67 | WEEKS=OPTSEP(WEEKS), 68 | DAYS=OPTSEP(DAYS), 69 | HOURCLOCK=HOURCLOCK), 70 | r'{DAYCLOCK}'.format( 71 | DAYCLOCK=DAYCLOCK), 72 | r'{SECCLOCK}'.format( 73 | SECCLOCK=SECCLOCK), 74 | #r'{YEARS}'.format( 75 | #YEARS=YEARS), 76 | #r'{MONTHS}'.format( 77 | #MONTHS=MONTHS), 78 | ] 79 | 80 | COMPILED_SIGN = re.compile(r'\s*' + SIGN + r'\s*(?P.*)$') 81 | COMPILED_TIMEFORMATS = [re.compile(r'\s*' + timefmt + r'\s*$', re.I) 82 | for timefmt in TIMEFORMATS] 83 | 84 | MULTIPLIERS = dict([ 85 | #('years', 60 * 60 * 24 * 365), 86 | #('months', 60 * 60 * 24 * 30), 87 | ('weeks', 60 * 60 * 24 * 7), 88 | ('days', 60 * 60 * 24), 89 | ('hours', 60 * 60), 90 | ('mins', 60), 91 | ('secs', 1) 92 | ]) 93 | 94 | def _interpret_as_minutes(sval, mdict): 95 | """ 96 | Times like "1:22" are ambiguous; do they represent minutes and seconds 97 | or hours and minutes? By default, timeparse assumes the latter. Call 98 | this function after parsing out a dictionary to change that assumption. 99 | 100 | >>> import pprint 101 | >>> pprint.pprint(_interpret_as_minutes('1:24', {'secs': '24', 'mins': '1'})) 102 | {'hours': '1', 'mins': '24'} 103 | """ 104 | if ( sval.count(':') == 1 105 | and '.' not in sval 106 | and (('hours' not in mdict) or (mdict['hours'] is None)) 107 | and (('days' not in mdict) or (mdict['days'] is None)) 108 | and (('weeks' not in mdict) or (mdict['weeks'] is None)) 109 | #and (('months' not in mdict) or (mdict['months'] is None)) 110 | #and (('years' not in mdict) or (mdict['years'] is None)) 111 | ): 112 | mdict['hours'] = mdict['mins'] 113 | mdict['mins'] = mdict['secs'] 114 | mdict.pop('secs') 115 | pass 116 | return mdict 117 | 118 | def timeparse(sval, granularity='seconds'): 119 | ''' 120 | Parse a time expression, returning it as a number of seconds. If 121 | possible, the return value will be an `int`; if this is not 122 | possible, the return will be a `float`. Returns `None` if a time 123 | expression cannot be parsed from the given string. 124 | 125 | Arguments: 126 | - `sval`: the string value to parse 127 | 128 | >>> timeparse('1:24') 129 | 84 130 | >>> timeparse(':22') 131 | 22 132 | >>> timeparse('1 minute, 24 secs') 133 | 84 134 | >>> timeparse('1m24s') 135 | 84 136 | >>> timeparse('1.2 minutes') 137 | 72 138 | >>> timeparse('1.2 seconds') 139 | 1.2 140 | 141 | Time expressions can be signed. 142 | 143 | >>> timeparse('- 1 minute') 144 | -60 145 | >>> timeparse('+ 1 minute') 146 | 60 147 | 148 | If granularity is specified as ``minutes``, then ambiguous digits following 149 | a colon will be interpreted as minutes; otherwise they are considered seconds. 150 | 151 | >>> timeparse('1:30') 152 | 90 153 | >>> timeparse('1:30', granularity='minutes') 154 | 5400 155 | ''' 156 | match = COMPILED_SIGN.match(sval) 157 | sign = -1 if match.groupdict()['sign'] == '-' else 1 158 | sval = match.groupdict()['unsigned'] 159 | for timefmt in COMPILED_TIMEFORMATS: 160 | match = timefmt.match(sval) 161 | if match and match.group(0).strip(): 162 | mdict = match.groupdict() 163 | if granularity == 'minutes': 164 | mdict = _interpret_as_minutes(sval, mdict) 165 | # if all of the fields are integer numbers 166 | if all(v.isdigit() for v in list(mdict.values()) if v): 167 | return sign * sum([MULTIPLIERS[k] * int(v, 10) for (k, v) in 168 | list(mdict.items()) if v is not None]) 169 | # if SECS is an integer number 170 | elif ('secs' not in mdict or 171 | mdict['secs'] is None or 172 | mdict['secs'].isdigit()): 173 | # we will return an integer 174 | return ( 175 | sign * int(sum([MULTIPLIERS[k] * float(v) for (k, v) in 176 | list(mdict.items()) if k != 'secs' and v is not None])) + 177 | (int(mdict['secs'], 10) if mdict['secs'] else 0)) 178 | else: 179 | # SECS is a float, we will return a float 180 | return sign * sum([MULTIPLIERS[k] * float(v) for (k, v) in 181 | list(mdict.items()) if v is not None]) 182 | -------------------------------------------------------------------------------- /pytimeparse/tests/testtimeparse.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | testtimeparse.py 6 | (c) Will Roberts 1 February, 2014 7 | 8 | Unit tests for the `timeparse` module. 9 | ''' 10 | 11 | from __future__ import absolute_import 12 | import doctest 13 | import re 14 | from .. import timeparse 15 | import unittest 16 | 17 | class TestTimeparse(unittest.TestCase): 18 | ''' 19 | Unit tests for the `timeparse` module. 20 | ''' 21 | 22 | def setUp(self): 23 | '''Setup function.''' 24 | pass 25 | 26 | def test_mins(self): 27 | '''Test parsing minutes.''' 28 | self.assertEqual(re.match(timeparse.MINS, '32min').groupdict(), 29 | {'mins': '32'}) 30 | self.assertEqual(re.match(timeparse.MINS, '32mins').groupdict(), 31 | {'mins': '32'}) 32 | self.assertEqual(re.match(timeparse.MINS, '32minute').groupdict(), 33 | {'mins': '32'}) 34 | self.assertEqual(re.match(timeparse.MINS, '32minutes').groupdict(), 35 | {'mins': '32'}) 36 | self.assertEqual(re.match(timeparse.MINS, '32mins').groupdict(), 37 | {'mins': '32'}) 38 | self.assertEqual(re.match(timeparse.MINS, '32min').groupdict(), 39 | {'mins': '32'}) 40 | 41 | def test_hrs(self): 42 | '''Test parsing hours.''' 43 | self.assertEqual(re.match(timeparse.HOURS, '32h').groupdict(), 44 | {'hours': '32'}) 45 | self.assertEqual(re.match(timeparse.HOURS, '32hr').groupdict(), 46 | {'hours': '32'}) 47 | self.assertEqual(re.match(timeparse.HOURS, '32hrs').groupdict(), 48 | {'hours': '32'}) 49 | self.assertEqual(re.match(timeparse.HOURS, '32hour').groupdict(), 50 | {'hours': '32'}) 51 | self.assertEqual(re.match(timeparse.HOURS, '32hours').groupdict(), 52 | {'hours': '32'}) 53 | self.assertEqual(re.match(timeparse.HOURS, '32 hours').groupdict(), 54 | {'hours': '32'}) 55 | self.assertEqual(re.match(timeparse.HOURS, '32 h').groupdict(), 56 | {'hours': '32'}) 57 | 58 | def test_time(self): 59 | '''Test parsing time expression.''' 60 | self.assertGreater( 61 | set(re.match(timeparse.TIMEFORMATS[0] + r'\s*$', 62 | '16h32m64s ').groupdict().items()), 63 | set([('hours', '16'), ('mins', '32'), ('secs', '64')])) 64 | 65 | def test_timeparse_multipliers(self): 66 | '''Test parsing time unit multipliers.''' 67 | self.assertEqual(timeparse.timeparse('32 min'), 68 | 1920) 69 | self.assertEqual(timeparse.timeparse('1 min'), 70 | 60) 71 | self.assertEqual(timeparse.timeparse('1 hours'), 72 | 3600) 73 | self.assertEqual(timeparse.timeparse('1 day'), 74 | 86400) 75 | self.assertEqual(timeparse.timeparse('1 sec'), 76 | 1) 77 | 78 | def test_timeparse_signs(self): 79 | '''Test parsing time signs.''' 80 | self.assertEqual(timeparse.timeparse('+32 m 1 s'), 1921) 81 | self.assertEqual(timeparse.timeparse('+ 32 m 1 s'), 1921) 82 | self.assertEqual(timeparse.timeparse('-32 m 1 s'), -1921) 83 | self.assertEqual(timeparse.timeparse('- 32 m 1 s'), -1921) 84 | self.assertIsNone(timeparse.timeparse('32 m - 1 s')) 85 | self.assertIsNone(timeparse.timeparse('32 m + 1 s')) 86 | 87 | def test_timeparse_1(self): 88 | '''timeparse test case 1.''' 89 | self.assertEqual(timeparse.timeparse('32m'), 1920) 90 | self.assertEqual(timeparse.timeparse('+32m'), 1920) 91 | self.assertEqual(timeparse.timeparse('-32m'), -1920) 92 | 93 | def test_timeparse_2(self): 94 | '''timeparse test case 2.''' 95 | self.assertEqual(timeparse.timeparse('2h32m'), 9120) 96 | self.assertEqual(timeparse.timeparse('+2h32m'), 9120) 97 | self.assertEqual(timeparse.timeparse('-2h32m'), -9120) 98 | 99 | def test_timeparse_3(self): 100 | '''timeparse test case 3.''' 101 | self.assertEqual(timeparse.timeparse('3d2h32m'), 268320) 102 | self.assertEqual(timeparse.timeparse('+3d2h32m'), 268320) 103 | self.assertEqual(timeparse.timeparse('-3d2h32m'), -268320) 104 | 105 | def test_timeparse_4(self): 106 | '''timeparse test case 4.''' 107 | self.assertEqual(timeparse.timeparse('1w3d2h32m'), 873120) 108 | self.assertEqual(timeparse.timeparse('+1w3d2h32m'), 873120) 109 | self.assertEqual(timeparse.timeparse('-1w3d2h32m'), -873120) 110 | 111 | def test_timeparse_5(self): 112 | '''timeparse test case 5.''' 113 | self.assertEqual(timeparse.timeparse('1w 3d 2h 32m'), 873120) 114 | self.assertEqual(timeparse.timeparse('+1w 3d 2h 32m'), 873120) 115 | self.assertEqual(timeparse.timeparse('-1w 3d 2h 32m'), -873120) 116 | 117 | def test_timeparse_6(self): 118 | '''timeparse test case 6.''' 119 | self.assertEqual(timeparse.timeparse('1 w 3 d 2 h 32 m'), 873120) 120 | self.assertEqual(timeparse.timeparse('+1 w 3 d 2 h 32 m'), 873120) 121 | self.assertEqual(timeparse.timeparse('-1 w 3 d 2 h 32 m'), -873120) 122 | 123 | def test_timeparse_7(self): 124 | '''timeparse test case 7.''' 125 | self.assertEqual(timeparse.timeparse('4:13'), 253) 126 | self.assertEqual(timeparse.timeparse('+4:13'), 253) 127 | self.assertEqual(timeparse.timeparse('-4:13'), -253) 128 | 129 | def test_timeparse_bare_seconds(self): 130 | '''timeparse test bare seconds, without minutes.''' 131 | self.assertEqual(timeparse.timeparse(':13'), 13) 132 | self.assertEqual(timeparse.timeparse('+:13'), 13) 133 | self.assertEqual(timeparse.timeparse('-:13'), -13) 134 | 135 | def test_timeparse_8(self): 136 | '''timeparse test case 8.''' 137 | self.assertEqual(timeparse.timeparse('4:13:02'), 15182) 138 | self.assertEqual(timeparse.timeparse('+4:13:02'), 15182) 139 | self.assertEqual(timeparse.timeparse('-4:13:02'), -15182) 140 | 141 | def test_timeparse_9(self): 142 | '''timeparse test case 9.''' 143 | self.assertAlmostEqual(timeparse.timeparse('4:13:02.266'), 15182.266) 144 | self.assertAlmostEqual(timeparse.timeparse('+4:13:02.266'), 15182.266) 145 | self.assertAlmostEqual(timeparse.timeparse('-4:13:02.266'), -15182.266) 146 | 147 | def test_timeparse_10(self): 148 | '''timeparse test case 10.''' 149 | self.assertAlmostEqual(timeparse.timeparse('2:04:13:02.266'), 150 | 187982.266) 151 | self.assertAlmostEqual(timeparse.timeparse('+2:04:13:02.266'), 152 | 187982.266) 153 | self.assertAlmostEqual(timeparse.timeparse('-2:04:13:02.266'), 154 | -187982.266) 155 | 156 | def test_timeparse_granularity_1(self): 157 | '''Check that minute-level granularity applies correctly.''' 158 | self.assertEqual(timeparse.timeparse('4:32', granularity='minutes'), 272*60) 159 | self.assertEqual(timeparse.timeparse('+4:32', granularity='minutes'), 272*60) 160 | self.assertEqual(timeparse.timeparse('-4:32', granularity='minutes'), -272*60) 161 | 162 | def test_timeparse_granularity_2(self): 163 | '''Check that minute-level granularity does not apply inappropriately.''' 164 | self.assertEqual(timeparse.timeparse('4:32:02', granularity='minutes'), 272*60+2) 165 | self.assertEqual(timeparse.timeparse('+4:32:02', granularity='minutes'), 272*60+2) 166 | self.assertEqual(timeparse.timeparse('-4:32:02', granularity='minutes'), -(272*60+2)) 167 | 168 | def test_timeparse_granularity_3(self): 169 | '''Check that minute-level granularity does not apply inappropriately.''' 170 | self.assertAlmostEqual(timeparse.timeparse('7:02.223', granularity='minutes'), 7*60 + 2.223) 171 | self.assertAlmostEqual(timeparse.timeparse('+7:02.223', granularity='minutes'), 7*60 + 2.223) 172 | self.assertAlmostEqual(timeparse.timeparse('-7:02.223', granularity='minutes'), -(7*60 + 2.223)) 173 | 174 | def test_timeparse_granularity_4(self): 175 | '''Check that minute-level granularity does not apply inappropriately.''' 176 | self.assertEqual(timeparse.timeparse('0:02', granularity='seconds'), 2) 177 | self.assertEqual(timeparse.timeparse('+0:02', granularity='seconds'), 2) 178 | self.assertEqual(timeparse.timeparse('-0:02', granularity='seconds'), -2) 179 | 180 | def test_timeparse_11(self): 181 | '''timeparse test case 11.''' 182 | # uptime format 183 | self.assertEqual(timeparse.timeparse('2 days, 4:13:02'), 187982) 184 | self.assertEqual(timeparse.timeparse('+2 days, 4:13:02'), 187982) 185 | self.assertEqual(timeparse.timeparse('-2 days, 4:13:02'), -187982) 186 | 187 | def test_timeparse_12(self): 188 | '''timeparse test case 12.''' 189 | self.assertAlmostEqual(timeparse.timeparse('2 days, 4:13:02.266'), 190 | 187982.266) 191 | self.assertAlmostEqual(timeparse.timeparse('+2 days, 4:13:02.266'), 192 | 187982.266) 193 | self.assertAlmostEqual(timeparse.timeparse('-2 days, 4:13:02.266'), 194 | -187982.266) 195 | 196 | def test_timeparse_13(self): 197 | '''timeparse test case 13.''' 198 | self.assertEqual(timeparse.timeparse('5hr34m56s'), 20096) 199 | self.assertEqual(timeparse.timeparse('+5hr34m56s'), 20096) 200 | self.assertEqual(timeparse.timeparse('-5hr34m56s'), -20096) 201 | 202 | def test_timeparse_14(self): 203 | '''timeparse test case 14.''' 204 | self.assertEqual(timeparse.timeparse('5 hours, 34 minutes, 56 seconds'), 205 | 20096) 206 | self.assertEqual(timeparse.timeparse('+5 hours, 34 minutes, 56 seconds'), 207 | 20096) 208 | self.assertEqual(timeparse.timeparse('-5 hours, 34 minutes, 56 seconds'), 209 | -20096) 210 | 211 | def test_timeparse_15(self): 212 | '''timeparse test case 15.''' 213 | self.assertEqual(timeparse.timeparse('5 hrs, 34 mins, 56 secs'), 20096) 214 | self.assertEqual(timeparse.timeparse('+5 hrs, 34 mins, 56 secs'), 20096) 215 | self.assertEqual(timeparse.timeparse('-5 hrs, 34 mins, 56 secs'), -20096) 216 | 217 | def test_timeparse_16(self): 218 | '''timeparse test case 16.''' 219 | self.assertEqual( 220 | timeparse.timeparse('2 days, 5 hours, 34 minutes, 56 seconds'), 221 | 192896) 222 | self.assertEqual( 223 | timeparse.timeparse('+2 days, 5 hours, 34 minutes, 56 seconds'), 224 | 192896) 225 | self.assertEqual( 226 | timeparse.timeparse('-2 days, 5 hours, 34 minutes, 56 seconds'), 227 | -192896) 228 | 229 | def test_timeparse_16b(self): 230 | '''timeparse test case 16b.''' 231 | self.assertAlmostEqual(timeparse.timeparse('1.75 s'), 1.75) 232 | self.assertAlmostEqual(timeparse.timeparse('+1.75 s'), 1.75) 233 | self.assertAlmostEqual(timeparse.timeparse('-1.75 s'), -1.75) 234 | 235 | def test_timeparse_16c(self): 236 | '''timeparse test case 16c.''' 237 | self.assertAlmostEqual(timeparse.timeparse('1.75 sec'), 1.75) 238 | self.assertAlmostEqual(timeparse.timeparse('+1.75 sec'), 1.75) 239 | self.assertAlmostEqual(timeparse.timeparse('-1.75 sec'), -1.75) 240 | 241 | def test_timeparse_16d(self): 242 | '''timeparse test case 16d.''' 243 | self.assertAlmostEqual(timeparse.timeparse('1.75 secs'), 1.75) 244 | self.assertAlmostEqual(timeparse.timeparse('+1.75 secs'), 1.75) 245 | self.assertAlmostEqual(timeparse.timeparse('-1.75 secs'), -1.75) 246 | 247 | def test_timeparse_16e(self): 248 | '''timeparse test case 16e.''' 249 | self.assertAlmostEqual(timeparse.timeparse('1.75 second'), 1.75) 250 | self.assertAlmostEqual(timeparse.timeparse('+1.75 second'), 1.75) 251 | self.assertAlmostEqual(timeparse.timeparse('-1.75 second'), -1.75) 252 | 253 | def test_timeparse_16f(self): 254 | '''timeparse test case 16f.''' 255 | self.assertAlmostEqual(timeparse.timeparse('1.75 seconds'), 1.75) 256 | self.assertAlmostEqual(timeparse.timeparse('+1.75 seconds'), 1.75) 257 | self.assertAlmostEqual(timeparse.timeparse('-1.75 seconds'), -1.75) 258 | 259 | def test_timeparse_17(self): 260 | '''timeparse test case 17.''' 261 | self.assertEqual(timeparse.timeparse('1.2 m'), 72) 262 | self.assertEqual(timeparse.timeparse('+1.2 m'), 72) 263 | self.assertEqual(timeparse.timeparse('-1.2 m'), -72) 264 | 265 | def test_timeparse_18(self): 266 | '''timeparse test case 18.''' 267 | self.assertEqual(timeparse.timeparse('1.2 min'), 72) 268 | self.assertEqual(timeparse.timeparse('+1.2 min'), 72) 269 | self.assertEqual(timeparse.timeparse('-1.2 min'), -72) 270 | 271 | def test_timeparse_19(self): 272 | '''timeparse test case 19.''' 273 | self.assertEqual(timeparse.timeparse('1.2 mins'), 72) 274 | self.assertEqual(timeparse.timeparse('+1.2 mins'), 72) 275 | self.assertEqual(timeparse.timeparse('-1.2 mins'), -72) 276 | 277 | def test_timeparse_20(self): 278 | '''timeparse test case 20.''' 279 | self.assertEqual(timeparse.timeparse('1.2 minute'), 72) 280 | self.assertEqual(timeparse.timeparse('+1.2 minute'), 72) 281 | self.assertEqual(timeparse.timeparse('-1.2 minute'), -72) 282 | 283 | def test_timeparse_21(self): 284 | '''timeparse test case 21.''' 285 | self.assertEqual(timeparse.timeparse('1.2 minutes'), 72) 286 | self.assertEqual(timeparse.timeparse('+1.2 minutes'), 72) 287 | self.assertEqual(timeparse.timeparse('-1.2 minutes'), -72) 288 | 289 | def test_timeparse_22(self): 290 | '''timeparse test case 22.''' 291 | self.assertEqual(timeparse.timeparse('172 hours'), 619200) 292 | self.assertEqual(timeparse.timeparse('+172 hours'), 619200) 293 | self.assertEqual(timeparse.timeparse('-172 hours'), -619200) 294 | 295 | def test_timeparse_23(self): 296 | '''timeparse test case 23.''' 297 | self.assertEqual(timeparse.timeparse('172 hr'), 619200) 298 | self.assertEqual(timeparse.timeparse('+172 hr'), 619200) 299 | self.assertEqual(timeparse.timeparse('-172 hr'), -619200) 300 | 301 | def test_timeparse_24(self): 302 | '''timeparse test case 24.''' 303 | self.assertEqual(timeparse.timeparse('172 h'), 619200) 304 | self.assertEqual(timeparse.timeparse('+172 h'), 619200) 305 | self.assertEqual(timeparse.timeparse('-172 h'), -619200) 306 | 307 | def test_timeparse_25(self): 308 | '''timeparse test case 25.''' 309 | self.assertEqual(timeparse.timeparse('172 hrs'), 619200) 310 | self.assertEqual(timeparse.timeparse('+172 hrs'), 619200) 311 | self.assertEqual(timeparse.timeparse('-172 hrs'), -619200) 312 | 313 | def test_timeparse_26(self): 314 | '''timeparse test case 26.''' 315 | self.assertEqual(timeparse.timeparse('172 hour'), 619200) 316 | self.assertEqual(timeparse.timeparse('+172 hour'), 619200) 317 | self.assertEqual(timeparse.timeparse('-172 hour'), -619200) 318 | 319 | def test_timeparse_27(self): 320 | '''timeparse test case 27.''' 321 | self.assertEqual(timeparse.timeparse('1.24 days'), 107136) 322 | self.assertEqual(timeparse.timeparse('+1.24 days'), 107136) 323 | self.assertEqual(timeparse.timeparse('-1.24 days'), -107136) 324 | 325 | def test_timeparse_28(self): 326 | '''timeparse test case 28.''' 327 | self.assertEqual(timeparse.timeparse('5 d'), 432000) 328 | self.assertEqual(timeparse.timeparse('+5 d'), 432000) 329 | self.assertEqual(timeparse.timeparse('-5 d'), -432000) 330 | 331 | def test_timeparse_29(self): 332 | '''timeparse test case 29.''' 333 | self.assertEqual(timeparse.timeparse('5 day'), 432000) 334 | self.assertEqual(timeparse.timeparse('+5 day'), 432000) 335 | self.assertEqual(timeparse.timeparse('-5 day'), -432000) 336 | 337 | def test_timeparse_30(self): 338 | '''timeparse test case 30.''' 339 | self.assertEqual(timeparse.timeparse('5 days'), 432000) 340 | self.assertEqual(timeparse.timeparse('+5 days'), 432000) 341 | self.assertEqual(timeparse.timeparse('-5 days'), -432000) 342 | 343 | def test_timeparse_31(self): 344 | '''timeparse test case 31.''' 345 | self.assertEqual(timeparse.timeparse('5.6 wk'), 3386880) 346 | self.assertEqual(timeparse.timeparse('+5.6 wk'), 3386880) 347 | self.assertEqual(timeparse.timeparse('-5.6 wk'), -3386880) 348 | 349 | def test_timeparse_32(self): 350 | '''timeparse test case 32.''' 351 | self.assertEqual(timeparse.timeparse('5.6 week'), 3386880) 352 | self.assertEqual(timeparse.timeparse('+5.6 week'), 3386880) 353 | self.assertEqual(timeparse.timeparse('-5.6 week'), -3386880) 354 | 355 | def test_timeparse_33(self): 356 | '''timeparse test case 33.''' 357 | self.assertEqual(timeparse.timeparse('5.6 weeks'), 3386880) 358 | self.assertEqual(timeparse.timeparse('+5.6 weeks'), 3386880) 359 | self.assertEqual(timeparse.timeparse('-5.6 weeks'), -3386880) 360 | 361 | def test_doctest(self): 362 | '''Run timeparse doctests.''' 363 | self.assertTrue(doctest.testmod(timeparse, raise_on_error=True)) 364 | --------------------------------------------------------------------------------