Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/msgpack/ext.py: 48%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1import datetime
2import struct
3from collections import namedtuple
5_EPOCH = datetime.datetime(1970, 1, 1, tzinfo=datetime.timezone.utc)
8class ExtType(namedtuple("ExtType", "code data")):
9 """ExtType represents ext type in msgpack."""
11 def __new__(cls, code, data):
12 if not isinstance(code, int):
13 raise TypeError("code must be int")
14 if not isinstance(data, bytes):
15 raise TypeError("data must be bytes")
16 if not 0 <= code <= 127:
17 raise ValueError("code must be 0~127")
18 return super().__new__(cls, code, data)
21class Timestamp:
22 """Timestamp represents the Timestamp extension type in msgpack.
24 When built with Cython, msgpack uses C methods to pack and unpack `Timestamp`.
25 When using pure-Python msgpack, :func:`to_bytes` and :func:`from_bytes` are used to pack and
26 unpack `Timestamp`.
28 This class is immutable: Do not override seconds and nanoseconds.
29 """
31 __slots__ = ["seconds", "nanoseconds"]
33 def __init__(self, seconds, nanoseconds=0):
34 """Initialize a Timestamp object.
36 :param int seconds:
37 Number of seconds since the UNIX epoch (00:00:00 UTC Jan 1 1970, minus leap seconds).
38 May be negative.
40 :param int nanoseconds:
41 Number of nanoseconds to add to `seconds` to get fractional time.
42 Maximum is 999_999_999. Default is 0.
44 Note: Negative times (before the UNIX epoch) are represented as neg. seconds + pos. ns.
45 """
46 if not isinstance(seconds, int):
47 raise TypeError("seconds must be an integer")
48 if not isinstance(nanoseconds, int):
49 raise TypeError("nanoseconds must be an integer")
50 if not (0 <= nanoseconds < 10**9):
51 raise ValueError("nanoseconds must be a non-negative integer less than 999999999.")
52 self.seconds = seconds
53 self.nanoseconds = nanoseconds
55 def __repr__(self):
56 """String representation of Timestamp."""
57 return f"Timestamp(seconds={self.seconds}, nanoseconds={self.nanoseconds})"
59 def __eq__(self, other):
60 """Check for equality with another Timestamp object"""
61 if type(other) is self.__class__:
62 return self.seconds == other.seconds and self.nanoseconds == other.nanoseconds
63 return False
65 def __ne__(self, other):
66 """not-equals method (see :func:`__eq__()`)"""
67 return not self.__eq__(other)
69 def __hash__(self):
70 return hash((self.seconds, self.nanoseconds))
72 @staticmethod
73 def from_bytes(b):
74 """Unpack bytes into a `Timestamp` object.
76 Used for pure-Python msgpack unpacking.
78 :param b: Payload from msgpack ext message with code -1
79 :type b: bytes
81 :returns: Timestamp object unpacked from msgpack ext payload
82 :rtype: Timestamp
83 """
84 if len(b) == 4:
85 seconds = struct.unpack("!L", b)[0]
86 nanoseconds = 0
87 elif len(b) == 8:
88 data64 = struct.unpack("!Q", b)[0]
89 seconds = data64 & 0x00000003FFFFFFFF
90 nanoseconds = data64 >> 34
91 elif len(b) == 12:
92 nanoseconds, seconds = struct.unpack("!Iq", b)
93 else:
94 raise ValueError(
95 "Timestamp type can only be created from 32, 64, or 96-bit byte objects"
96 )
97 return Timestamp(seconds, nanoseconds)
99 def to_bytes(self):
100 """Pack this Timestamp object into bytes.
102 Used for pure-Python msgpack packing.
104 :returns data: Payload for EXT message with code -1 (timestamp type)
105 :rtype: bytes
106 """
107 if (self.seconds >> 34) == 0: # seconds is non-negative and fits in 34 bits
108 data64 = self.nanoseconds << 34 | self.seconds
109 if data64 & 0xFFFFFFFF00000000 == 0:
110 # nanoseconds is zero and seconds < 2**32, so timestamp 32
111 data = struct.pack("!L", data64)
112 else:
113 # timestamp 64
114 data = struct.pack("!Q", data64)
115 else:
116 # timestamp 96
117 data = struct.pack("!Iq", self.nanoseconds, self.seconds)
118 return data
120 @staticmethod
121 def from_unix(unix_sec):
122 """Create a Timestamp from posix timestamp in seconds.
124 :param unix_float: Posix timestamp in seconds.
125 :type unix_float: int or float
126 """
127 seconds = int(unix_sec // 1)
128 nanoseconds = int((unix_sec % 1) * 10**9)
129 return Timestamp(seconds, nanoseconds)
131 def to_unix(self):
132 """Get the timestamp as a floating-point value.
134 :returns: posix timestamp
135 :rtype: float
136 """
137 return self.seconds + self.nanoseconds / 1e9
139 @staticmethod
140 def from_unix_nano(unix_ns):
141 """Create a Timestamp from posix timestamp in nanoseconds.
143 :param int unix_ns: Posix timestamp in nanoseconds.
144 :rtype: Timestamp
145 """
146 return Timestamp(*divmod(unix_ns, 10**9))
148 def to_unix_nano(self):
149 """Get the timestamp as a unixtime in nanoseconds.
151 :returns: posix timestamp in nanoseconds
152 :rtype: int
153 """
154 return self.seconds * 10**9 + self.nanoseconds
156 def to_datetime(self):
157 """Get the timestamp as a UTC datetime.
159 :rtype: `datetime.datetime`
160 """
161 return _EPOCH + datetime.timedelta(
162 seconds=self.seconds, microseconds=self.nanoseconds // 1000
163 )
165 @staticmethod
166 def from_datetime(dt):
167 """Create a Timestamp from datetime with tzinfo.
169 :rtype: Timestamp
170 """
171 # Use integer timedelta arithmetic (like the Cython packer) rather than
172 # ``int(dt.timestamp() // 1)``. ``datetime.timestamp()`` returns a float
173 # that cannot hold microsecond precision for datetimes far from the epoch,
174 # so it may round the whole-second part up while the exact ``microsecond``
175 # is still used for the nanoseconds -- producing a Timestamp one second in
176 # the future (and OverflowError near datetime.max).
177 if dt.tzinfo is None:
178 # Match datetime.timestamp(): a naive datetime is treated as local time.
179 dt = dt.astimezone()
180 delta = dt - _EPOCH
181 return Timestamp(
182 seconds=delta.days * 86400 + delta.seconds,
183 nanoseconds=delta.microseconds * 1000,
184 )