diff --git a/CHANGELOG.md b/CHANGELOG.md
index f3985a72782e75031b541ef14d21a24db5bf6f03..8fb8d9e96390a8f49c4116052a600ff367c8058c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,7 @@
 * changed the behaviour of `JSONEncoder` when it comes to serializing unknown objects
 * moved the exception `LockIsHeld` to it's proper place
 * fixed `dump_frames_on` not to log on **sys.stderr**
+* added `satella.time.measure`
 
 # v2.4.16
 
diff --git a/docs/index.rst b/docs/index.rst
index 2608553ab4f7e40311b3d5de5e918fc48be4f125..7fa8dfbe450bdb0d13a093fcee0cc3914f32125b 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -23,6 +23,7 @@ Visit the project's page at GitHub_!
            posix
            import
            files
+           time
 
 
 Indices and tables
diff --git a/docs/time.rst b/docs/time.rst
new file mode 100644
index 0000000000000000000000000000000000000000..dbec771ddaf0a37e9c1249dd622a2fe5c89dd95e
--- /dev/null
+++ b/docs/time.rst
@@ -0,0 +1,7 @@
+time
+====
+
+Sometimes you just need to measure how long does a routine call take.
+
+.. autoclass:: satella.time.measure
+    :members:
diff --git a/satella/__init__.py b/satella/__init__.py
index 2f1aa7da2daa0cdf0d078a0ee9360f4c71a78372..2e27e304b5c425a59075371eeedb58e110a613e7 100644
--- a/satella/__init__.py
+++ b/satella/__init__.py
@@ -1 +1 @@
-__version__ = '2.4.17a9'
+__version__ = '2.4.17'
diff --git a/satella/time.py b/satella/time.py
new file mode 100644
index 0000000000000000000000000000000000000000..1db71846c974eee71b93cc2c687186323633ddda
--- /dev/null
+++ b/satella/time.py
@@ -0,0 +1,67 @@
+import logging
+import typing as tp
+import time
+import functools
+
+logger = logging.getLogger(__name__)
+
+__all__ = ['measure']
+
+
+class measure:
+    """
+    A class used to measure time elapsed. Use for example like this:
+
+    >>> with measure() as measurement:
+    >>>     time.sleep(1)
+    >>>     print('This has taken so far', measurement(), 'seconds')
+    >>>     time.sleep(1)
+    >>> print('A total of ', measurement(), 'seconds have elapsed')
+
+    You can also use the .start() method instead of context manager. Time measurement
+    will stop after exiting or calling .stop() depending on stop_on_stop flag.
+
+    You can also decorate your functions to have them keep track time of their execution, like that:
+
+    >>> @measure()
+    >>> def measuring(measurement_object: measure, *args, **kwargs):
+    >>>     ...
+
+    :param stop_on_stop: stop elapsing time upon calling .stop()/exiting the context manager
+    """
+    def __init__(self, stop_on_stop: bool = True):
+        self.started_on = None
+        self.elapsed = None
+        self.stopped_on = None
+        self.stop_on_stop = stop_on_stop
+
+    def start(self) -> None:
+        """Start measuring time"""
+        self.started_on = time.monotonic()
+
+    def __call__(self, fun: tp.Optional[tp.Callable] = None) -> float:
+        if fun is None:
+            if self.started_on is None:
+                raise RuntimeError('Time measurement did not start yet, use .start()')
+            if self.stop_on_stop and self.elapsed is not None:
+                return self.elapsed
+            return time.monotonic() - self.started_on
+        else:
+            @functools.wraps(fun)
+            def inner(*args, **kwargs):
+                with self:
+                    return fun(self, *args, **kwargs)
+            return inner
+
+    def __enter__(self):
+        self.start()
+        return self
+
+    def stop(self) -> None:
+        """Stop counting time"""
+        self.stopped_on = time.monotonic()
+        self.elapsed = self.stopped_on - self.started_on
+
+    def __exit__(self, exc_type, exc_val, exc_tb):
+        self.stop()
+        return False
diff --git a/tests/test_time.py b/tests/test_time.py
new file mode 100644
index 0000000000000000000000000000000000000000..5389cc71ac9f94bc4ec4735bf11bd0c29e163a15
--- /dev/null
+++ b/tests/test_time.py
@@ -0,0 +1,20 @@
+import unittest
+import typing as tp
+import time
+from satella.time import measure
+
+
+class TestTime(unittest.TestCase):
+    def test_measure(self):
+        with measure() as measurement:
+            time.sleep(0.5)
+
+        self.assertGreaterEqual(measurement(), 0.5)
+
+    def test_measure_decorator(self):
+        @measure()
+        def measured(measurement):
+            time.sleep(0.5)
+            self.assertGreaterEqual(measurement(), 0.5)
+        measured()
+