2008-01-16 04:43:50 +03:00
|
|
|
#!/usr/bin/env python
|
2010-02-13 01:34:53 +03:00
|
|
|
|
2011-01-07 04:44:19 +03:00
|
|
|
# test_dates.py - unit test for dates handling
|
|
|
|
#
|
2019-02-17 04:34:52 +03:00
|
|
|
# Copyright (C) 2008-2019 James Henstridge <james@jamesh.id.au>
|
2020-01-18 00:10:44 +03:00
|
|
|
# Copyright (C) 2020 The Psycopg Team
|
2011-01-07 04:44:19 +03:00
|
|
|
#
|
|
|
|
# psycopg2 is free software: you can redistribute it and/or modify it
|
|
|
|
# under the terms of the GNU Lesser General Public License as published
|
|
|
|
# by the Free Software Foundation, either version 3 of the License, or
|
|
|
|
# (at your option) any later version.
|
|
|
|
#
|
|
|
|
# In addition, as a special exception, the copyright holders give
|
|
|
|
# permission to link this program with the OpenSSL library (or with
|
|
|
|
# modified versions of OpenSSL that use the same license as OpenSSL),
|
|
|
|
# and distribute linked combinations including the two.
|
|
|
|
#
|
|
|
|
# You must obey the GNU Lesser General Public License in all respects for
|
|
|
|
# all of the code used other than OpenSSL.
|
|
|
|
#
|
|
|
|
# psycopg2 is distributed in the hope that it will be useful, but WITHOUT
|
|
|
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
|
|
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
|
|
|
# License for more details.
|
|
|
|
|
2008-01-16 04:43:50 +03:00
|
|
|
import math
|
2019-03-16 21:56:56 +03:00
|
|
|
import pickle
|
2019-03-16 18:30:15 +03:00
|
|
|
from datetime import date, datetime, time, timedelta
|
|
|
|
|
2008-01-16 04:43:50 +03:00
|
|
|
import psycopg2
|
2012-01-17 19:51:32 +04:00
|
|
|
from psycopg2.tz import FixedOffsetTimezone, ZERO
|
2017-12-02 04:59:53 +03:00
|
|
|
import unittest
|
2020-07-22 04:05:05 +03:00
|
|
|
from .testutils import ConnectingTestCase, skip_before_postgres, skip_if_crdb
|
2008-01-16 04:43:50 +03:00
|
|
|
|
2019-03-16 21:56:56 +03:00
|
|
|
try:
|
|
|
|
from mx.DateTime import Date, Time, DateTime, DateTimeDeltaFrom
|
|
|
|
except ImportError:
|
|
|
|
# Tests will be skipped
|
|
|
|
pass
|
|
|
|
|
2016-10-11 02:10:53 +03:00
|
|
|
|
2017-02-24 05:48:41 +03:00
|
|
|
def total_seconds(d):
|
|
|
|
"""Return total number of seconds of a timedelta as a float."""
|
|
|
|
return d.days * 24 * 60 * 60 + d.seconds + d.microseconds / 1000000.0
|
|
|
|
|
|
|
|
|
2008-01-16 04:43:50 +03:00
|
|
|
class CommonDatetimeTestsMixin:
|
|
|
|
|
|
|
|
def execute(self, *args):
|
2009-02-17 09:03:33 +03:00
|
|
|
self.curs.execute(*args)
|
|
|
|
return self.curs.fetchone()[0]
|
2008-01-16 04:43:50 +03:00
|
|
|
|
|
|
|
def test_parse_date(self):
|
2009-02-17 09:03:33 +03:00
|
|
|
value = self.DATE('2007-01-01', self.curs)
|
2011-11-01 11:42:08 +04:00
|
|
|
self.assert_(value is not None)
|
2008-01-16 04:43:50 +03:00
|
|
|
self.assertEqual(value.year, 2007)
|
|
|
|
self.assertEqual(value.month, 1)
|
|
|
|
self.assertEqual(value.day, 1)
|
|
|
|
|
|
|
|
def test_parse_null_date(self):
|
2009-02-17 09:03:33 +03:00
|
|
|
value = self.DATE(None, self.curs)
|
2008-01-16 04:43:50 +03:00
|
|
|
self.assertEqual(value, None)
|
|
|
|
|
2008-03-17 07:06:44 +03:00
|
|
|
def test_parse_incomplete_date(self):
|
2009-02-17 09:03:33 +03:00
|
|
|
self.assertRaises(psycopg2.DataError, self.DATE, '2007', self.curs)
|
|
|
|
self.assertRaises(psycopg2.DataError, self.DATE, '2007-01', self.curs)
|
2008-01-16 04:43:50 +03:00
|
|
|
|
|
|
|
def test_parse_time(self):
|
2009-02-17 09:03:33 +03:00
|
|
|
value = self.TIME('13:30:29', self.curs)
|
2011-11-01 11:42:08 +04:00
|
|
|
self.assert_(value is not None)
|
2008-01-16 04:43:50 +03:00
|
|
|
self.assertEqual(value.hour, 13)
|
|
|
|
self.assertEqual(value.minute, 30)
|
|
|
|
self.assertEqual(value.second, 29)
|
|
|
|
|
|
|
|
def test_parse_null_time(self):
|
2009-02-17 09:03:33 +03:00
|
|
|
value = self.TIME(None, self.curs)
|
2008-01-16 04:43:50 +03:00
|
|
|
self.assertEqual(value, None)
|
|
|
|
|
2008-03-17 07:06:44 +03:00
|
|
|
def test_parse_incomplete_time(self):
|
2009-02-17 09:03:33 +03:00
|
|
|
self.assertRaises(psycopg2.DataError, self.TIME, '13', self.curs)
|
|
|
|
self.assertRaises(psycopg2.DataError, self.TIME, '13:30', self.curs)
|
2008-03-17 07:06:44 +03:00
|
|
|
|
2008-01-16 04:43:50 +03:00
|
|
|
def test_parse_datetime(self):
|
2009-02-17 09:03:33 +03:00
|
|
|
value = self.DATETIME('2007-01-01 13:30:29', self.curs)
|
2011-11-01 11:42:08 +04:00
|
|
|
self.assert_(value is not None)
|
2008-01-16 04:43:50 +03:00
|
|
|
self.assertEqual(value.year, 2007)
|
|
|
|
self.assertEqual(value.month, 1)
|
|
|
|
self.assertEqual(value.day, 1)
|
|
|
|
self.assertEqual(value.hour, 13)
|
|
|
|
self.assertEqual(value.minute, 30)
|
|
|
|
self.assertEqual(value.second, 29)
|
|
|
|
|
|
|
|
def test_parse_null_datetime(self):
|
2009-02-17 09:03:33 +03:00
|
|
|
value = self.DATETIME(None, self.curs)
|
2008-01-16 04:43:50 +03:00
|
|
|
self.assertEqual(value, None)
|
|
|
|
|
2012-01-17 19:51:26 +04:00
|
|
|
def test_parse_incomplete_datetime(self):
|
2008-03-17 07:06:44 +03:00
|
|
|
self.assertRaises(psycopg2.DataError,
|
2009-02-17 09:03:33 +03:00
|
|
|
self.DATETIME, '2007', self.curs)
|
2008-03-17 07:06:44 +03:00
|
|
|
self.assertRaises(psycopg2.DataError,
|
2009-02-17 09:03:33 +03:00
|
|
|
self.DATETIME, '2007-01', self.curs)
|
2008-03-17 07:06:44 +03:00
|
|
|
self.assertRaises(psycopg2.DataError,
|
2009-02-17 09:03:33 +03:00
|
|
|
self.DATETIME, '2007-01-01 13', self.curs)
|
2008-03-17 07:06:44 +03:00
|
|
|
self.assertRaises(psycopg2.DataError,
|
2009-02-17 09:03:33 +03:00
|
|
|
self.DATETIME, '2007-01-01 13:30', self.curs)
|
2008-03-17 07:06:44 +03:00
|
|
|
|
2008-01-16 04:43:50 +03:00
|
|
|
def test_parse_null_interval(self):
|
2009-02-17 09:03:33 +03:00
|
|
|
value = self.INTERVAL(None, self.curs)
|
2008-01-16 04:43:50 +03:00
|
|
|
self.assertEqual(value, None)
|
|
|
|
|
|
|
|
|
2013-04-07 03:23:30 +04:00
|
|
|
class DatetimeTests(ConnectingTestCase, CommonDatetimeTestsMixin):
|
2008-01-16 04:43:50 +03:00
|
|
|
"""Tests for the datetime based date handling in psycopg2."""
|
|
|
|
|
|
|
|
def setUp(self):
|
2013-04-07 03:23:30 +04:00
|
|
|
ConnectingTestCase.setUp(self)
|
2009-02-17 09:03:33 +03:00
|
|
|
self.curs = self.conn.cursor()
|
2011-10-19 05:25:53 +04:00
|
|
|
self.DATE = psycopg2.extensions.PYDATE
|
|
|
|
self.TIME = psycopg2.extensions.PYTIME
|
|
|
|
self.DATETIME = psycopg2.extensions.PYDATETIME
|
|
|
|
self.INTERVAL = psycopg2.extensions.PYINTERVAL
|
2008-01-16 04:43:50 +03:00
|
|
|
|
|
|
|
def test_parse_bc_date(self):
|
|
|
|
# datetime does not support BC dates
|
2009-02-17 09:03:33 +03:00
|
|
|
self.assertRaises(ValueError, self.DATE, '00042-01-01 BC', self.curs)
|
2008-01-16 04:43:50 +03:00
|
|
|
|
|
|
|
def test_parse_bc_datetime(self):
|
|
|
|
# datetime does not support BC dates
|
|
|
|
self.assertRaises(ValueError, self.DATETIME,
|
2009-02-17 09:03:33 +03:00
|
|
|
'00042-01-01 13:30:29 BC', self.curs)
|
2008-01-16 04:43:50 +03:00
|
|
|
|
|
|
|
def test_parse_time_microseconds(self):
|
2009-02-17 09:03:33 +03:00
|
|
|
value = self.TIME('13:30:29.123456', self.curs)
|
2008-01-16 04:43:50 +03:00
|
|
|
self.assertEqual(value.second, 29)
|
|
|
|
self.assertEqual(value.microsecond, 123456)
|
|
|
|
|
|
|
|
def test_parse_datetime_microseconds(self):
|
2009-02-17 09:03:33 +03:00
|
|
|
value = self.DATETIME('2007-01-01 13:30:29.123456', self.curs)
|
2008-01-16 04:43:50 +03:00
|
|
|
self.assertEqual(value.second, 29)
|
|
|
|
self.assertEqual(value.microsecond, 123456)
|
|
|
|
|
2009-02-17 09:03:33 +03:00
|
|
|
def check_time_tz(self, str_offset, offset):
|
|
|
|
base = time(13, 30, 29)
|
|
|
|
base_str = '13:30:29'
|
|
|
|
|
|
|
|
value = self.TIME(base_str + str_offset, self.curs)
|
|
|
|
|
|
|
|
# Value has time zone info and correct UTC offset.
|
|
|
|
self.assertNotEqual(value.tzinfo, None),
|
|
|
|
self.assertEqual(value.utcoffset(), timedelta(seconds=offset))
|
|
|
|
|
|
|
|
# Time portion is correct.
|
|
|
|
self.assertEqual(value.replace(tzinfo=None), base)
|
|
|
|
|
|
|
|
def test_parse_time_timezone(self):
|
|
|
|
self.check_time_tz("+01", 3600)
|
|
|
|
self.check_time_tz("-01", -3600)
|
|
|
|
self.check_time_tz("+01:15", 4500)
|
|
|
|
self.check_time_tz("-01:15", -4500)
|
|
|
|
# The Python datetime module does not support time zone
|
2010-05-20 05:07:50 +04:00
|
|
|
# offsets that are not a whole number of minutes.
|
|
|
|
# We round the offset to the nearest minute.
|
2016-10-11 02:10:53 +03:00
|
|
|
self.check_time_tz("+01:15:00", 60 * (60 + 15))
|
|
|
|
self.check_time_tz("+01:15:29", 60 * (60 + 15))
|
|
|
|
self.check_time_tz("+01:15:30", 60 * (60 + 16))
|
|
|
|
self.check_time_tz("+01:15:59", 60 * (60 + 16))
|
2010-05-20 05:07:50 +04:00
|
|
|
self.check_time_tz("-01:15:00", -60 * (60 + 15))
|
|
|
|
self.check_time_tz("-01:15:29", -60 * (60 + 15))
|
|
|
|
self.check_time_tz("-01:15:30", -60 * (60 + 16))
|
|
|
|
self.check_time_tz("-01:15:59", -60 * (60 + 16))
|
2009-02-17 09:03:33 +03:00
|
|
|
|
|
|
|
def check_datetime_tz(self, str_offset, offset):
|
2009-02-17 06:39:57 +03:00
|
|
|
base = datetime(2007, 1, 1, 13, 30, 29)
|
|
|
|
base_str = '2007-01-01 13:30:29'
|
|
|
|
|
2009-02-17 09:03:33 +03:00
|
|
|
value = self.DATETIME(base_str + str_offset, self.curs)
|
2009-02-17 06:39:57 +03:00
|
|
|
|
2009-02-17 09:03:33 +03:00
|
|
|
# Value has time zone info and correct UTC offset.
|
|
|
|
self.assertNotEqual(value.tzinfo, None),
|
|
|
|
self.assertEqual(value.utcoffset(), timedelta(seconds=offset))
|
2009-02-17 06:39:57 +03:00
|
|
|
|
|
|
|
# Datetime is correct.
|
|
|
|
self.assertEqual(value.replace(tzinfo=None), base)
|
|
|
|
|
2009-02-17 09:03:33 +03:00
|
|
|
# Conversion to UTC produces the expected offset.
|
2009-02-17 06:39:57 +03:00
|
|
|
UTC = FixedOffsetTimezone(0, "UTC")
|
|
|
|
value_utc = value.astimezone(UTC).replace(tzinfo=None)
|
|
|
|
self.assertEqual(base - value_utc, timedelta(seconds=offset))
|
|
|
|
|
2009-02-17 09:03:33 +03:00
|
|
|
def test_parse_datetime_timezone(self):
|
|
|
|
self.check_datetime_tz("+01", 3600)
|
|
|
|
self.check_datetime_tz("-01", -3600)
|
|
|
|
self.check_datetime_tz("+01:15", 4500)
|
|
|
|
self.check_datetime_tz("-01:15", -4500)
|
|
|
|
# The Python datetime module does not support time zone
|
2010-05-20 05:07:50 +04:00
|
|
|
# offsets that are not a whole number of minutes.
|
|
|
|
# We round the offset to the nearest minute.
|
2016-10-11 02:10:53 +03:00
|
|
|
self.check_datetime_tz("+01:15:00", 60 * (60 + 15))
|
|
|
|
self.check_datetime_tz("+01:15:29", 60 * (60 + 15))
|
|
|
|
self.check_datetime_tz("+01:15:30", 60 * (60 + 16))
|
|
|
|
self.check_datetime_tz("+01:15:59", 60 * (60 + 16))
|
2010-05-20 05:07:50 +04:00
|
|
|
self.check_datetime_tz("-01:15:00", -60 * (60 + 15))
|
|
|
|
self.check_datetime_tz("-01:15:29", -60 * (60 + 15))
|
|
|
|
self.check_datetime_tz("-01:15:30", -60 * (60 + 16))
|
|
|
|
self.check_datetime_tz("-01:15:59", -60 * (60 + 16))
|
2009-02-17 09:03:33 +03:00
|
|
|
|
|
|
|
def test_parse_time_no_timezone(self):
|
|
|
|
self.assertEqual(self.TIME("13:30:29", self.curs).tzinfo, None)
|
|
|
|
self.assertEqual(self.TIME("13:30:29.123456", self.curs).tzinfo, None)
|
|
|
|
|
|
|
|
def test_parse_datetime_no_timezone(self):
|
|
|
|
self.assertEqual(
|
|
|
|
self.DATETIME("2007-01-01 13:30:29", self.curs).tzinfo, None)
|
|
|
|
self.assertEqual(
|
|
|
|
self.DATETIME("2007-01-01 13:30:29.123456", self.curs).tzinfo, None)
|
2009-02-17 06:39:57 +03:00
|
|
|
|
2008-01-16 04:43:50 +03:00
|
|
|
def test_parse_interval(self):
|
2009-02-17 09:03:33 +03:00
|
|
|
value = self.INTERVAL('42 days 12:34:56.123456', self.curs)
|
2008-01-16 04:43:50 +03:00
|
|
|
self.assertNotEqual(value, None)
|
|
|
|
self.assertEqual(value.days, 42)
|
|
|
|
self.assertEqual(value.seconds, 45296)
|
|
|
|
self.assertEqual(value.microseconds, 123456)
|
|
|
|
|
|
|
|
def test_parse_negative_interval(self):
|
2009-02-17 09:03:33 +03:00
|
|
|
value = self.INTERVAL('-42 days -12:34:56.123456', self.curs)
|
2008-01-16 04:43:50 +03:00
|
|
|
self.assertNotEqual(value, None)
|
|
|
|
self.assertEqual(value.days, -43)
|
|
|
|
self.assertEqual(value.seconds, 41103)
|
|
|
|
self.assertEqual(value.microseconds, 876544)
|
|
|
|
|
2013-05-11 02:25:06 +04:00
|
|
|
def test_parse_infinity(self):
|
|
|
|
value = self.DATETIME('-infinity', self.curs)
|
|
|
|
self.assertEqual(str(value), '0001-01-01 00:00:00')
|
|
|
|
value = self.DATETIME('infinity', self.curs)
|
|
|
|
self.assertEqual(str(value), '9999-12-31 23:59:59.999999')
|
|
|
|
value = self.DATE('infinity', self.curs)
|
|
|
|
self.assertEqual(str(value), '9999-12-31')
|
|
|
|
|
2008-01-16 04:43:50 +03:00
|
|
|
def test_adapt_date(self):
|
|
|
|
value = self.execute('select (%s)::date::text',
|
|
|
|
[date(2007, 1, 1)])
|
|
|
|
self.assertEqual(value, '2007-01-01')
|
|
|
|
|
|
|
|
def test_adapt_time(self):
|
|
|
|
value = self.execute('select (%s)::time::text',
|
|
|
|
[time(13, 30, 29)])
|
|
|
|
self.assertEqual(value, '13:30:29')
|
|
|
|
|
2020-07-28 00:58:43 +03:00
|
|
|
@skip_if_crdb("cast adds tz")
|
2008-01-16 04:43:50 +03:00
|
|
|
def test_adapt_datetime(self):
|
|
|
|
value = self.execute('select (%s)::timestamp::text',
|
|
|
|
[datetime(2007, 1, 1, 13, 30, 29)])
|
|
|
|
self.assertEqual(value, '2007-01-01 13:30:29')
|
|
|
|
|
|
|
|
def test_adapt_timedelta(self):
|
|
|
|
value = self.execute('select extract(epoch from (%s)::interval)',
|
|
|
|
[timedelta(days=42, seconds=45296,
|
|
|
|
microseconds=123456)])
|
|
|
|
seconds = math.floor(value)
|
|
|
|
self.assertEqual(seconds, 3674096)
|
|
|
|
self.assertEqual(int(round((value - seconds) * 1000000)), 123456)
|
|
|
|
|
2013-05-11 02:25:06 +04:00
|
|
|
def test_adapt_negative_timedelta(self):
|
2008-01-16 04:43:50 +03:00
|
|
|
value = self.execute('select extract(epoch from (%s)::interval)',
|
|
|
|
[timedelta(days=-42, seconds=45296,
|
|
|
|
microseconds=123456)])
|
|
|
|
seconds = math.floor(value)
|
|
|
|
self.assertEqual(seconds, -3583504)
|
|
|
|
self.assertEqual(int(round((value - seconds) * 1000000)), 123456)
|
|
|
|
|
2010-05-04 04:52:15 +04:00
|
|
|
def _test_type_roundtrip(self, o1):
|
|
|
|
o2 = self.execute("select %s;", (o1,))
|
|
|
|
self.assertEqual(type(o1), type(o2))
|
2010-05-05 03:53:15 +04:00
|
|
|
return o2
|
2010-05-04 04:52:15 +04:00
|
|
|
|
|
|
|
def _test_type_roundtrip_array(self, o1):
|
|
|
|
o1 = [o1]
|
|
|
|
o2 = self.execute("select %s;", (o1,))
|
|
|
|
self.assertEqual(type(o1[0]), type(o2[0]))
|
|
|
|
|
|
|
|
def test_type_roundtrip_date(self):
|
2016-10-11 02:10:53 +03:00
|
|
|
self._test_type_roundtrip(date(2010, 5, 3))
|
2010-05-04 04:52:15 +04:00
|
|
|
|
|
|
|
def test_type_roundtrip_datetime(self):
|
2016-10-11 02:10:53 +03:00
|
|
|
dt = self._test_type_roundtrip(datetime(2010, 5, 3, 10, 20, 30))
|
2010-05-05 03:53:15 +04:00
|
|
|
self.assertEqual(None, dt.tzinfo)
|
|
|
|
|
|
|
|
def test_type_roundtrip_datetimetz(self):
|
2019-03-16 21:56:56 +03:00
|
|
|
tz = FixedOffsetTimezone(8 * 60)
|
2016-10-11 02:10:53 +03:00
|
|
|
dt1 = datetime(2010, 5, 3, 10, 20, 30, tzinfo=tz)
|
2010-05-05 03:53:15 +04:00
|
|
|
dt2 = self._test_type_roundtrip(dt1)
|
|
|
|
self.assertNotEqual(None, dt2.tzinfo)
|
|
|
|
self.assertEqual(dt1, dt2)
|
2010-05-04 04:52:15 +04:00
|
|
|
|
|
|
|
def test_type_roundtrip_time(self):
|
2016-10-11 02:10:53 +03:00
|
|
|
tm = self._test_type_roundtrip(time(10, 20, 30))
|
2014-11-13 11:32:06 +03:00
|
|
|
self.assertEqual(None, tm.tzinfo)
|
|
|
|
|
|
|
|
def test_type_roundtrip_timetz(self):
|
2019-03-16 21:56:56 +03:00
|
|
|
tz = FixedOffsetTimezone(8 * 60)
|
2016-10-11 02:10:53 +03:00
|
|
|
tm1 = time(10, 20, 30, tzinfo=tz)
|
2014-11-13 11:32:06 +03:00
|
|
|
tm2 = self._test_type_roundtrip(tm1)
|
|
|
|
self.assertNotEqual(None, tm2.tzinfo)
|
|
|
|
self.assertEqual(tm1, tm2)
|
2010-05-04 04:52:15 +04:00
|
|
|
|
|
|
|
def test_type_roundtrip_interval(self):
|
|
|
|
self._test_type_roundtrip(timedelta(seconds=30))
|
|
|
|
|
|
|
|
def test_type_roundtrip_date_array(self):
|
2016-10-11 02:10:53 +03:00
|
|
|
self._test_type_roundtrip_array(date(2010, 5, 3))
|
2010-05-04 04:52:15 +04:00
|
|
|
|
|
|
|
def test_type_roundtrip_datetime_array(self):
|
2016-10-11 02:10:53 +03:00
|
|
|
self._test_type_roundtrip_array(datetime(2010, 5, 3, 10, 20, 30))
|
2010-05-04 04:52:15 +04:00
|
|
|
|
2017-07-24 16:20:52 +03:00
|
|
|
def test_type_roundtrip_datetimetz_array(self):
|
|
|
|
self._test_type_roundtrip_array(
|
|
|
|
datetime(2010, 5, 3, 10, 20, 30, tzinfo=FixedOffsetTimezone(0)))
|
|
|
|
|
2010-05-04 04:52:15 +04:00
|
|
|
def test_type_roundtrip_time_array(self):
|
2016-10-11 02:10:53 +03:00
|
|
|
self._test_type_roundtrip_array(time(10, 20, 30))
|
2010-05-04 04:52:15 +04:00
|
|
|
|
|
|
|
def test_type_roundtrip_interval_array(self):
|
|
|
|
self._test_type_roundtrip_array(timedelta(seconds=30))
|
|
|
|
|
2015-02-08 15:50:02 +03:00
|
|
|
@skip_before_postgres(8, 1)
|
2015-02-08 03:41:50 +03:00
|
|
|
def test_time_24(self):
|
|
|
|
t = self.execute("select '24:00'::time;")
|
|
|
|
self.assertEqual(t, time(0, 0))
|
|
|
|
|
|
|
|
t = self.execute("select '24:00+05'::timetz;")
|
|
|
|
self.assertEqual(t, time(0, 0, tzinfo=FixedOffsetTimezone(300)))
|
|
|
|
|
|
|
|
t = self.execute("select '24:00+05:30'::timetz;")
|
|
|
|
self.assertEqual(t, time(0, 0, tzinfo=FixedOffsetTimezone(330)))
|
|
|
|
|
2017-03-01 03:44:28 +03:00
|
|
|
@skip_before_postgres(8, 1)
|
2017-02-24 04:10:27 +03:00
|
|
|
def test_large_interval(self):
|
|
|
|
t = self.execute("select '999999:00:00'::interval")
|
2017-02-24 04:41:34 +03:00
|
|
|
self.assertEqual(total_seconds(t), 999999 * 60 * 60)
|
|
|
|
|
|
|
|
t = self.execute("select '-999999:00:00'::interval")
|
|
|
|
self.assertEqual(total_seconds(t), -999999 * 60 * 60)
|
|
|
|
|
|
|
|
t = self.execute("select '999999:00:00.1'::interval")
|
|
|
|
self.assertEqual(total_seconds(t), 999999 * 60 * 60 + 0.1)
|
|
|
|
|
|
|
|
t = self.execute("select '999999:00:00.9'::interval")
|
|
|
|
self.assertEqual(total_seconds(t), 999999 * 60 * 60 + 0.9)
|
|
|
|
|
|
|
|
t = self.execute("select '-999999:00:00.1'::interval")
|
|
|
|
self.assertEqual(total_seconds(t), -999999 * 60 * 60 - 0.1)
|
|
|
|
|
|
|
|
t = self.execute("select '-999999:00:00.9'::interval")
|
|
|
|
self.assertEqual(total_seconds(t), -999999 * 60 * 60 - 0.9)
|
2017-02-24 04:10:27 +03:00
|
|
|
|
2017-02-24 05:48:41 +03:00
|
|
|
def test_micros_rounding(self):
|
|
|
|
t = self.execute("select '0.1'::interval")
|
|
|
|
self.assertEqual(total_seconds(t), 0.1)
|
|
|
|
|
|
|
|
t = self.execute("select '0.01'::interval")
|
|
|
|
self.assertEqual(total_seconds(t), 0.01)
|
|
|
|
|
|
|
|
t = self.execute("select '0.000001'::interval")
|
|
|
|
self.assertEqual(total_seconds(t), 1e-6)
|
|
|
|
|
|
|
|
t = self.execute("select '0.0000004'::interval")
|
|
|
|
self.assertEqual(total_seconds(t), 0)
|
|
|
|
|
|
|
|
t = self.execute("select '0.0000006'::interval")
|
|
|
|
self.assertEqual(total_seconds(t), 1e-6)
|
|
|
|
|
2017-02-24 16:03:54 +03:00
|
|
|
def test_interval_overflow(self):
|
|
|
|
cur = self.conn.cursor()
|
|
|
|
# hack a cursor to receive values too extreme to be represented
|
|
|
|
# but still I want an error, not a random number
|
|
|
|
psycopg2.extensions.register_type(
|
|
|
|
psycopg2.extensions.new_type(
|
|
|
|
psycopg2.STRING.values, 'WAT', psycopg2.extensions.INTERVAL),
|
|
|
|
cur)
|
|
|
|
|
|
|
|
def f(val):
|
|
|
|
cur.execute("select '%s'::text" % val)
|
|
|
|
return cur.fetchone()[0]
|
|
|
|
|
|
|
|
self.assertRaises(OverflowError, f, '100000000000000000:00:00')
|
|
|
|
self.assertRaises(OverflowError, f, '00:100000000000000000:00:00')
|
|
|
|
self.assertRaises(OverflowError, f, '00:00:100000000000000000:00')
|
|
|
|
self.assertRaises(OverflowError, f, '00:00:00.100000000000000000')
|
|
|
|
|
2020-07-28 00:58:43 +03:00
|
|
|
@skip_if_crdb("infinity date")
|
2017-03-22 06:03:02 +03:00
|
|
|
def test_adapt_infinity_tz(self):
|
|
|
|
t = self.execute("select 'infinity'::timestamp")
|
|
|
|
self.assert_(t.tzinfo is None)
|
|
|
|
self.assert_(t > datetime(4000, 1, 1))
|
|
|
|
|
|
|
|
t = self.execute("select '-infinity'::timestamp")
|
|
|
|
self.assert_(t.tzinfo is None)
|
|
|
|
self.assert_(t < datetime(1000, 1, 1))
|
|
|
|
|
|
|
|
t = self.execute("select 'infinity'::timestamptz")
|
|
|
|
self.assert_(t.tzinfo is not None)
|
|
|
|
self.assert_(t > datetime(4000, 1, 1, tzinfo=FixedOffsetTimezone()))
|
|
|
|
|
|
|
|
t = self.execute("select '-infinity'::timestamptz")
|
|
|
|
self.assert_(t.tzinfo is not None)
|
|
|
|
self.assert_(t < datetime(1000, 1, 1, tzinfo=FixedOffsetTimezone()))
|
|
|
|
|
2017-06-16 02:54:38 +03:00
|
|
|
def test_redshift_day(self):
|
|
|
|
# Redshift is reported returning 1 day interval as microsec (bug #558)
|
|
|
|
cur = self.conn.cursor()
|
|
|
|
psycopg2.extensions.register_type(
|
|
|
|
psycopg2.extensions.new_type(
|
|
|
|
psycopg2.STRING.values, 'WAT', psycopg2.extensions.INTERVAL),
|
|
|
|
cur)
|
|
|
|
|
|
|
|
for s, v in [
|
|
|
|
('0', timedelta(0)),
|
2017-06-16 21:39:18 +03:00
|
|
|
('1', timedelta(microseconds=1)),
|
|
|
|
('-1', timedelta(microseconds=-1)),
|
|
|
|
('1000000', timedelta(seconds=1)),
|
2017-06-16 02:54:38 +03:00
|
|
|
('86400000000', timedelta(days=1)),
|
|
|
|
('-86400000000', timedelta(days=-1)),
|
|
|
|
]:
|
|
|
|
cur.execute("select %s::text", (s,))
|
|
|
|
r = cur.fetchone()[0]
|
|
|
|
self.assertEqual(r, v, "%s -> %s != %s" % (s, r, v))
|
|
|
|
|
2020-07-28 00:58:43 +03:00
|
|
|
@skip_if_crdb("interval style")
|
2018-05-20 15:40:57 +03:00
|
|
|
@skip_before_postgres(8, 4)
|
2018-05-20 15:22:38 +03:00
|
|
|
def test_interval_iso_8601_not_supported(self):
|
|
|
|
# We may end up supporting, but no pressure for it
|
|
|
|
cur = self.conn.cursor()
|
|
|
|
cur.execute("set local intervalstyle to iso_8601")
|
|
|
|
cur.execute("select '1 day 2 hours'::interval")
|
|
|
|
self.assertRaises(psycopg2.NotSupportedError, cur.fetchone)
|
|
|
|
|
2008-01-16 04:43:50 +03:00
|
|
|
|
2019-03-13 16:33:01 +03:00
|
|
|
@unittest.skipUnless(
|
|
|
|
hasattr(psycopg2._psycopg, 'MXDATETIME'),
|
|
|
|
'Requires mx.DateTime support'
|
|
|
|
)
|
2013-04-07 03:23:30 +04:00
|
|
|
class mxDateTimeTests(ConnectingTestCase, CommonDatetimeTestsMixin):
|
2008-01-16 04:43:50 +03:00
|
|
|
"""Tests for the mx.DateTime based date handling in psycopg2."""
|
|
|
|
|
|
|
|
def setUp(self):
|
2013-04-07 03:23:30 +04:00
|
|
|
ConnectingTestCase.setUp(self)
|
2009-02-17 09:03:33 +03:00
|
|
|
self.curs = self.conn.cursor()
|
2008-01-16 04:43:50 +03:00
|
|
|
self.DATE = psycopg2._psycopg.MXDATE
|
|
|
|
self.TIME = psycopg2._psycopg.MXTIME
|
|
|
|
self.DATETIME = psycopg2._psycopg.MXDATETIME
|
|
|
|
self.INTERVAL = psycopg2._psycopg.MXINTERVAL
|
|
|
|
|
2010-05-04 04:52:15 +04:00
|
|
|
psycopg2.extensions.register_type(self.DATE, self.conn)
|
|
|
|
psycopg2.extensions.register_type(self.TIME, self.conn)
|
|
|
|
psycopg2.extensions.register_type(self.DATETIME, self.conn)
|
|
|
|
psycopg2.extensions.register_type(self.INTERVAL, self.conn)
|
|
|
|
psycopg2.extensions.register_type(psycopg2.extensions.MXDATEARRAY, self.conn)
|
|
|
|
psycopg2.extensions.register_type(psycopg2.extensions.MXTIMEARRAY, self.conn)
|
2016-10-11 02:10:53 +03:00
|
|
|
psycopg2.extensions.register_type(
|
|
|
|
psycopg2.extensions.MXDATETIMEARRAY, self.conn)
|
|
|
|
psycopg2.extensions.register_type(
|
|
|
|
psycopg2.extensions.MXINTERVALARRAY, self.conn)
|
2010-05-04 04:52:15 +04:00
|
|
|
|
2009-02-17 09:03:33 +03:00
|
|
|
def tearDown(self):
|
|
|
|
self.conn.close()
|
|
|
|
|
2008-01-16 04:43:50 +03:00
|
|
|
def test_parse_bc_date(self):
|
2009-02-17 09:03:33 +03:00
|
|
|
value = self.DATE('00042-01-01 BC', self.curs)
|
2011-11-01 11:42:08 +04:00
|
|
|
self.assert_(value is not None)
|
2008-01-16 04:43:50 +03:00
|
|
|
# mx.DateTime numbers BC dates from 0 rather than 1.
|
|
|
|
self.assertEqual(value.year, -41)
|
|
|
|
self.assertEqual(value.month, 1)
|
|
|
|
self.assertEqual(value.day, 1)
|
|
|
|
|
|
|
|
def test_parse_bc_datetime(self):
|
2009-02-17 09:03:33 +03:00
|
|
|
value = self.DATETIME('00042-01-01 13:30:29 BC', self.curs)
|
2011-11-01 11:42:08 +04:00
|
|
|
self.assert_(value is not None)
|
2008-01-16 04:43:50 +03:00
|
|
|
# mx.DateTime numbers BC dates from 0 rather than 1.
|
|
|
|
self.assertEqual(value.year, -41)
|
|
|
|
self.assertEqual(value.month, 1)
|
|
|
|
self.assertEqual(value.day, 1)
|
|
|
|
self.assertEqual(value.hour, 13)
|
|
|
|
self.assertEqual(value.minute, 30)
|
|
|
|
self.assertEqual(value.second, 29)
|
|
|
|
|
|
|
|
def test_parse_time_microseconds(self):
|
2009-02-17 09:03:33 +03:00
|
|
|
value = self.TIME('13:30:29.123456', self.curs)
|
2008-01-16 04:43:50 +03:00
|
|
|
self.assertEqual(math.floor(value.second), 29)
|
|
|
|
self.assertEqual(
|
|
|
|
int((value.second - math.floor(value.second)) * 1000000), 123456)
|
|
|
|
|
|
|
|
def test_parse_datetime_microseconds(self):
|
2009-02-17 09:03:33 +03:00
|
|
|
value = self.DATETIME('2007-01-01 13:30:29.123456', self.curs)
|
2008-01-16 04:43:50 +03:00
|
|
|
self.assertEqual(math.floor(value.second), 29)
|
|
|
|
self.assertEqual(
|
|
|
|
int((value.second - math.floor(value.second)) * 1000000), 123456)
|
|
|
|
|
2009-02-17 09:03:33 +03:00
|
|
|
def test_parse_time_timezone(self):
|
|
|
|
# Time zone information is ignored.
|
|
|
|
expected = Time(13, 30, 29)
|
|
|
|
self.assertEqual(expected, self.TIME("13:30:29+01", self.curs))
|
|
|
|
self.assertEqual(expected, self.TIME("13:30:29-01", self.curs))
|
|
|
|
self.assertEqual(expected, self.TIME("13:30:29+01:15", self.curs))
|
|
|
|
self.assertEqual(expected, self.TIME("13:30:29-01:15", self.curs))
|
|
|
|
self.assertEqual(expected, self.TIME("13:30:29+01:15:42", self.curs))
|
|
|
|
self.assertEqual(expected, self.TIME("13:30:29-01:15:42", self.curs))
|
|
|
|
|
|
|
|
def test_parse_datetime_timezone(self):
|
|
|
|
# Time zone information is ignored.
|
|
|
|
expected = DateTime(2007, 1, 1, 13, 30, 29)
|
|
|
|
self.assertEqual(
|
|
|
|
expected, self.DATETIME("2007-01-01 13:30:29+01", self.curs))
|
|
|
|
self.assertEqual(
|
|
|
|
expected, self.DATETIME("2007-01-01 13:30:29-01", self.curs))
|
|
|
|
self.assertEqual(
|
|
|
|
expected, self.DATETIME("2007-01-01 13:30:29+01:15", self.curs))
|
|
|
|
self.assertEqual(
|
|
|
|
expected, self.DATETIME("2007-01-01 13:30:29-01:15", self.curs))
|
|
|
|
self.assertEqual(
|
|
|
|
expected, self.DATETIME("2007-01-01 13:30:29+01:15:42", self.curs))
|
|
|
|
self.assertEqual(
|
|
|
|
expected, self.DATETIME("2007-01-01 13:30:29-01:15:42", self.curs))
|
|
|
|
|
2008-01-16 04:43:50 +03:00
|
|
|
def test_parse_interval(self):
|
2009-02-17 09:03:33 +03:00
|
|
|
value = self.INTERVAL('42 days 05:50:05', self.curs)
|
2011-11-01 11:42:08 +04:00
|
|
|
self.assert_(value is not None)
|
2008-01-16 04:43:50 +03:00
|
|
|
self.assertEqual(value.day, 42)
|
|
|
|
self.assertEqual(value.hour, 5)
|
|
|
|
self.assertEqual(value.minute, 50)
|
|
|
|
self.assertEqual(value.second, 5)
|
|
|
|
|
|
|
|
def test_adapt_time(self):
|
|
|
|
value = self.execute('select (%s)::time::text',
|
|
|
|
[Time(13, 30, 29)])
|
|
|
|
self.assertEqual(value, '13:30:29')
|
|
|
|
|
|
|
|
def test_adapt_datetime(self):
|
|
|
|
value = self.execute('select (%s)::timestamp::text',
|
|
|
|
[DateTime(2007, 1, 1, 13, 30, 29.123456)])
|
|
|
|
self.assertEqual(value, '2007-01-01 13:30:29.123456')
|
|
|
|
|
|
|
|
def test_adapt_bc_datetime(self):
|
|
|
|
value = self.execute('select (%s)::timestamp::text',
|
|
|
|
[DateTime(-41, 1, 1, 13, 30, 29.123456)])
|
2011-06-03 03:54:33 +04:00
|
|
|
# microsecs for BC timestamps look not available in PG < 8.4
|
|
|
|
# but more likely it's determined at compile time.
|
|
|
|
self.assert_(value in (
|
|
|
|
'0042-01-01 13:30:29.123456 BC',
|
|
|
|
'0042-01-01 13:30:29 BC'), value)
|
2008-01-16 04:43:50 +03:00
|
|
|
|
|
|
|
def test_adapt_timedelta(self):
|
|
|
|
value = self.execute('select extract(epoch from (%s)::interval)',
|
|
|
|
[DateTimeDeltaFrom(days=42,
|
|
|
|
seconds=45296.123456)])
|
|
|
|
seconds = math.floor(value)
|
|
|
|
self.assertEqual(seconds, 3674096)
|
|
|
|
self.assertEqual(int(round((value - seconds) * 1000000)), 123456)
|
|
|
|
|
2013-05-11 02:25:06 +04:00
|
|
|
def test_adapt_negative_timedelta(self):
|
2008-01-16 04:43:50 +03:00
|
|
|
value = self.execute('select extract(epoch from (%s)::interval)',
|
|
|
|
[DateTimeDeltaFrom(days=-42,
|
|
|
|
seconds=45296.123456)])
|
|
|
|
seconds = math.floor(value)
|
|
|
|
self.assertEqual(seconds, -3583504)
|
|
|
|
self.assertEqual(int(round((value - seconds) * 1000000)), 123456)
|
|
|
|
|
2010-05-04 04:52:15 +04:00
|
|
|
def _test_type_roundtrip(self, o1):
|
|
|
|
o2 = self.execute("select %s;", (o1,))
|
|
|
|
self.assertEqual(type(o1), type(o2))
|
|
|
|
|
|
|
|
def _test_type_roundtrip_array(self, o1):
|
|
|
|
o1 = [o1]
|
|
|
|
o2 = self.execute("select %s;", (o1,))
|
|
|
|
self.assertEqual(type(o1[0]), type(o2[0]))
|
|
|
|
|
|
|
|
def test_type_roundtrip_date(self):
|
2016-10-11 02:10:53 +03:00
|
|
|
self._test_type_roundtrip(Date(2010, 5, 3))
|
2010-05-04 04:52:15 +04:00
|
|
|
|
|
|
|
def test_type_roundtrip_datetime(self):
|
2016-10-11 02:10:53 +03:00
|
|
|
self._test_type_roundtrip(DateTime(2010, 5, 3, 10, 20, 30))
|
2010-05-04 04:52:15 +04:00
|
|
|
|
|
|
|
def test_type_roundtrip_time(self):
|
2016-10-11 02:10:53 +03:00
|
|
|
self._test_type_roundtrip(Time(10, 20, 30))
|
2010-05-04 04:52:15 +04:00
|
|
|
|
|
|
|
def test_type_roundtrip_interval(self):
|
|
|
|
self._test_type_roundtrip(DateTimeDeltaFrom(seconds=30))
|
|
|
|
|
|
|
|
def test_type_roundtrip_date_array(self):
|
2016-10-11 02:10:53 +03:00
|
|
|
self._test_type_roundtrip_array(Date(2010, 5, 3))
|
2010-05-04 04:52:15 +04:00
|
|
|
|
|
|
|
def test_type_roundtrip_datetime_array(self):
|
2016-10-11 02:10:53 +03:00
|
|
|
self._test_type_roundtrip_array(DateTime(2010, 5, 3, 10, 20, 30))
|
2010-05-04 04:52:15 +04:00
|
|
|
|
|
|
|
def test_type_roundtrip_time_array(self):
|
2016-10-11 02:10:53 +03:00
|
|
|
self._test_type_roundtrip_array(Time(10, 20, 30))
|
2010-05-04 04:52:15 +04:00
|
|
|
|
|
|
|
def test_type_roundtrip_interval_array(self):
|
|
|
|
self._test_type_roundtrip_array(DateTimeDeltaFrom(seconds=30))
|
|
|
|
|
2008-01-16 04:43:50 +03:00
|
|
|
|
2010-05-07 21:28:31 +04:00
|
|
|
class FromTicksTestCase(unittest.TestCase):
|
2010-05-07 21:06:18 +04:00
|
|
|
# bug "TimestampFromTicks() throws ValueError (2-2.0.14)"
|
|
|
|
# reported by Jozsef Szalay on 2010-05-06
|
2010-05-07 21:28:31 +04:00
|
|
|
def test_timestamp_value_error_sec_59_99(self):
|
2010-05-07 21:06:18 +04:00
|
|
|
s = psycopg2.TimestampFromTicks(1273173119.99992)
|
|
|
|
self.assertEqual(s.adapted,
|
|
|
|
datetime(2010, 5, 6, 14, 11, 59, 999920,
|
|
|
|
tzinfo=FixedOffsetTimezone(-5 * 60)))
|
|
|
|
|
2010-05-07 21:28:31 +04:00
|
|
|
def test_date_value_error_sec_59_99(self):
|
|
|
|
s = psycopg2.DateFromTicks(1273173119.99992)
|
2018-01-11 02:52:03 +03:00
|
|
|
# The returned date is local
|
|
|
|
self.assert_(s.adapted in [date(2010, 5, 6), date(2010, 5, 7)])
|
2010-05-07 21:28:31 +04:00
|
|
|
|
|
|
|
def test_time_value_error_sec_59_99(self):
|
|
|
|
s = psycopg2.TimeFromTicks(1273173119.99992)
|
2010-05-07 22:05:59 +04:00
|
|
|
self.assertEqual(s.adapted.replace(hour=0),
|
|
|
|
time(0, 11, 59, 999920))
|
2010-05-07 21:28:31 +04:00
|
|
|
|
2010-05-07 21:06:18 +04:00
|
|
|
|
2012-01-17 19:51:32 +04:00
|
|
|
class FixedOffsetTimezoneTests(unittest.TestCase):
|
|
|
|
|
|
|
|
def test_init_with_no_args(self):
|
|
|
|
tzinfo = FixedOffsetTimezone()
|
|
|
|
self.assert_(tzinfo._offset is ZERO)
|
|
|
|
self.assert_(tzinfo._name is None)
|
|
|
|
|
|
|
|
def test_repr_with_positive_offset(self):
|
|
|
|
tzinfo = FixedOffsetTimezone(5 * 60)
|
2016-10-11 02:10:53 +03:00
|
|
|
self.assertEqual(repr(tzinfo),
|
|
|
|
"psycopg2.tz.FixedOffsetTimezone(offset=300, name=None)")
|
2012-01-17 19:51:32 +04:00
|
|
|
|
|
|
|
def test_repr_with_negative_offset(self):
|
|
|
|
tzinfo = FixedOffsetTimezone(-5 * 60)
|
2016-10-11 02:10:53 +03:00
|
|
|
self.assertEqual(repr(tzinfo),
|
|
|
|
"psycopg2.tz.FixedOffsetTimezone(offset=-300, name=None)")
|
2012-01-17 19:51:32 +04:00
|
|
|
|
|
|
|
def test_repr_with_name(self):
|
|
|
|
tzinfo = FixedOffsetTimezone(name="FOO")
|
2016-10-11 02:10:53 +03:00
|
|
|
self.assertEqual(repr(tzinfo),
|
|
|
|
"psycopg2.tz.FixedOffsetTimezone(offset=0, name='FOO')")
|
2012-01-17 19:51:32 +04:00
|
|
|
|
2012-01-25 20:23:19 +04:00
|
|
|
def test_instance_caching(self):
|
2016-10-11 02:10:53 +03:00
|
|
|
self.assert_(FixedOffsetTimezone(name="FOO")
|
|
|
|
is FixedOffsetTimezone(name="FOO"))
|
|
|
|
self.assert_(FixedOffsetTimezone(7 * 60)
|
|
|
|
is FixedOffsetTimezone(7 * 60))
|
|
|
|
self.assert_(FixedOffsetTimezone(-9 * 60, 'FOO')
|
|
|
|
is FixedOffsetTimezone(-9 * 60, 'FOO'))
|
|
|
|
self.assert_(FixedOffsetTimezone(9 * 60)
|
|
|
|
is not FixedOffsetTimezone(9 * 60, 'FOO'))
|
|
|
|
self.assert_(FixedOffsetTimezone(name='FOO')
|
|
|
|
is not FixedOffsetTimezone(9 * 60, 'FOO'))
|
2012-01-25 20:23:19 +04:00
|
|
|
|
2012-10-22 00:47:32 +04:00
|
|
|
def test_pickle(self):
|
|
|
|
# ticket #135
|
|
|
|
tz11 = FixedOffsetTimezone(60)
|
|
|
|
tz12 = FixedOffsetTimezone(120)
|
|
|
|
for proto in [-1, 0, 1, 2]:
|
|
|
|
tz21, tz22 = pickle.loads(pickle.dumps([tz11, tz12], proto))
|
|
|
|
self.assertEqual(tz11, tz21)
|
|
|
|
self.assertEqual(tz12, tz22)
|
|
|
|
|
|
|
|
tz11 = FixedOffsetTimezone(60, name='foo')
|
|
|
|
tz12 = FixedOffsetTimezone(120, name='bar')
|
|
|
|
for proto in [-1, 0, 1, 2]:
|
|
|
|
tz21, tz22 = pickle.loads(pickle.dumps([tz11, tz12], proto))
|
|
|
|
self.assertEqual(tz11, tz21)
|
|
|
|
self.assertEqual(tz12, tz22)
|
|
|
|
|
2013-04-07 03:23:30 +04:00
|
|
|
|
2008-01-16 04:43:50 +03:00
|
|
|
def test_suite():
|
|
|
|
return unittest.TestLoader().loadTestsFromName(__name__)
|
|
|
|
|
2018-10-23 02:39:14 +03:00
|
|
|
|
2010-02-13 01:34:53 +03:00
|
|
|
if __name__ == "__main__":
|
|
|
|
unittest.main()
|