Skip to content

Commit b8965c9

Browse files
committed
Created time utilities
1 parent a1d55bb commit b8965c9

File tree

2 files changed

+184
-0
lines changed

2 files changed

+184
-0
lines changed

servercom/_utils.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
"""Some utilities that help out behind-the-scenes."""
2+
3+
class Immutable:
4+
"""Superclass for immutable objects
5+
"""
6+
7+
def __init__(self):
8+
"""As soon as this constructor is called, the object becomes immutable
9+
"""
10+
self._frozen = True
11+
12+
def __delattr__(self, *args, **kwargs):
13+
if hasattr(self, '_frozen'):
14+
raise AttributeError("This object is immutable. You cannot delete instance variables from it.")
15+
object.__delattr__(self, *args, **kwargs)
16+
17+
def __setattr__(self, *args, **kwargs):
18+
if hasattr(self, '_frozen'):
19+
raise AttributeError("This object is immutable. You cannot set any instance variables.")
20+
object.__setattr__(self, *args, **kwargs)

servercom/timetools.py

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
"""Time Tools
2+
Some tools for dealing with times on microcontrollers
3+
(to a precision of 1 second)
4+
"""
5+
6+
from time import monotonic
7+
from ._utils import Immutable
8+
9+
# Time unit declarations in seconds:
10+
MINUTE = 60
11+
HOUR = 3600
12+
DAY = HOUR * 24
13+
WEEK = DAY * 7
14+
YEAR = DAY * 365
15+
16+
# Time zone UTC offsets:
17+
EST = -5 * HOUR
18+
EDT = -4 * HOUR
19+
20+
class Time(Immutable):
21+
"""Represents an instant in time based around a UNIX timestamp"""
22+
23+
# (monotonic time, epoch time) where both represent the same physical time
24+
_timeset = (None, None)
25+
_time_offset = None
26+
27+
# Class Methods:
28+
@classmethod
29+
def set_time(cls, unix_timestamp: int) -> None:
30+
cls._timeset = (int(monotonic()), int(unix_timestamp))
31+
cls._time_offset = int(unix_timestamp - monotonic())
32+
33+
@classmethod
34+
def from_unix_time(cls, unix_time: int) -> int:
35+
"""Returns a monotonic timestamp from a unix timestamp"""
36+
return int(unix_time - cls._time_offset)
37+
38+
@classmethod
39+
def from_monotonic_time(cls, monotonic_time: int) -> int:
40+
"""Returns a UNIX timestamp for a given monotonic time"""
41+
return int(monotonic_time + cls._time_offset)
42+
43+
@classmethod
44+
def get_unix_time(cls) -> int:
45+
return cls.from_monotonic_time(monotonic())
46+
47+
@classmethod
48+
def now(cls) -> 'Time':
49+
return cls(cls.get_unix_time())
50+
51+
# Constructor and Instance Methods:
52+
def __init__(self, unix_timestamp: int, absolute: bool = True):
53+
self.seconds = unix_timestamp
54+
self.absolute = absolute
55+
super().__init__()
56+
57+
def offset(self, seconds:int=0, minutes:int=0, hours:int=0, days:int=0, weeks:int=0, years:int=0) -> 'Time':
58+
"""Offsets this time by a given amount
59+
60+
Args:
61+
seconds (int, optional): seconds to offset by. Defaults to 0.
62+
minutes (int, optional): minutes to offset by. Defaults to 0.
63+
hours (int, optional): hours to offset by. Defaults to 0.
64+
days (int, optional): days to offset by. Defaults to 0.
65+
weeks (int, optional): weeks to offset by. Defaults to 0.
66+
years (int, optional): years to offset by. Defaults to 0.
67+
68+
Returns:
69+
Time: Returns self to allow daisy-chain operations
70+
"""
71+
return Time(self.seconds
72+
+ seconds
73+
+ minutes * MINUTE
74+
+ hours * HOUR
75+
+ days * DAY
76+
+ weeks * WEEK
77+
+ years * YEAR
78+
)
79+
80+
def since_last(self, timeunit: int) -> 'Time':
81+
"""Returns a Time object representing time since the beginning of the
82+
most recent <MINUTE|HOUR|DAY|WEEK|YEAR>
83+
84+
Args:
85+
timeunit (int): the length, in seconds, of the specified time unit
86+
87+
Returns:
88+
Time: the new representative Time object
89+
"""
90+
return Time(
91+
self.seconds % timeunit,
92+
absolute=False
93+
)
94+
95+
def __add__(self, other: 'Time') -> 'Time':
96+
if not isinstance(other, Time):
97+
raise TypeError(f"Cannot add {type(other)} to Time")
98+
return Time(
99+
self.seconds + other.seconds,
100+
absolute=False
101+
)
102+
103+
def __sub__(self, other: 'Time') -> 'Time':
104+
if not isinstance(other, Time):
105+
raise TypeError(f"Cannot subtract {type(other)} from Time")
106+
return Time(
107+
self.seconds - other.seconds,
108+
absolute=False
109+
)
110+
111+
def elapsed_since(self, other: 'Time') -> 'Time':
112+
""" Returns time elapsed since a given time
113+
"""
114+
return self - other
115+
116+
def elapsed_until(self, other: 'Time') -> 'Time':
117+
""" Returns time elapsed between this and a future time
118+
"""
119+
return other - self
120+
121+
def __mul__(self, other) -> 'Time':
122+
if not (isinstance(other, int) or isinstance(other, float)):
123+
raise TypeError(f"You can only multiply Time objects by ints and floats")
124+
return Time(
125+
self.seconds * other,
126+
absolute=False
127+
)
128+
129+
def __div__(self, other) -> 'Time':
130+
if not (isinstance(other, int) or isinstance(other, float)):
131+
raise TypeError(f"You can only divide Time objects by ints and floats")
132+
return Time(
133+
self.seconds // other,
134+
absolute=False
135+
)
136+
137+
def __mod__(self, other: 'Time') -> 'Time':
138+
if not (isinstance(other, Time) or isinstance(other, int)):
139+
raise TypeError(f"Cannot modulo {type(other)} with Time")
140+
return self.since_last(int(other))
141+
142+
def __str__(self) -> str:
143+
if self.absolute:
144+
return f"{self.seconds} seconds since 1 January 1970 00:00:00 UTC"
145+
return f"{self.seconds} seconds"
146+
147+
def __repr__(self) -> str:
148+
return self.__str__()
149+
150+
def __abs__(self) -> 'Time':
151+
return Time(
152+
abs(self.seconds),
153+
self.absolute
154+
)
155+
156+
def __int__(self) -> int:
157+
return self.seconds
158+
159+
@property
160+
def monotonic(self) -> int:
161+
"""Returns the equivalent monotonic time"""
162+
if self.absolute:
163+
return self.from_unix_time(self.seconds)
164+
return monotonic() + self.seconds

0 commit comments

Comments
 (0)