mirror of
https://github.com/psycopg/psycopg2.git
synced 2025-07-11 00:32:22 +03:00
Merge branch 'timezone-seconds'
This commit is contained in:
commit
af05c3a1ec
7
NEWS
7
NEWS
|
@ -6,6 +6,8 @@ What's new in psycopg 2.9
|
||||||
|
|
||||||
- ``with connection`` starts a transaction on autocommit transactions too
|
- ``with connection`` starts a transaction on autocommit transactions too
|
||||||
(:ticket:`#941`).
|
(:ticket:`#941`).
|
||||||
|
- Timezones with fractional minutes are supported on Python 3.7 and following
|
||||||
|
(:ticket:`#1272`).
|
||||||
- Escape table and column names in `~cursor.copy_from()` and
|
- Escape table and column names in `~cursor.copy_from()` and
|
||||||
`~cursor.copy_to()`.
|
`~cursor.copy_to()`.
|
||||||
- Connection exceptions with sqlstate ``08XXX`` reclassified as
|
- Connection exceptions with sqlstate ``08XXX`` reclassified as
|
||||||
|
@ -17,6 +19,11 @@ What's new in psycopg 2.9
|
||||||
Other changes:
|
Other changes:
|
||||||
|
|
||||||
- Dropped support for Python 2.7, 3.4, 3.5 (:tickets:`#1198, #1000, #1197`).
|
- Dropped support for Python 2.7, 3.4, 3.5 (:tickets:`#1198, #1000, #1197`).
|
||||||
|
- Dropped support for mx.DateTime.
|
||||||
|
- Use `datetime.timezone` objects by default in datetime objects instead of
|
||||||
|
`~psycopg2.tz.FixedOffsetTimezone`.
|
||||||
|
- The `psycopg2.tz` module is deprecated and scheduled to be dropped in the
|
||||||
|
next major release.
|
||||||
- Build system for Linux/MacOS binary packages moved to GitHub action, now
|
- Build system for Linux/MacOS binary packages moved to GitHub action, now
|
||||||
providing :pep:`600`\-style wheels packages.
|
providing :pep:`600`\-style wheels packages.
|
||||||
|
|
||||||
|
|
|
@ -128,8 +128,6 @@ rst_epilog = """
|
||||||
.. _transaction isolation level:
|
.. _transaction isolation level:
|
||||||
https://www.postgresql.org/docs/current/static/transaction-iso.html
|
https://www.postgresql.org/docs/current/static/transaction-iso.html
|
||||||
|
|
||||||
.. _mx.DateTime: https://www.egenix.com/products/python/mxBase/mxDateTime/
|
|
||||||
|
|
||||||
.. |MVCC| replace:: :abbr:`MVCC (Multiversion concurrency control)`
|
.. |MVCC| replace:: :abbr:`MVCC (Multiversion concurrency control)`
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
|
@ -498,8 +498,10 @@ The ``cursor`` class
|
||||||
|
|
||||||
The time zone factory used to handle data types such as
|
The time zone factory used to handle data types such as
|
||||||
:sql:`TIMESTAMP WITH TIME ZONE`. It should be a `~datetime.tzinfo`
|
:sql:`TIMESTAMP WITH TIME ZONE`. It should be a `~datetime.tzinfo`
|
||||||
object. A few implementations are available in the `psycopg2.tz`
|
object. Default is `datetime.timezone`.
|
||||||
module.
|
|
||||||
|
.. versionchanged:: 2.9
|
||||||
|
previosly the default factory was `psycopg2.tz.FixedOffsetTimezone`.
|
||||||
|
|
||||||
|
|
||||||
.. method:: nextset()
|
.. method:: nextset()
|
||||||
|
|
|
@ -453,13 +453,6 @@ deal with Python objects adaptation:
|
||||||
|
|
||||||
Specialized adapters for Python datetime objects.
|
Specialized adapters for Python datetime objects.
|
||||||
|
|
||||||
.. class:: DateFromMx
|
|
||||||
TimeFromMx
|
|
||||||
TimestampFromMx
|
|
||||||
IntervalFromMx
|
|
||||||
|
|
||||||
Specialized adapters for `mx.DateTime`_ objects.
|
|
||||||
|
|
||||||
.. data:: adapters
|
.. data:: adapters
|
||||||
|
|
||||||
Dictionary of the currently registered object adapters. Use
|
Dictionary of the currently registered object adapters. Use
|
||||||
|
@ -1004,20 +997,6 @@ from the database. See :ref:`unicode-handling` for details.
|
||||||
Typecasters to convert time-related data types to Python `!datetime`
|
Typecasters to convert time-related data types to Python `!datetime`
|
||||||
objects.
|
objects.
|
||||||
|
|
||||||
.. data:: MXDATE
|
|
||||||
MXDATETIME
|
|
||||||
MXDATETIMETZ
|
|
||||||
MXINTERVAL
|
|
||||||
MXTIME
|
|
||||||
MXDATEARRAY
|
|
||||||
MXDATETIMEARRAY
|
|
||||||
MXDATETIMETZARRAY
|
|
||||||
MXINTERVALARRAY
|
|
||||||
MXTIMEARRAY
|
|
||||||
|
|
||||||
Typecasters to convert time-related data types to `mx.DateTime`_ objects.
|
|
||||||
Only available if Psycopg was compiled with `!mx` support.
|
|
||||||
|
|
||||||
.. versionchanged:: 2.2
|
.. versionchanged:: 2.2
|
||||||
previously the `DECIMAL` typecaster and the specific time-related
|
previously the `DECIMAL` typecaster and the specific time-related
|
||||||
typecasters (`!PY*` and `!MX*`) were not exposed by the `extensions`
|
typecasters (`!PY*` and `!MX*`) were not exposed by the `extensions`
|
||||||
|
|
|
@ -230,7 +230,6 @@ If you have less standard requirements such as:
|
||||||
|
|
||||||
- creating a :ref:`debug build <debug-build>`,
|
- creating a :ref:`debug build <debug-build>`,
|
||||||
- using :program:`pg_config` not in the :envvar:`PATH`,
|
- using :program:`pg_config` not in the :envvar:`PATH`,
|
||||||
- supporting ``mx.DateTime``,
|
|
||||||
|
|
||||||
then take a look at the ``setup.cfg`` file.
|
then take a look at the ``setup.cfg`` file.
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,10 @@
|
||||||
|
|
||||||
.. module:: psycopg2.tz
|
.. module:: psycopg2.tz
|
||||||
|
|
||||||
|
.. deprecated:: 2.9
|
||||||
|
The module will be dropped in psycopg 2.10. Use `datetime.timezone`
|
||||||
|
instead.
|
||||||
|
|
||||||
This module holds two different tzinfo implementations that can be used as the
|
This module holds two different tzinfo implementations that can be used as the
|
||||||
`tzinfo` argument to `~datetime.datetime` constructors, directly passed to
|
`tzinfo` argument to `~datetime.datetime` constructors, directly passed to
|
||||||
Psycopg functions or used to set the `cursor.tzinfo_factory` attribute in
|
Psycopg functions or used to set the `cursor.tzinfo_factory` attribute in
|
||||||
|
|
|
@ -540,7 +540,6 @@ or `!memoryview` (in Python 3).
|
||||||
single: Date objects; Adaptation
|
single: Date objects; Adaptation
|
||||||
single: Time objects; Adaptation
|
single: Time objects; Adaptation
|
||||||
single: Interval objects; Adaptation
|
single: Interval objects; Adaptation
|
||||||
single: mx.DateTime; Adaptation
|
|
||||||
|
|
||||||
.. _adapt-date:
|
.. _adapt-date:
|
||||||
|
|
||||||
|
@ -550,8 +549,7 @@ Date/Time objects adaptation
|
||||||
Python builtin `~datetime.datetime`, `~datetime.date`,
|
Python builtin `~datetime.datetime`, `~datetime.date`,
|
||||||
`~datetime.time`, `~datetime.timedelta` are converted into PostgreSQL's
|
`~datetime.time`, `~datetime.timedelta` are converted into PostgreSQL's
|
||||||
:sql:`timestamp[tz]`, :sql:`date`, :sql:`time[tz]`, :sql:`interval` data types.
|
:sql:`timestamp[tz]`, :sql:`date`, :sql:`time[tz]`, :sql:`interval` data types.
|
||||||
Time zones are supported too. The Egenix `mx.DateTime`_ objects are adapted
|
Time zones are supported too.
|
||||||
the same way::
|
|
||||||
|
|
||||||
>>> dt = datetime.datetime.now()
|
>>> dt = datetime.datetime.now()
|
||||||
>>> dt
|
>>> dt
|
||||||
|
@ -576,29 +574,39 @@ Time zones handling
|
||||||
'''''''''''''''''''
|
'''''''''''''''''''
|
||||||
|
|
||||||
The PostgreSQL type :sql:`timestamp with time zone` (a.k.a.
|
The PostgreSQL type :sql:`timestamp with time zone` (a.k.a.
|
||||||
:sql:`timestamptz`) is converted into Python `~datetime.datetime` objects with
|
:sql:`timestamptz`) is converted into Python `~datetime.datetime` objects.
|
||||||
a `~datetime.datetime.tzinfo` attribute set to a
|
|
||||||
`~psycopg2.tz.FixedOffsetTimezone` instance.
|
|
||||||
|
|
||||||
>>> cur.execute("SET TIME ZONE 'Europe/Rome';") # UTC + 1 hour
|
>>> cur.execute("SET TIME ZONE 'Europe/Rome'") # UTC + 1 hour
|
||||||
>>> cur.execute("SELECT '2010-01-01 10:30:45'::timestamptz;")
|
>>> cur.execute("SELECT '2010-01-01 10:30:45'::timestamptz")
|
||||||
>>> cur.fetchone()[0].tzinfo
|
>>> cur.fetchone()[0]
|
||||||
psycopg2.tz.FixedOffsetTimezone(offset=60, name=None)
|
datetime.datetime(2010, 1, 1, 10, 30, 45,
|
||||||
|
tzinfo=datetime.timezone(datetime.timedelta(seconds=3600)))
|
||||||
|
|
||||||
Note that only time zones with an integer number of minutes are supported:
|
.. note::
|
||||||
this is a limitation of the Python `datetime` module. A few historical time
|
|
||||||
zones had seconds in the UTC offset: these time zones will have the offset
|
|
||||||
rounded to the nearest minute, with an error of up to 30 seconds.
|
|
||||||
|
|
||||||
>>> cur.execute("SET TIME ZONE 'Asia/Calcutta';") # offset was +5:53:20
|
Before Python 3.7, the `datetime` module only supported timezones with an
|
||||||
>>> cur.execute("SELECT '1930-01-01 10:30:45'::timestamptz;")
|
integer number of minutes. A few historical time zones had seconds in the
|
||||||
>>> cur.fetchone()[0].tzinfo
|
UTC offset: these time zones will have the offset rounded to the nearest
|
||||||
psycopg2.tz.FixedOffsetTimezone(offset=353, name=None)
|
minute, with an error of up to 30 seconds, on Python versions before 3.7.
|
||||||
|
|
||||||
|
>>> cur.execute("SET TIME ZONE 'Asia/Calcutta'") # offset was +5:21:10
|
||||||
|
>>> cur.execute("SELECT '1900-01-01 10:30:45'::timestamptz")
|
||||||
|
>>> cur.fetchone()[0].tzinfo
|
||||||
|
# On Python 3.6: 5h, 21m
|
||||||
|
datetime.timezone(datetime.timedelta(0, 19260))
|
||||||
|
# On Python 3.7 and following: 5h, 21m, 10s
|
||||||
|
datetime.timezone(datetime.timedelta(seconds=19270))
|
||||||
|
|
||||||
.. versionchanged:: 2.2.2
|
.. versionchanged:: 2.2.2
|
||||||
timezones with seconds are supported (with rounding). Previously such
|
timezones with seconds are supported (with rounding). Previously such
|
||||||
timezones raised an error.
|
timezones raised an error.
|
||||||
|
|
||||||
|
.. versionchanged:: 2.9
|
||||||
|
timezones with seconds are supported without rounding.
|
||||||
|
|
||||||
|
.. versionchanged:: 2.9
|
||||||
|
use `datetime.timezone` as default tzinfo object instead of
|
||||||
|
`~psycopg2.tz.FixedOffsetTimezone`.
|
||||||
|
|
||||||
.. index::
|
.. index::
|
||||||
double: Date objects; Infinite
|
double: Date objects; Infinite
|
||||||
|
|
|
@ -61,8 +61,6 @@ from psycopg2._psycopg import ( # noqa
|
||||||
__version__, __libpq_version__,
|
__version__, __libpq_version__,
|
||||||
)
|
)
|
||||||
|
|
||||||
from psycopg2 import tz # noqa
|
|
||||||
|
|
||||||
|
|
||||||
# Register default adapters.
|
# Register default adapters.
|
||||||
|
|
||||||
|
|
|
@ -42,14 +42,6 @@ from psycopg2._psycopg import ( # noqa
|
||||||
ROWIDARRAY, STRINGARRAY, TIME, TIMEARRAY, UNICODE, UNICODEARRAY,
|
ROWIDARRAY, STRINGARRAY, TIME, TIMEARRAY, UNICODE, UNICODEARRAY,
|
||||||
AsIs, Binary, Boolean, Float, Int, QuotedString, )
|
AsIs, Binary, Boolean, Float, Int, QuotedString, )
|
||||||
|
|
||||||
try:
|
|
||||||
from psycopg2._psycopg import ( # noqa
|
|
||||||
MXDATE, MXDATETIME, MXDATETIMETZ, MXINTERVAL, MXTIME, MXDATEARRAY,
|
|
||||||
MXDATETIMEARRAY, MXDATETIMETZARRAY, MXINTERVALARRAY, MXTIMEARRAY,
|
|
||||||
DateFromMx, TimeFromMx, TimestampFromMx, IntervalFromMx, )
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
from psycopg2._psycopg import ( # noqa
|
from psycopg2._psycopg import ( # noqa
|
||||||
PYDATE, PYDATETIME, PYDATETIMETZ, PYINTERVAL, PYTIME, PYDATEARRAY,
|
PYDATE, PYDATETIME, PYDATETIMETZ, PYINTERVAL, PYTIME, PYDATEARRAY,
|
||||||
PYDATETIMEARRAY, PYDATETIMETZARRAY, PYINTERVALARRAY, PYTIMEARRAY,
|
PYDATETIMEARRAY, PYDATETIMETZARRAY, PYINTERVALARRAY, PYTIMEARRAY,
|
||||||
|
|
45
lib/tz.py
45
lib/tz.py
|
@ -45,6 +45,11 @@ class FixedOffsetTimezone(datetime.tzinfo):
|
||||||
offset and name that instance will be returned. This saves memory and
|
offset and name that instance will be returned. This saves memory and
|
||||||
improves comparability.
|
improves comparability.
|
||||||
|
|
||||||
|
.. versionchanged:: 2.9
|
||||||
|
|
||||||
|
The constructor can take either a timedelta or a number of minutes of
|
||||||
|
offset. Previously only minutes were supported.
|
||||||
|
|
||||||
.. __: https://docs.python.org/library/datetime.html
|
.. __: https://docs.python.org/library/datetime.html
|
||||||
"""
|
"""
|
||||||
_name = None
|
_name = None
|
||||||
|
@ -54,7 +59,9 @@ class FixedOffsetTimezone(datetime.tzinfo):
|
||||||
|
|
||||||
def __init__(self, offset=None, name=None):
|
def __init__(self, offset=None, name=None):
|
||||||
if offset is not None:
|
if offset is not None:
|
||||||
self._offset = datetime.timedelta(minutes=offset)
|
if not isinstance(offset, datetime.timedelta):
|
||||||
|
offset = datetime.timedelta(minutes=offset)
|
||||||
|
self._offset = offset
|
||||||
if name is not None:
|
if name is not None:
|
||||||
self._name = name
|
self._name = name
|
||||||
|
|
||||||
|
@ -70,13 +77,23 @@ class FixedOffsetTimezone(datetime.tzinfo):
|
||||||
return tz
|
return tz
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
offset_mins = self._offset.seconds // 60 + self._offset.days * 24 * 60
|
|
||||||
return "psycopg2.tz.FixedOffsetTimezone(offset=%r, name=%r)" \
|
return "psycopg2.tz.FixedOffsetTimezone(offset=%r, name=%r)" \
|
||||||
% (offset_mins, self._name)
|
% (self._offset, self._name)
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
if isinstance(other, FixedOffsetTimezone):
|
||||||
|
return self._offset == other._offset
|
||||||
|
else:
|
||||||
|
return NotImplemented
|
||||||
|
|
||||||
|
def __ne__(self, other):
|
||||||
|
if isinstance(other, FixedOffsetTimezone):
|
||||||
|
return self._offset != other._offset
|
||||||
|
else:
|
||||||
|
return NotImplemented
|
||||||
|
|
||||||
def __getinitargs__(self):
|
def __getinitargs__(self):
|
||||||
offset_mins = self._offset.seconds // 60 + self._offset.days * 24 * 60
|
return self._offset, self._name
|
||||||
return offset_mins, self._name
|
|
||||||
|
|
||||||
def utcoffset(self, dt):
|
def utcoffset(self, dt):
|
||||||
return self._offset
|
return self._offset
|
||||||
|
@ -84,14 +101,16 @@ class FixedOffsetTimezone(datetime.tzinfo):
|
||||||
def tzname(self, dt):
|
def tzname(self, dt):
|
||||||
if self._name is not None:
|
if self._name is not None:
|
||||||
return self._name
|
return self._name
|
||||||
else:
|
|
||||||
seconds = self._offset.seconds + self._offset.days * 86400
|
minutes, seconds = divmod(self._offset.total_seconds(), 60)
|
||||||
hours, seconds = divmod(seconds, 3600)
|
hours, minutes = divmod(minutes, 60)
|
||||||
minutes = seconds / 60
|
rv = "%+03d" % hours
|
||||||
if minutes:
|
if minutes or seconds:
|
||||||
return "%+03d:%d" % (hours, minutes)
|
rv += ":%02d" % minutes
|
||||||
else:
|
if seconds:
|
||||||
return "%+03d" % hours
|
rv += ":%02d" % seconds
|
||||||
|
|
||||||
|
return rv
|
||||||
|
|
||||||
def dst(self, dt):
|
def dst(self, dt):
|
||||||
return ZERO
|
return ZERO
|
||||||
|
|
|
@ -423,8 +423,8 @@ psyco_TimeFromTicks(PyObject *self, PyObject *args)
|
||||||
PyObject *
|
PyObject *
|
||||||
psyco_TimestampFromTicks(PyObject *self, PyObject *args)
|
psyco_TimestampFromTicks(PyObject *self, PyObject *args)
|
||||||
{
|
{
|
||||||
PyObject *m = NULL;
|
pydatetimeObject *wrapper = NULL;
|
||||||
PyObject *tz = NULL;
|
PyObject *dt_aware = NULL;
|
||||||
PyObject *res = NULL;
|
PyObject *res = NULL;
|
||||||
struct tm tm;
|
struct tm tm;
|
||||||
time_t t;
|
time_t t;
|
||||||
|
@ -433,10 +433,6 @@ psyco_TimestampFromTicks(PyObject *self, PyObject *args)
|
||||||
if (!PyArg_ParseTuple(args, "d", &ticks))
|
if (!PyArg_ParseTuple(args, "d", &ticks))
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
/* get psycopg2.tz.LOCAL from pythonland */
|
|
||||||
if (!(m = PyImport_ImportModule("psycopg2.tz"))) { goto exit; }
|
|
||||||
if (!(tz = PyObject_GetAttrString(m, "LOCAL"))) { goto exit; }
|
|
||||||
|
|
||||||
t = (time_t)floor(ticks);
|
t = (time_t)floor(ticks);
|
||||||
ticks -= (double)t;
|
ticks -= (double)t;
|
||||||
if (!localtime_r(&t, &tm)) {
|
if (!localtime_r(&t, &tm)) {
|
||||||
|
@ -444,14 +440,29 @@ psyco_TimestampFromTicks(PyObject *self, PyObject *args)
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
res = _psyco_Timestamp(
|
/* Convert the tm to a wrapper containing a naive datetime.datetime */
|
||||||
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
|
if (!(wrapper = (pydatetimeObject *)_psyco_Timestamp(
|
||||||
tm.tm_hour, tm.tm_min, (double)tm.tm_sec + ticks,
|
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
|
||||||
tz);
|
tm.tm_hour, tm.tm_min, (double)tm.tm_sec + ticks, NULL))) {
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Localize the datetime and assign it back to the wrapper */
|
||||||
|
if (!(dt_aware = PyObject_CallMethod(
|
||||||
|
wrapper->wrapped, "astimezone", NULL))) {
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
Py_CLEAR(wrapper->wrapped);
|
||||||
|
wrapper->wrapped = dt_aware;
|
||||||
|
dt_aware = NULL;
|
||||||
|
|
||||||
|
/* the wrapper is ready to be returned */
|
||||||
|
res = (PyObject *)wrapper;
|
||||||
|
wrapper = NULL;
|
||||||
|
|
||||||
exit:
|
exit:
|
||||||
Py_XDECREF(tz);
|
Py_XDECREF(dt_aware);
|
||||||
Py_XDECREF(m);
|
Py_XDECREF(wrapper);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,302 +0,0 @@
|
||||||
/* adapter_mxdatetime.c - mx date/time objects
|
|
||||||
*
|
|
||||||
* Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
|
|
||||||
* Copyright (C) 2020 The Psycopg Team
|
|
||||||
*
|
|
||||||
* This file is part of psycopg.
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#define PSYCOPG_MODULE
|
|
||||||
#include "psycopg/psycopg.h"
|
|
||||||
|
|
||||||
#include "psycopg/adapter_mxdatetime.h"
|
|
||||||
#include "psycopg/microprotocols_proto.h"
|
|
||||||
|
|
||||||
#include <mxDateTime.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
|
|
||||||
/* Return 0 on success, -1 on failure, but don't set an exception */
|
|
||||||
|
|
||||||
int
|
|
||||||
psyco_adapter_mxdatetime_init(void)
|
|
||||||
{
|
|
||||||
if (mxDateTime_ImportModuleAndAPI()) {
|
|
||||||
Dprintf("psyco_adapter_mxdatetime_init: mx.DateTime initialization failed");
|
|
||||||
PyErr_Clear();
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* mxdatetime_str, mxdatetime_getquoted - return result of quoting */
|
|
||||||
|
|
||||||
static PyObject *
|
|
||||||
mxdatetime_str(mxdatetimeObject *self)
|
|
||||||
{
|
|
||||||
mxDateTimeObject *dt;
|
|
||||||
mxDateTimeDeltaObject *dtd;
|
|
||||||
char buf[128] = { 0, };
|
|
||||||
|
|
||||||
switch (self->type) {
|
|
||||||
|
|
||||||
case PSYCO_MXDATETIME_DATE:
|
|
||||||
dt = (mxDateTimeObject *)self->wrapped;
|
|
||||||
if (dt->year >= 1)
|
|
||||||
PyOS_snprintf(buf, sizeof(buf) - 1, "'%04ld-%02d-%02d'::date",
|
|
||||||
dt->year, (int)dt->month, (int)dt->day);
|
|
||||||
else
|
|
||||||
PyOS_snprintf(buf, sizeof(buf) - 1, "'%04ld-%02d-%02d BC'::date",
|
|
||||||
1 - dt->year, (int)dt->month, (int)dt->day);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case PSYCO_MXDATETIME_TIMESTAMP:
|
|
||||||
dt = (mxDateTimeObject *)self->wrapped;
|
|
||||||
if (dt->year >= 1)
|
|
||||||
PyOS_snprintf(buf, sizeof(buf) - 1,
|
|
||||||
"'%04ld-%02d-%02dT%02d:%02d:%09.6f'::timestamp",
|
|
||||||
dt->year, (int)dt->month, (int)dt->day,
|
|
||||||
(int)dt->hour, (int)dt->minute, dt->second);
|
|
||||||
else
|
|
||||||
PyOS_snprintf(buf, sizeof(buf) - 1,
|
|
||||||
"'%04ld-%02d-%02dT%02d:%02d:%09.6f BC'::timestamp",
|
|
||||||
1 - dt->year, (int)dt->month, (int)dt->day,
|
|
||||||
(int)dt->hour, (int)dt->minute, dt->second);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case PSYCO_MXDATETIME_TIME:
|
|
||||||
case PSYCO_MXDATETIME_INTERVAL:
|
|
||||||
/* given the limitation of the mx.DateTime module that uses the same
|
|
||||||
type for both time and delta values we need to do some black magic
|
|
||||||
and make sure we're not using an adapt()ed interval as a simple
|
|
||||||
time */
|
|
||||||
dtd = (mxDateTimeDeltaObject *)self->wrapped;
|
|
||||||
if (0 <= dtd->seconds && dtd->seconds < 24*3600) {
|
|
||||||
PyOS_snprintf(buf, sizeof(buf) - 1, "'%02d:%02d:%09.6f'::time",
|
|
||||||
(int)dtd->hour, (int)dtd->minute, dtd->second);
|
|
||||||
} else {
|
|
||||||
double ss = dtd->hour*3600.0 + dtd->minute*60.0 + dtd->second;
|
|
||||||
|
|
||||||
if (dtd->seconds >= 0)
|
|
||||||
PyOS_snprintf(buf, sizeof(buf) - 1, "'%ld days %.6f seconds'::interval",
|
|
||||||
dtd->day, ss);
|
|
||||||
else
|
|
||||||
PyOS_snprintf(buf, sizeof(buf) - 1, "'-%ld days -%.6f seconds'::interval",
|
|
||||||
dtd->day, ss);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return PyString_FromString(buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
static PyObject *
|
|
||||||
mxdatetime_getquoted(mxdatetimeObject *self, PyObject *args)
|
|
||||||
{
|
|
||||||
return mxdatetime_str(self);
|
|
||||||
}
|
|
||||||
|
|
||||||
static PyObject *
|
|
||||||
mxdatetime_conform(mxdatetimeObject *self, PyObject *args)
|
|
||||||
{
|
|
||||||
PyObject *res, *proto;
|
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args, "O", &proto)) return NULL;
|
|
||||||
|
|
||||||
if (proto == (PyObject*)&isqlquoteType)
|
|
||||||
res = (PyObject*)self;
|
|
||||||
else
|
|
||||||
res = Py_None;
|
|
||||||
|
|
||||||
Py_INCREF(res);
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** the MxDateTime object **/
|
|
||||||
|
|
||||||
/* object member list */
|
|
||||||
|
|
||||||
static struct PyMemberDef mxdatetimeObject_members[] = {
|
|
||||||
{"adapted", T_OBJECT, offsetof(mxdatetimeObject, wrapped), READONLY},
|
|
||||||
{"type", T_INT, offsetof(mxdatetimeObject, type), READONLY},
|
|
||||||
{NULL}
|
|
||||||
};
|
|
||||||
|
|
||||||
/* object method table */
|
|
||||||
|
|
||||||
static PyMethodDef mxdatetimeObject_methods[] = {
|
|
||||||
{"getquoted", (PyCFunction)mxdatetime_getquoted, METH_NOARGS,
|
|
||||||
"getquoted() -> wrapped object value as SQL date/time"},
|
|
||||||
{"__conform__", (PyCFunction)mxdatetime_conform, METH_VARARGS, NULL},
|
|
||||||
{NULL} /* Sentinel */
|
|
||||||
};
|
|
||||||
|
|
||||||
/* initialization and finalization methods */
|
|
||||||
|
|
||||||
static int
|
|
||||||
mxdatetime_setup(mxdatetimeObject *self, PyObject *obj, int type)
|
|
||||||
{
|
|
||||||
Dprintf("mxdatetime_setup: init mxdatetime object at %p, refcnt = "
|
|
||||||
FORMAT_CODE_PY_SSIZE_T,
|
|
||||||
self, Py_REFCNT(self)
|
|
||||||
);
|
|
||||||
|
|
||||||
self->type = type;
|
|
||||||
Py_INCREF(obj);
|
|
||||||
self->wrapped = obj;
|
|
||||||
|
|
||||||
Dprintf("mxdatetime_setup: good mxdatetime object at %p, refcnt = "
|
|
||||||
FORMAT_CODE_PY_SSIZE_T,
|
|
||||||
self, Py_REFCNT(self)
|
|
||||||
);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
mxdatetime_dealloc(PyObject* obj)
|
|
||||||
{
|
|
||||||
mxdatetimeObject *self = (mxdatetimeObject *)obj;
|
|
||||||
|
|
||||||
Py_CLEAR(self->wrapped);
|
|
||||||
|
|
||||||
Dprintf("mxdatetime_dealloc: deleted mxdatetime object at %p, refcnt = "
|
|
||||||
FORMAT_CODE_PY_SSIZE_T,
|
|
||||||
obj, Py_REFCNT(obj)
|
|
||||||
);
|
|
||||||
|
|
||||||
Py_TYPE(obj)->tp_free(obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
mxdatetime_init(PyObject *obj, PyObject *args, PyObject *kwds)
|
|
||||||
{
|
|
||||||
PyObject *mx;
|
|
||||||
int type = -1; /* raise an error if type was not passed! */
|
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args, "O|i", &mx, &type))
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
return mxdatetime_setup((mxdatetimeObject *)obj, mx, type);
|
|
||||||
}
|
|
||||||
|
|
||||||
static PyObject *
|
|
||||||
mxdatetime_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
|
|
||||||
{
|
|
||||||
return type->tp_alloc(type, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* object type */
|
|
||||||
|
|
||||||
#define mxdatetimeType_doc \
|
|
||||||
"MxDateTime(mx, type) -> new mx.DateTime wrapper object"
|
|
||||||
|
|
||||||
PyTypeObject mxdatetimeType = {
|
|
||||||
PyVarObject_HEAD_INIT(NULL, 0)
|
|
||||||
"psycopg2._psycopg.MxDateTime",
|
|
||||||
sizeof(mxdatetimeObject), 0,
|
|
||||||
mxdatetime_dealloc, /*tp_dealloc*/
|
|
||||||
0, /*tp_print*/
|
|
||||||
0, /*tp_getattr*/
|
|
||||||
0, /*tp_setattr*/
|
|
||||||
0, /*tp_compare*/
|
|
||||||
0, /*tp_repr*/
|
|
||||||
0, /*tp_as_number*/
|
|
||||||
0, /*tp_as_sequence*/
|
|
||||||
0, /*tp_as_mapping*/
|
|
||||||
0, /*tp_hash */
|
|
||||||
0, /*tp_call*/
|
|
||||||
(reprfunc)mxdatetime_str, /*tp_str*/
|
|
||||||
0, /*tp_getattro*/
|
|
||||||
0, /*tp_setattro*/
|
|
||||||
0, /*tp_as_buffer*/
|
|
||||||
Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /*tp_flags*/
|
|
||||||
mxdatetimeType_doc, /*tp_doc*/
|
|
||||||
0, /*tp_traverse*/
|
|
||||||
0, /*tp_clear*/
|
|
||||||
0, /*tp_richcompare*/
|
|
||||||
0, /*tp_weaklistoffset*/
|
|
||||||
0, /*tp_iter*/
|
|
||||||
0, /*tp_iternext*/
|
|
||||||
mxdatetimeObject_methods, /*tp_methods*/
|
|
||||||
mxdatetimeObject_members, /*tp_members*/
|
|
||||||
0, /*tp_getset*/
|
|
||||||
0, /*tp_base*/
|
|
||||||
0, /*tp_dict*/
|
|
||||||
0, /*tp_descr_get*/
|
|
||||||
0, /*tp_descr_set*/
|
|
||||||
0, /*tp_dictoffset*/
|
|
||||||
mxdatetime_init, /*tp_init*/
|
|
||||||
0, /*tp_alloc*/
|
|
||||||
mxdatetime_new, /*tp_new*/
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/** module-level functions **/
|
|
||||||
|
|
||||||
PyObject *
|
|
||||||
psyco_DateFromMx(PyObject *self, PyObject *args)
|
|
||||||
{
|
|
||||||
PyObject *mx;
|
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args, "O!", mxDateTime.DateTime_Type, &mx))
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
return PyObject_CallFunction((PyObject *)&mxdatetimeType, "Oi", mx,
|
|
||||||
PSYCO_MXDATETIME_DATE);
|
|
||||||
}
|
|
||||||
|
|
||||||
PyObject *
|
|
||||||
psyco_TimeFromMx(PyObject *self, PyObject *args)
|
|
||||||
{
|
|
||||||
PyObject *mx;
|
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args, "O!", mxDateTime.DateTimeDelta_Type, &mx))
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
return PyObject_CallFunction((PyObject *)&mxdatetimeType, "Oi", mx,
|
|
||||||
PSYCO_MXDATETIME_TIME);
|
|
||||||
}
|
|
||||||
|
|
||||||
PyObject *
|
|
||||||
psyco_TimestampFromMx(PyObject *self, PyObject *args)
|
|
||||||
{
|
|
||||||
PyObject *mx;
|
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args, "O!", mxDateTime.DateTime_Type, &mx))
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
return PyObject_CallFunction((PyObject *)&mxdatetimeType, "Oi", mx,
|
|
||||||
PSYCO_MXDATETIME_TIMESTAMP);
|
|
||||||
}
|
|
||||||
|
|
||||||
PyObject *
|
|
||||||
psyco_IntervalFromMx(PyObject *self, PyObject *args)
|
|
||||||
{
|
|
||||||
PyObject *mx;
|
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args, "O!", mxDateTime.DateTime_Type, &mx))
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
return PyObject_CallFunction((PyObject *)&mxdatetimeType, "Oi", mx,
|
|
||||||
PSYCO_MXDATETIME_INTERVAL);
|
|
||||||
}
|
|
|
@ -1,70 +0,0 @@
|
||||||
/* adapter_mxdatetime.h - definition for the mx date/time types
|
|
||||||
*
|
|
||||||
* Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
|
|
||||||
* Copyright (C) 2020 The Psycopg Team
|
|
||||||
*
|
|
||||||
* This file is part of psycopg.
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef PSYCOPG_MXDATETIME_H
|
|
||||||
#define PSYCOPG_MXDATETIME_H 1
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
extern HIDDEN PyTypeObject mxdatetimeType;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
PyObject_HEAD
|
|
||||||
|
|
||||||
PyObject *wrapped;
|
|
||||||
int type;
|
|
||||||
#define PSYCO_MXDATETIME_TIME 0
|
|
||||||
#define PSYCO_MXDATETIME_DATE 1
|
|
||||||
#define PSYCO_MXDATETIME_TIMESTAMP 2
|
|
||||||
#define PSYCO_MXDATETIME_INTERVAL 3
|
|
||||||
|
|
||||||
} mxdatetimeObject;
|
|
||||||
|
|
||||||
HIDDEN int psyco_adapter_mxdatetime_init(void);
|
|
||||||
|
|
||||||
HIDDEN PyObject *psyco_DateFromMx(PyObject *module, PyObject *args);
|
|
||||||
#define psyco_DateFromMx_doc \
|
|
||||||
"DateFromMx(mx) -> new date"
|
|
||||||
|
|
||||||
HIDDEN PyObject *psyco_TimeFromMx(PyObject *module, PyObject *args);
|
|
||||||
#define psyco_TimeFromMx_doc \
|
|
||||||
"TimeFromMx(mx) -> new time"
|
|
||||||
|
|
||||||
HIDDEN PyObject *psyco_TimestampFromMx(PyObject *module, PyObject *args);
|
|
||||||
#define psyco_TimestampFromMx_doc \
|
|
||||||
"TimestampFromMx(mx) -> new timestamp"
|
|
||||||
|
|
||||||
HIDDEN PyObject *psyco_IntervalFromMx(PyObject *module, PyObject *args);
|
|
||||||
#define psyco_IntervalFromMx_doc \
|
|
||||||
"IntervalFromMx(mx) -> new interval"
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif /* !defined(PSYCOPG_MXDATETIME_H) */
|
|
|
@ -1951,10 +1951,11 @@ cursor_setup(cursorObject *self, connectionObject *conn, const char *name)
|
||||||
|
|
||||||
/* default tzinfo factory */
|
/* default tzinfo factory */
|
||||||
{
|
{
|
||||||
|
/* The datetime api doesn't seem to have a constructor to make a
|
||||||
|
* datetime.timezone, so use the Python interface. */
|
||||||
PyObject *m = NULL;
|
PyObject *m = NULL;
|
||||||
if ((m = PyImport_ImportModule("psycopg2.tz"))) {
|
if ((m = PyImport_ImportModule("datetime"))) {
|
||||||
self->tzinfo_factory = PyObject_GetAttrString(
|
self->tzinfo_factory = PyObject_GetAttrString(m, "timezone");
|
||||||
m, "FixedOffsetTimezone");
|
|
||||||
Py_DECREF(m);
|
Py_DECREF(m);
|
||||||
}
|
}
|
||||||
if (!self->tzinfo_factory) {
|
if (!self->tzinfo_factory) {
|
||||||
|
|
|
@ -53,11 +53,6 @@
|
||||||
#include "psycopg/adapter_list.h"
|
#include "psycopg/adapter_list.h"
|
||||||
#include "psycopg/typecast_binary.h"
|
#include "psycopg/typecast_binary.h"
|
||||||
|
|
||||||
#ifdef HAVE_MXDATETIME
|
|
||||||
#include <mxDateTime.h>
|
|
||||||
#include "psycopg/adapter_mxdatetime.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* some module-level variables, like the datetime module */
|
/* some module-level variables, like the datetime module */
|
||||||
#include <datetime.h>
|
#include <datetime.h>
|
||||||
#include "psycopg/adapter_datetime.h"
|
#include "psycopg/adapter_datetime.h"
|
||||||
|
@ -358,30 +353,6 @@ adapters_init(PyObject *module)
|
||||||
if (0 > microprotocols_add(PyDateTimeAPI->DeltaType, NULL, obj)) { goto exit; }
|
if (0 > microprotocols_add(PyDateTimeAPI->DeltaType, NULL, obj)) { goto exit; }
|
||||||
Py_CLEAR(obj);
|
Py_CLEAR(obj);
|
||||||
|
|
||||||
#ifdef HAVE_MXDATETIME
|
|
||||||
/* As above, we use the callable objects from the psycopg module.
|
|
||||||
These object are not be available at runtime if mx.DateTime import
|
|
||||||
failed (e.g. it was available at build time but not at runtime). */
|
|
||||||
if (PyMapping_HasKeyString(dict, "TimestampFromMx")) {
|
|
||||||
if (!(obj = PyMapping_GetItemString(dict, "TimestampFromMx"))) {
|
|
||||||
goto exit;
|
|
||||||
}
|
|
||||||
if (0 > microprotocols_add(mxDateTime.DateTime_Type, NULL, obj)) {
|
|
||||||
goto exit;
|
|
||||||
}
|
|
||||||
Py_CLEAR(obj);
|
|
||||||
|
|
||||||
/* if we found the above, we have this too. */
|
|
||||||
if (!(obj = PyMapping_GetItemString(dict, "TimeFromMx"))) {
|
|
||||||
goto exit;
|
|
||||||
}
|
|
||||||
if (0 > microprotocols_add(mxDateTime.DateTimeDelta_Type, NULL, obj)) {
|
|
||||||
goto exit;
|
|
||||||
}
|
|
||||||
Py_CLEAR(obj);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* Success! */
|
/* Success! */
|
||||||
rv = 0;
|
rv = 0;
|
||||||
|
|
||||||
|
@ -943,34 +914,6 @@ datetime_init(void)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
RAISES_NEG static int
|
|
||||||
mxdatetime_init(PyObject *module)
|
|
||||||
{
|
|
||||||
Dprintf("psycopgmodule: initializing mx.DateTime module");
|
|
||||||
|
|
||||||
#ifdef HAVE_MXDATETIME
|
|
||||||
Py_SET_TYPE(&mxdatetimeType, &PyType_Type);
|
|
||||||
if (0 > PyType_Ready(&mxdatetimeType)) { return -1; }
|
|
||||||
|
|
||||||
if (mxDateTime_ImportModuleAndAPI()) {
|
|
||||||
Dprintf("psycopgmodule: mx.DateTime module import failed");
|
|
||||||
PyErr_Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* If we can't find mx.DateTime objects at runtime,
|
|
||||||
* remove them from the module (and, as consequence, from the adapters). */
|
|
||||||
if (0 != psyco_adapter_mxdatetime_init()) {
|
|
||||||
PyObject *dict;
|
|
||||||
if (!(dict = PyModule_GetDict(module))) { return -1; }
|
|
||||||
if (0 > PyDict_DelItemString(dict, "DateFromMx")) { return -1; }
|
|
||||||
if (0 > PyDict_DelItemString(dict, "TimeFromMx")) { return -1; }
|
|
||||||
if (0 > PyDict_DelItemString(dict, "TimestampFromMx")) { return -1; }
|
|
||||||
if (0 > PyDict_DelItemString(dict, "IntervalFromMx")) { return -1; }
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** method table and module initialization **/
|
/** method table and module initialization **/
|
||||||
|
|
||||||
static PyMethodDef psycopgMethods[] = {
|
static PyMethodDef psycopgMethods[] = {
|
||||||
|
@ -1014,18 +957,6 @@ static PyMethodDef psycopgMethods[] = {
|
||||||
{"IntervalFromPy", (PyCFunction)psyco_IntervalFromPy,
|
{"IntervalFromPy", (PyCFunction)psyco_IntervalFromPy,
|
||||||
METH_VARARGS, psyco_IntervalFromPy_doc},
|
METH_VARARGS, psyco_IntervalFromPy_doc},
|
||||||
|
|
||||||
#ifdef HAVE_MXDATETIME
|
|
||||||
/* to be deleted if not found at import time */
|
|
||||||
{"DateFromMx", (PyCFunction)psyco_DateFromMx,
|
|
||||||
METH_VARARGS, psyco_DateFromMx_doc},
|
|
||||||
{"TimeFromMx", (PyCFunction)psyco_TimeFromMx,
|
|
||||||
METH_VARARGS, psyco_TimeFromMx_doc},
|
|
||||||
{"TimestampFromMx", (PyCFunction)psyco_TimestampFromMx,
|
|
||||||
METH_VARARGS, psyco_TimestampFromMx_doc},
|
|
||||||
{"IntervalFromMx", (PyCFunction)psyco_IntervalFromMx,
|
|
||||||
METH_VARARGS, psyco_IntervalFromMx_doc},
|
|
||||||
#endif
|
|
||||||
|
|
||||||
{"set_wait_callback", (PyCFunction)psyco_set_wait_callback,
|
{"set_wait_callback", (PyCFunction)psyco_set_wait_callback,
|
||||||
METH_O, psyco_set_wait_callback_doc},
|
METH_O, psyco_set_wait_callback_doc},
|
||||||
{"get_wait_callback", (PyCFunction)psyco_get_wait_callback,
|
{"get_wait_callback", (PyCFunction)psyco_get_wait_callback,
|
||||||
|
@ -1086,7 +1017,6 @@ INIT_MODULE(_psycopg)(void)
|
||||||
if (0 > add_module_constants(module)) { goto exit; }
|
if (0 > add_module_constants(module)) { goto exit; }
|
||||||
if (0 > add_module_types(module)) { goto exit; }
|
if (0 > add_module_types(module)) { goto exit; }
|
||||||
if (0 > datetime_init()) { goto exit; }
|
if (0 > datetime_init()) { goto exit; }
|
||||||
if (0 > mxdatetime_init(module)) { goto exit; }
|
|
||||||
if (0 > encodings_init(module)) { goto exit; }
|
if (0 > encodings_init(module)) { goto exit; }
|
||||||
if (0 > typecast_init(module)) { goto exit; }
|
if (0 > typecast_init(module)) { goto exit; }
|
||||||
if (0 > adapters_init(module)) { goto exit; }
|
if (0 > adapters_init(module)) { goto exit; }
|
||||||
|
|
|
@ -32,15 +32,6 @@
|
||||||
|
|
||||||
/* useful function used by some typecasters */
|
/* useful function used by some typecasters */
|
||||||
|
|
||||||
#ifdef HAVE_MXDATETIME
|
|
||||||
static const char *
|
|
||||||
skip_until_space(const char *s)
|
|
||||||
{
|
|
||||||
while (*s && *s != ' ') s++;
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static const char *
|
static const char *
|
||||||
skip_until_space2(const char *s, Py_ssize_t *len)
|
skip_until_space2(const char *s, Py_ssize_t *len)
|
||||||
{
|
{
|
||||||
|
@ -82,11 +73,11 @@ typecast_parse_date(const char* s, const char** t, Py_ssize_t* len,
|
||||||
cz += 1;
|
cz += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Is this a BC date? If so, adjust the year value. Note that
|
/* Is this a BC date? If so, adjust the year value. However
|
||||||
* mx.DateTime numbers BC dates from zero rather than one. The
|
* Python datetime module does not support BC dates, so this will raise
|
||||||
* Python datetime module does not support BC dates at all. */
|
* an exception downstream. */
|
||||||
if (*len >= 2 && s[*len-2] == 'B' && s[*len-1] == 'C')
|
if (*len >= 2 && s[*len-2] == 'B' && s[*len-1] == 'C')
|
||||||
*year = 1 - (*year);
|
*year = -(*year);
|
||||||
|
|
||||||
if (t != NULL) *t = s;
|
if (t != NULL) *t = s;
|
||||||
|
|
||||||
|
@ -175,11 +166,6 @@ typecast_parse_time(const char* s, const char** t, Py_ssize_t* len,
|
||||||
#include "psycopg/typecast_basic.c"
|
#include "psycopg/typecast_basic.c"
|
||||||
#include "psycopg/typecast_binary.c"
|
#include "psycopg/typecast_binary.c"
|
||||||
#include "psycopg/typecast_datetime.c"
|
#include "psycopg/typecast_datetime.c"
|
||||||
|
|
||||||
#ifdef HAVE_MXDATETIME
|
|
||||||
#include "psycopg/typecast_mxdatetime.c"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "psycopg/typecast_array.c"
|
#include "psycopg/typecast_array.c"
|
||||||
|
|
||||||
static long int typecast_default_DEFAULT[] = {0};
|
static long int typecast_default_DEFAULT[] = {0};
|
||||||
|
@ -218,29 +204,6 @@ static typecastObject_initlist typecast_pydatetime[] = {
|
||||||
{NULL, NULL, NULL}
|
{NULL, NULL, NULL}
|
||||||
};
|
};
|
||||||
|
|
||||||
#ifdef HAVE_MXDATETIME
|
|
||||||
#define typecast_MXDATETIMEARRAY_cast typecast_GENERIC_ARRAY_cast
|
|
||||||
#define typecast_MXDATETIMETZARRAY_cast typecast_GENERIC_ARRAY_cast
|
|
||||||
#define typecast_MXDATEARRAY_cast typecast_GENERIC_ARRAY_cast
|
|
||||||
#define typecast_MXTIMEARRAY_cast typecast_GENERIC_ARRAY_cast
|
|
||||||
#define typecast_MXINTERVALARRAY_cast typecast_GENERIC_ARRAY_cast
|
|
||||||
|
|
||||||
/* a list of initializers, used to make the typecasters accessible anyway */
|
|
||||||
static typecastObject_initlist typecast_mxdatetime[] = {
|
|
||||||
{"MXDATETIME", typecast_DATETIME_types, typecast_MXDATE_cast},
|
|
||||||
{"MXDATETIMETZ", typecast_DATETIMETZ_types, typecast_MXDATE_cast},
|
|
||||||
{"MXTIME", typecast_TIME_types, typecast_MXTIME_cast},
|
|
||||||
{"MXDATE", typecast_DATE_types, typecast_MXDATE_cast},
|
|
||||||
{"MXINTERVAL", typecast_INTERVAL_types, typecast_MXINTERVAL_cast},
|
|
||||||
{"MXDATETIMEARRAY", typecast_DATETIMEARRAY_types, typecast_MXDATETIMEARRAY_cast, "MXDATETIME"},
|
|
||||||
{"MXDATETIMETZARRAY", typecast_DATETIMETZARRAY_types, typecast_MXDATETIMETZARRAY_cast, "MXDATETIMETZ"},
|
|
||||||
{"MXTIMEARRAY", typecast_TIMEARRAY_types, typecast_MXTIMEARRAY_cast, "MXTIME"},
|
|
||||||
{"MXDATEARRAY", typecast_DATEARRAY_types, typecast_MXDATEARRAY_cast, "MXDATE"},
|
|
||||||
{"MXINTERVALARRAY", typecast_INTERVALARRAY_types, typecast_MXINTERVALARRAY_cast, "MXINTERVAL"},
|
|
||||||
{NULL, NULL, NULL}
|
|
||||||
};
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
/** the type dictionary and associated functions **/
|
/** the type dictionary and associated functions **/
|
||||||
|
|
||||||
|
@ -291,18 +254,6 @@ typecast_init(PyObject *module)
|
||||||
psyco_default_cast = typecast_from_c(&typecast_default, dict);
|
psyco_default_cast = typecast_from_c(&typecast_default, dict);
|
||||||
|
|
||||||
/* register the date/time typecasters with their original names */
|
/* register the date/time typecasters with their original names */
|
||||||
#ifdef HAVE_MXDATETIME
|
|
||||||
if (0 == typecast_mxdatetime_init()) {
|
|
||||||
for (i = 0; typecast_mxdatetime[i].name != NULL; i++) {
|
|
||||||
t = (typecastObject *)typecast_from_c(&(typecast_mxdatetime[i]), dict);
|
|
||||||
if (t == NULL) { goto exit; }
|
|
||||||
PyDict_SetItem(dict, t->name, (PyObject *)t);
|
|
||||||
Py_DECREF((PyObject *)t);
|
|
||||||
t = NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (0 > typecast_datetime_init()) { goto exit; }
|
if (0 > typecast_datetime_init()) { goto exit; }
|
||||||
for (i = 0; typecast_pydatetime[i].name != NULL; i++) {
|
for (i = 0; typecast_pydatetime[i].name != NULL; i++) {
|
||||||
t = (typecastObject *)typecast_from_c(&(typecast_pydatetime[i]), dict);
|
t = (typecastObject *)typecast_from_c(&(typecast_pydatetime[i]), dict);
|
||||||
|
|
|
@ -104,9 +104,18 @@ _parse_inftz(const char *str, PyObject *curs)
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(tzinfo = PyObject_CallFunction(tzinfo_factory, "i", 0))) {
|
#if PY_VERSION_HEX < 0x03070000
|
||||||
goto exit;
|
{
|
||||||
|
PyObject *tzoff;
|
||||||
|
if (!(tzoff = PyDelta_FromDSU(0, 0, 0))) { goto exit; }
|
||||||
|
tzinfo = PyObject_CallFunctionObjArgs(tzinfo_factory, tzoff, NULL);
|
||||||
|
Py_DECREF(tzoff);
|
||||||
|
if (!tzinfo) { goto exit; }
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
tzinfo = PyDateTime_TimeZone_UTC;
|
||||||
|
Py_INCREF(tzinfo);
|
||||||
|
#endif
|
||||||
|
|
||||||
/* m.replace(tzinfo=tzinfo) */
|
/* m.replace(tzinfo=tzinfo) */
|
||||||
if (!(args = PyTuple_New(0))) { goto exit; }
|
if (!(args = PyTuple_New(0))) { goto exit; }
|
||||||
|
@ -129,10 +138,11 @@ static PyObject *
|
||||||
_parse_noninftz(const char *str, Py_ssize_t len, PyObject *curs)
|
_parse_noninftz(const char *str, Py_ssize_t len, PyObject *curs)
|
||||||
{
|
{
|
||||||
PyObject* rv = NULL;
|
PyObject* rv = NULL;
|
||||||
|
PyObject *tzoff = NULL;
|
||||||
PyObject *tzinfo = NULL;
|
PyObject *tzinfo = NULL;
|
||||||
PyObject *tzinfo_factory;
|
PyObject *tzinfo_factory;
|
||||||
int n, y=0, m=0, d=0;
|
int n, y=0, m=0, d=0;
|
||||||
int hh=0, mm=0, ss=0, us=0, tz=0;
|
int hh=0, mm=0, ss=0, us=0, tzsec=0;
|
||||||
const char *tp = NULL;
|
const char *tp = NULL;
|
||||||
|
|
||||||
Dprintf("typecast_PYDATETIMETZ_cast: s = %s", str);
|
Dprintf("typecast_PYDATETIMETZ_cast: s = %s", str);
|
||||||
|
@ -147,11 +157,11 @@ _parse_noninftz(const char *str, Py_ssize_t len, PyObject *curs)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (len > 0) {
|
if (len > 0) {
|
||||||
n = typecast_parse_time(tp, NULL, &len, &hh, &mm, &ss, &us, &tz);
|
n = typecast_parse_time(tp, NULL, &len, &hh, &mm, &ss, &us, &tzsec);
|
||||||
Dprintf("typecast_PYDATETIMETZ_cast: n = %d,"
|
Dprintf("typecast_PYDATETIMETZ_cast: n = %d,"
|
||||||
" len = " FORMAT_CODE_PY_SSIZE_T ","
|
" len = " FORMAT_CODE_PY_SSIZE_T ","
|
||||||
" hh = %d, mm = %d, ss = %d, us = %d, tz = %d",
|
" hh = %d, mm = %d, ss = %d, us = %d, tzsec = %d",
|
||||||
n, len, hh, mm, ss, us, tz);
|
n, len, hh, mm, ss, us, tzsec);
|
||||||
if (n < 3 || n > 6) {
|
if (n < 3 || n > 6) {
|
||||||
PyErr_SetString(DataError, "unable to parse time");
|
PyErr_SetString(DataError, "unable to parse time");
|
||||||
goto exit;
|
goto exit;
|
||||||
|
@ -169,17 +179,20 @@ _parse_noninftz(const char *str, Py_ssize_t len, PyObject *curs)
|
||||||
if (n >= 5 && tzinfo_factory != Py_None) {
|
if (n >= 5 && tzinfo_factory != Py_None) {
|
||||||
/* we have a time zone, calculate minutes and create
|
/* we have a time zone, calculate minutes and create
|
||||||
appropriate tzinfo object calling the factory */
|
appropriate tzinfo object calling the factory */
|
||||||
Dprintf("typecast_PYDATETIMETZ_cast: UTC offset = %ds", tz);
|
Dprintf("typecast_PYDATETIMETZ_cast: UTC offset = %ds", tzsec);
|
||||||
|
|
||||||
/* The datetime module requires that time zone offsets be
|
#if PY_VERSION_HEX < 0x03070000
|
||||||
a whole number of minutes, so truncate the seconds to the
|
/* Before Python 3.7 the timezone offset had to be a whole number
|
||||||
closest minute. */
|
* of minutes, so round the seconds to the closest minute */
|
||||||
// printf("%d %d %d\n", tz, tzmin, round(tz / 60.0));
|
tzsec = 60 * (int)round(tzsec / 60.0);
|
||||||
if (!(tzinfo = PyObject_CallFunction(tzinfo_factory, "i",
|
#endif
|
||||||
(int)round(tz / 60.0)))) {
|
if (!(tzoff = PyDelta_FromDSU(0, tzsec, 0))) { goto exit; }
|
||||||
|
if (!(tzinfo = PyObject_CallFunctionObjArgs(
|
||||||
|
tzinfo_factory, tzoff, NULL))) {
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
Py_INCREF(Py_None);
|
Py_INCREF(Py_None);
|
||||||
tzinfo = Py_None;
|
tzinfo = Py_None;
|
||||||
}
|
}
|
||||||
|
@ -192,6 +205,7 @@ _parse_noninftz(const char *str, Py_ssize_t len, PyObject *curs)
|
||||||
y, m, d, hh, mm, ss, us, tzinfo);
|
y, m, d, hh, mm, ss, us, tzinfo);
|
||||||
|
|
||||||
exit:
|
exit:
|
||||||
|
Py_XDECREF(tzoff);
|
||||||
Py_XDECREF(tzinfo);
|
Py_XDECREF(tzinfo);
|
||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
|
@ -232,17 +246,18 @@ typecast_PYDATETIMETZ_cast(const char *str, Py_ssize_t len, PyObject *curs)
|
||||||
static PyObject *
|
static PyObject *
|
||||||
typecast_PYTIME_cast(const char *str, Py_ssize_t len, PyObject *curs)
|
typecast_PYTIME_cast(const char *str, Py_ssize_t len, PyObject *curs)
|
||||||
{
|
{
|
||||||
PyObject* obj = NULL;
|
PyObject* rv = NULL;
|
||||||
|
PyObject *tzoff = NULL;
|
||||||
PyObject *tzinfo = NULL;
|
PyObject *tzinfo = NULL;
|
||||||
PyObject *tzinfo_factory;
|
PyObject *tzinfo_factory;
|
||||||
int n, hh=0, mm=0, ss=0, us=0, tz=0;
|
int n, hh=0, mm=0, ss=0, us=0, tzsec=0;
|
||||||
|
|
||||||
if (str == NULL) { Py_RETURN_NONE; }
|
if (str == NULL) { Py_RETURN_NONE; }
|
||||||
|
|
||||||
n = typecast_parse_time(str, NULL, &len, &hh, &mm, &ss, &us, &tz);
|
n = typecast_parse_time(str, NULL, &len, &hh, &mm, &ss, &us, &tzsec);
|
||||||
Dprintf("typecast_PYTIME_cast: n = %d, len = " FORMAT_CODE_PY_SSIZE_T ", "
|
Dprintf("typecast_PYTIME_cast: n = %d, len = " FORMAT_CODE_PY_SSIZE_T ", "
|
||||||
"hh = %d, mm = %d, ss = %d, us = %d, tz = %d",
|
"hh = %d, mm = %d, ss = %d, us = %d, tzsec = %d",
|
||||||
n, len, hh, mm, ss, us, tz);
|
n, len, hh, mm, ss, us, tzsec);
|
||||||
|
|
||||||
if (n < 3 || n > 6) {
|
if (n < 3 || n > 6) {
|
||||||
PyErr_SetString(DataError, "unable to parse time");
|
PyErr_SetString(DataError, "unable to parse time");
|
||||||
|
@ -254,25 +269,32 @@ typecast_PYTIME_cast(const char *str, Py_ssize_t len, PyObject *curs)
|
||||||
}
|
}
|
||||||
tzinfo_factory = ((cursorObject *)curs)->tzinfo_factory;
|
tzinfo_factory = ((cursorObject *)curs)->tzinfo_factory;
|
||||||
if (n >= 5 && tzinfo_factory != Py_None) {
|
if (n >= 5 && tzinfo_factory != Py_None) {
|
||||||
/* we have a time zone, calculate minutes and create
|
/* we have a time zone, calculate seconds and create
|
||||||
appropriate tzinfo object calling the factory */
|
appropriate tzinfo object calling the factory */
|
||||||
Dprintf("typecast_PYTIME_cast: UTC offset = %ds", tz);
|
Dprintf("typecast_PYTIME_cast: UTC offset = %ds", tzsec);
|
||||||
|
|
||||||
/* The datetime module requires that time zone offsets be
|
#if PY_VERSION_HEX < 0x03070000
|
||||||
a whole number of minutes, so truncate the seconds to the
|
/* Before Python 3.7 the timezone offset had to be a whole number
|
||||||
closest minute. */
|
* of minutes, so round the seconds to the closest minute */
|
||||||
tzinfo = PyObject_CallFunction(tzinfo_factory, "i",
|
tzsec = 60 * (int)round(tzsec / 60.0);
|
||||||
(int)round(tz / 60.0));
|
#endif
|
||||||
} else {
|
if (!(tzoff = PyDelta_FromDSU(0, tzsec, 0))) { goto exit; }
|
||||||
|
if (!(tzinfo = PyObject_CallFunctionObjArgs(tzinfo_factory, tzoff, NULL))) {
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
Py_INCREF(Py_None);
|
Py_INCREF(Py_None);
|
||||||
tzinfo = Py_None;
|
tzinfo = Py_None;
|
||||||
}
|
}
|
||||||
if (tzinfo != NULL) {
|
|
||||||
obj = PyObject_CallFunction((PyObject*)PyDateTimeAPI->TimeType, "iiiiO",
|
rv = PyObject_CallFunction((PyObject*)PyDateTimeAPI->TimeType, "iiiiO",
|
||||||
hh, mm, ss, us, tzinfo);
|
hh, mm, ss, us, tzinfo);
|
||||||
Py_DECREF(tzinfo);
|
|
||||||
}
|
exit:
|
||||||
return obj;
|
Py_XDECREF(tzoff);
|
||||||
|
Py_XDECREF(tzinfo);
|
||||||
|
return rv;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,241 +0,0 @@
|
||||||
/* typecast_mxdatetime.c - date and time typecasting functions to mx types
|
|
||||||
*
|
|
||||||
* Copyright (C) 2001-2019 Federico Di Gregorio <fog@debian.org>
|
|
||||||
* Copyright (C) 2020 The Psycopg Team
|
|
||||||
*
|
|
||||||
* This file is part of psycopg.
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "mxDateTime.h"
|
|
||||||
|
|
||||||
|
|
||||||
/* Return 0 on success, -1 on failure, but don't set an exception */
|
|
||||||
|
|
||||||
static int
|
|
||||||
typecast_mxdatetime_init(void)
|
|
||||||
{
|
|
||||||
if (mxDateTime_ImportModuleAndAPI()) {
|
|
||||||
Dprintf("typecast_mxdatetime_init: mx.DateTime initialization failed");
|
|
||||||
PyErr_Clear();
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/** DATE - cast a date into mx.DateTime python object **/
|
|
||||||
|
|
||||||
static PyObject *
|
|
||||||
typecast_MXDATE_cast(const char *str, Py_ssize_t len, PyObject *curs)
|
|
||||||
{
|
|
||||||
int n, y=0, m=0, d=0;
|
|
||||||
int hh=0, mm=0, ss=0, us=0, tz=0;
|
|
||||||
const char *tp = NULL;
|
|
||||||
|
|
||||||
if (str == NULL) { Py_RETURN_NONE; }
|
|
||||||
|
|
||||||
Dprintf("typecast_MXDATE_cast: s = %s", str);
|
|
||||||
|
|
||||||
/* check for infinity */
|
|
||||||
if (!strcmp(str, "infinity") || !strcmp(str, "-infinity")) {
|
|
||||||
if (str[0] == '-') {
|
|
||||||
return mxDateTime.DateTime_FromDateAndTime(-999998,1,1, 0,0,0);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return mxDateTime.DateTime_FromDateAndTime(999999,12,31, 0,0,0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
n = typecast_parse_date(str, &tp, &len, &y, &m, &d);
|
|
||||||
Dprintf("typecast_MXDATE_cast: tp = %p n = %d,"
|
|
||||||
" len = " FORMAT_CODE_PY_SSIZE_T ","
|
|
||||||
" y = %d, m = %d, d = %d", tp, n, len, y, m, d);
|
|
||||||
if (n != 3) {
|
|
||||||
PyErr_SetString(DataError, "unable to parse date");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (len > 0) {
|
|
||||||
n = typecast_parse_time(tp, NULL, &len, &hh, &mm, &ss, &us, &tz);
|
|
||||||
Dprintf("typecast_MXDATE_cast: n = %d,"
|
|
||||||
" len = " FORMAT_CODE_PY_SSIZE_T ","
|
|
||||||
" hh = %d, mm = %d, ss = %d, us = %d, tz = %d",
|
|
||||||
n, len, hh, mm, ss, us, tz);
|
|
||||||
if (n != 0 && (n < 3 || n > 6)) {
|
|
||||||
PyErr_SetString(DataError, "unable to parse time");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Dprintf("typecast_MXDATE_cast: fractionary seconds: %lf",
|
|
||||||
(double)ss + (double)us/(double)1000000.0);
|
|
||||||
return mxDateTime.DateTime_FromDateAndTime(y, m, d, hh, mm,
|
|
||||||
(double)ss + (double)us/(double)1000000.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** TIME - parse time into an mx.DateTime object **/
|
|
||||||
|
|
||||||
static PyObject *
|
|
||||||
typecast_MXTIME_cast(const char *str, Py_ssize_t len, PyObject *curs)
|
|
||||||
{
|
|
||||||
int n, hh=0, mm=0, ss=0, us=0, tz=0;
|
|
||||||
|
|
||||||
if (str == NULL) { Py_RETURN_NONE; }
|
|
||||||
|
|
||||||
Dprintf("typecast_MXTIME_cast: s = %s", str);
|
|
||||||
|
|
||||||
n = typecast_parse_time(str, NULL, &len, &hh, &mm, &ss, &us, &tz);
|
|
||||||
Dprintf("typecast_MXTIME_cast: time parsed, %d components", n);
|
|
||||||
Dprintf("typecast_MXTIME_cast: hh = %d, mm = %d, ss = %d, us = %d",
|
|
||||||
hh, mm, ss, us);
|
|
||||||
|
|
||||||
if (n < 3 || n > 6) {
|
|
||||||
PyErr_SetString(DataError, "unable to parse time");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
Dprintf("typecast_MXTIME_cast: fractionary seconds: %lf",
|
|
||||||
(double)ss + (double)us/(double)1000000.0);
|
|
||||||
return mxDateTime.DateTimeDelta_FromTime(hh, mm,
|
|
||||||
(double)ss + (double)us/(double)1000000.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** INTERVAL - parse an interval into an mx.DateTimeDelta **/
|
|
||||||
|
|
||||||
static PyObject *
|
|
||||||
typecast_MXINTERVAL_cast(const char *str, Py_ssize_t len, PyObject *curs)
|
|
||||||
{
|
|
||||||
long years = 0, months = 0, days = 0, denominator = 1;
|
|
||||||
double hours = 0.0, minutes = 0.0, seconds = 0.0, hundredths = 0.0;
|
|
||||||
double v = 0.0, sign = 1.0;
|
|
||||||
int part = 0;
|
|
||||||
|
|
||||||
if (str == NULL) { Py_RETURN_NONE; }
|
|
||||||
|
|
||||||
Dprintf("typecast_MXINTERVAL_cast: s = %s", str);
|
|
||||||
|
|
||||||
while (*str) {
|
|
||||||
switch (*str) {
|
|
||||||
|
|
||||||
case '-':
|
|
||||||
sign = -1.0;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case '0': case '1': case '2': case '3': case '4':
|
|
||||||
case '5': case '6': case '7': case '8': case '9':
|
|
||||||
v = v * 10.0 + (double)(*str - '0');
|
|
||||||
Dprintf("typecast_MXINTERVAL_cast: v = %f", v);
|
|
||||||
if (part == 6){
|
|
||||||
denominator *= 10;
|
|
||||||
Dprintf("typecast_MXINTERVAL_cast: denominator = %ld",
|
|
||||||
denominator);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'y':
|
|
||||||
if (part == 0) {
|
|
||||||
years = (long)(v*sign);
|
|
||||||
str = skip_until_space(str);
|
|
||||||
Dprintf("typecast_MXINTERVAL_cast: years = %ld, rest = %s",
|
|
||||||
years, str);
|
|
||||||
v = 0.0; sign = 1.0; part = 1;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'm':
|
|
||||||
if (part <= 1) {
|
|
||||||
months = (long)(v*sign);
|
|
||||||
str = skip_until_space(str);
|
|
||||||
Dprintf("typecast_MXINTERVAL_cast: months = %ld, rest = %s",
|
|
||||||
months, str);
|
|
||||||
v = 0.0; sign = 1.0; part = 2;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'd':
|
|
||||||
if (part <= 2) {
|
|
||||||
days = (long)(v*sign);
|
|
||||||
str = skip_until_space(str);
|
|
||||||
Dprintf("typecast_MXINTERVAL_cast: days = %ld, rest = %s",
|
|
||||||
days, str);
|
|
||||||
v = 0.0; sign = 1.0; part = 3;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ':':
|
|
||||||
if (part <= 3) {
|
|
||||||
hours = v;
|
|
||||||
Dprintf("typecast_MXINTERVAL_cast: hours = %f", hours);
|
|
||||||
v = 0.0; part = 4;
|
|
||||||
}
|
|
||||||
else if (part == 4) {
|
|
||||||
minutes = v;
|
|
||||||
Dprintf("typecast_MXINTERVAL_cast: minutes = %f", minutes);
|
|
||||||
v = 0.0; part = 5;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case '.':
|
|
||||||
if (part == 5) {
|
|
||||||
seconds = v;
|
|
||||||
Dprintf("typecast_MXINTERVAL_cast: seconds = %f", seconds);
|
|
||||||
v = 0.0; part = 6;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
str++;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* manage last value, be it minutes or seconds or hundredths of a second */
|
|
||||||
if (part == 4) {
|
|
||||||
minutes = v;
|
|
||||||
Dprintf("typecast_MXINTERVAL_cast: minutes = %f", minutes);
|
|
||||||
}
|
|
||||||
else if (part == 5) {
|
|
||||||
seconds = v;
|
|
||||||
Dprintf("typecast_MXINTERVAL_cast: seconds = %f", seconds);
|
|
||||||
}
|
|
||||||
else if (part == 6) {
|
|
||||||
hundredths = v;
|
|
||||||
Dprintf("typecast_MXINTERVAL_cast: hundredths = %f", hundredths);
|
|
||||||
hundredths = hundredths/denominator;
|
|
||||||
Dprintf("typecast_MXINTERVAL_cast: fractions = %.20f", hundredths);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* calculates seconds */
|
|
||||||
if (sign < 0.0) {
|
|
||||||
seconds = - (hundredths + seconds + minutes*60 + hours*3600);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
seconds += hundredths + minutes*60 + hours*3600;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* calculates days */
|
|
||||||
days += years*365 + months*30;
|
|
||||||
|
|
||||||
Dprintf("typecast_MXINTERVAL_cast: days = %ld, seconds = %f",
|
|
||||||
days, seconds);
|
|
||||||
return mxDateTime.DateTimeDelta_FromDaysAndSeconds(days, seconds);
|
|
||||||
}
|
|
|
@ -58,7 +58,6 @@
|
||||||
<None Include="psycopg\adapter_binary.h" />
|
<None Include="psycopg\adapter_binary.h" />
|
||||||
<None Include="psycopg\adapter_datetime.h" />
|
<None Include="psycopg\adapter_datetime.h" />
|
||||||
<None Include="psycopg\adapter_list.h" />
|
<None Include="psycopg\adapter_list.h" />
|
||||||
<None Include="psycopg\adapter_mxdatetime.h" />
|
|
||||||
<None Include="psycopg\adapter_pboolean.h" />
|
<None Include="psycopg\adapter_pboolean.h" />
|
||||||
<None Include="psycopg\adapter_qstring.h" />
|
<None Include="psycopg\adapter_qstring.h" />
|
||||||
<None Include="psycopg\config.h" />
|
<None Include="psycopg\config.h" />
|
||||||
|
@ -166,7 +165,6 @@
|
||||||
<Compile Include="psycopg\adapter_binary.c" />
|
<Compile Include="psycopg\adapter_binary.c" />
|
||||||
<Compile Include="psycopg\adapter_datetime.c" />
|
<Compile Include="psycopg\adapter_datetime.c" />
|
||||||
<Compile Include="psycopg\adapter_list.c" />
|
<Compile Include="psycopg\adapter_list.c" />
|
||||||
<Compile Include="psycopg\adapter_mxdatetime.c" />
|
|
||||||
<Compile Include="psycopg\adapter_pboolean.c" />
|
<Compile Include="psycopg\adapter_pboolean.c" />
|
||||||
<Compile Include="psycopg\adapter_qstring.c" />
|
<Compile Include="psycopg\adapter_qstring.c" />
|
||||||
<Compile Include="psycopg\connection_int.c" />
|
<Compile Include="psycopg\connection_int.c" />
|
||||||
|
@ -187,7 +185,6 @@
|
||||||
<Compile Include="psycopg\typecast_binary.c" />
|
<Compile Include="psycopg\typecast_binary.c" />
|
||||||
<Compile Include="psycopg\typecast_builtins.c" />
|
<Compile Include="psycopg\typecast_builtins.c" />
|
||||||
<Compile Include="psycopg\typecast_datetime.c" />
|
<Compile Include="psycopg\typecast_datetime.c" />
|
||||||
<Compile Include="psycopg\typecast_mxdatetime.c" />
|
|
||||||
<Compile Include="psycopg\utils.c" />
|
<Compile Include="psycopg\utils.c" />
|
||||||
<Compile Include="psycopg\win32_support.c" />
|
<Compile Include="psycopg\win32_support.c" />
|
||||||
<Compile Include="psycopg\lobject_int.c" />
|
<Compile Include="psycopg\lobject_int.c" />
|
||||||
|
|
|
@ -1,16 +1,12 @@
|
||||||
[build_ext]
|
[build_ext]
|
||||||
# PSYCOPG_DEBUG can be added to enable verbose debug information
|
# PSYCOPG_DEBUG can be added to enable verbose debug information
|
||||||
define=
|
define=PSYCOPG_DEBUG
|
||||||
|
|
||||||
# "pg_config" is required to locate PostgreSQL headers and libraries needed to
|
# "pg_config" is required to locate PostgreSQL headers and libraries needed to
|
||||||
# build psycopg2. If pg_config is not in the path or is installed under a
|
# build psycopg2. If pg_config is not in the path or is installed under a
|
||||||
# different name set the following option to the pg_config full path.
|
# different name set the following option to the pg_config full path.
|
||||||
pg_config=
|
pg_config=
|
||||||
|
|
||||||
# If the build system does not find the mx.DateTime headers, try
|
|
||||||
# setting its value to the right path.
|
|
||||||
mx_include_dir=
|
|
||||||
|
|
||||||
# For Windows only:
|
# For Windows only:
|
||||||
# Set to 1 if the PostgreSQL library was built with OpenSSL.
|
# Set to 1 if the PostgreSQL library was built with OpenSSL.
|
||||||
# Required to link in OpenSSL libraries and dependencies.
|
# Required to link in OpenSSL libraries and dependencies.
|
||||||
|
|
19
setup.py
19
setup.py
|
@ -230,7 +230,6 @@ class psycopg_build_ext(build_ext):
|
||||||
def initialize_options(self):
|
def initialize_options(self):
|
||||||
build_ext.initialize_options(self)
|
build_ext.initialize_options(self)
|
||||||
self.pgdir = None
|
self.pgdir = None
|
||||||
self.mx_include_dir = None
|
|
||||||
self.have_ssl = have_ssl
|
self.have_ssl = have_ssl
|
||||||
self.static_libpq = static_libpq
|
self.static_libpq = static_libpq
|
||||||
self.pg_config = None
|
self.pg_config = None
|
||||||
|
@ -497,24 +496,6 @@ depends = [
|
||||||
parser = configparser.ConfigParser()
|
parser = configparser.ConfigParser()
|
||||||
parser.read('setup.cfg')
|
parser.read('setup.cfg')
|
||||||
|
|
||||||
# check for mx package
|
|
||||||
mxincludedir = ''
|
|
||||||
if parser.has_option('build_ext', 'mx_include_dir'):
|
|
||||||
mxincludedir = parser.get('build_ext', 'mx_include_dir')
|
|
||||||
if not mxincludedir:
|
|
||||||
# look for mxDateTime.h; prefer one located in venv
|
|
||||||
candidate_dirs = [os.path.join(d, 'mx', 'DateTime', 'mxDateTime') for d in sys.path] \
|
|
||||||
+ [os.path.join(get_python_inc(plat_specific=1), "mx")]
|
|
||||||
candidate_dirs = [d for d in candidate_dirs if os.path.exists(os.path.join(d, 'mxDateTime.h'))] or ['']
|
|
||||||
mxincludedir = candidate_dirs[0]
|
|
||||||
if mxincludedir.strip() and os.path.exists(mxincludedir):
|
|
||||||
# Build the support for mx: we will check at runtime if it can be imported
|
|
||||||
include_dirs.append(mxincludedir)
|
|
||||||
define_macros.append(('HAVE_MXDATETIME', '1'))
|
|
||||||
sources.append('adapter_mxdatetime.c')
|
|
||||||
depends.extend(['adapter_mxdatetime.h', 'typecast_mxdatetime.c'])
|
|
||||||
version_flags.append('mx')
|
|
||||||
|
|
||||||
# generate a nice version string to avoid confusion when users report bugs
|
# generate a nice version string to avoid confusion when users report bugs
|
||||||
version_flags.append('pq3') # no more a choice
|
version_flags.append('pq3') # no more a choice
|
||||||
version_flags.append('ext') # no more a choice
|
version_flags.append('ext') # no more a choice
|
||||||
|
|
|
@ -23,21 +23,16 @@
|
||||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
||||||
# License for more details.
|
# License for more details.
|
||||||
|
|
||||||
|
import sys
|
||||||
import math
|
import math
|
||||||
import pickle
|
import pickle
|
||||||
from datetime import date, datetime, time, timedelta
|
from datetime import date, datetime, time, timedelta, timezone
|
||||||
|
|
||||||
import psycopg2
|
import psycopg2
|
||||||
from psycopg2.tz import FixedOffsetTimezone, ZERO
|
from psycopg2.tz import FixedOffsetTimezone, ZERO
|
||||||
import unittest
|
import unittest
|
||||||
from .testutils import ConnectingTestCase, skip_before_postgres, skip_if_crdb
|
from .testutils import ConnectingTestCase, skip_before_postgres, skip_if_crdb
|
||||||
|
|
||||||
try:
|
|
||||||
from mx.DateTime import Date, Time, DateTime, DateTimeDeltaFrom
|
|
||||||
except ImportError:
|
|
||||||
# Tests will be skipped
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def total_seconds(d):
|
def total_seconds(d):
|
||||||
"""Return total number of seconds of a timedelta as a float."""
|
"""Return total number of seconds of a timedelta as a float."""
|
||||||
|
@ -157,17 +152,27 @@ class DatetimeTests(ConnectingTestCase, CommonDatetimeTestsMixin):
|
||||||
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)
|
||||||
self.check_time_tz("-01:15", -4500)
|
self.check_time_tz("-01:15", -4500)
|
||||||
# The Python datetime module does not support time zone
|
if sys.version_info < (3, 7):
|
||||||
# offsets that are not a whole number of minutes.
|
# The Python < 3.7 datetime module does not support time zone
|
||||||
# We round the offset to the nearest minute.
|
# offsets that are not a whole number of minutes.
|
||||||
self.check_time_tz("+01:15:00", 60 * (60 + 15))
|
# We round the offset to the nearest minute.
|
||||||
self.check_time_tz("+01:15:29", 60 * (60 + 15))
|
self.check_time_tz("+01:15:00", 60 * (60 + 15))
|
||||||
self.check_time_tz("+01:15:30", 60 * (60 + 16))
|
self.check_time_tz("+01:15:29", 60 * (60 + 15))
|
||||||
self.check_time_tz("+01:15:59", 60 * (60 + 16))
|
self.check_time_tz("+01:15:30", 60 * (60 + 16))
|
||||||
self.check_time_tz("-01:15:00", -60 * (60 + 15))
|
self.check_time_tz("+01:15:59", 60 * (60 + 16))
|
||||||
self.check_time_tz("-01:15:29", -60 * (60 + 15))
|
self.check_time_tz("-01:15:00", -60 * (60 + 15))
|
||||||
self.check_time_tz("-01:15:30", -60 * (60 + 16))
|
self.check_time_tz("-01:15:29", -60 * (60 + 15))
|
||||||
self.check_time_tz("-01:15:59", -60 * (60 + 16))
|
self.check_time_tz("-01:15:30", -60 * (60 + 16))
|
||||||
|
self.check_time_tz("-01:15:59", -60 * (60 + 16))
|
||||||
|
else:
|
||||||
|
self.check_time_tz("+01:15:00", 60 * (60 + 15))
|
||||||
|
self.check_time_tz("+01:15:29", 60 * (60 + 15) + 29)
|
||||||
|
self.check_time_tz("+01:15:30", 60 * (60 + 15) + 30)
|
||||||
|
self.check_time_tz("+01:15:59", 60 * (60 + 15) + 59)
|
||||||
|
self.check_time_tz("-01:15:00", -(60 * (60 + 15)))
|
||||||
|
self.check_time_tz("-01:15:29", -(60 * (60 + 15) + 29))
|
||||||
|
self.check_time_tz("-01:15:30", -(60 * (60 + 15) + 30))
|
||||||
|
self.check_time_tz("-01:15:59", -(60 * (60 + 15) + 59))
|
||||||
|
|
||||||
def check_datetime_tz(self, str_offset, offset):
|
def check_datetime_tz(self, str_offset, offset):
|
||||||
base = datetime(2007, 1, 1, 13, 30, 29)
|
base = datetime(2007, 1, 1, 13, 30, 29)
|
||||||
|
@ -183,26 +188,52 @@ class DatetimeTests(ConnectingTestCase, CommonDatetimeTestsMixin):
|
||||||
self.assertEqual(value.replace(tzinfo=None), base)
|
self.assertEqual(value.replace(tzinfo=None), base)
|
||||||
|
|
||||||
# Conversion to UTC produces the expected offset.
|
# Conversion to UTC produces the expected offset.
|
||||||
UTC = FixedOffsetTimezone(0, "UTC")
|
UTC = timezone(timedelta(0))
|
||||||
value_utc = value.astimezone(UTC).replace(tzinfo=None)
|
value_utc = value.astimezone(UTC).replace(tzinfo=None)
|
||||||
self.assertEqual(base - value_utc, timedelta(seconds=offset))
|
self.assertEqual(base - value_utc, timedelta(seconds=offset))
|
||||||
|
|
||||||
|
def test_default_tzinfo(self):
|
||||||
|
self.curs.execute("select '2000-01-01 00:00+02:00'::timestamptz")
|
||||||
|
dt = self.curs.fetchone()[0]
|
||||||
|
self.assert_(isinstance(dt.tzinfo, timezone))
|
||||||
|
self.assertEqual(dt,
|
||||||
|
datetime(2000, 1, 1, tzinfo=timezone(timedelta(minutes=120))))
|
||||||
|
|
||||||
|
def test_fotz_tzinfo(self):
|
||||||
|
self.curs.tzinfo_factory = FixedOffsetTimezone
|
||||||
|
self.curs.execute("select '2000-01-01 00:00+02:00'::timestamptz")
|
||||||
|
dt = self.curs.fetchone()[0]
|
||||||
|
self.assert_(not isinstance(dt.tzinfo, timezone))
|
||||||
|
self.assert_(isinstance(dt.tzinfo, FixedOffsetTimezone))
|
||||||
|
self.assertEqual(dt,
|
||||||
|
datetime(2000, 1, 1, tzinfo=timezone(timedelta(minutes=120))))
|
||||||
|
|
||||||
def test_parse_datetime_timezone(self):
|
def test_parse_datetime_timezone(self):
|
||||||
self.check_datetime_tz("+01", 3600)
|
self.check_datetime_tz("+01", 3600)
|
||||||
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)
|
||||||
self.check_datetime_tz("-01:15", -4500)
|
self.check_datetime_tz("-01:15", -4500)
|
||||||
# The Python datetime module does not support time zone
|
if sys.version_info < (3, 7):
|
||||||
# offsets that are not a whole number of minutes.
|
# The Python < 3.7 datetime module does not support time zone
|
||||||
# We round the offset to the nearest minute.
|
# offsets that are not a whole number of minutes.
|
||||||
self.check_datetime_tz("+01:15:00", 60 * (60 + 15))
|
# We round the offset to the nearest minute.
|
||||||
self.check_datetime_tz("+01:15:29", 60 * (60 + 15))
|
self.check_datetime_tz("+01:15:00", 60 * (60 + 15))
|
||||||
self.check_datetime_tz("+01:15:30", 60 * (60 + 16))
|
self.check_datetime_tz("+01:15:29", 60 * (60 + 15))
|
||||||
self.check_datetime_tz("+01:15:59", 60 * (60 + 16))
|
self.check_datetime_tz("+01:15:30", 60 * (60 + 16))
|
||||||
self.check_datetime_tz("-01:15:00", -60 * (60 + 15))
|
self.check_datetime_tz("+01:15:59", 60 * (60 + 16))
|
||||||
self.check_datetime_tz("-01:15:29", -60 * (60 + 15))
|
self.check_datetime_tz("-01:15:00", -60 * (60 + 15))
|
||||||
self.check_datetime_tz("-01:15:30", -60 * (60 + 16))
|
self.check_datetime_tz("-01:15:29", -60 * (60 + 15))
|
||||||
self.check_datetime_tz("-01:15:59", -60 * (60 + 16))
|
self.check_datetime_tz("-01:15:30", -60 * (60 + 16))
|
||||||
|
self.check_datetime_tz("-01:15:59", -60 * (60 + 16))
|
||||||
|
else:
|
||||||
|
self.check_datetime_tz("+01:15:00", 60 * (60 + 15))
|
||||||
|
self.check_datetime_tz("+01:15:29", 60 * (60 + 15) + 29)
|
||||||
|
self.check_datetime_tz("+01:15:30", 60 * (60 + 15) + 30)
|
||||||
|
self.check_datetime_tz("+01:15:59", 60 * (60 + 15) + 59)
|
||||||
|
self.check_datetime_tz("-01:15:00", -(60 * (60 + 15)))
|
||||||
|
self.check_datetime_tz("-01:15:29", -(60 * (60 + 15) + 29))
|
||||||
|
self.check_datetime_tz("-01:15:30", -(60 * (60 + 15) + 30))
|
||||||
|
self.check_datetime_tz("-01:15:59", -(60 * (60 + 15) + 59))
|
||||||
|
|
||||||
def test_parse_time_no_timezone(self):
|
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", self.curs).tzinfo, None)
|
||||||
|
@ -286,7 +317,7 @@ class DatetimeTests(ConnectingTestCase, CommonDatetimeTestsMixin):
|
||||||
self.assertEqual(None, dt.tzinfo)
|
self.assertEqual(None, dt.tzinfo)
|
||||||
|
|
||||||
def test_type_roundtrip_datetimetz(self):
|
def test_type_roundtrip_datetimetz(self):
|
||||||
tz = FixedOffsetTimezone(8 * 60)
|
tz = timezone(timedelta(minutes=8 * 60))
|
||||||
dt1 = datetime(2010, 5, 3, 10, 20, 30, tzinfo=tz)
|
dt1 = datetime(2010, 5, 3, 10, 20, 30, tzinfo=tz)
|
||||||
dt2 = self._test_type_roundtrip(dt1)
|
dt2 = self._test_type_roundtrip(dt1)
|
||||||
self.assertNotEqual(None, dt2.tzinfo)
|
self.assertNotEqual(None, dt2.tzinfo)
|
||||||
|
@ -297,7 +328,7 @@ class DatetimeTests(ConnectingTestCase, CommonDatetimeTestsMixin):
|
||||||
self.assertEqual(None, tm.tzinfo)
|
self.assertEqual(None, tm.tzinfo)
|
||||||
|
|
||||||
def test_type_roundtrip_timetz(self):
|
def test_type_roundtrip_timetz(self):
|
||||||
tz = FixedOffsetTimezone(8 * 60)
|
tz = timezone(timedelta(minutes=8 * 60))
|
||||||
tm1 = time(10, 20, 30, tzinfo=tz)
|
tm1 = time(10, 20, 30, tzinfo=tz)
|
||||||
tm2 = self._test_type_roundtrip(tm1)
|
tm2 = self._test_type_roundtrip(tm1)
|
||||||
self.assertNotEqual(None, tm2.tzinfo)
|
self.assertNotEqual(None, tm2.tzinfo)
|
||||||
|
@ -314,7 +345,7 @@ class DatetimeTests(ConnectingTestCase, CommonDatetimeTestsMixin):
|
||||||
|
|
||||||
def test_type_roundtrip_datetimetz_array(self):
|
def test_type_roundtrip_datetimetz_array(self):
|
||||||
self._test_type_roundtrip_array(
|
self._test_type_roundtrip_array(
|
||||||
datetime(2010, 5, 3, 10, 20, 30, tzinfo=FixedOffsetTimezone(0)))
|
datetime(2010, 5, 3, 10, 20, 30, tzinfo=timezone(timedelta(0))))
|
||||||
|
|
||||||
def test_type_roundtrip_time_array(self):
|
def test_type_roundtrip_time_array(self):
|
||||||
self._test_type_roundtrip_array(time(10, 20, 30))
|
self._test_type_roundtrip_array(time(10, 20, 30))
|
||||||
|
@ -328,10 +359,10 @@ class DatetimeTests(ConnectingTestCase, CommonDatetimeTestsMixin):
|
||||||
self.assertEqual(t, time(0, 0))
|
self.assertEqual(t, time(0, 0))
|
||||||
|
|
||||||
t = self.execute("select '24:00+05'::timetz;")
|
t = self.execute("select '24:00+05'::timetz;")
|
||||||
self.assertEqual(t, time(0, 0, tzinfo=FixedOffsetTimezone(300)))
|
self.assertEqual(t, time(0, 0, tzinfo=timezone(timedelta(minutes=300))))
|
||||||
|
|
||||||
t = self.execute("select '24:00+05:30'::timetz;")
|
t = self.execute("select '24:00+05:30'::timetz;")
|
||||||
self.assertEqual(t, time(0, 0, tzinfo=FixedOffsetTimezone(330)))
|
self.assertEqual(t, time(0, 0, tzinfo=timezone(timedelta(minutes=330))))
|
||||||
|
|
||||||
@skip_before_postgres(8, 1)
|
@skip_before_postgres(8, 1)
|
||||||
def test_large_interval(self):
|
def test_large_interval(self):
|
||||||
|
@ -399,11 +430,11 @@ class DatetimeTests(ConnectingTestCase, CommonDatetimeTestsMixin):
|
||||||
|
|
||||||
t = self.execute("select 'infinity'::timestamptz")
|
t = self.execute("select 'infinity'::timestamptz")
|
||||||
self.assert_(t.tzinfo is not None)
|
self.assert_(t.tzinfo is not None)
|
||||||
self.assert_(t > datetime(4000, 1, 1, tzinfo=FixedOffsetTimezone()))
|
self.assert_(t > datetime(4000, 1, 1, tzinfo=timezone(timedelta(0))))
|
||||||
|
|
||||||
t = self.execute("select '-infinity'::timestamptz")
|
t = self.execute("select '-infinity'::timestamptz")
|
||||||
self.assert_(t.tzinfo is not None)
|
self.assert_(t.tzinfo is not None)
|
||||||
self.assert_(t < datetime(1000, 1, 1, tzinfo=FixedOffsetTimezone()))
|
self.assert_(t < datetime(1000, 1, 1, tzinfo=timezone(timedelta(0))))
|
||||||
|
|
||||||
def test_redshift_day(self):
|
def test_redshift_day(self):
|
||||||
# Redshift is reported returning 1 day interval as microsec (bug #558)
|
# Redshift is reported returning 1 day interval as microsec (bug #558)
|
||||||
|
@ -435,169 +466,6 @@ class DatetimeTests(ConnectingTestCase, CommonDatetimeTestsMixin):
|
||||||
self.assertRaises(psycopg2.NotSupportedError, cur.fetchone)
|
self.assertRaises(psycopg2.NotSupportedError, cur.fetchone)
|
||||||
|
|
||||||
|
|
||||||
@unittest.skipUnless(
|
|
||||||
hasattr(psycopg2._psycopg, 'MXDATETIME'),
|
|
||||||
'Requires mx.DateTime support'
|
|
||||||
)
|
|
||||||
class mxDateTimeTests(ConnectingTestCase, CommonDatetimeTestsMixin):
|
|
||||||
"""Tests for the mx.DateTime based date handling in psycopg2."""
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
ConnectingTestCase.setUp(self)
|
|
||||||
self.curs = self.conn.cursor()
|
|
||||||
self.DATE = psycopg2._psycopg.MXDATE
|
|
||||||
self.TIME = psycopg2._psycopg.MXTIME
|
|
||||||
self.DATETIME = psycopg2._psycopg.MXDATETIME
|
|
||||||
self.INTERVAL = psycopg2._psycopg.MXINTERVAL
|
|
||||||
|
|
||||||
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)
|
|
||||||
psycopg2.extensions.register_type(
|
|
||||||
psycopg2.extensions.MXDATETIMEARRAY, self.conn)
|
|
||||||
psycopg2.extensions.register_type(
|
|
||||||
psycopg2.extensions.MXINTERVALARRAY, self.conn)
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
self.conn.close()
|
|
||||||
|
|
||||||
def test_parse_bc_date(self):
|
|
||||||
value = self.DATE('00042-01-01 BC', self.curs)
|
|
||||||
self.assert_(value is not None)
|
|
||||||
# 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):
|
|
||||||
value = self.DATETIME('00042-01-01 13:30:29 BC', self.curs)
|
|
||||||
self.assert_(value is not None)
|
|
||||||
# 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):
|
|
||||||
value = self.TIME('13:30:29.123456', self.curs)
|
|
||||||
self.assertEqual(math.floor(value.second), 29)
|
|
||||||
self.assertEqual(
|
|
||||||
int((value.second - math.floor(value.second)) * 1000000), 123456)
|
|
||||||
|
|
||||||
def test_parse_datetime_microseconds(self):
|
|
||||||
value = self.DATETIME('2007-01-01 13:30:29.123456', self.curs)
|
|
||||||
self.assertEqual(math.floor(value.second), 29)
|
|
||||||
self.assertEqual(
|
|
||||||
int((value.second - math.floor(value.second)) * 1000000), 123456)
|
|
||||||
|
|
||||||
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))
|
|
||||||
|
|
||||||
def test_parse_interval(self):
|
|
||||||
value = self.INTERVAL('42 days 05:50:05', self.curs)
|
|
||||||
self.assert_(value is not None)
|
|
||||||
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)])
|
|
||||||
# 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)
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
def test_adapt_negative_timedelta(self):
|
|
||||||
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)
|
|
||||||
|
|
||||||
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):
|
|
||||||
self._test_type_roundtrip(Date(2010, 5, 3))
|
|
||||||
|
|
||||||
def test_type_roundtrip_datetime(self):
|
|
||||||
self._test_type_roundtrip(DateTime(2010, 5, 3, 10, 20, 30))
|
|
||||||
|
|
||||||
def test_type_roundtrip_time(self):
|
|
||||||
self._test_type_roundtrip(Time(10, 20, 30))
|
|
||||||
|
|
||||||
def test_type_roundtrip_interval(self):
|
|
||||||
self._test_type_roundtrip(DateTimeDeltaFrom(seconds=30))
|
|
||||||
|
|
||||||
def test_type_roundtrip_date_array(self):
|
|
||||||
self._test_type_roundtrip_array(Date(2010, 5, 3))
|
|
||||||
|
|
||||||
def test_type_roundtrip_datetime_array(self):
|
|
||||||
self._test_type_roundtrip_array(DateTime(2010, 5, 3, 10, 20, 30))
|
|
||||||
|
|
||||||
def test_type_roundtrip_time_array(self):
|
|
||||||
self._test_type_roundtrip_array(Time(10, 20, 30))
|
|
||||||
|
|
||||||
def test_type_roundtrip_interval_array(self):
|
|
||||||
self._test_type_roundtrip_array(DateTimeDeltaFrom(seconds=30))
|
|
||||||
|
|
||||||
|
|
||||||
class FromTicksTestCase(unittest.TestCase):
|
class FromTicksTestCase(unittest.TestCase):
|
||||||
# bug "TimestampFromTicks() throws ValueError (2-2.0.14)"
|
# bug "TimestampFromTicks() throws ValueError (2-2.0.14)"
|
||||||
# reported by Jozsef Szalay on 2010-05-06
|
# reported by Jozsef Szalay on 2010-05-06
|
||||||
|
@ -605,7 +473,7 @@ class FromTicksTestCase(unittest.TestCase):
|
||||||
s = psycopg2.TimestampFromTicks(1273173119.99992)
|
s = psycopg2.TimestampFromTicks(1273173119.99992)
|
||||||
self.assertEqual(s.adapted,
|
self.assertEqual(s.adapted,
|
||||||
datetime(2010, 5, 6, 14, 11, 59, 999920,
|
datetime(2010, 5, 6, 14, 11, 59, 999920,
|
||||||
tzinfo=FixedOffsetTimezone(-5 * 60)))
|
tzinfo=timezone(timedelta(minutes=-5 * 60))))
|
||||||
|
|
||||||
def test_date_value_error_sec_59_99(self):
|
def test_date_value_error_sec_59_99(self):
|
||||||
s = psycopg2.DateFromTicks(1273173119.99992)
|
s = psycopg2.DateFromTicks(1273173119.99992)
|
||||||
|
@ -628,17 +496,27 @@ class FixedOffsetTimezoneTests(unittest.TestCase):
|
||||||
def test_repr_with_positive_offset(self):
|
def test_repr_with_positive_offset(self):
|
||||||
tzinfo = FixedOffsetTimezone(5 * 60)
|
tzinfo = FixedOffsetTimezone(5 * 60)
|
||||||
self.assertEqual(repr(tzinfo),
|
self.assertEqual(repr(tzinfo),
|
||||||
"psycopg2.tz.FixedOffsetTimezone(offset=300, name=None)")
|
"psycopg2.tz.FixedOffsetTimezone(offset=%r, name=None)"
|
||||||
|
% timedelta(minutes=5 * 60))
|
||||||
|
|
||||||
def test_repr_with_negative_offset(self):
|
def test_repr_with_negative_offset(self):
|
||||||
tzinfo = FixedOffsetTimezone(-5 * 60)
|
tzinfo = FixedOffsetTimezone(-5 * 60)
|
||||||
self.assertEqual(repr(tzinfo),
|
self.assertEqual(repr(tzinfo),
|
||||||
"psycopg2.tz.FixedOffsetTimezone(offset=-300, name=None)")
|
"psycopg2.tz.FixedOffsetTimezone(offset=%r, name=None)"
|
||||||
|
% timedelta(minutes=-5 * 60))
|
||||||
|
|
||||||
|
def test_init_with_timedelta(self):
|
||||||
|
td = timedelta(minutes=5 * 60)
|
||||||
|
tzinfo = FixedOffsetTimezone(td)
|
||||||
|
self.assertEqual(tzinfo, FixedOffsetTimezone(5 * 60))
|
||||||
|
self.assertEqual(repr(tzinfo),
|
||||||
|
"psycopg2.tz.FixedOffsetTimezone(offset=%r, name=None)" % td)
|
||||||
|
|
||||||
def test_repr_with_name(self):
|
def test_repr_with_name(self):
|
||||||
tzinfo = FixedOffsetTimezone(name="FOO")
|
tzinfo = FixedOffsetTimezone(name="FOO")
|
||||||
self.assertEqual(repr(tzinfo),
|
self.assertEqual(repr(tzinfo),
|
||||||
"psycopg2.tz.FixedOffsetTimezone(offset=0, name='FOO')")
|
"psycopg2.tz.FixedOffsetTimezone(offset=%r, name='FOO')"
|
||||||
|
% timedelta(0))
|
||||||
|
|
||||||
def test_instance_caching(self):
|
def test_instance_caching(self):
|
||||||
self.assert_(FixedOffsetTimezone(name="FOO")
|
self.assert_(FixedOffsetTimezone(name="FOO")
|
||||||
|
|
|
@ -20,7 +20,7 @@ import json
|
||||||
import uuid
|
import uuid
|
||||||
import warnings
|
import warnings
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from datetime import date, datetime
|
from datetime import date, datetime, timedelta, timezone
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from pickle import dumps, loads
|
from pickle import dumps, loads
|
||||||
|
|
||||||
|
@ -38,7 +38,6 @@ from psycopg2.extras import (
|
||||||
Inet, Json, NumericRange, Range, RealDictConnection,
|
Inet, Json, NumericRange, Range, RealDictConnection,
|
||||||
register_composite, register_hstore, register_range,
|
register_composite, register_hstore, register_range,
|
||||||
)
|
)
|
||||||
from psycopg2.tz import FixedOffsetTimezone
|
|
||||||
|
|
||||||
|
|
||||||
class TypesExtrasTests(ConnectingTestCase):
|
class TypesExtrasTests(ConnectingTestCase):
|
||||||
|
@ -1282,7 +1281,7 @@ class RangeTestCase(unittest.TestCase):
|
||||||
Date-Time ranges should return a human-readable string as well on
|
Date-Time ranges should return a human-readable string as well on
|
||||||
string conversion.
|
string conversion.
|
||||||
'''
|
'''
|
||||||
tz = FixedOffsetTimezone(-5 * 60, "EST")
|
tz = timezone(timedelta(minutes=-5 * 60), "EST")
|
||||||
r = DateTimeTZRange(datetime(2010, 1, 1, tzinfo=tz),
|
r = DateTimeTZRange(datetime(2010, 1, 1, tzinfo=tz),
|
||||||
datetime(2011, 1, 1, tzinfo=tz))
|
datetime(2011, 1, 1, tzinfo=tz))
|
||||||
expected = '[2010-01-01 00:00:00-05:00, 2011-01-01 00:00:00-05:00)'
|
expected = '[2010-01-01 00:00:00-05:00, 2011-01-01 00:00:00-05:00)'
|
||||||
|
@ -1377,9 +1376,9 @@ class RangeCasterTestCase(ConnectingTestCase):
|
||||||
|
|
||||||
def test_cast_timestamptz(self):
|
def test_cast_timestamptz(self):
|
||||||
cur = self.conn.cursor()
|
cur = self.conn.cursor()
|
||||||
ts1 = datetime(2000, 1, 1, tzinfo=FixedOffsetTimezone(600))
|
ts1 = datetime(2000, 1, 1, tzinfo=timezone(timedelta(minutes=600)))
|
||||||
ts2 = datetime(2000, 12, 31, 23, 59, 59, 999,
|
ts2 = datetime(2000, 12, 31, 23, 59, 59, 999,
|
||||||
tzinfo=FixedOffsetTimezone(600))
|
tzinfo=timezone(timedelta(minutes=600)))
|
||||||
cur.execute("select tstzrange(%s, %s, '[]')", (ts1, ts2))
|
cur.execute("select tstzrange(%s, %s, '[]')", (ts1, ts2))
|
||||||
r = cur.fetchone()[0]
|
r = cur.fetchone()[0]
|
||||||
self.assert_(isinstance(r, DateTimeTZRange))
|
self.assert_(isinstance(r, DateTimeTZRange))
|
||||||
|
@ -1465,9 +1464,9 @@ class RangeCasterTestCase(ConnectingTestCase):
|
||||||
self.assert_(isinstance(r1, DateTimeRange))
|
self.assert_(isinstance(r1, DateTimeRange))
|
||||||
self.assert_(r1.isempty)
|
self.assert_(r1.isempty)
|
||||||
|
|
||||||
ts1 = datetime(2000, 1, 1, tzinfo=FixedOffsetTimezone(600))
|
ts1 = datetime(2000, 1, 1, tzinfo=timezone(timedelta(minutes=600)))
|
||||||
ts2 = datetime(2000, 12, 31, 23, 59, 59, 999,
|
ts2 = datetime(2000, 12, 31, 23, 59, 59, 999,
|
||||||
tzinfo=FixedOffsetTimezone(600))
|
tzinfo=timezone(timedelta(minutes=600)))
|
||||||
r = DateTimeTZRange(ts1, ts2, '(]')
|
r = DateTimeTZRange(ts1, ts2, '(]')
|
||||||
cur.execute("select %s", (r,))
|
cur.execute("select %s", (r,))
|
||||||
r1 = cur.fetchone()[0]
|
r1 = cur.fetchone()[0]
|
||||||
|
|
Loading…
Reference in New Issue
Block a user