diff env/lib/python3.7/site-packages/isodate/duration.py @ 0:26e78fe6e8c4 draft

"planemo upload commit c699937486c35866861690329de38ec1a5d9f783"
author shellac
date Sat, 02 May 2020 07:14:21 -0400
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/env/lib/python3.7/site-packages/isodate/duration.py	Sat May 02 07:14:21 2020 -0400
@@ -0,0 +1,321 @@
+##############################################################################
+# Copyright 2009, Gerhard Weis
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+#  * Redistributions of source code must retain the above copyright notice,
+#    this list of conditions and the following disclaimer.
+#  * Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions and the following disclaimer in the documentation
+#    and/or other materials provided with the distribution.
+#  * Neither the name of the authors nor the names of its contributors
+#    may be used to endorse or promote products derived from this software
+#    without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT
+##############################################################################
+'''
+This module defines a Duration class.
+
+The class Duration allows to define durations in years and months and can be
+used as limited replacement for timedelta objects.
+'''
+from datetime import timedelta
+from decimal import Decimal, ROUND_FLOOR
+
+
+def fquotmod(val, low, high):
+    '''
+    A divmod function with boundaries.
+
+    '''
+    # assumes that all the maths is done with Decimals.
+    # divmod for Decimal uses truncate instead of floor as builtin
+    # divmod, so we have to do it manually here.
+    a, b = val - low, high - low
+    div = (a / b).to_integral(ROUND_FLOOR)
+    mod = a - div * b
+    # if we were not usig Decimal, it would look like this.
+    # div, mod = divmod(val - low, high - low)
+    mod += low
+    return int(div), mod
+
+
+def max_days_in_month(year, month):
+    '''
+    Determines the number of days of a specific month in a specific year.
+    '''
+    if month in (1, 3, 5, 7, 8, 10, 12):
+        return 31
+    if month in (4, 6, 9, 11):
+        return 30
+    if ((year % 400) == 0) or ((year % 100) != 0) and ((year % 4) == 0):
+        return 29
+    return 28
+
+
+class Duration(object):
+    '''
+    A class which represents a duration.
+
+    The difference to datetime.timedelta is, that this class handles also
+    differences given in years and months.
+    A Duration treats differences given in year, months separately from all
+    other components.
+
+    A Duration can be used almost like any timedelta object, however there
+    are some restrictions:
+      * It is not really possible to compare Durations, because it is unclear,
+        whether a duration of 1 year is bigger than 365 days or not.
+      * Equality is only tested between the two (year, month vs. timedelta)
+        basic components.
+
+    A Duration can also be converted into a datetime object, but this requires
+    a start date or an end date.
+
+    The algorithm to add a duration to a date is defined at
+    http://www.w3.org/TR/xmlschema-2/#adding-durations-to-dateTimes
+    '''
+
+    def __init__(self, days=0, seconds=0, microseconds=0, milliseconds=0,
+                 minutes=0, hours=0, weeks=0, months=0, years=0):
+        '''
+        Initialise this Duration instance with the given parameters.
+        '''
+        if not isinstance(months, Decimal):
+            months = Decimal(str(months))
+        if not isinstance(years, Decimal):
+            years = Decimal(str(years))
+        self.months = months
+        self.years = years
+        self.tdelta = timedelta(days, seconds, microseconds, milliseconds,
+                                minutes, hours, weeks)
+
+    def __getstate__(self):
+        return self.__dict__
+
+    def __setstate__(self, state):
+        self.__dict__.update(state)
+
+    def __getattr__(self, name):
+        '''
+        Provide direct access to attributes of included timedelta instance.
+        '''
+        return getattr(self.tdelta, name)
+
+    def __str__(self):
+        '''
+        Return a string representation of this duration similar to timedelta.
+        '''
+        params = []
+        if self.years:
+            params.append('%d years' % self.years)
+        if self.months:
+            fmt = "%d months"
+            if self.months <= 1:
+                fmt = "%d month"
+            params.append(fmt % self.months)
+        params.append(str(self.tdelta))
+        return ', '.join(params)
+
+    def __repr__(self):
+        '''
+        Return a string suitable for repr(x) calls.
+        '''
+        return "%s.%s(%d, %d, %d, years=%d, months=%d)" % (
+            self.__class__.__module__, self.__class__.__name__,
+            self.tdelta.days, self.tdelta.seconds,
+            self.tdelta.microseconds, self.years, self.months)
+
+    def __hash__(self):
+        '''
+        Return a hash of this instance so that it can be used in, for
+        example, dicts and sets.
+        '''
+        return hash((self.tdelta, self.months, self.years))
+
+    def __neg__(self):
+        """
+        A simple unary minus.
+
+        Returns a new Duration instance with all it's negated.
+        """
+        negduration = Duration(years=-self.years, months=-self.months)
+        negduration.tdelta = -self.tdelta
+        return negduration
+
+    def __add__(self, other):
+        '''
+        Durations can be added with Duration, timedelta, date and datetime
+        objects.
+        '''
+        if isinstance(other, Duration):
+            newduration = Duration(years=self.years + other.years,
+                                   months=self.months + other.months)
+            newduration.tdelta = self.tdelta + other.tdelta
+            return newduration
+        try:
+            # try anything that looks like a date or datetime
+            # 'other' has attributes year, month, day
+            # and relies on 'timedelta + other' being implemented
+            if (not(float(self.years).is_integer() and
+                    float(self.months).is_integer())):
+                raise ValueError('fractional years or months not supported'
+                                 ' for date calculations')
+            newmonth = other.month + self.months
+            carry, newmonth = fquotmod(newmonth, 1, 13)
+            newyear = other.year + self.years + carry
+            maxdays = max_days_in_month(newyear, newmonth)
+            if other.day > maxdays:
+                newday = maxdays
+            else:
+                newday = other.day
+            newdt = other.replace(year=newyear, month=newmonth, day=newday)
+            # does a timedelta + date/datetime
+            return self.tdelta + newdt
+        except AttributeError:
+            # other probably was not a date/datetime compatible object
+            pass
+        try:
+            # try if other is a timedelta
+            # relies on timedelta + timedelta supported
+            newduration = Duration(years=self.years, months=self.months)
+            newduration.tdelta = self.tdelta + other
+            return newduration
+        except AttributeError:
+            # ignore ... other probably was not a timedelta compatible object
+            pass
+        # we have tried everything .... return a NotImplemented
+        return NotImplemented
+
+    __radd__ = __add__
+
+    def __mul__(self, other):
+        if isinstance(other, int):
+            newduration = Duration(
+                years=self.years * other,
+                months=self.months * other)
+            newduration.tdelta = self.tdelta * other
+            return newduration
+        return NotImplemented
+
+    __rmul__ = __mul__
+
+    def __sub__(self, other):
+        '''
+        It is possible to subtract Duration and timedelta objects from Duration
+        objects.
+        '''
+        if isinstance(other, Duration):
+            newduration = Duration(years=self.years - other.years,
+                                   months=self.months - other.months)
+            newduration.tdelta = self.tdelta - other.tdelta
+            return newduration
+        try:
+            # do maths with our timedelta object ....
+            newduration = Duration(years=self.years, months=self.months)
+            newduration.tdelta = self.tdelta - other
+            return newduration
+        except TypeError:
+            # looks like timedelta - other is not implemented
+            pass
+        return NotImplemented
+
+    def __rsub__(self, other):
+        '''
+        It is possible to subtract Duration objecs from date, datetime and
+        timedelta objects.
+
+        TODO: there is some weird behaviour in date - timedelta ...
+              if timedelta has seconds or microseconds set, then
+              date - timedelta != date + (-timedelta)
+              for now we follow this behaviour to avoid surprises when mixing
+              timedeltas with Durations, but in case this ever changes in
+              the stdlib we can just do:
+                return -self + other
+              instead of all the current code
+        '''
+        if isinstance(other, timedelta):
+            tmpdur = Duration()
+            tmpdur.tdelta = other
+            return tmpdur - self
+        try:
+            # check if other behaves like a date/datetime object
+            # does it have year, month, day and replace?
+            if (not(float(self.years).is_integer() and
+                    float(self.months).is_integer())):
+                raise ValueError('fractional years or months not supported'
+                                 ' for date calculations')
+            newmonth = other.month - self.months
+            carry, newmonth = fquotmod(newmonth, 1, 13)
+            newyear = other.year - self.years + carry
+            maxdays = max_days_in_month(newyear, newmonth)
+            if other.day > maxdays:
+                newday = maxdays
+            else:
+                newday = other.day
+            newdt = other.replace(year=newyear, month=newmonth, day=newday)
+            return newdt - self.tdelta
+        except AttributeError:
+            # other probably was not compatible with data/datetime
+            pass
+        return NotImplemented
+
+    def __eq__(self, other):
+        '''
+        If the years, month part and the timedelta part are both equal, then
+        the two Durations are considered equal.
+        '''
+        if isinstance(other, Duration):
+            if (((self.years * 12 + self.months) ==
+                 (other.years * 12 + other.months) and
+                 self.tdelta == other.tdelta)):
+                return True
+            return False
+        # check if other con be compared against timedelta object
+        # will raise an AssertionError when optimisation is off
+        if self.years == 0 and self.months == 0:
+            return self.tdelta == other
+        return False
+
+    def __ne__(self, other):
+        '''
+        If the years, month part or the timedelta part is not equal, then
+        the two Durations are considered not equal.
+        '''
+        if isinstance(other, Duration):
+            if (((self.years * 12 + self.months) !=
+                 (other.years * 12 + other.months) or
+                 self.tdelta != other.tdelta)):
+                return True
+            return False
+        # check if other can be compared against timedelta object
+        # will raise an AssertionError when optimisation is off
+        if self.years == 0 and self.months == 0:
+            return self.tdelta != other
+        return True
+
+    def totimedelta(self, start=None, end=None):
+        '''
+        Convert this duration into a timedelta object.
+
+        This method requires a start datetime or end datetimem, but raises
+        an exception if both are given.
+        '''
+        if start is None and end is None:
+            raise ValueError("start or end required")
+        if start is not None and end is not None:
+            raise ValueError("only start or end allowed")
+        if start is not None:
+            return (start + self) - start
+        return end - (end - self)