Skip to content

Commit

Permalink
Switch to monotonic clock for timers (#13)
Browse files Browse the repository at this point in the history
  • Loading branch information
xolox committed Jan 16, 2017
1 parent 87f0194 commit b89fcdf
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 14 deletions.
30 changes: 21 additions & 9 deletions humanfriendly/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Human friendly input/output in Python.
#
# Author: Peter Odding <peter@peterodding.com>
# Last Change: January 10, 2017
# Last Change: January 16, 2017
# URL: https://humanfriendly.readthedocs.io

"""The main module of the `humanfriendly` package."""
Expand Down Expand Up @@ -36,7 +36,7 @@
from humanfriendly.prompts import prompt_for_choice # NOQA

# Compatibility with Python 2 and 3.
from humanfriendly.compat import is_string
from humanfriendly.compat import is_string, monotonic

# Semi-standard module versioning.
__version__ = '2.2.1'
Expand Down Expand Up @@ -581,13 +581,23 @@ def __init__(self, start_time=None, resumable=False):
:param start_time: The start time (a float, defaults to the current time).
:param resumable: Create a resumable timer (defaults to :data:`False`).
When `start_time` is given :class:`Timer` uses :func:`time.time()` as a
clock source, otherwise it uses :func:`humanfriendly.compat.monotonic()`.
"""
self.resumable = resumable
if self.resumable:
if resumable:
self.monotonic = True
self.resumable = True
self.start_time = 0.0
self.total_time = 0.0
elif start_time:
self.monotonic = False
self.resumable = False
self.start_time = start_time
else:
self.start_time = start_time or time.time()
self.monotonic = True
self.resumable = False
self.start_time = monotonic()

def __enter__(self):
"""
Expand All @@ -598,7 +608,7 @@ def __enter__(self):
"""
if not self.resumable:
raise ValueError("Timer is not resumable!")
self.start_time = time.time()
self.start_time = monotonic()
return self

def __exit__(self, exc_type=None, exc_value=None, traceback=None):
Expand All @@ -609,8 +619,9 @@ def __exit__(self, exc_type=None, exc_value=None, traceback=None):
"""
if not self.resumable:
raise ValueError("Timer is not resumable!")
self.total_time += time.time() - self.start_time
self.start_time = 0
if self.start_time:
self.total_time += monotonic() - self.start_time
self.start_time = 0.0

@property
def elapsed_time(self):
Expand All @@ -621,7 +632,8 @@ def elapsed_time(self):
if self.resumable:
elapsed_time += self.total_time
if self.start_time:
elapsed_time += time.time() - self.start_time
current_time = monotonic() if self.monotonic else time.time()
elapsed_time += current_time - self.start_time
return elapsed_time

@property
Expand Down
36 changes: 33 additions & 3 deletions humanfriendly/compat.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Human friendly input/output in Python.
#
# Author: Peter Odding <peter@peterodding.com>
# Last Change: September 28, 2016
# Last Change: January 16, 2017
# URL: https://humanfriendly.readthedocs.io

"""
Expand Down Expand Up @@ -29,20 +29,50 @@
Alias for :func:`python2:unicode` (in Python 2) or :class:`python3:str` (in
Python 3). See also :func:`coerce_string()`.
.. data:: monotonic
Alias for :func:`python3:time.monotonic()` (in Python 3.3 and higher) or
`monotonic.monotonic()` (a `conditional dependency
<https://pypi.python.org/pypi/monotonic/>`_ on older Python versions).
"""

__all__ = (
'StringIO',
'basestring',
'coerce_string',
'interactive_prompt',
'is_string',
'is_unicode',
'monotonic',
'unicode',
)

try:
# Python 2.
unicode = unicode
basestring = basestring
interactive_prompt = raw_input # NOQA
interactive_prompt = raw_input
from StringIO import StringIO
except (ImportError, NameError):
# Python 3.
unicode = str
basestring = str
interactive_prompt = input
from io import StringIO # NOQA
from io import StringIO

try:
# Python 3.3 and higher.
from time import monotonic
except ImportError:
# A replacement for older Python versions:
# https://pypi.python.org/pypi/monotonic/
try:
from monotonic import monotonic
except (ImportError, RuntimeError):
# We fall back to the old behavior of using time.time() instead of
# failing when {time,monotonic}.monotonic() are both missing.
from time import time as monotonic


def coerce_string(value):
Expand Down
19 changes: 17 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,30 @@ def get_install_requires():
install_requires = []
if sys.version_info[:2] <= (2, 6) or sys.version_info[:2] == (3, 0):
install_requires.append('importlib')
return install_requires
if sys.version_info[:2] < (3, 3):
install_requires.append('monotonic')
return sorted(install_requires)


def get_extras_require():
"""Add conditional dependencies for Python 2 (when creating wheel distributions)."""
extras_require = {}
if have_environment_marker_support():
expression = ':python_version == "2.6" or python_version == "3.0"'
# Conditional `importlib' dependency.
expression = ':%s' % ' or '.join([
'python_version == "2.6"',
'python_version == "3.0"',
])
extras_require[expression] = ['importlib']
# Conditional `monotonic' dependency.
expression = ':%s' % ' or '.join([
'python_version == "2.6"',
'python_version == "2.7"',
'python_version == "3.0"',
'python_version == "3.1"',
'python_version == "3.2"',
])
extras_require[expression] = ['monotonic']
return extras_require


Expand Down

0 comments on commit b89fcdf

Please sign in to comment.