Source code for mpas_analysis.shared.timekeeping.MpasRelativeDelta

# This software is open source software available under the BSD-3 license.
#
# Copyright (c) 2022 Triad National Security, LLC. All rights reserved.
# Copyright (c) 2022 Lawrence Livermore National Security, LLC. All rights
# reserved.
# Copyright (c) 2022 UT-Battelle, LLC. All rights reserved.
#
# Additional copyright and license information can be found in the LICENSE file
# distributed with this code, or at
# https://raw.githubusercontent.com/MPAS-Dev/MPAS-Analysis/master/LICENSE

import datetime
from dateutil.relativedelta import relativedelta
from calendar import monthrange, isleap


[docs]class MpasRelativeDelta(relativedelta): """ ``MpasRelativeDelta`` is a subclass of ``dateutil.relativedelta`` for relative time intervals with different MPAS calendars. Only relative intervals (years, months, etc.) are supported and not the absolute date specifications (year, month, etc.). Addition/subtraction of ``datetime.datetime`` objects or other ``MpasRelativeDelta`` (but currently not ``datetime.date``, ``datetime.timedelta`` or other related objects) is supported. """ # Authors # ------- # Xylar Asay-Davis
[docs] def __init__(self, dt1=None, dt2=None, years=0, months=0, days=0, hours=0, minutes=0, seconds=0, calendar='gregorian'): if calendar not in ['gregorian', 'gregorian_noleap']: raise ValueError('Unsupported MPAs calendar {}'.format(calendar)) self.calendar = calendar super(MpasRelativeDelta, self).__init__(dt1=dt1, dt2=dt2, years=years, months=months, days=days, hours=hours, minutes=minutes, seconds=seconds)
def __add__(self, other): if not isinstance(other, (datetime.datetime, MpasRelativeDelta)): return NotImplemented if isinstance(other, MpasRelativeDelta): if self.calendar != other.calendar: raise ValueError('MpasRelativeDelta objects can only be added ' 'if their calendars match.') years = self.years + other.years months = self.months + other.months if months > 12: years += 1 months -= 12 elif months < 1: years -= 1 months += 12 return self.__class__(years=years, months=months, days=self.days + other.days, hours=self.hours + other.hours, minutes=self.minutes + other.minutes, seconds=self.seconds + other.seconds, calendar=self.calendar) year = other.year + self.years month = other.month if self.months != 0: assert 1 <= abs(self.months) <= 12 month += self.months if month > 12: year += 1 month -= 12 elif month < 1: year -= 1 month += 12 if self.calendar == 'gregorian': daysInMonth = monthrange(year, month)[1] elif self.calendar == 'gregorian_noleap': # use year 0001, which is not a leapyear daysInMonth = monthrange(1, month)[1] day = min(daysInMonth, other.day) repl = {"year": year, "month": month, "day": day} days = self.days if self.calendar == 'gregorian_noleap' and isleap(year): if month == 2 and day + days >= 29: # skip forward over the leap day days += 1 elif month == 3 and day + days <= 0: # skip backward over the leap day days -= 1 return (other.replace(**repl) + datetime.timedelta(days=days, hours=self.hours, minutes=self.minutes, seconds=self.seconds)) def __radd__(self, other): return self.__add__(other) def __rsub__(self, other): return self.__neg__().__add__(other) def __sub__(self, other): if not isinstance(other, MpasRelativeDelta): return NotImplemented return self.__add__(other.__neg__()) def __neg__(self): return self.__class__(years=-self.years, months=-self.months, days=-self.days, hours=-self.hours, minutes=-self.minutes, seconds=-self.seconds, calendar=self.calendar) def __mul__(self, other): try: f = float(other) except TypeError: return NotImplemented return self.__class__(years=int(self.years * f), months=int(self.months * f), days=int(self.days * f), hours=int(self.hours * f), minutes=int(self.minutes * f), seconds=int(self.seconds * f), calendar=self.calendar) __rmul__ = __mul__ def __div__(self, other): try: reciprocal = 1 / float(other) except TypeError: return NotImplemented return self.__mul__(reciprocal) __truediv__ = __div__ def __repr__(self): outList = [] for attr in ["years", "months", "days", "leapdays", "hours", "minutes", "seconds", "microseconds"]: value = getattr(self, attr) if value: outList.append("{attr}={value:+g}".format(attr=attr, value=value)) outList.append("calendar='{}'".format(self.calendar)) return "{classname}({attrs})".format(classname=self.__class__.__name__, attrs=", ".join(outList))