0

I want to create a list of dates that selects the same date every year over a range of years.

start_date = dt.date(2009, 2, 10)
end_date = dt.date(2019, 5, 1)
new_date = []

current_date = start_date
while current_date < end_date:
    if (current_date.year % 4) == 0:
        new_date.append(current_date + dt.timedelta(days=366))
    else: 
        new_date.append(current_date + dt.timedelta(days=365))
    if (current_date.year % 4) == 0:
        current_date += dt.timedelta(days=366)     
    else:
        current_date += dt.timedelta(days=365) 
new_date

This outputs:

[datetime.date(2010, 2, 10),
 datetime.date(2011, 2, 10),
 datetime.date(2012, 2, 10),
 datetime.date(2013, 2, 10),
 datetime.date(2014, 2, 10),
 datetime.date(2015, 2, 10),
 datetime.date(2016, 2, 10),
 datetime.date(2017, 2, 10),
 datetime.date(2018, 2, 10),
 datetime.date(2019, 2, 10),
 datetime.date(2020, 2, 10)]

But when I change the day to be after the February 29, the leap years have the dates offset by one day.

start_date = dt.date(2009, 3, 10)
end_date = dt.date(2019, 5, 1)
new_date = []

current_date = start_date
while current_date < end_date:
    if (current_date.year % 4) == 0:
        new_date.append(current_date + dt.timedelta(days=366))
    else: 
        new_date.append(current_date + dt.timedelta(days=365))
    if (current_date.year % 4) == 0:
        current_date += dt.timedelta(days=366)     
    else:
        current_date += dt.timedelta(days=365) 
new_date

[datetime.date(2010, 3, 10),
 datetime.date(2011, 3, 10),
 datetime.date(2012, 3, 9),
 datetime.date(2013, 3, 10),
 datetime.date(2014, 3, 10),
 datetime.date(2015, 3, 10),
 datetime.date(2016, 3, 9),
 datetime.date(2017, 3, 10),
 datetime.date(2018, 3, 10),
 datetime.date(2019, 3, 10),
 datetime.date(2020, 3, 9)]

What is the reason for this and how do I fix it?

Eli Turasky
  • 981
  • 2
  • 11
  • 28
  • 6
    why the timedelta? if you just want a specific date every year, why not create that specifically? – FObersteiner Jun 10 '20 at 18:11
  • Not sure how I would accomplish that, which is why I tried using timedelta. – Eli Turasky Jun 10 '20 at 18:20
  • In addition to the previous comments, I'd recommend using something like `date.replace(year = date.year + 1)` instead of doing this manually (see [this question](https://stackoverflow.com/questions/15741618/add-one-year-in-current-date-python) for example) – Richard Jun 10 '20 at 18:21
  • 2
    @EliTurasky `dt.date(current_date.year + 1, start_date.month, start_date.day)` – r.ook Jun 10 '20 at 18:22
  • Oh, I see. Thanks for these comments. – Eli Turasky Jun 10 '20 at 18:24

5 Answers5

0

If I get this correctly, you could achieve what you need like so:

from datetime import date

start_date, end_date = date(2009, 3, 10), date(2019, 5, 1)

new_date = []
for y in range(start_date.year+1, end_date.year+2): # +1 and +2 is arbitrary, adjust as needed
    new_date.append(date(y, start_date.month, start_date.day))

new_date
# [datetime.date(2010, 3, 10),
#  datetime.date(2011, 3, 10),
#  datetime.date(2012, 3, 10),
#  datetime.date(2013, 3, 10),
#  datetime.date(2014, 3, 10),
#  datetime.date(2015, 3, 10),
#  datetime.date(2016, 3, 10),
#  datetime.date(2017, 3, 10),
#  datetime.date(2018, 3, 10),
#  datetime.date(2019, 3, 10),
#  datetime.date(2020, 3, 10)]

or as a list comprehension

d0, d1 = date(2009, 3, 10), date(2019, 5, 1)
new_date = [date(y, d0.month, d0.day) for y in range(d0.year+1, d1.year+2)]
FObersteiner
  • 22,500
  • 8
  • 42
  • 72
0

Keeping your code (although it can be done more efficiently), you just need to check if the next year is a leap year:

import calendar
import pprint
import datetime as dt

def increaseYear(start_date):
    end_date = dt.date(2019, 5, 1)
    new_date = []

    current_date = start_date
    while current_date < end_date:
        # using calendar.isleap here, there are more conditions
        # than year%4==0: https://stackoverflow.com/questions/11621740/how-to-determine-whether-a-year-is-a-leap-year
        _nextYearisLeap = calendar.isleap(current_date.year + 1)
        if _nextYearisLeap:
            new_date.append(current_date + dt.timedelta(days=366))
        else: 
            new_date.append(current_date + dt.timedelta(days=365))
        current_date = new_date[-1]
    return new_date


