102

In Python, how do I get a datetime object for '3 years ago today'?

UPDATE: FWIW, I don't care hugely about accuracy... i.e. it's Feb 29th today, I don't care whether I'm given Feb 28th or March 1st in my answer. Concision is more important than configurability, in this case.

Asclepius
  • 57,944
  • 17
  • 167
  • 143
AP257
  • 89,519
  • 86
  • 202
  • 261
  • possible duplicate of [How to create a DateTime equal to 15 minutes ago?](http://stackoverflow.com/questions/4541629/how-to-create-a-datetime-equal-to-15-minutes-ago) – Marc B Mar 01 '11 at 17:40
  • 3
    Presumably if it's March 1 today, you want to get March 1 no matter if a leap year occurs in between or not? I think all the existing answers fail in that regard. – Mark Ransom Mar 01 '11 at 17:52
  • Almost, but not quite: the difference between 15 minutes, which is invariable, and 3 years, which is not, is significant. – JasonFruit Mar 01 '11 at 17:53
  • @Jason I agree with you, although from the OP's edit he doesn't seem super concerned about that, so that being the case it would be a duplicate. – Davy8 Mar 01 '11 at 17:58
  • 4
    This site really needs a way for the community to override when people accept a clearly incorrect answer. 3*365 days is not 3 years, and there's a correct answer right there. – Glenn Maynard Mar 01 '11 at 18:19
  • @Glenn Maynard - You could edit the title. Since he really didn't want 3 years ago exactly. – Chris Dutrow Mar 20 '12 at 20:05

7 Answers7

198

If you need to be exact use the dateutil module to calculate relative dates

from datetime import datetime
from dateutil.relativedelta import relativedelta

three_yrs_ago = datetime.now() - relativedelta(years=3)
Asclepius
  • 57,944
  • 17
  • 167
  • 143
Vince Spicer
  • 4,325
  • 3
  • 21
  • 11
131
import datetime
datetime.datetime.now() - datetime.timedelta(days=3*365)
Fábio Diniz
  • 10,077
  • 3
  • 38
  • 45
  • 51
    AP257 said: "I don't care hugely about accuracy". I interpreted as "I don't care about leap years" – Fábio Diniz Mar 01 '11 at 18:42
  • 7
    Is it correct to give a less concise but more accurate answer when the asker explicitly told the opposite? – Fábio Diniz Mar 01 '11 at 20:16
  • 8
    +1 For simplicity. Maybe the OP just wanted an example of how to do something like this in general. – Chris Dutrow Mar 20 '12 at 20:00
  • Thanks for simplicity. I also don't care about accuracy. I was actually looking for a difference of days anyway. – VertigoRay May 14 '16 at 17:10
  • The example given for "I don't care hugely about accuracy" suggests that the questioner didn't actually think about things enough. The example is of not caring whether February 29 becomes February 28 or March 1, but this code can (and usually will) do stuff like turn March 5 into March 6. Someone who doesn't care about the first thing will probably still care about the second. – user2357112 Oct 05 '18 at 22:59
  • You can deal precisely with leap years by instead multiplying by 365.2422, if you care specifically about where the earth is in its orbit. – KWx Sep 24 '19 at 05:08
32

Subtracting 365*3 days is wrong, of course--you're crossing a leap year more than half the time.

dt = datetime.now()
dt = dt.replace(year=dt.year-3)
# datetime.datetime(2008, 3, 1, 13, 2, 36, 274276)

ED: To get the leap-year issue right,

def subtract_years(dt, years):
    try:
        dt = dt.replace(year=dt.year-years)
    except ValueError:
        dt = dt.replace(year=dt.year-years, day=dt.day-1)
    return dt
Glenn Maynard
  • 55,829
  • 10
  • 121
  • 131
4
def add_years(dt, years):
    try:
        result = datetime.datetime(dt.year + years, dt.month, dt.day, dt.hour, dt.minute, dt.second, dt.microsecond, dt.tzinfo)
    except ValueError:
        result = datetime.datetime(dt.year + years, dt.month, dt.day - 1, dt.hour, dt.minute, dt.second, dt.microsecond, dt.tzinfo)
    return result

>>> add_years(datetime.datetime.now(), -3)
datetime.datetime(2008, 3, 1, 12, 2, 35, 22000)
>>> add_years(datetime.datetime(2008, 2, 29), -3)
datetime.datetime(2005, 2, 28, 0, 0)
Mark Ransom
  • 299,747
  • 42
  • 398
  • 622
1

This works to cater for leap year corner cases and non-leap years too. Because, if day = 29 and month = 2 (Feb), a non-leap year would throw a value error because there is no 29th Feb and the last day of Feb would be 28th, thus doing a -1 on the date works in a try-except block.

from datetime import datetime

last_year = datetime.today().year - 1
month = datetime.today().month
day = datetime.today().day

try:
    # try returning same date last year
    last_year_date = datetime.strptime(f"{last_year}-{month}-{day}",'%Y-%m-%d').date()
except ValueError: 
    # incase of error due to leap year, return date - 1 in last year
    last_year_date = datetime.strptime(f"{last_year}-{month}-{day-1}",'%Y-%m-%d').date()

print(last_year_date)
-1
In [3]: import datetime as dt

In [4]: today=dt.date.today()

In [5]: three_years_ago=today-dt.timedelta(days=3*365)

In [6]: three_years_ago
Out[6]: datetime.date(2008, 3, 1)
unutbu
  • 842,883
  • 184
  • 1,785
  • 1,677
-1

I was looking for a solution using only the standard library, and this worked for me. Note that without the check, Feb. 29th will give you a ValueError for most years. On all other days, it will give you the date for "three years ago today".

today = date.today()
day = 28 if today.month == 2 and today.day == 29 else today.day
three_years_ago = date(today.year - 3, today.month, day)
Dominus.Vobiscum
  • 702
  • 1
  • 9
  • 16