18

If I want to add 100 years in my program, why is it showing the wrong date?

import datetime
stringDate= "January 10, 1920"
dateObject= datetime.datetime.strptime(stringDate, "%B %d, %Y")
endDate= dateObject+datetime.timedelta(days=100*365)
print dateObject.date()
print endDate.date()
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
ismail khan
  • 287
  • 1
  • 3
  • 16

4 Answers4

20

The number of seconds in a year is not fixed. Think you know how many days are in a year? Think again.

To perform period (calendar) arithmetic, you could use dateutil.relativedelta:

#!/usr/bin/env python
from datetime import date
from dateutil.relativedelta import relativedelta # $ pip install python-dateutil

print(date(1920, 1, 10) + relativedelta(years=+100))
# -> 2020-01-10

To understand, why d.replace(year=d.year + 100) fails, consider:

print(date(2000, 2, 29) + relativedelta(years=+100))
2100-02-28

Notice that 2100 is not a leap year while 2000 is a leap year.

If the only units you want to add is year then you could implement it using only stdlib:

from calendar import isleap

def add_years(d, years):
    new_year = d.year + years
    try:
        return d.replace(year=new_year)
    except ValueError:
        if (d.month == 2 and d.day == 29 and # leap day
            isleap(d.year) and not isleap(new_year)):
            return d.replace(year=new_year, day=28)
        raise

Example:

from datetime import date

print(add_years(date(1920, 1, 10), 100))
# -> 2020-01-10
print(add_years(date(2000, 2, 29), 100))
# -> 2100-02-28
print(add_years(date(2000, 2, 29), 4))
# -> 2004-02-29
Community
  • 1
  • 1
jfs
  • 399,953
  • 195
  • 994
  • 1,670
  • @amunnelly it is not a typo. It is for readability (to highlight the sign of the operation). – jfs Apr 18 '18 at 10:13
  • `@jfs` you're right. Just noticed which side the + is on. My apologies - I'm having a slow morning. I'll delete the original comment. – amunnelly Apr 18 '18 at 10:29
  • @jfs Your first two links go to a deleted question and a paywalled site. – ekhumoro Aug 11 '20 at 11:50
  • @ekhumoro I can see information in both links. They provide background info -- if you can't see them, just ignore them (though I think you should be able to see them) -- the answer can be used without them. At a glance, it doesn't look like the accepted answer in your linked duplicate handles OP's case. – jfs Aug 11 '20 at 17:36
  • @jfs Only 10k users can see deleted questions, and paywalled sites also aren't accessible to everyone. This question is very obviously a duplicate since the only difference is in the number of years. The [linked question](https://stackoverflow.com/q/15741618/984421) has several answers, [one of which](https://stackoverflow.com/a/15742722/984421) contains a solution identical to yours. There is no accepted answer. – ekhumoro Aug 11 '20 at 17:58
  • @ekhumoro: as I said: "They provide background info -- if you can't see them, just ignore them." And you are right: I should have said "the most upvoted", not "accepted". I don't see "identical" solution in the answer that you've linked (e.g., my answer contains a stdlib-only example). – jfs Aug 11 '20 at 19:40
  • @jfs This is not about you and me, it is about other users. I don't understand why you don't want everyone to benefit from your answers. The sensible thing to do would be to move the deleted material to a community wiki so it is visible to everyone. It also baffles me why you would object to linking this question to an obvious duplicate, as that can only help other users to find the solutions they need. It seems you are more interested in somehow "protecting" your own answer than helping other members of the community. – ekhumoro Aug 11 '20 at 22:11
  • @ekhumoro let's avoid personal attacks. Assume that the intent behind my actions are for the benefit of the community even if you disagrees with them. 1- I believe that a person interested in the current question is better served with my answer (it works and you don't need to try/verify several answers before you find the correct one unlike with the answers from your linked question: in other words: the questions are not exact duplicates). 2- the deleted material is not part of the current answer but If you think it is worth it, you could publish it anywhere the license allows. – jfs Aug 12 '20 at 10:12
  • 1
    @jfs I have no idea what you mean by "personal attacks". Your actions make no sense if your intention is to benefit the community. Let other users decide for themselves what answers they wish to use. The deleted material is clearly part of your answer, since you linked directly to it. – ekhumoro Aug 12 '20 at 10:35
15

You can't just add 100 * 365 days, because there are leap years with 366 days in that timespan. Over your 100 year span you are missing 25 days.

Better to just use the datetime.replace() method here:

endDate = dateObject.replace(year=dateObject.year + 100)

This can still fail for February 29th in a leap year, as depending on the number of years you add you'd end up with an invalid date. You could move back to February 28th in that case, or use March 31st; handle the exception thrown and switch to your chosen replacement:

years = 100
try:
    endDate = dateObject.replace(year=dateObject.year + years)
except ValueError::
    # Leap day in a leap year, move date to February 28th
    endDate = dateObject.replace(year=dateObject.year + years, day=28)

Demo:

>>> import datetime
>>> dateObject = datetime.datetime(1920, 1, 10, 0, 0)
>>> dateObject.replace(year=dateObject.year + 100)
datetime.datetime(2020, 1, 10, 0, 0)
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
1

man 3 mktime

Anyone who ever did C knows the answer.

mktime automatically adds overflowing values to the next bigger unit. You just need to convert it back to a datetime.

For example you can feed it with 2019-07-40, which converts to 2019-08-09.

>>> datetime.fromtimestamp(mktime((2019, 7, 40, 0, 0, 0, 0, 0, 0)))
datetime.datetime(2019, 8, 9, 0, 0)

Or 2019-03-(-1) is converted to 2019-02-27:

>>> datetime.fromtimestamp(mktime((2019, 3, -1, 0, 0, 0, 0, 0, 0)))
datetime.datetime(2019, 2, 27, 0, 0)

So you just take your old date and add whatever you like:

now = datetime.datetime.now()
hundred_days_later = datetime.datetime.fromtimestamp(mktime((now.year, now.month, now.day + 100, now.hour, now.minute, now.second, 0, 0, 0)))
0

For the past 5 years, I've been using pandas Timestamp and Timedelta for everything date related.

For example, to add 3 years to any date in string format:

date = "2003-07-01"
date_start = Timestamp(date)
date_end = Timestamp(date_start.year+3, date_start.month, date_start.day)
date_end
# Out:
Timestamp('2006-07-01 00:00:00')

To add 40 days to a date, use Timedelta (it does not support years, or else we could have used it for the last problem):

date_end = date_start + Timedelta(40, unit="days")
date_end
# Out:
Timestamp('2003-08-10 00:00:00')
Contango
  • 76,540
  • 58
  • 260
  • 305