-
Notifications
You must be signed in to change notification settings - Fork 1k
/
Copy pathhours.py
115 lines (88 loc) · 2.5 KB
/
hours.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
"""
Abusing ``__class_getitem__`` to make a nano-DSL for working
with hours, minutes, and seconds--these last two in base 60.
``H`` is an alias for the ``Hours`` class::
>>> H[1]
1:00
>>> H[1:30]
1:30
>>> H[1::5]
1:00:05
>>> H[::5]
0:00:05
An ``H`` instance can be converted to a float number of hours::
>>> float(H[1:15])
1.25
>>> float(H[1:30:30]) # doctest: +ELLIPSIS
1.5083333...
>>> float(H[1::5]) # doctest: +ELLIPSIS
1.0013888...
The ``H`` constructor accepts hours, minutes, and/or seconds::
>>> H(1.5)
1:30
>>> H(1.9)
1:54
>>> H(1, 30, 30)
1:30:30
>>> H(s = 7205)
2:00:05
>>> H(1/3)
0:20
>>> H(1/1000)
0:00:03.6
An ``H`` instance is iterable, for convenient unpacking::
>>> hms = H[1:22:33]
>>> h, m, s = hms
>>> h, m, s
(1, 22, 33)
>>> tuple(hms)
(1, 22, 33)
``H`` instances can be added::
>>> H[1:45:12] + H[2:15:50]
4:01:02
"""
from typing import Tuple, Union
def normalize(s: float) -> Tuple[int, int, float]:
h, r = divmod(s, 3600)
m, s = divmod(r, 60)
return int(h), int(m), s
def valid_base_60(n, unit):
if not (0 <= n < 60):
raise ValueError(f'invalid {unit} {n}')
return n
class Hours:
h: int
_m: int
_s: float
def __class_getitem__(cls, parts: Union[slice, float]) -> 'Hours':
if isinstance(parts, slice):
h = parts.start or 0
m = valid_base_60(parts.stop or 0, 'minutes')
s = valid_base_60(parts.step or 0, 'seconds')
else:
h, m, s = normalize(parts * 3600)
return Hours(h, m, s)
def __init__(self, h: float = 0, m: float = 0, s: float = 0):
if h < 0 or m < 0 or s < 0:
raise ValueError('invalid negative argument')
self.h, self.m, self.s = normalize(h * 3600 + m * 60 + s)
def __repr__(self):
h, m, s = self
display_s = f'{s:06.3f}'
display_s = display_s.rstrip('0').rstrip('.')
if display_s == '00':
return f'{h}:{m:02d}'
return f'{h}:{m:02d}:{display_s}'
def __float__(self):
return self.h + self.m / 60 + self.s / 3600
def __eq__(self, other):
return repr(self) == repr(other)
def __iter__(self):
yield self.h
yield self.m
yield self.s
def __add__(self, other):
if not isinstance(other, Hours):
return NotImplemented
return Hours(*(a + b for a, b in zip(self, other)))
H = Hours