|
6 | 6 | import tarantool.msgpack_ext.types.timezones as tt_timezones
|
7 | 7 | from tarantool.error import MsgpackError
|
8 | 8 |
|
| 9 | +from tarantool.msgpack_ext.types.interval import Interval, Adjust |
| 10 | + |
9 | 11 | # https://www.tarantool.io/en/doc/latest/dev_guide/internals/msgpack_extensions/#the-datetime-type
|
10 | 12 | #
|
11 | 13 | # The datetime MessagePack representation looks like this:
|
|
47 | 49 | NSEC_IN_SEC = 1000000000
|
48 | 50 | NSEC_IN_MKSEC = 1000
|
49 | 51 | SEC_IN_MIN = 60
|
| 52 | +MONTH_IN_YEAR = 12 |
50 | 53 |
|
51 | 54 | def get_bytes_as_int(data, cursor, size):
|
52 | 55 | part = data[cursor:cursor + size]
|
@@ -168,6 +171,83 @@ def __init__(self, data=None, *, timestamp=None, year=None, month=None,
|
168 | 171 | self._datetime = datetime
|
169 | 172 | self._tz = ''
|
170 | 173 |
|
| 174 | + def _interval_operation(self, other, sign=1): |
| 175 | + self_dt = self._datetime |
| 176 | + |
| 177 | + # https://github.com/tarantool/tarantool/wiki/Datetime-Internals#date-adjustions-and-leap-years |
| 178 | + months = other.year * MONTH_IN_YEAR + other.month |
| 179 | + |
| 180 | + res = self_dt + pandas.DateOffset(months = sign * months) |
| 181 | + |
| 182 | + # pandas.DateOffset works exactly like Adjust.NONE |
| 183 | + if other.adjust == Adjust.EXCESS: |
| 184 | + if self_dt.day > res.day: |
| 185 | + res = res + pandas.DateOffset(days = self_dt.day - res.day) |
| 186 | + elif other.adjust == Adjust.LAST: |
| 187 | + if self_dt.is_month_end: |
| 188 | + # day replaces days |
| 189 | + res = res.replace(day = res.days_in_month) |
| 190 | + |
| 191 | + res = res + pandas.Timedelta(weeks = sign * other.week, |
| 192 | + days = sign * other.day, |
| 193 | + hours = sign * other.hour, |
| 194 | + minutes = sign * other.minute, |
| 195 | + seconds = sign * other.sec, |
| 196 | + microseconds = sign * (other.nsec // NSEC_IN_MKSEC), |
| 197 | + nanoseconds = sign * (other.nsec % NSEC_IN_MKSEC)) |
| 198 | + |
| 199 | + if res.tzinfo is not None: |
| 200 | + tzoffset = compute_offset(res) |
| 201 | + else: |
| 202 | + tzoffset = 0 |
| 203 | + return Datetime(year=res.year, month=res.month, day=res.day, |
| 204 | + hour=res.hour, minute=res.minute, sec=res.second, |
| 205 | + nsec=res.nanosecond + res.microsecond * NSEC_IN_MKSEC, |
| 206 | + tzoffset=tzoffset, tz=self.tz) |
| 207 | + |
| 208 | + def __add__(self, other): |
| 209 | + if not isinstance(other, Interval): |
| 210 | + raise TypeError(f"unsupported operand type(s) for +: '{type(self)}' and '{type(other)}'") |
| 211 | + |
| 212 | + return self._interval_operation(other, sign=1) |
| 213 | + |
| 214 | + def __sub__(self, other): |
| 215 | + if isinstance(other, Datetime): |
| 216 | + self_dt = self._datetime |
| 217 | + other_dt = other._datetime |
| 218 | + |
| 219 | + # Tarantool datetime subtraction ignores timezone info, but it is a bug: |
| 220 | + # |
| 221 | + # Tarantool 2.10.1-0-g482d91c66 |
| 222 | + # |
| 223 | + # tarantool> datetime.new{tz='MSK'} - datetime.new{tz='UTC'} |
| 224 | + # --- |
| 225 | + # - +0 seconds |
| 226 | + # ... |
| 227 | + # |
| 228 | + # Refer to https://github.com/tarantool/tarantool/issues/7698 |
| 229 | + # for possible updates. |
| 230 | + |
| 231 | + if self_dt.tzinfo != other_dt.tzinfo: |
| 232 | + other_dt = other_dt.tz_convert(self_dt.tzinfo) |
| 233 | + |
| 234 | + self_nsec = self_dt.microsecond * NSEC_IN_MKSEC + self_dt.nanosecond |
| 235 | + other_nsec = other_dt.microsecond * NSEC_IN_MKSEC + other_dt.nanosecond |
| 236 | + |
| 237 | + return Interval( |
| 238 | + year = self_dt.year - other_dt.year, |
| 239 | + month = self_dt.month - other_dt.month, |
| 240 | + day = self_dt.day - other_dt.day, |
| 241 | + hour = self_dt.hour - other_dt.hour, |
| 242 | + minute = self_dt.minute - other_dt.minute, |
| 243 | + sec = self_dt.second - other_dt.second, |
| 244 | + nsec = self_nsec - other_nsec, |
| 245 | + ) |
| 246 | + elif isinstance(other, Interval): |
| 247 | + return self._interval_operation(other, sign=-1) |
| 248 | + else: |
| 249 | + raise TypeError(f"unsupported operand type(s) for -: '{type(self)}' and '{type(other)}'") |
| 250 | + |
171 | 251 | def __eq__(self, other):
|
172 | 252 | if isinstance(other, Datetime):
|
173 | 253 | return self._datetime == other._datetime
|
|
0 commit comments