commit f8717b4bffd46c8c0ee7147f5b50927737b2b5e1 Author: Steven Engler opara@cs.georgetown.edu Date: Mon Oct 25 14:46:39 2021 -0400
Add timezone to parsed datetimes --- stem/control.py | 3 ++- stem/descriptor/__init__.py | 3 ++- stem/descriptor/extrainfo_descriptor.py | 2 +- stem/descriptor/networkstatus.py | 2 +- stem/descriptor/router_status_entry.py | 3 ++- stem/descriptor/tordnsel.py | 6 +++--- stem/response/events.py | 8 +++++--- stem/util/str_tools.py | 15 +++++++++++---- 8 files changed, 27 insertions(+), 15 deletions(-)
diff --git a/stem/control.py b/stem/control.py index e162704b..40ca6bed 100644 --- a/stem/control.py +++ b/stem/control.py @@ -247,6 +247,7 @@ If you're fine with allowing your script to raise exceptions then this can be mo import asyncio import calendar import collections +import datetime import functools import inspect import io @@ -1537,7 +1538,7 @@ class Controller(BaseController): self.get_info('accounting/bytes-left'), )
- interval_end = stem.util.str_tools._parse_timestamp(interval_end) + interval_end = stem.util.str_tools._parse_timestamp(interval_end, datetime.timezone.utc) used_read, used_written = [int(val) for val in used.split(' ', 1)] left_read, left_written = [int(val) for val in left.split(' ', 1)]
diff --git a/stem/descriptor/__init__.py b/stem/descriptor/__init__.py index c015d246..d928ff21 100644 --- a/stem/descriptor/__init__.py +++ b/stem/descriptor/__init__.py @@ -108,6 +108,7 @@ import base64 import codecs import collections import copy +import datetime import hashlib import io import os @@ -683,7 +684,7 @@ def _parse_timestamp_line(keyword: str, attribute: str) -> Callable[['stem.descr value = _value(keyword, entries)
try: - setattr(descriptor, attribute, stem.util.str_tools._parse_timestamp(value)) + setattr(descriptor, attribute, stem.util.str_tools._parse_timestamp(value, datetime.timezone.utc)) except ValueError: raise ValueError("Timestamp on %s line wasn't parsable: %s %s" % (keyword, keyword, value))
diff --git a/stem/descriptor/extrainfo_descriptor.py b/stem/descriptor/extrainfo_descriptor.py index 8d9cbf30..ad08d361 100644 --- a/stem/descriptor/extrainfo_descriptor.py +++ b/stem/descriptor/extrainfo_descriptor.py @@ -239,7 +239,7 @@ def _parse_timestamp_and_interval(keyword: str, content: str) -> Tuple[datetime. raise ValueError("%s line's interval wasn't a number: %s" % (keyword, line))
try: - timestamp = stem.util.str_tools._parse_timestamp(timestamp_str) + timestamp = stem.util.str_tools._parse_timestamp(timestamp_str, datetime.timezone.utc) return timestamp, int(interval), remainder except ValueError: raise ValueError("%s line's timestamp wasn't parsable: %s" % (keyword, line)) diff --git a/stem/descriptor/networkstatus.py b/stem/descriptor/networkstatus.py index e8cf90eb..a69f501f 100644 --- a/stem/descriptor/networkstatus.py +++ b/stem/descriptor/networkstatus.py @@ -1962,7 +1962,7 @@ class BridgeNetworkStatusDocument(NetworkStatusDocument): published_line = published_line.split(' ', 1)[1].strip()
try: - self.published = stem.util.str_tools._parse_timestamp(published_line) + self.published = stem.util.str_tools._parse_timestamp(published_line, datetime.timezone.utc) except ValueError: if validate: raise ValueError("Bridge network status document's 'published' time wasn't parsable: %s" % published_line) diff --git a/stem/descriptor/router_status_entry.py b/stem/descriptor/router_status_entry.py index aa94a703..eed34fac 100644 --- a/stem/descriptor/router_status_entry.py +++ b/stem/descriptor/router_status_entry.py @@ -22,6 +22,7 @@ sources... """
import binascii +import datetime import io
import stem.exit_policy @@ -161,7 +162,7 @@ def _parse_r_line(descriptor: 'stem.descriptor.Descriptor', entries: ENTRY_TYPE)
try: published = '%s %s' % (r_comp[3], r_comp[4]) - descriptor.published = stem.util.str_tools._parse_timestamp(published) + descriptor.published = stem.util.str_tools._parse_timestamp(published, datetime.timezone.utc) except ValueError: raise ValueError("Publication time time wasn't parsable: r %s" % value)
diff --git a/stem/descriptor/tordnsel.py b/stem/descriptor/tordnsel.py index 32cf1863..ffb67578 100644 --- a/stem/descriptor/tordnsel.py +++ b/stem/descriptor/tordnsel.py @@ -98,13 +98,13 @@ class TorDNSEL(Descriptor): self.fingerprint = value elif keyword == 'Published': try: - self.published = stem.util.str_tools._parse_timestamp(value) + self.published = stem.util.str_tools._parse_timestamp(value, datetime.timezone.utc) except ValueError: if validate: raise ValueError("Published time wasn't parsable: %s" % value) elif keyword == 'LastStatus': try: - self.last_status = stem.util.str_tools._parse_timestamp(value) + self.last_status = stem.util.str_tools._parse_timestamp(value, datetime.timezone.utc) except ValueError: if validate: raise ValueError("LastStatus time wasn't parsable: %s" % value) @@ -119,7 +119,7 @@ class TorDNSEL(Descriptor): raise ValueError('Unexpected block content: %s' % block_content)
try: - date = stem.util.str_tools._parse_timestamp(date_str) + date = stem.util.str_tools._parse_timestamp(date_str, datetime.timezone.utc) self.exit_addresses.append((address, date)) except ValueError: if validate: diff --git a/stem/response/events.py b/stem/response/events.py index 2b9b6dfd..a55771cc 100644 --- a/stem/response/events.py +++ b/stem/response/events.py @@ -221,12 +221,14 @@ class AddrMapEvent(Event): self.expiry = None else: try: - self.expiry = stem.util.str_tools._parse_timestamp(self.expiry) + # control-spec: "Expiry is expressed as the local time (rather than + # UTC). This is a bug, left in for backward compatibility" + self.expiry = stem.util.str_tools._parse_timestamp(self.expiry, None) except ValueError: raise stem.ProtocolError('Unable to parse date in ADDRMAP event: %s' % self)
if self.utc_expiry is not None: - self.utc_expiry = stem.util.str_tools._parse_timestamp(self.utc_expiry) + self.utc_expiry = stem.util.str_tools._parse_timestamp(self.utc_expiry, datetime.timezone.utc)
if self.cached is not None: if self.cached == 'YES': @@ -518,7 +520,7 @@ class ClientsSeenEvent(Event):
def _parse(self) -> None: if self.start_time is not None: - self.start_time = stem.util.str_tools._parse_timestamp(self.start_time) + self.start_time = stem.util.str_tools._parse_timestamp(self.start_time, datetime.timezone.utc)
if self.locales is not None: locale_to_count = {} diff --git a/stem/util/str_tools.py b/stem/util/str_tools.py index a9a34364..0012e96a 100644 --- a/stem/util/str_tools.py +++ b/stem/util/str_tools.py @@ -26,7 +26,7 @@ import sys import stem.util import stem.util.enum
-from typing import List, Sequence, Tuple, Union, overload +from typing import List, Optional, Sequence, Tuple, Union, overload
# label conversion tuples of the form... # (bits / bytes / seconds, short label, long label) @@ -481,7 +481,7 @@ def parse_short_time_label(label: str) -> int: raise ValueError('Non-numeric value in time entry: %s' % label)
-def _parse_timestamp(entry: str) -> datetime.datetime: +def _parse_timestamp(entry: str, tz: Optional[datetime.timezone]) -> datetime.datetime: """ Parses the date and time that in format like like...
@@ -490,6 +490,8 @@ def _parse_timestamp(entry: str) -> datetime.datetime: 2012-11-08 16:48:41
:param entry: timestamp to be parsed + :param tz: timezone of the entry (if **None**, the returned **datetime** will + be "naive")
:returns: **datetime** for the time represented by the timestamp
@@ -504,7 +506,12 @@ def _parse_timestamp(entry: str) -> datetime.datetime: except AttributeError: raise ValueError('Expected timestamp in format YYYY-MM-DD HH:MM:ss but got ' + entry)
- return datetime.datetime(time[0], time[1], time[2], time[3], time[4], time[5]) + dt = datetime.datetime(time[0], time[1], time[2], time[3], time[4], time[5]) + + if tz != None: + dt.replace(tzinfo=tz) + + return dt
def _parse_iso_timestamp(entry: str) -> 'datetime.datetime': @@ -541,7 +548,7 @@ def _parse_iso_timestamp(entry: str) -> 'datetime.datetime': else: raise ValueError("timestamp didn't contain delimeter 'T' between date and time")
- timestamp = _parse_timestamp(timestamp_str) + timestamp = _parse_timestamp(timestamp_str, datetime.timezone.utc) return timestamp + datetime.timedelta(microseconds = int(microseconds))