From 2b277dd558fc9a0e35b88374eaecaca635545c2c Mon Sep 17 00:00:00 2001 From: binares Date: Thu, 1 Aug 2019 19:47:38 +0300 Subject: [PATCH] Fix (de)serialization of negative timestamps (#1241) --- telethon/extensions/binaryreader.py | 11 ++++++----- telethon/tl/tlobject.py | 28 +++++++++++++++++++++------- 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/telethon/extensions/binaryreader.py b/telethon/extensions/binaryreader.py index 5382caaf..80b76f94 100644 --- a/telethon/extensions/binaryreader.py +++ b/telethon/extensions/binaryreader.py @@ -2,14 +2,18 @@ This module contains the BinaryReader utility class. """ import os -from datetime import datetime, timezone +from datetime import datetime, timezone, timedelta from io import BufferedReader, BytesIO from struct import unpack +import time from ..errors import TypeNotFoundError from ..tl.alltlobjects import tlobjects from ..tl.core import core_objects +_EPOCH_NAIVE = datetime(*time.gmtime(0)[:6]) +_EPOCH = _EPOCH_NAIVE.replace(tzinfo=timezone.utc) + class BinaryReader: """ @@ -120,10 +124,7 @@ class BinaryReader: into a Python datetime object. """ value = self.read_int() - if value == 0: - return None - else: - return datetime.fromtimestamp(value, tz=timezone.utc) + return _EPOCH + timedelta(seconds=value) def tgread_object(self): """Reads a Telegram object.""" diff --git a/telethon/tl/tlobject.py b/telethon/tl/tlobject.py index 49a11d3b..451fad17 100644 --- a/telethon/tl/tlobject.py +++ b/telethon/tl/tlobject.py @@ -1,7 +1,21 @@ import base64 import json import struct -from datetime import datetime, date, timedelta +from datetime import datetime, date, timedelta, timezone +import time + +_EPOCH_NAIVE = datetime(*time.gmtime(0)[:6]) +_EPOCH_NAIVE_LOCAL = datetime(*time.localtime(0)[:6]) +_EPOCH = _EPOCH_NAIVE.replace(tzinfo=timezone.utc) + + +def _datetime_to_timestamp(dt): + # If no timezone is specified, it is assumed to be in utc zone + if dt.tzinfo is None: + dt = dt.replace(tzinfo=timezone.utc) + # We use .total_seconds() method instead of simply dt.timestamp(), + # because on Windows the latter raises OSError on datetimes ~< datetime(1970,1,1) + return int((dt - _EPOCH).total_seconds()) def _json_default(value): @@ -121,21 +135,21 @@ class TLObject: @staticmethod def serialize_datetime(dt): - if not dt: + if not dt and not isinstance(dt, timedelta): return b'\0\0\0\0' if isinstance(dt, datetime): - dt = int(dt.timestamp()) + dt = _datetime_to_timestamp(dt) elif isinstance(dt, date): - dt = int(datetime(dt.year, dt.month, dt.day).timestamp()) + dt = _datetime_to_timestamp(datetime(dt.year, dt.month, dt.day)) elif isinstance(dt, float): dt = int(dt) elif isinstance(dt, timedelta): - # Timezones are tricky. datetime.now() + ... timestamp() works - dt = int((datetime.now() + dt).timestamp()) + # Timezones are tricky. datetime.utcnow() + ... timestamp() works + dt = _datetime_to_timestamp(datetime.utcnow() + dt) if isinstance(dt, int): - return struct.pack('