diff options
author | Wolfgang Müller | 2021-05-02 12:38:56 +0200 |
---|---|---|
committer | Wolfgang Müller | 2021-05-02 12:38:56 +0200 |
commit | f95a23e0e5c23b7ec13e63501b6ab6df540cb695 (patch) | |
tree | 107c181ce88abf258a4fa1cadacdb103d12f63be | |
parent | 0b6ce2c57d53d947b15f0d429d2216df61ce3783 (diff) | |
download | quarg-f95a23e0e5c23b7ec13e63501b6ab6df540cb695.tar.gz |
Handle timezones correctly
Quassel uses UTC message timestamps in its database, but does not save
any timezone information along with them. Up until now, we were reading
those timestamps from the database naively - resulting in datetime
objects that could not be identified as UTC.
The same happened with timestamps we got from dateutil.isoparse. If the
user did not specify an offset explicitly, the timestamp would be parsed
and passed to the program "as is", effectively being interpreted as UTC
because they were compared to database timestamps.
This commit will ensure that the correct timezone is saved for every
datetime object we encounter. Timestamps from the database are marked as
UTC. If the user does not explicitly specify an offset, the timestamp is
assumed to be in local time.
Furthermore, when printing out message timestamps, make sure to convert
them to the user's local timezone first.
-rw-r--r-- | quarg/database/tables.py | 20 | ||||
-rw-r--r-- | quarg/quassel/formatter.py | 4 | ||||
-rw-r--r-- | quarg/utils.py | 4 |
3 files changed, 25 insertions, 3 deletions
diff --git a/quarg/database/tables.py b/quarg/database/tables.py index 10e6057..743ffa2 100644 --- a/quarg/database/tables.py +++ b/quarg/database/tables.py @@ -1,10 +1,26 @@ +import datetime + from sqlalchemy.schema import Column, ForeignKey -from sqlalchemy.types import BigInteger, Boolean, DateTime, Integer, Text +from sqlalchemy.types import BigInteger, Boolean, DateTime, Integer, Text, TypeDecorator from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import relationship # pylint: disable=too-few-public-methods +# Timestamps are saved in the database in UTC without timezone info, so attach +# a UTC timezone to the datetime object +class DateTimeUTC(TypeDecorator): + # pylint complains that process_{bind,literal}_param and python_type are + # abstract but not overriden. This seems to not be necessary with + # SQLAlchemy, so squash those warnings + # pylint: disable=abstract-method + impl = DateTime + + def process_result_value(self, value, dialect): + if value is not None: + value = value.replace(tzinfo=datetime.timezone.utc) + return value + Base = declarative_base() # Note: We have commented out unused columns to keep SQLAlchemy from selecting @@ -13,7 +29,7 @@ Base = declarative_base() class Backlog(Base): __tablename__ = 'backlog' messageid = Column(BigInteger, primary_key=True) - time = Column(DateTime) + time = Column(DateTimeUTC) bufferid = Column(Integer, ForeignKey('buffer.bufferid')) type = Column(Integer) flags = Column(Integer) diff --git a/quarg/quassel/formatter.py b/quarg/quassel/formatter.py index b6235e8..51eec00 100644 --- a/quarg/quassel/formatter.py +++ b/quarg/quassel/formatter.py @@ -33,7 +33,9 @@ class Message(NamedTuple): def format_from(backlog_row): message = Message.from_backlog(backlog_row) formatter = FORMATTERS[message.type] - timestamp = message.time.isoformat(sep=' ', timespec='seconds') + + # make sure to convert timestamps to local time before printing + timestamp = message.time.astimezone().isoformat(sep=' ', timespec='seconds') return f'{timestamp}\t{message.buffer}\t{formatter(message)}' diff --git a/quarg/utils.py b/quarg/utils.py index 7fa270e..903c057 100644 --- a/quarg/utils.py +++ b/quarg/utils.py @@ -12,4 +12,8 @@ def parse_isodate(date): except OverflowError as err: errx(f'isoparse: date overflows: \'{date}\'') + # If no offset is given, assume local time + if parsed.tzinfo is None: + parsed = parsed.astimezone() + return parsed |