user1283776
user1283776

Reputation: 21764

Simplify code dealing with time and duration that currently uses datetime and isodate?

The code below is supposed to:

Example of input strings are:

duration_string = "P10W"
duration_string = "P1Y"

Here is the code

    duration = isodate.parse_duration(duration_string)

    if isinstance(duration, datetime.timedelta):
        if not duration > datetime.timedelta(0):
            raise Exception('duration invalid')
        if duration > datetime.timedelta(3660):
            raise Exception('duration cannot be longer than 10 years')
    elif isinstance(duration, isodate.Duration):
        if not duration > 0:
            raise Exception('duration invalid')
        if duration > isodate.duration.Duration(0, 0, 0, years=10, months=0):
            log.debug("duration %s isodate %s" % (duration, isodate.duration.Duration(0, 0, 0, years=10, months=0)))
            raise Exception('duration cannot be longer than 10 years')

Is there an easier way to do this than the monstrocity I have produced?

In addition to needing simplification, the line duration > isodate.duration.Duration(0, 0, 0, years=10, months=0) does not work.

I am using Python 2.7

Upvotes: 1

Views: 158

Answers (2)

user1283776
user1283776

Reputation: 21764

Here is an alternative solution that I ended up using:

    if isinstance(duration, datetime.timedelta):
        if not duration > 0:
            raise Exception('duration invalid')
        if duration > 3650:
            raise Exception('maximum duration is 3650 days')
    elif isinstance(duration, isodate.Duration):
        if duration.years > 10:
            raise Exception('maximum duration is 10 years')
        if duration.months > 120:
            raise Exception('maximum duration is 120 months')

Upvotes: 0

FHTMitchell
FHTMitchell

Reputation: 12157

Ok, so if you absolutely must use isodate duration parsing, keep the isodate library. I would however mention that the isodate library is incomplete, has a number of poor design decisions and is just generally bad.

However if you must use their parsing tool, this is probably a good way.

import isodate
import functools

@functools.total_ordering  # if we implement < ==, will implement <=, >, >=
class Duration(isodate.Duration):
    # inherit from isodate.Duration -- gives us ==

    # constants 
    seconds_in_day = 60**2 * 24
    approx_days_in_month = 30
    approx_days_in_year = 365

    def approx_total_seconds(self):
        """approx total seconds in duration"""
        # self.months and self.years are stored as `Decimal`s for some reason...
        return self.tdelta.total_seconds() \
               + float(self.months) * self.approx_days_in_month *  self.seconds_in_day \
               + float(self.years) * self.approx_days_in_year * self.seconds_in_day

    def __lt__(self, other):
        """defines self < other"""
        if not isinstance(other, Duration):
            return NotImplemented
        return self.approx_total_seconds() < other.approx_total_seconds()

    @classmethod
    def parse_duration(cls, datestring):
        """a version of isodate.parse_duration that returns out class"""

        iso_dur = isodate.parse_duration(datestring)

        # iso_date.parse_duration can return either a Duration or a timedelta...
        if isinstance(iso_dur, isodate.Duration):
            return cls(seconds=iso_dur.tdelta.total_seconds(),
                       months=iso_dur.months, years=iso_dur.years)
        else:
            return cls(seconds=iso_dur.total_seconds())


ten_weeks = Duration.parse_duration('P10W')
one_year = Duration.parse_duration('P1Y')

print(ten_weeks.approx_total_seconds())
print(one_year.approx_total_seconds())

print(ten_weeks < one_year)
print(ten_weeks > one_year)

Outputs

6048000.0
31536000.0
True
False

If you don't need the isodate parsing (and I suspect you don't) you could just do

@functools.TotalOrdering
class ApproxTimeDelta:

    approx_days_in_week = 7
    approx_days_in_month = 30
    approx_days_in_year = 365

    def __init__(self, days, weeks, months, years):
        self.days = days + \
                    weeks * self.approx_days_in_week + \
                    months * self.approx_days_in_month + \
                    years * self.approx_days_in_year

    def __eq__(self, other):
        return self.days == other.days

    def __lt__(self, other):
        return self.days < other.days

and pass the years/months/weeks/days as integers and compare as before.

Upvotes: 1

Related Questions