# Copyright Iris contributors
#
# This file is part of Iris and is released under the BSD license.
# See LICENSE in the root of the repository for full licensing details.
"""Time handling."""
import functools
[docs]@functools.total_ordering
class PartialDateTime:
"""Allow partial comparisons against datetime-like objects.
A :class:`PartialDateTime` object specifies values for some subset of
the calendar/time fields (year, month, hour, etc.) for comparing
with :class:`datetime.datetime`-like instances.
Comparisons are defined against any other class with all of the
attributes: year, month, day, hour, minute, and second.
Notably, this includes :class:`datetime.datetime` and
:class:`cftime.datetime`. Comparison also extends to the
microsecond attribute for classes, such as
:class:`datetime.datetime`, which define it.
A :class:`PartialDateTime` object is not limited to any particular
calendar, so no restriction is placed on the range of values
allowed in its component fields. Thus, it is perfectly legitimate to
create an instance as: `PartialDateTime(month=2, day=30)`.
"""
__slots__ = (
"year",
"month",
"day",
"hour",
"minute",
"second",
"microsecond",
)
#: A dummy value provided as a workaround to allow comparisons with
#: :class:`datetime.datetime`.
#: See https://bugs.python.org/issue8005.
#: NB. It doesn't even matter what this value is.
timetuple = None
def __init__(
self,
year=None,
month=None,
day=None,
hour=None,
minute=None,
second=None,
microsecond=None,
):
"""Allow partial comparisons against datetime-like objects.
Parameters
----------
year : int
The year number as an integer, or None.
month : int
The month number as an integer, or None.
day : int
The day number as an integer, or None.
hour : int
The hour number as an integer, or None.
minute : int
The minute number as an integer, or None.
second : int
The second number as an integer, or None.
microsecond : int
The microsecond number as an integer, or None.
Examples
--------
To select any days of the year after the 3rd of April:
>>> from iris.time import PartialDateTime
>>> import datetime
>>> pdt = PartialDateTime(month=4, day=3)
>>> datetime.datetime(2014, 4, 1) > pdt
False
>>> datetime.datetime(2014, 4, 5) > pdt
True
>>> datetime.datetime(2014, 5, 1) > pdt
True
>>> datetime.datetime(2015, 2, 1) > pdt
False
"""
self.year = year
self.month = month
self.day = day
self.hour = hour
self.minute = minute
self.second = second
self.microsecond = microsecond
def __repr__(self):
attr_pieces = [
"{}={}".format(name, getattr(self, name))
for name in self.__slots__
if getattr(self, name) is not None
]
result = "{}({})".format(type(self).__name__, ", ".join(attr_pieces))
return result
def __gt__(self, other):
if isinstance(other, type(self)):
raise TypeError("Cannot order PartialDateTime instances.")
result = False
try:
# Everything except 'microsecond' is mandatory
for attr_name in self.__slots__[:-1]:
attr = getattr(self, attr_name)
other_attr = getattr(other, attr_name)
if attr is not None and attr != other_attr:
result = attr > other_attr
break
# 'microsecond' is optional
if result and hasattr(other, "microsecond"):
attr = self.microsecond
other_attr = other.microsecond
if attr is not None and attr != other_attr:
result = attr > other_attr
except AttributeError:
result = NotImplemented
return result
def __eq__(self, other):
if isinstance(other, type(self)):
slots = self.__slots__
self_tuple = tuple(getattr(self, name) for name in slots)
other_tuple = tuple(getattr(other, name) for name in slots)
result = self_tuple == other_tuple
else:
result = True
try:
# Everything except 'microsecond' is mandatory
for attr_name in self.__slots__[:-1]:
attr = getattr(self, attr_name)
other_attr = getattr(other, attr_name)
if attr is not None and attr != other_attr:
result = False
break
# 'microsecond' is optional
if result and hasattr(other, "microsecond"):
attr = self.microsecond
other_attr = other.microsecond
if attr is not None and attr != other_attr:
result = False
except AttributeError:
result = other.__eq__(self)
if result is NotImplemented:
# Equality is undefined between these objects. We don't
# want Python to fall back to the default `object`
# behaviour (which compares using object IDs), so we raise
# an exception here instead.
fmt = "unable to compare PartialDateTime with {}"
raise TypeError(fmt.format(type(other)))
return result
def __ne__(self, other):
result = self.__eq__(other)
if result is not NotImplemented:
result = not result
return result