Skip to content

Commit

Permalink
Hotfix release v1.1.5
Browse files Browse the repository at this point in the history
  - Fix issue #3 splitting xs:gYear type, creating an unordered base
    type XPathGregorianYear and then deriving GregorianYear10 also from
    OrderedDateTime to implement comparison operators.
  - Rewritten in the same way the remaining gregorian types, inverting
    the bases (put XSD 1.0 only types as the base).
  • Loading branch information
brunato committed Feb 23, 2019
1 parent 36fcdba commit a1506e8
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 60 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
CHANGELOG
*********

`v1.1.5`_ (2019-02-23)
======================
* Differentiated unordered XPath gregorian types from ordered types for XSD
* Fix issue #2

`v1.1.4`_ (2019-02-21)
======================
* Implementation of a full Static Analysis Phase at parse() level
Expand Down Expand Up @@ -105,3 +110,4 @@ handling of static context evaluation
.. _v1.1.2: https://github.com/brunato/elementpath/compare/v1.1.1...v1.1.2
.. _v1.1.3: https://github.com/brunato/elementpath/compare/v1.1.2...v1.1.3
.. _v1.1.4: https://github.com/brunato/elementpath/compare/v1.1.3...v1.1.4
.. _v1.1.5: https://github.com/brunato/elementpath/compare/v1.1.4...v1.1.5
94 changes: 59 additions & 35 deletions elementpath/datatypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ class AbstractDateTime(object):
A class for representing XSD date/time objects. It uses and internal datetime.datetime
attribute and an integer attribute for processing BCE years or for years after 9999 CE.
"""
version = '1.1'
version = '1.0'
_pattern = re.compile(r'^$')
_utc_timezone = Timezone(datetime.timedelta(0))

Expand Down Expand Up @@ -238,9 +238,9 @@ def iso_year(self):
"""The ISO string representation of the year field."""
year = self.year
if -9999 <= year < -1:
return '{:05}'.format(year + 1 if self.version == '1.1' else year)
return '{:05}'.format(year if self.version == '1.0' else year + 1)
elif year == -1:
return '{:04}'.format(year + 1 if self.version == '1.1' else year)
return '{:04}'.format(year if self.version == '1.0' else year + 1)
elif 0 <= year <= 9999:
return '{:04}'.format(year)
else:
Expand Down Expand Up @@ -306,7 +306,7 @@ def fromstring(cls, datetime_string, tzinfo=None):
kwargs['microsecond'] = 0 if pow10 < 0 else kwargs['microsecond'] * 10**pow10

year = kwargs.get('year')
if year is not None and year <= 0 and cls.version == '1.1':
if year is not None and year <= 0 and cls.version != '1.0':
kwargs['year'] -= 1
return cls(**kwargs)

Expand Down Expand Up @@ -415,7 +415,7 @@ def fromdelta(cls, delta, adjust_timezone=False):
else:
year = dt.year

if issubclass(cls, Date):
if issubclass(cls, Date10):
if adjust_timezone and dt.hour or dt.minute:
if dt.tzinfo is None:
hour, minute = dt.hour, dt.minute
Expand Down Expand Up @@ -521,15 +521,15 @@ def __sub__(self, other):
return self._date_operator(operator.sub, other)


class DateTime(OrderedDateTime):
"""Class for representing xs:dateTime data."""
class DateTime10(OrderedDateTime):
"""XSD 1.0 xs:dateTime builtin type"""
_pattern = re.compile(
r'^(?P<year>(?:-)?[0-9]*[0-9]{4})-(?P<month>[0-9]{2})-(?P<day>[0-9]{2})'
r'(T(?P<hour>[0-9]{2}):(?P<minute>[0-9]{2}):(?P<second>[0-9]{2})(?:\.(?P<microsecond>[0-9]+))?)?'
r'(?P<tzinfo>Z|[+-](?:(?:0[0-9]|1[0-3]):[0-5][0-9]|14:00))?$')

def __init__(self, year, month, day, hour=0, minute=0, second=0, microsecond=0, tzinfo=None):
super(DateTime, self).__init__(year, month, day, hour, minute, second, microsecond, tzinfo)
super(DateTime10, self).__init__(year, month, day, hour, minute, second, microsecond, tzinfo)

def __str__(self):
if self.microsecond:
Expand All @@ -542,94 +542,118 @@ def __str__(self):
)


class DateTime10(DateTime):
version = '1.0'
class DateTime(DateTime10):
"""XSD 1.1 xs:dateTime builtin type"""
version = '1.1'


class Date(OrderedDateTime):
"""Class for representing xs:date data."""
class Date10(OrderedDateTime):
"""XSD 1.0 xs:date builtin type"""
_pattern = re.compile(r'^(?P<year>(?:-)?[0-9]*[0-9]{4})-(?P<month>[0-9]{2})-(?P<day>[0-9]{2})'
r'(?P<tzinfo>Z|[+-](?:(?:0[0-9]|1[0-3]):[0-5][0-9]|14:00))?$')

def __init__(self, year, month, day, tzinfo=None):
super(Date, self).__init__(year, month, day, tzinfo=tzinfo)
super(Date10, self).__init__(year, month, day, tzinfo=tzinfo)

def __str__(self):
return '{}-{:02}-{:02}{}'.format(self.iso_year, self.month, self.day, str(self.tzinfo or ''))


class Date10(Date):
version = '1.0'
class Date(Date10):
"""XSD 1.1 xs:date builtin type"""
version = '1.1'


class GregorianDay(AbstractDateTime):
"""Class for representing xs:gDay data."""
class XPathGregorianDay(AbstractDateTime):
"""xs:gDay datatype for XPath expressions"""
_pattern = re.compile(r'^---(?P<day>[0-9]{2})(?P<tzinfo>Z|[+-](?:(?:0[0-9]|1[0-3]):[0-5][0-9]|14:00))?$')

def __init__(self, day, tzinfo=None):
super(GregorianDay, self).__init__(day=day, tzinfo=tzinfo)
super(XPathGregorianDay, self).__init__(day=day, tzinfo=tzinfo)

def __str__(self):
return '---{:02}{}'.format(self.day, str(self.tzinfo or ''))


class GregorianMonth(AbstractDateTime):
"""Class for representing xs:gMonth data."""
class GregorianDay(XPathGregorianDay, OrderedDateTime):
"""XSD xs:gDay builtin type"""


class XPathGregorianMonth(AbstractDateTime):
"""xs:gMonth datatype for XPath expressions"""
_pattern = re.compile(r'^--(?P<month>[0-9]{2})(?P<tzinfo>Z|[+-](?:(?:0[0-9]|1[0-3]):[0-5][0-9]|14:00))?$')

def __init__(self, month, tzinfo=None):
super(GregorianMonth, self).__init__(month=month, tzinfo=tzinfo)
super(XPathGregorianMonth, self).__init__(month=month, tzinfo=tzinfo)

def __str__(self):
return '--{:02}{}'.format(self.month, str(self.tzinfo or ''))


class GregorianMonthDay(AbstractDateTime):
"""Class for representing xs:gMonthDay data."""
class GregorianMonth(XPathGregorianMonth, OrderedDateTime):
"""XSD xs:gMonth builtin type"""


class XPathGregorianMonthDay(AbstractDateTime):
"""xs:gMonthDay datatype for XPath expressions"""
_pattern = re.compile(r'^--(?P<month>[0-9]{2})-(?P<day>[0-9]{2})'
r'(?P<tzinfo>Z|[+-](?:(?:0[0-9]|1[0-3]):[0-5][0-9]|14:00))?$')

def __init__(self, month, day, tzinfo=None):
super(GregorianMonthDay, self).__init__(month=month, day=day, tzinfo=tzinfo)
super(XPathGregorianMonthDay, self).__init__(month=month, day=day, tzinfo=tzinfo)

def __str__(self):
return '--{:02}-{:02}{}'.format(self.month, self.day, str(self.tzinfo or ''))


class GregorianYear(AbstractDateTime):
"""Class for representing xs:gYear data."""
class GregorianMonthDay(XPathGregorianMonthDay, OrderedDateTime):
"""XSD xs:gMonthDay builtin type"""


class XPathGregorianYear(AbstractDateTime):
"""xs:gYear datatype for XPath expressions"""
_pattern = re.compile(r'^(?P<year>(?:-)?[0-9]*[0-9]{4})'
r'(?P<tzinfo>Z|[+-](?:(?:0[0-9]|1[0-3]):[0-5][0-9]|14:00))?$')

def __init__(self, year, tzinfo=None):
super(GregorianYear, self).__init__(year, tzinfo=tzinfo)
super(XPathGregorianYear, self).__init__(year, tzinfo=tzinfo)

def __str__(self):
return '{}{}'.format(self.iso_year, str(self.tzinfo or ''))


class GregorianYear10(GregorianYear):
version = '1.0'
class GregorianYear10(XPathGregorianYear, OrderedDateTime):
"""XSD 1.0 xs:gYear builtin type"""


class GregorianYear(GregorianYear10):
"""XSD 1.1 xs:gYear builtin type"""
version = '1.1'


class GregorianYearMonth(AbstractDateTime):
"""Class for representing xs:gYearMonth data."""
class XPathGregorianYearMonth(AbstractDateTime):
"""xs:gYearMonth datatype for XPath expressions"""
_pattern = re.compile(r'^(?P<year>(?:-)?[0-9]*[0-9]{4})-(?P<month>[0-9]{2})'
r'(?P<tzinfo>Z|[+-](?:(?:0[0-9]|1[0-3]):[0-5][0-9]|14:00))?$')

def __init__(self, year, month, tzinfo=None):
super(GregorianYearMonth, self).__init__(year, month, tzinfo=tzinfo)
super(XPathGregorianYearMonth, self).__init__(year, month, tzinfo=tzinfo)

def __str__(self):
return '{}-{:02}{}'.format(self.iso_year, self.month, str(self.tzinfo or ''))


class GregorianYearMonth10(GregorianYearMonth):
version = '1.0'
class GregorianYearMonth10(XPathGregorianYearMonth, OrderedDateTime):
"""XSD 1.0 xs:gYearMonth builtin type"""


class GregorianYearMonth(GregorianYearMonth10):
"""XSD 1.1 xs:gYearMonth builtin type"""
version = '1.1'


class Time(AbstractDateTime):
"""Class for representing xs:time data."""
"""XSD xs:time builtin type"""
_pattern = re.compile(
r'^(?P<hour>[0-9]{2}):(?P<minute>[0-9]{2}):(?P<second>[0-9]{2})(?:\.(?P<microsecond>[0-9]+))?'
r'(?P<tzinfo>Z|[+-](?:(?:0[0-9]|1[0-3]):[0-5][0-9]|14:00))?$')
Expand Down
18 changes: 9 additions & 9 deletions elementpath/xpath2_constructors.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@
from .compat import unicode_type, urlparse, URLError, string_base_type
from .exceptions import ElementPathError, xpath_error
from .xpath_helpers import is_attribute_node, boolean_value, string_value
from .datatypes import DateTime10, Date10, Time, GregorianDay, GregorianMonth, GregorianMonthDay, \
GregorianYear10, GregorianYearMonth10, UntypedAtomic, Duration, YearMonthDuration, DayTimeDuration, \
WHITESPACES_PATTERN, QNAME_PATTERN, NMTOKEN_PATTERN, NAME_PATTERN, NCNAME_PATTERN, HEX_BINARY_PATTERN, \
NOT_BASE64_BINARY_PATTERN, LANGUAGE_CODE_PATTERN, WRONG_ESCAPE_PATTERN
from .datatypes import DateTime10, Date10, Time, XPathGregorianDay, XPathGregorianMonth, \
XPathGregorianMonthDay, XPathGregorianYear, XPathGregorianYearMonth, UntypedAtomic, Duration, \
YearMonthDuration, DayTimeDuration, WHITESPACES_PATTERN, QNAME_PATTERN, NMTOKEN_PATTERN, NAME_PATTERN, \
NCNAME_PATTERN, HEX_BINARY_PATTERN, NOT_BASE64_BINARY_PATTERN, LANGUAGE_CODE_PATTERN, WRONG_ESCAPE_PATTERN
from .xpath2_functions import XPath2Parser


Expand Down Expand Up @@ -217,27 +217,27 @@ def cast(value, tz=None):

@constructor('gDay')
def cast(value, tz=None):
return GregorianDay.fromstring(value, tzinfo=tz)
return XPathGregorianDay.fromstring(value, tzinfo=tz)


@constructor('gMonth')
def cast(value, tz=None):
return GregorianMonth.fromstring(value, tzinfo=tz)
return XPathGregorianMonth.fromstring(value, tzinfo=tz)


@constructor('gMonthDay')
def cast(value, tz=None):
return GregorianMonthDay.fromstring(value, tzinfo=tz)
return XPathGregorianMonthDay.fromstring(value, tzinfo=tz)


@constructor('gYear')
def cast(value, tz=None):
return GregorianYear10.fromstring(value, tzinfo=tz)
return XPathGregorianYear.fromstring(value, tzinfo=tz)


@constructor('gYearMonth')
def cast(value, tz=None):
return GregorianYearMonth10.fromstring(value, tzinfo=tz)
return XPathGregorianYearMonth.fromstring(value, tzinfo=tz)


@constructor('time')
Expand Down
32 changes: 16 additions & 16 deletions elementpath/xpath2_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import unicodedata

from .compat import PY3, string_base_type, unicode_chr, urlparse, urljoin, urllib_quote, unicode_type
from .datatypes import QNAME_PATTERN, DateTime, Date, Time, Timezone, Duration, DayTimeDuration
from .datatypes import QNAME_PATTERN, DateTime10, Date10, Time, Timezone, Duration, DayTimeDuration
from .namespaces import prefixed_to_qname, get_namespace
from .xpath_context import XPathSchemaContext
from .xpath_helpers import is_document_node, is_xpath_node, is_element_node, is_attribute_node, \
Expand Down Expand Up @@ -817,67 +817,67 @@ def evaluate(self, context=None):

@method(function('year-from-dateTime', nargs=1))
def evaluate(self, context=None):
item = self.get_argument(context, cls=DateTime)
item = self.get_argument(context, cls=DateTime10)
return [] if item is None else -(item.year + 1) if item.bce else item.year


@method(function('month-from-dateTime', nargs=1))
def evaluate(self, context=None):
item = self.get_argument(context, cls=DateTime)
item = self.get_argument(context, cls=DateTime10)
return [] if item is None else item.month


@method(function('day-from-dateTime', nargs=1))
def evaluate(self, context=None):
item = self.get_argument(context, cls=DateTime)
item = self.get_argument(context, cls=DateTime10)
return [] if item is None else item.day


@method(function('hours-from-dateTime', nargs=1))
def evaluate(self, context=None):
item = self.get_argument(context, cls=DateTime)
item = self.get_argument(context, cls=DateTime10)
return [] if item is None else item.hour


@method(function('minutes-from-dateTime', nargs=1))
def evaluate(self, context=None):
item = self.get_argument(context, cls=DateTime)
item = self.get_argument(context, cls=DateTime10)
return [] if item is None else item.minute


@method(function('seconds-from-dateTime', nargs=1))
def evaluate(self, context=None):
item = self.get_argument(context, cls=DateTime)
item = self.get_argument(context, cls=DateTime10)
return [] if item is None else item.second


@method(function('timezone-from-dateTime', nargs=1))
def evaluate(self, context=None):
item = self.get_argument(context, cls=DateTime)
item = self.get_argument(context, cls=DateTime10)
return [] if item is None else DayTimeDuration(seconds=item.tzinfo.offset.total_seconds())


@method(function('year-from-date', nargs=1))
def evaluate(self, context=None):
item = self.get_argument(context, cls=Date)
item = self.get_argument(context, cls=Date10)
return [] if item is None else item.year


@method(function('month-from-date', nargs=1))
def evaluate(self, context=None):
item = self.get_argument(context, cls=Date)
item = self.get_argument(context, cls=Date10)
return [] if item is None else item.month


@method(function('day-from-date', nargs=1))
def evaluate(self, context=None):
item = self.get_argument(context, cls=Date)
item = self.get_argument(context, cls=Date10)
return [] if item is None else item.day


@method(function('timezone-from-date', nargs=1))
def evaluate(self, context=None):
item = self.get_argument(context, cls=Date)
item = self.get_argument(context, cls=Date10)
return [] if item is None else DayTimeDuration(seconds=item.tzinfo.offset.total_seconds())


Expand Down Expand Up @@ -909,12 +909,12 @@ def evaluate(self, context=None):
# Timezone adjustment functions
@method(function('adjust-dateTime-to-timezone', nargs=(1, 2)))
def evaluate(self, context=None):
return self.adjust_datetime(context, DateTime)
return self.adjust_datetime(context, DateTime10)


@method(function('adjust-date-to-timezone', nargs=(1, 2)))
def evaluate(self, context=None):
return self.adjust_datetime(context, Date)
return self.adjust_datetime(context, Date10)


@method(function('adjust-time-to-timezone', nargs=(1, 2)))
Expand All @@ -927,13 +927,13 @@ def evaluate(self, context=None):
@method(function('current-dateTime', nargs=0))
def evaluate(self, context=None):
dt = datetime.datetime.now() if context is None else context.current_dt
return DateTime(dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, dt.microsecond, dt.tzinfo)
return DateTime10(dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, dt.microsecond, dt.tzinfo)


@method(function('current-date', nargs=0))
def evaluate(self, context=None):
dt = datetime.datetime.now() if context is None else context.current_dt
return Date(dt.year, dt.month, dt.day, tzinfo=dt.tzinfo)
return Date10(dt.year, dt.month, dt.day, tzinfo=dt.tzinfo)


@method(function('current-time', nargs=0))
Expand Down

0 comments on commit a1506e8

Please sign in to comment.