for startDate in (dt.date(2009, 2, 10), dt.date(2009, 3, 10)):
    dates = increaseYear(startDate)
    pprint.pprint(dates)

Output:

[datetime.date(2010, 2, 10),
 datetime.date(2011, 2, 10),
 datetime.date(2012, 2, 11),
 datetime.date(2013, 2, 10),
 datetime.date(2014, 2, 10),
 datetime.date(2015, 2, 10),
 datetime.date(2016, 2, 11),
 datetime.date(2017, 2, 10),
 datetime.date(2018, 2, 10),
 datetime.date(2019, 2, 10),
 datetime.date(2020, 2, 11)]
[datetime.date(2010, 3, 10),
 datetime.date(2011, 3, 10),
 datetime.date(2012, 3, 10),
 datetime.date(2013, 3, 10),
 datetime.date(2014, 3, 10),
 datetime.date(2015, 3, 10),
 datetime.date(2016, 3, 10),
 datetime.date(2017, 3, 10),
 datetime.date(2018, 3, 10),
 datetime.date(2019, 3, 10),
 datetime.date(2020, 3, 10)]
Maurice Meyer
  • 17,279
  • 4
  • 30
  • 47
0

This will not make sense if you select a leap day, so I'm assuming you don't.

import datetime

def date_range(start, end):
    ret = []
    while start < end:
        ret.append(start)
        start = start.replace(year=start.year + 1)
    return ret

then your example gives

>>> start_date = datetime.date(2009, 2, 10)
>>> end_date = datetime.date(2019, 5, 1)
>>> date_range(start_date, end_date)
[datetime.date(2009, 2, 10),
 datetime.date(2010, 2, 10),
 datetime.date(2011, 2, 10),
 datetime.date(2012, 2, 10),
 datetime.date(2013, 2, 10),
 datetime.date(2014, 2, 10),
 datetime.date(2015, 2, 10),
 datetime.date(2016, 2, 10),
 datetime.date(2017, 2, 10),
 datetime.date(2018, 2, 10),
 datetime.date(2019, 2, 10)]

this answer also properly handles months, unlike a generator based solely upon the year.

Cireo
  • 4,197
  • 1
  • 19
  • 24
0

You could use the dateutil module to account for a leap year:

import datetime as dt
from dateutil.relativedelta import relativedelta

dt.date(2011, 3, 10) + relativedelta(years=1)
Output:
datetime.date(2012, 3, 10)

dt.date(2020, 2, 29) + relativedelta(years=1)
Output:
datetime.date(2021, 2, 28)
Trace Malloc
  • 394
  • 1
  • 3
  • 5
0

To understand where your solution went wrong, first, understand that the count of day by year varies if it's a leap or non-leap year:

        Leap  ~Leap
Jan 31    31     31
   ...   ...    ...
Feb 28    59     59
Feb 29    60    n/a
Mar 01    61     60
Mar 02    62     61

Observe how the days are offset by 1 day between a leap and non-leap year after Feb 29. As such, your first example is akin to (assume a non-existent convenience attribute of is_leap):

map(lambda x: x + 366 if x.is_leap else 365, [41, 41, 41, 41, ...])

Whereas your second example would be:

map(lambda x: x + 366 if x.is_leap else 365, [69, 69, 70, 69, ...])

To boil it down - your handling of when and how to offset the leap year is already in the wrong place.

You already have some answers that provide you alternative, so I'll just cut this answer short and leave the explanation of why your implementation work. Personally I would approach it like MrFuppes did and create a new datetime object by adding the year directly:

start_date = dt.date(2009, 3, 10)
end_date = dt.date(2019, 5, 1)
def create_new_dates(start, end):
    cur = start
    while cur < end:
        cur = dt.date(cur.year + 1, cur.month, cur.day)
        yield cur

new_dates = list(create_new_dates(start_date, end_date))

Result:

[datetime.date(2010, 3, 10),
 datetime.date(2011, 3, 10),
 datetime.date(2012, 3, 10),
 datetime.date(2013, 3, 10),
 datetime.date(2014, 3, 10),
 datetime.date(2015, 3, 10),
 datetime.date(2016, 3, 10),
 datetime.date(2017, 3, 10),
 datetime.date(2018, 3, 10),
 datetime.date(2019, 3, 10),
 datetime.date(2020, 3, 10)]
r.ook
  • 13,466
  • 2
  • 22
  • 39