Skip to content

Commit a92224a

Browse files
authoredOct 20, 2019
Merge pull request #199 from weka-io/time-interval
timing/units: added a TimeInterval class
2 parents bd4baa1 + cb90dfd commit a92224a

File tree

4 files changed

+166
-8
lines changed

4 files changed

+166
-8
lines changed
 

‎CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
66

77
## [Unreleased]
88

9+
### Changed
10+
- units: Duration(inf) is now 'Eternity' instead of 'Never'
11+
- timing: The Timer class now renders the duration using the Duration's repr, instead of as a float
12+
913
### Added
1014
- units: Added a Percentage class
15+
- timing: Added a TimeInterval class, for use for timestamp comparisons. Can be converted to/from the Timer class
1116

1217

1318
## [0.3.1] - 2019-07-30

‎easypy/timing.py

+118-6
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,94 @@
66
from .units import Duration
77

88

9+
inf = float("inf") # same as math.inf, but compatible with 3.4
10+
11+
12+
def to_timestamp(t):
13+
return t.timestamp() if isinstance(t, datetime) else t
14+
15+
16+
class TimeInterval(object):
17+
__slots__ = ("t0", "t1")
18+
19+
NO_START = -inf
20+
NO_END = inf
21+
22+
def __init__(self, from_time=NO_START, to_time=NO_END):
23+
self.t0 = self.NO_START if from_time is None else to_timestamp(from_time)
24+
self.t1 = self.NO_END if to_time is None else to_timestamp(to_time)
25+
26+
@property
27+
def start_time(self):
28+
if self.t0 > self.NO_START:
29+
return datetime.fromtimestamp(self.t0)
30+
31+
@property
32+
def end_time(self):
33+
if self.t1 < self.NO_END:
34+
return datetime.fromtimestamp(self.t1)
35+
36+
@property
37+
def duration_delta(self):
38+
return timedelta(seconds=self.duration) if self.duration < inf else None
39+
40+
@property
41+
def duration(self):
42+
return Duration(self.t1 - self.t0)
43+
44+
def __contains__(self, t):
45+
"""
46+
check if timestamp is within the timer's interval
47+
"""
48+
49+
if isinstance(t, Timer):
50+
t = t.to_interval()
51+
52+
if isinstance(t, TimeInterval):
53+
return self.t0 <= t.t0 and t.t1 <= self.t1
54+
55+
t = to_timestamp(t)
56+
return self.t0 <= t <= self.t1
57+
58+
def to_timer(self):
59+
assert self.t0 > self.NO_START, "Can't convert a start-less TimeInterval to a Timer"
60+
t = Timer(self.t0)
61+
t.t1 = self.t1 if self.t1 < self.NO_END else None
62+
return t
63+
64+
def render(self):
65+
t0, duration, t1 = self.t0, self.duration, self.t1
66+
if (t0, t1) == (self.NO_START, self.NO_END):
67+
return "Eternity"
68+
69+
parts = [".", "."]
70+
71+
if t0 > self.NO_START:
72+
parts[0] = time.strftime("%T", time.localtime(t0))
73+
74+
if t1 < self.NO_END:
75+
parts[-1] = time.strftime("%T", time.localtime(self.t1))
76+
77+
if duration < inf:
78+
parts.insert(1, "({})".format(duration))
79+
80+
return "..".join(parts)
81+
82+
def __str__(self):
83+
return "<TI %s>" % self.render()
84+
85+
def __repr__(self):
86+
return "TimeInterval(%s)" % self.render()
87+
88+
def _repr_pretty_(self, p, cycle):
89+
# used by IPython
90+
from easypy.colors import MAGENTA
91+
if cycle:
92+
p.text('TimeInterval(...)')
93+
return
94+
p.text(MAGENTA(self.render()))
95+
96+
997
class Timer(object):
1098

1199
"""
@@ -26,10 +114,12 @@ class Timer(object):
26114
# do something ...
27115
"""
28116

117+
__slots__ = ("t0", "t1", "expiration")
118+
29119
def __init__(self, now=None, expiration=None):
30120
self.reset(now)
31121
self.t1 = None
32-
self.expiration = expiration
122+
self.expiration = None if expiration is None else Duration(expiration)
33123

