11

Is there an elegant was to convert between relativedelta and timedelta?

The use case is getting user input ISO date. Python's isodate will return either isodate.duration.Duration or datetime.timedelta.

We need the features of relativedelta (per What is the difference between "datetime.timedelta" and "dateutil.relativedelta.relativedelta" when working only with days? -- it does more) so need to convert both these types to a relativedata.

Community
  • 1
  • 1
Williams
  • 4,044
  • 1
  • 37
  • 53

4 Answers4

9

Just take the total number of seconds and microseconds, that's all a timedelta object stores:

def to_relativedelta(tdelta):
    return relativedelta(seconds=int(tdelta.total_seconds()),
                         microseconds=tdelta.microseconds)

>>> to_relativedelta(timedelta(seconds=0.3))
relativedelta(microseconds=+300000)
>>> to_relativedelta(timedelta(seconds=3))
relativedelta(seconds=+3)
>>> to_relativedelta(timedelta(seconds=300))
relativedelta(minutes=+5)
>>> to_relativedelta(timedelta(seconds=3000000))
relativedelta(days=+34, hours=+17, minutes=+20)
Petr Viktorin
  • 65,510
  • 9
  • 81
  • 81
  • 1
    The microseconds are included in the total_seconds. – Lennart Regebro Jun 07 '13 at 07:19
  • @LennartRegebro: Yes, but relativedelta doesn't accept fractional seconds (`relativedelta(seconds=0.3)` gives `relativedelta(seconds=+0)`). Also, as the documentation states, microseconds may not be accurate for very large intervals. – Petr Viktorin Jun 07 '13 at 07:26
  • relativedelta(seconds=0.3) gives relativedelta(seconds=0.3) to me. I'm using python-dateutil 1.5 – falsetru Jun 07 '13 at 07:29
  • @falsetru, that's weird. It should at least normalize to `relativedelta(microseconds=+300000)`. (I'm using version 1.5 on Python 2.7 and 2.0 on Python 3, on Linux.) – Petr Viktorin Jun 07 '13 at 07:45
  • @petr-viktorin, My comment was wrong. I spelled "second" instead of "seconds". – falsetru Jun 07 '13 at 07:59
  • I've reported a bug, https://bugs.launchpad.net/dateutil/+bug/1188507. Still, if you want microseconds in a relativedelta you need to give it explicitly. – Petr Viktorin Jun 07 '13 at 08:21
  • Weird, I thought "at least he should have an int() around total seconds". And now, there it is, but no edit have happened. Perhaps I was blind. :-) – Lennart Regebro Jun 07 '13 at 09:11
  • if you're getting weird numbers on the relativedelta, try using the `.normalized()` method mentioned in the docs. – Isaac C. Nov 09 '22 at 23:27
2
d = datetime.timedelta(...)
dateutil.relativedelta.relativedelta(seconds=d.total_seconds())
falsetru
  • 357,413
  • 63
  • 732
  • 636
1

I assume you're dealing with a timedelta because two datetimes were subtracted. Relativedelta has dt1 and dt2 arguments in its init.

Just pass the two datetimes you used to get your timedelta e and you'll have your relative delta.

-1

Using seconds directly with relativedelta does not populate the months and years fields and there is a reason of course that we wouldn't know if it's a leap year or a month with 31 days.

So something like this:

[In]:  tdelta = datetime.now() - datetime(1971, 1, 1)
[In]:  relativedelta(seconds=tdelta.total_seconds())
[Out]: relativedelta(days=+16958, hours=+13, minutes=+19, seconds=+49)

gives relative delta with lots of days and no months. While this can be okay in certain cases, in some we might need the years and months. Therefore:

def timedelta_to_relativedelta(tdelta):
    assert isinstance(tdelta, timedelta)

    seconds_in = {
        'year'  : 365 * 24 * 60 * 60,
        'month' : 30 * 24 * 60 * 60,
        'day'   : 24 * 60 * 60,
        'hour'  : 60 * 60,
        'minute': 60
    }

    years, rem = divmod(tdelta.total_seconds(), seconds_in['year'])
    months, rem = divmod(rem, seconds_in['month'])
    days, rem = divmod(rem, seconds_in['day'])
    hours, rem = divmod(rem, seconds_in['hour'])
    minutes, rem = divmod(rem, seconds_in['minute'])
    seconds = rem

    return relativedelta(years=years, months=months, days=days, hours=hours, minutes=minutes, seconds=seconds)

This might not be a very Pythonic solution but it works.

Note: This assumes that a year has 365 days (ignores leap non leap years) and months have 30 days.

SureshS
  • 589
  • 8
  • 23