From: Daniele Varrazzo Date: Tue, 27 Oct 2020 13:43:37 +0000 (+0100) Subject: Fixed timezone parsing on Python 3.6 X-Git-Tag: 3.0.dev0~428 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=c458b53d7f873d85cbc8999a93e26b8845eaf104;p=thirdparty%2Fpsycopg.git Fixed timezone parsing on Python 3.6 --- diff --git a/psycopg3/psycopg3/types/date.py b/psycopg3/psycopg3/types/date.py index 9f888e6d8..9d4f16f24 100644 --- a/psycopg3/psycopg3/types/date.py +++ b/psycopg3/psycopg3/types/date.py @@ -5,6 +5,7 @@ Adapters for date/time types. # Copyright (C) 2020 The Psycopg Team import re +import sys import codecs from datetime import date, datetime, time, timedelta from typing import cast @@ -186,9 +187,15 @@ class TimeTzLoader(TimeLoader): _format = "%H:%M:%S.%f%z" _format_no_micro = _format.replace(".%f", "") + def __init__(self, oid: int, context: AdaptContext): + if sys.version_info < (3, 7): + self.load = self._load_py36 + + super().__init__(oid, context) + def load(self, data: bytes) -> time: # Hack to convert +HH in +HHMM - if data[-3:-2] in (b"-", b"+"): + if data[-3] in (43, 45): data += b"00" fmt = self._format if b"." in data else self._format_no_micro @@ -199,6 +206,16 @@ class TimeTzLoader(TimeLoader): return dt.time().replace(tzinfo=dt.tzinfo) + def _load_py36(self, data: bytes) -> time: + # Drop seconds from timezone for Python 3.6 + # Also, Python 3.6 doesn't support HHMM, only HH:MM + if data[-6] in (43, 45): # +-HH:MM -> +-HHMM + data = data[:-3] + data[-2:] + elif data[-9] in (43, 45): # +-HH:MM:SS -> +-HHMM + data = data[:-6] + data[-5:-3] + + return TimeTzLoader.load(self, data) + @Loader.text(builtins["timestamp"].oid) class TimestampLoader(DateLoader): @@ -254,6 +271,12 @@ class TimestampLoader(DateLoader): @Loader.text(builtins["timestamptz"].oid) class TimestamptzLoader(TimestampLoader): + def __init__(self, oid: int, context: AdaptContext): + if sys.version_info < (3, 7): + self.load = self._load_py36 + + super().__init__(oid, context) + def _format_from_context(self) -> str: ds = self._get_datestyle() if ds.startswith(b"I"): # ISO @@ -282,8 +305,21 @@ class TimestamptzLoader(TimestampLoader): def load(self, data: bytes) -> datetime: # Hack to convert +HH in +HHMM - if data[-3:-2] in (b"-", b"+"): + if data[-3] in (43, 45): + data += b"00" + + return super().load(data) + + def _load_py36(self, data: bytes) -> datetime: + # Drop seconds from timezone for Python 3.6 + # Also, Python 3.6 doesn't support HHMM, only HH:MM + tzsep = (43, 45) # + and - bytes + if data[-3] in tzsep: # +HH, -HH data += b"00" + elif data[-6] in tzsep: + data = data[:-3] + data[-2:] + elif data[-9] in tzsep: + data = data[:-6] + data[-5:-3] return super().load(data) diff --git a/tests/types/test_date.py b/tests/types/test_date.py index 8810de925..036e102b8 100644 --- a/tests/types/test_date.py +++ b/tests/types/test_date.py @@ -1,4 +1,6 @@ +import sys import datetime as dt + import pytest from psycopg3 import DataError @@ -194,6 +196,12 @@ def test_load_datetime_overflow(conn, val, datestyle_out): ], ) def test_dump_datetimetz(conn, val, expr): + # adjust for Python 3.6 missing seconds in tzinfo + if val.count(":") > 1: + expr = expr.rsplit(":", 1)[0] + val, rest = val.rsplit(":", 1) + val += rest[3:] # skip tz seconds, but include micros + cur = conn.cursor() cur.execute("set timezone to '-02:00'") cur.execute(f"select '{expr}'::timestamptz = %s", (as_dt(val),)) @@ -523,9 +531,11 @@ def as_tzinfo(s): else: mul = 1 - tzoff = mul * dt.timedelta( - **dict(zip(("hours", "minutes", "seconds"), map(int, s.split(":")))) - ) + if sys.version_info < (3, 7): + fields = ("hours", "minutes") + else: + fields = ("hours", "minutes", "seconds") + tzoff = mul * dt.timedelta(**dict(zip(fields, map(int, s.split(":"))))) return dt.timezone(tzoff)