mirror of
https://github.com/msgpack/msgpack-python.git
synced 2025-10-20 12:23:16 +00:00
Refine Timestamp APIs (#395)
This commit is contained in:
parent
aab29ff277
commit
887d3a7d22
3 changed files with 69 additions and 50 deletions
|
@ -7,8 +7,11 @@ import struct
|
||||||
|
|
||||||
PY2 = sys.version_info[0] == 2
|
PY2 = sys.version_info[0] == 2
|
||||||
|
|
||||||
if not PY2:
|
if PY2:
|
||||||
long = int
|
int_types = (int, long)
|
||||||
|
_utc = None
|
||||||
|
else:
|
||||||
|
int_types = int
|
||||||
try:
|
try:
|
||||||
_utc = datetime.timezone.utc
|
_utc = datetime.timezone.utc
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
|
@ -23,8 +26,6 @@ class ExtType(namedtuple("ExtType", "code data")):
|
||||||
raise TypeError("code must be int")
|
raise TypeError("code must be int")
|
||||||
if not isinstance(data, bytes):
|
if not isinstance(data, bytes):
|
||||||
raise TypeError("data must be bytes")
|
raise TypeError("data must be bytes")
|
||||||
if code == -1:
|
|
||||||
return Timestamp.from_bytes(data)
|
|
||||||
if not 0 <= code <= 127:
|
if not 0 <= code <= 127:
|
||||||
raise ValueError("code must be 0~127")
|
raise ValueError("code must be 0~127")
|
||||||
return super(ExtType, cls).__new__(cls, code, data)
|
return super(ExtType, cls).__new__(cls, code, data)
|
||||||
|
@ -42,34 +43,26 @@ class Timestamp(object):
|
||||||
def __init__(self, seconds, nanoseconds=0):
|
def __init__(self, seconds, nanoseconds=0):
|
||||||
"""Initialize a Timestamp object.
|
"""Initialize a Timestamp object.
|
||||||
|
|
||||||
:param seconds: Number of seconds since the UNIX epoch (00:00:00 UTC Jan 1 1970, minus leap seconds). May be
|
:param int seconds:
|
||||||
negative. If :code:`seconds` includes a fractional part, :code:`nanoseconds` must be 0.
|
Number of seconds since the UNIX epoch (00:00:00 UTC Jan 1 1970, minus leap seconds).
|
||||||
:type seconds: int or float
|
May be negative.
|
||||||
|
|
||||||
:param nanoseconds: Number of nanoseconds to add to `seconds` to get fractional time. Maximum is 999_999_999.
|
:param int nanoseconds:
|
||||||
Default is 0.
|
Number of nanoseconds to add to `seconds` to get fractional time.
|
||||||
:type nanoseconds: int
|
Maximum is 999_999_999. Default is 0.
|
||||||
|
|
||||||
Note: Negative times (before the UNIX epoch) are represented as negative seconds + positive ns.
|
Note: Negative times (before the UNIX epoch) are represented as negative seconds + positive ns.
|
||||||
"""
|
"""
|
||||||
if not isinstance(seconds, (int, long, float)):
|
if not isinstance(seconds, int_types):
|
||||||
raise TypeError("seconds must be numeric")
|
raise TypeError("seconds must be an interger")
|
||||||
if not isinstance(nanoseconds, (int, long)):
|
if not isinstance(nanoseconds, int_types):
|
||||||
raise TypeError("nanoseconds must be an integer")
|
raise TypeError("nanoseconds must be an integer")
|
||||||
if nanoseconds:
|
if not (0 <= nanoseconds < 10 ** 9):
|
||||||
if nanoseconds < 0 or nanoseconds % 1 != 0 or nanoseconds > (1e9 - 1):
|
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"nanoseconds must be a non-negative integer less than 999999999."
|
"nanoseconds must be a non-negative integer less than 999999999."
|
||||||
)
|
)
|
||||||
if not isinstance(seconds, (int, long)):
|
self.seconds = seconds
|
||||||
raise ValueError(
|
|
||||||
"seconds must be an integer if also providing nanoseconds."
|
|
||||||
)
|
|
||||||
self.nanoseconds = nanoseconds
|
self.nanoseconds = nanoseconds
|
||||||
else:
|
|
||||||
# round helps with floating point issues
|
|
||||||
self.nanoseconds = int(round(seconds % 1 * 1e9, 0))
|
|
||||||
self.seconds = int(seconds // 1)
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
"""String representation of Timestamp."""
|
"""String representation of Timestamp."""
|
||||||
|
@ -137,7 +130,18 @@ class Timestamp(object):
|
||||||
data = struct.pack("!Iq", self.nanoseconds, self.seconds)
|
data = struct.pack("!Iq", self.nanoseconds, self.seconds)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def to_float(self):
|
@staticmethod
|
||||||
|
def from_unix(unix_sec):
|
||||||
|
"""Create a Timestamp from posix timestamp in seconds.
|
||||||
|
|
||||||
|
:param unix_float: Posix timestamp in seconds.
|
||||||
|
:type unix_float: int or float.
|
||||||
|
"""
|
||||||
|
seconds = int(unix_sec // 1)
|
||||||
|
nanoseconds = int((unix_sec % 1) * 10 ** 9)
|
||||||
|
return Timestamp(seconds, nanoseconds)
|
||||||
|
|
||||||
|
def to_unix(self):
|
||||||
"""Get the timestamp as a floating-point value.
|
"""Get the timestamp as a floating-point value.
|
||||||
|
|
||||||
:returns: posix timestamp
|
:returns: posix timestamp
|
||||||
|
@ -146,28 +150,37 @@ class Timestamp(object):
|
||||||
return self.seconds + self.nanoseconds / 1e9
|
return self.seconds + self.nanoseconds / 1e9
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_float(unix_float):
|
def from_unix_nano(unix_ns):
|
||||||
seconds = int(unix_float)
|
"""Create a Timestamp from posix timestamp in nanoseconds.
|
||||||
nanoseconds = int((unix_float % 1) * 1000000000)
|
|
||||||
return Timestamp(seconds, nanoseconds)
|
|
||||||
|
|
||||||
def to_unix_ns(self):
|
:param int unix_ns: Posix timestamp in nanoseconds.
|
||||||
|
:rtype: Timestamp
|
||||||
|
"""
|
||||||
|
return Timestamp(*divmod(unix_ns, 10 ** 9))
|
||||||
|
|
||||||
|
def to_unix_nano(self):
|
||||||
"""Get the timestamp as a unixtime in nanoseconds.
|
"""Get the timestamp as a unixtime in nanoseconds.
|
||||||
|
|
||||||
:returns: posix timestamp in nanoseconds
|
:returns: posix timestamp in nanoseconds
|
||||||
:rtype: int
|
:rtype: int
|
||||||
"""
|
"""
|
||||||
return int(self.seconds * 1e9 + self.nanoseconds)
|
return self.seconds * 10 ** 9 + self.nanoseconds
|
||||||
|
|
||||||
if not PY2:
|
|
||||||
|
|
||||||
def to_datetime(self):
|
def to_datetime(self):
|
||||||
"""Get the timestamp as a UTC datetime.
|
"""Get the timestamp as a UTC datetime.
|
||||||
|
|
||||||
|
Python 2 is not supported.
|
||||||
|
|
||||||
:rtype: datetime.
|
:rtype: datetime.
|
||||||
"""
|
"""
|
||||||
return datetime.datetime.fromtimestamp(self.to_float(), _utc)
|
return datetime.datetime.fromtimestamp(self.to_unix(), _utc)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_datetime(dt):
|
def from_datetime(dt):
|
||||||
return Timestamp.from_float(dt.timestamp())
|
"""Create a Timestamp from datetime with tzinfo.
|
||||||
|
|
||||||
|
Python 2 is not supported.
|
||||||
|
|
||||||
|
:rtype: Timestamp
|
||||||
|
"""
|
||||||
|
return Timestamp.from_unix(dt.timestamp())
|
||||||
|
|
|
@ -691,9 +691,9 @@ class Unpacker(object):
|
||||||
if n == -1: # timestamp
|
if n == -1: # timestamp
|
||||||
ts = Timestamp.from_bytes(bytes(obj))
|
ts = Timestamp.from_bytes(bytes(obj))
|
||||||
if self._timestamp == 1:
|
if self._timestamp == 1:
|
||||||
return ts.to_float()
|
return ts.to_unix()
|
||||||
elif self._timestamp == 2:
|
elif self._timestamp == 2:
|
||||||
return ts.to_unix_ns()
|
return ts.to_unix_nano()
|
||||||
elif self._timestamp == 3:
|
elif self._timestamp == 3:
|
||||||
return ts.to_datetime()
|
return ts.to_datetime()
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -37,19 +37,25 @@ def test_timestamp():
|
||||||
assert ts.seconds == 2 ** 63 - 1 and ts.nanoseconds == 999999999
|
assert ts.seconds == 2 ** 63 - 1 and ts.nanoseconds == 999999999
|
||||||
|
|
||||||
# negative fractional
|
# negative fractional
|
||||||
ts = Timestamp(-2.3) # s: -3, ns: 700000000
|
ts = Timestamp.from_unix(-2.3) # s: -3, ns: 700000000
|
||||||
|
assert ts.seconds == -3 and ts.nanoseconds == 700000000
|
||||||
assert ts.to_bytes() == b"\x29\xb9\x27\x00\xff\xff\xff\xff\xff\xff\xff\xfd"
|
assert ts.to_bytes() == b"\x29\xb9\x27\x00\xff\xff\xff\xff\xff\xff\xff\xfd"
|
||||||
packed = msgpack.packb(ts)
|
packed = msgpack.packb(ts)
|
||||||
assert packed == b"\xc7\x0c\xff" + ts.to_bytes()
|
assert packed == b"\xc7\x0c\xff" + ts.to_bytes()
|
||||||
unpacked = msgpack.unpackb(packed)
|
unpacked = msgpack.unpackb(packed)
|
||||||
assert ts == unpacked
|
assert ts == unpacked
|
||||||
assert ts.seconds == -3 and ts.nanoseconds == 700000000
|
|
||||||
|
|
||||||
|
def test_timestamp_from():
|
||||||
|
t = Timestamp(42, 14000)
|
||||||
|
assert Timestamp.from_unix(42.000014) == t
|
||||||
|
assert Timestamp.from_unix_nano(42000014000) == t
|
||||||
|
|
||||||
|
|
||||||
def test_timestamp_to():
|
def test_timestamp_to():
|
||||||
t = Timestamp(42, 14)
|
t = Timestamp(42, 14000)
|
||||||
assert t.to_float() == 42.000000014
|
assert t.to_unix() == 42.000014
|
||||||
assert t.to_unix_ns() == 42000000014
|
assert t.to_unix_nano() == 42000014000
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(sys.version_info[0] == 2, reason="datetime support is PY3+ only")
|
@pytest.mark.skipif(sys.version_info[0] == 2, reason="datetime support is PY3+ only")
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue