'Using datetime.timedelta to add years

I am doing some time calculations in Python.

Goal:

Part of this is trying to :

Given a date, add time interval (X years, X months, X weeks), return date

ie

  • input args: input_time (datetime.date), interval (datetime.timedelta)
  • return: datetime.date

I looked at the datetime and datetime.timedelta docs

class datetime.timedelta(days=0, seconds=0, microseconds=0, milliseconds=0, minutes=0, hours=0, weeks=0)¶.

These seem to work well if I want to add a certain number of hours or weeks. However,

Problem:

  • I am trying to implement an operation such as date + 1 year and can't figure it out

E.g.

start = datetime.datetime(2000, 1, 1)
# expected output: datetime.datetime(2001, 1, 1)


# with the weeks, etc arguments given in timedelta, this fails unsurprisingly e.g 
start + datetime.timedelta(weeks = 52)

# returns datetime.datetime(2000, 12, 30, 0, 0)

Question

  • Is this year-based operation possible with the basic tools of datetime - if so, how would I go about it?

  • I realize that for the year example, I could just do start.replace(year = 2001), but that approach will fail if I have months or weeks as input.

  • From my understanding, the dateutil library has more advanced features, but I was not clear how well it interacts with the in-built datetime objects.

I have reviewed this similar question but it did not help me with this.

Any help is much appreciated!

Running Python 3.6.5 on MacOs.



Solution 1:[1]

timedelta does not support years, because the duration of a year depends on which year (for example, leap years have Feb 29).

You could use a relativedelta instead (from PyPI package python-dateutil) which does support years and takes into account the baseline date for additions.

>>> from dateutil.relativedelta import relativedelta
>>> import datetime
>>> d = datetime.date(2020, 2, 29)
>>> d
datetime.date(2020, 2, 29)
>>> d + relativedelta(years=1)
datetime.date(2021, 2, 28)

Solution 2:[2]

You can hard code a new year value of the datetime using replace instead :)

This avoids leap years etc.

year_later = current.replace(year=current.year + 1)

Note that if the current date happens to be the 29th of February, this will raise a ValueError with the message: "Day is out of range for month". So you need to handle this special case, like this:

if current.month == 2 and current.day == 29:
    year_later = current.replace(year=current.year + 1, day=28)
else:
    year_later = current.replace(year=current.year + 1)

Solution 3:[3]

My quick and dirty method is to use

y = [number of years]
timedelta(days= y * 365)

I found this question looking for a more elegant solution. For my uses a precise answer wasn't necessary. I don't mind losing a day each leap year in this particular case.

Solution 4:[4]

My goal was very similar to yours. I wanted to have the same date, just on the next year. But i figured i can't avoid accounting for leap years. In the end it all boils down to what exactly is the requirement. Theoretically we can just add 365 days (and either loose a day when there was a Feb 29), or check in the next 365 days if there is a 29th of February, in which case add 1 more day. But checking that would have been complex, and in the end i used a simple check for the new date's day if it is different from the original and then add 1 more day. I understand with dateutil.relativedelta it is easier, but i wanted to do it without extra imports

demo:

from datetime import datetime, timedelta

dates = [datetime(1999, 1, 1),
         datetime(1999, 12, 31),
         datetime(2000, 1, 1),
         datetime(2019, 3, 1),
         datetime(2019, 1, 1),
         datetime(2020, 1, 1),
         datetime(2020, 3, 1),
         datetime(2020, 2, 29)
         ]
for date in dates:
    plus1year_date = date + timedelta(days=365)
    print(date, "\t - original date")
    print(plus1year_date, "\t - plus1year_date (+365 days)")
    if date.day != plus1year_date.day:
        plus1year_date = plus1year_date + timedelta(days=1)
        print(plus1year_date, "\t - plus1year_date adjusted for leap year")
    else:
        print("No need to adjust for leap year")
    print('--------------------------------------------------------------')

# Expected output:
# 1999-01-01 00:00:00    - original date
# 2000-01-01 00:00:00    - plus1year_date (+365 days)
# No need to adjust for leap year
# --------------------------------------------------------------
# 1999-12-31 00:00:00    - original date
# 2000-12-30 00:00:00    - plus1year_date (+365 days)
# 2000-12-31 00:00:00    - plus1year_date adjusted for leap year
# --------------------------------------------------------------
# 2000-01-01 00:00:00    - original date
# 2000-12-31 00:00:00    - plus1year_date (+365 days)
# 2001-01-01 00:00:00    - plus1year_date adjusted for leap year
# --------------------------------------------------------------
# ...

Edit: @MDoe - I forgot to mention that with the .year + 1 method one can end up with invalid dates if it is Feb 29 (ValueError). Also if somebody wants to add an interval that could include more than one leap year, then my code won't be correct.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Flimm
Solution 2 Flimm
Solution 3 JDenman6
Solution 4