34124
def reset(self, now=None):
35125
self.t0 = now or time.time()
@@ -43,6 +133,22 @@ def iter(self, sleep=1):
43133
time.sleep(sleep)
44134
yield self.remain
45135

136+
def to_interval(self):
137+
return TimeInterval(self.t0, self.t1)
138+
139+
def __contains__(self, timestamp):
140+
"""
141+
check if timestamp is within the timer's interval
142+
"""
143+
timestamp = to_timestamp(timestamp)
144+
if timestamp < self.t0:
145+
return False
146+
if not self.t1:
147+
return True
148+
if timestamp > self.t1:
149+
return False
150+
return True
151+
46152
__iter__ = iter
47153

48154
@property
@@ -51,11 +157,11 @@ def stopped(self):
51157

52158
@property
53159
def duration_delta(self):
54-
return timedelta(seconds=(self.t1 or time.time()) - self.t0)
160+
return timedelta(seconds=self.duration)
55161

56162
@property
57163
def duration(self):
58-
return Duration(self.duration_delta.total_seconds())
164+
return Duration((self.t1 or time.time()) - self.t0)
59165

60166
@property
61167
def elapsed_delta(self):
@@ -88,9 +194,7 @@ def render(self):
88194
t0, duration, elapsed, expired, stopped = self.t0, self.duration, self.elapsed, self.expired, self.stopped
89195
st = time.strftime("%T", time.localtime(t0))
90196
if expired:
91-
duration = "{:0.1f}+{:0.2f}".format(self.expiration, expired)
92-
else:
93-
duration = "{:0.2f}".format(duration)
197+
duration = "{}+{}".format(self.expiration, expired)
94198
if stopped:
95199
et = time.strftime("%T", time.localtime(self.t1))
96200
fmt = "{st}..({duration})..{et}"
@@ -106,6 +210,14 @@ def __str__(self):
106210
def __repr__(self):
107211
return "Timer(%s)" % self.render()
108212

213+
def _repr_pretty_(self, p, cycle):
214+
# used by IPython
215+
from easypy.colors import MAGENTA, RED
216+
if cycle:
217+
p.text('Timer(...)')
218+
return
219+
p.text((RED if self.expired else MAGENTA)(self.render()))
220+
109221

110222
class BackoffTimer(Timer):
111223

‎easypy/units.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,7 @@ def __format__(self, spec):
306306

307307
def render(self, unit=None, precision=None):
308308
if self == NEVER:
309-
return "Never"
309+
return "Eternity"
310310

311311
if not unit:
312312
for unit_size in self.SORTED_UNITS:
@@ -362,7 +362,7 @@ def _repr_pretty_(self, p, cycle):
362362
WEEK = Duration(7 * DAY)
363363
MONTH = Duration(31 * DAY)
364364
YEAR = Duration(365 * DAY)
365-
NEVER = Duration("inf")
365+
ETERNITY = NEVER = Duration("inf")
366366

367367
# ------
368368

‎tests/test_timing.py

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
from easypy.timing import Timer, TimeInterval
2+
3+
4+
def test_time_interval1():
5+
6+
st = 150000000
7+
t = Timer(st)
8+
t.t1 = t.t0 + 1
9+
10+
ti = t.to_interval()
11+
12+
assert t in ti
13+
14+
assert t.t0 in ti
15+
assert t.t1 in ti
16+
17+
assert t.t0 - 1 not in ti
18+
assert t.t1 + 1 not in ti
19+
20+
assert ti.duration == t.duration
21+
assert ti.duration == 1
22+
assert ti.duration_delta.total_seconds() == 1
23+
24+
assert str(ti) == '<TI 05:40:00..(1s)..05:40:01>'
25+
26+
assert str(t.to_interval().to_timer()) == str(t)
27+
28+
29+
def test_time_interval2():
30+
st = 150000000
31+
ti = TimeInterval()
32+
assert str(ti) == '<TI Eternity>'
33+
34+
ti = TimeInterval(from_time=st)
35+
assert str(ti) == '<TI 05:40:00...>'
36+
37+
ti = TimeInterval(from_time=st, to_time=st)
38+
assert str(ti) == '<TI 05:40:00..(0.0ms)..05:40:00>'
39+
40+
ti = TimeInterval(to_time=st)
41+
assert str(ti) == '<TI ...05:40:00>'

0 commit comments

Comments
 (0)
Please sign in to comment.