8000 Refine Timestamp APIs (#395) · ossdev07/msgpack-python@887d3a7 · GitHub
[go: up one dir, main page]

Skip to content

Commit 887d3a7

Browse files
authored
Refine Timestamp APIs (msgpack#395)
1 parent aab29ff commit 887d3a7

File tree

3 files changed

+69
-50
lines changed

3 files changed

+69
-50
lines changed

msgpack/ext.py

Lines changed: 56 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,11 @@
77

88
PY2 = sys.version_info[0] == 2
99

10-
if not PY2:
11-
long = int
10+
if PY2:
11+
int_types = (int, long)
12+
_utc = None
13+
else:
14+
int_types = int
1215
try:
1316
_utc = datetime.timezone.utc
1417
except AttributeError:
@@ -23,8 +26,6 @@ def __new__(cls, code, data):
2326
raise TypeError("code must be int")
2427
if not isinstance(data, bytes):
2528
raise TypeError("data must be bytes")
26-
if code == -1:
27-
return Timestamp.from_bytes(data)
2829
if not 0 <= code <= 127:
2930
raise ValueError("code must be 0~127")
3031
return super(ExtType, cls).__new__(cls, code, data)
@@ -42,34 +43,26 @@ class Timestamp(object):
4243
def __init__(self, seconds, nanoseconds=0):
4344
"""Initialize a Timestamp object.
4445
45-
:param seconds: Number of seconds since the UNIX epoch (00:00:00 UTC Jan 1 1970, minus leap seconds). May be
46-
negative. If :code:`seconds` includes a fractional part, :code:`nanoseconds` must be 0.
47-
:type seconds: int or float
46+
:param int seconds:
47+
Number of seconds since the UNIX epoch (00:00:00 UTC Jan 1 1970, minus leap seconds).
48+
May be negative.
4849
49-
:param nanoseconds: Number of nanoseconds to add to `seconds` to get fractional time. Maximum is 999_999_999.
50-
Default is 0.
51-
:type nanoseconds: int
50+
:param int nanoseconds:
51+
Number of nanoseconds to add to `seconds` to get fractional time.
52+
Maximum is 999_999_999. Default is 0.
5253
5354
Note: Negative times (before the UNIX epoch) are represented as negative seconds + positive ns.
5455
"""
55-
if not isinstance(seconds, (int, long, float)):
56-
raise TypeError("seconds must be numeric")
57-
if not isinstance(nanoseconds, (int, long)):
56+
if not isinstance(seconds, int_types):
57+
raise TypeError("seconds must be an interger")
58+
if not isinstance(nanoseconds, int_types):
5859
raise TypeError("nanoseconds must be an integer")
59-
if nanoseconds:
60-
if nanoseconds < 0 or nanoseconds % 1 != 0 or nanoseconds > (1e9 - 1):
61-
raise ValueError(
62-
"nanoseconds must be a non-negative integer less than 999999999."
63-
)
64-
if not isinstance(seconds, (int, long)):
65-
raise ValueError(
66-
"seconds must be an integer if also providing nanoseconds."
67-
)
68-
self.nanoseconds = nanoseconds
69-
else:
70-
# round helps with floating point issues
71-
self.nanoseconds = int(round(seconds % 1 * 1e9, 0))
72-
self.seconds = int(seconds // 1)
60+
if not (0 <= nanoseconds < 10 ** 9):
61+
raise ValueError(
62+
"nanoseconds must be a non-negative integer less than 999999999."
63+
)
64+
self.seconds = seconds
65+
self.nanoseconds = nanoseconds
7366

7467
def __repr__(self):
7568
"""String representation of Timestamp."""
@@ -137,7 +130,18 @@ def to_bytes(self):
137130
data = struct.pack("!Iq", self.nanoseconds, self.seconds)
138131
return data
139132

140-
def to_float(self):
133+
@staticmethod
134+
def from_unix(unix_sec):
135+
"""Create a Timestamp from posix timestamp in seconds.
136+
137+
:param unix_float: Posix timestamp in seconds.
138+
:type unix_float: int or float.
139+
"""
140+
seconds = int(unix_sec // 1)
141+
nanoseconds = int((unix_sec % 1) * 10 ** 9)
142+
return Timestamp(seconds, nanoseconds)
143+
144+
def to_unix(self):
141145
"""Get the timestamp as a floating-point value.
142146
143147
:returns: posix timestamp
@@ -146,28 +150,37 @@ def to_float(self):
146150
return self.seconds + self.nanoseconds / 1e9
147151

148152
@staticmethod
149-
def from_float(unix_float):
150-
seconds = int(unix_float)
151-
nanoseconds = int((unix_float % 1) * 1000000000)
152-
return Timestamp(seconds, nanoseconds)
153+
def from_unix_nano(unix_ns):
154+
"""Create a Timestamp from posix timestamp in nanoseconds.
153155
154-
def to_unix_ns(self):
156+
:param int unix_ns: Posix timestamp in nanoseconds.
157+
:rtype: Timestamp
158+
"""
159+
return Timestamp(*divmod(unix_ns, 10 ** 9))
160+
161+
def to_unix_nano(self):
155162
"""Get the timestamp as a unixtime in nanoseconds.
156163
157164
:returns: posix timestamp in nanoseconds
158165
:rtype: int
159166
"""
160-
return int(self.seconds * 1e9 + self.nanoseconds)
167+
return self.seconds * 10 ** 9 + self.nanoseconds
161168

162-
if not PY2:
169+
def to_datetime(self):
170+
"""Get the timestamp as a UTC datetime.
163171
164-
def to_datetime(self):
165-
"""Get the timestamp as a UTC datetime.
172+
Python 2 is not supported.
166173
167-
:rtype: datetime.
168-
"""
169-
return datetime.datetime.fromtimestamp(self.to_float(), _utc)
174+
:rtype: datetime.
175+
""" 427E
176+
return datetime.datetime.fromtimestamp(self.to_unix(), _utc)
170177

171-
@staticmethod
172-
def from_datetime(dt):
173-
return Timestamp.from_float(dt.timestamp())
178+
@staticmethod
179+
def from_datetime(dt):
180+
"""Create a Timestamp from datetime with tzinfo.
181+
182+
Python 2 is not supported.
183+
184+
:rtype: Timestamp
185+
"""
186+
return Timestamp.from_unix(dt.timestamp())

msgpack/fallback.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -691,9 +691,9 @@ def _unpack(self, execute=EX_CONSTRUCT):
691691
if n == -1: # timestamp
692692
ts = Timestamp.from_bytes(bytes(obj))
693693
if self._timestamp == 1:
694-
return ts.to_float()
694+
return ts.to_unix()
695695
elif self._timestamp == 2:
696-
return ts.to_unix_ns()
696+
return ts.to_unix_nano()
697697
elif self._timestamp == 3:
698698
return ts.to_datetime()
699699
else:

test/test_timestamp.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,19 +37,25 @@ def test_timestamp():
3737
assert ts.seconds == 2 ** 63 - 1 and ts.nanoseconds == 999999999
3838

3939
# negative fractional
40-
ts = Timestamp(-2.3) # s: -3, ns: 700000000
40+
ts = Timestamp.from_unix(-2.3) # s: -3, ns: 700000000
41+
assert ts.seconds == -3 and ts.nanoseconds == 700000000
4142
assert ts.to_bytes() == b"\x29\xb9\x27\x00\xff\xff\xff\xff\xff\xff\xff\xfd"
4243
packed = msgpack.packb(ts)
4344
assert packed == b"\xc7\x0c\xff" + ts.to_bytes()
4445
unpacked = msgpack.unpackb(packed)
4546
assert ts == unpacked
46-
assert ts.seconds == -3 and ts.nanoseconds == 700000000
47+
48+
49+
def test_timestamp_from():
50+
t = Timestamp(42, 14000)
51+
assert Timestamp.from_unix(42.000014) == t
52+
assert Timestamp.from_unix_nano(42000014000) == t
4753

4854

4955
def test_timestamp_to():
50-
t = Timestamp(42, 14)
51-
assert t.to_float() == 42.000000014
52-
assert t.to_unix_ns() == 42000000014
56+
t = Timestamp(42, 14000)
57+
assert t.to_unix() == 42.000014
58+
assert t.to_unix_nano() == 42000014000
5359

5460

5561
@pytest.mark.skipif(sys.version_info[0] == 2, reason="datetime support is PY3+ only")

0 commit comments

Comments
 (0)
0