patternpythonMinor
Building a list of dates between two dates
Viewed 0 times
datesbuildingtwobetweenlist
Problem
My solution to this feels 'icky' and I've got calendar math falling out of my ears after working on similar problems for a week so I can't think straight about this.
Is there a better way to code this?
```
import datetime
from dateutil.relativedelta import relativedelta
def date_count(start, end, day_of_month=1):
"""
Return a list of datetime.date objects that lie in-between start and end.
The first element of the returned list will always be start and the last
element in the returned list will always be:
datetime.date(end.year, end.month, day_of_month)
If start.day is equal to day_of_month the second element will be:
start + 1 month
If start.day is after day_of_month then the second element will be:
the day_of_month in the next month
If start.day is before day_of_month then the second element will be:
datetime.date(start.year, start.month, day_of_month)
>>> start = datetime.date(2012, 1, 15)
>>> end = datetime.date(2012, 4, 1)
>>> date_count(start, end, day_of_month=1) #doctest: +NORMALIZE_WHITESPACE
[datetime.date(2012, 1, 15), datetime.date(2012, 2, 1),
datetime.date(2012, 3, 1), datetime.date(2012, 4, 1)]
Notice that it's not a full month between the first two elements in the
list.
If you have a start day before day_of_month:
>>> start = datetime.date(2012, 1, 10)
>>> end = datetime.date(2012, 4, 1)
>>> date_count(start, end, day_of_month=15) #doctest: +NORMALIZE_WHITESPACE
[datetime.date(2012, 1, 10), datetime.date(2012, 1, 15),
datetime.date(2012, 2, 15), datetime.date(2012, 3, 15),
datetime.date(2012, 4, 15)]
Notice that it's not a full month between the first two elements in the
list and that the last day is rounded to
datetime.date(end.year, end.month, day_of_month)
"""
last_element = datetime.date(end.year, end.month, day_of_month)
if start.day == day_of_month:
second_element = start + relativedelta
Is there a better way to code this?
```
import datetime
from dateutil.relativedelta import relativedelta
def date_count(start, end, day_of_month=1):
"""
Return a list of datetime.date objects that lie in-between start and end.
The first element of the returned list will always be start and the last
element in the returned list will always be:
datetime.date(end.year, end.month, day_of_month)
If start.day is equal to day_of_month the second element will be:
start + 1 month
If start.day is after day_of_month then the second element will be:
the day_of_month in the next month
If start.day is before day_of_month then the second element will be:
datetime.date(start.year, start.month, day_of_month)
>>> start = datetime.date(2012, 1, 15)
>>> end = datetime.date(2012, 4, 1)
>>> date_count(start, end, day_of_month=1) #doctest: +NORMALIZE_WHITESPACE
[datetime.date(2012, 1, 15), datetime.date(2012, 2, 1),
datetime.date(2012, 3, 1), datetime.date(2012, 4, 1)]
Notice that it's not a full month between the first two elements in the
list.
If you have a start day before day_of_month:
>>> start = datetime.date(2012, 1, 10)
>>> end = datetime.date(2012, 4, 1)
>>> date_count(start, end, day_of_month=15) #doctest: +NORMALIZE_WHITESPACE
[datetime.date(2012, 1, 10), datetime.date(2012, 1, 15),
datetime.date(2012, 2, 15), datetime.date(2012, 3, 15),
datetime.date(2012, 4, 15)]
Notice that it's not a full month between the first two elements in the
list and that the last day is rounded to
datetime.date(end.year, end.month, day_of_month)
"""
last_element = datetime.date(end.year, end.month, day_of_month)
if start.day == day_of_month:
second_element = start + relativedelta
Solution
How about...
And by the way it seems like a nice opportunity to use yield, if you wanted to.
Another possibility - discard the first value if it is earlier than the start date:
Or use a list comprehension to iterate over the number of months between start and end.
Finally, I don't know dateutil but it seems you can use
def date_count(start, end, day_of_month=1):
dates = [start]
next_date = start.replace(day=day_of_month)
if day_of_month > start.day:
dates.append(next_date)
while next_date < end.replace(day=day_of_month):
next_date += relativedelta(next_date, months=+1)
dates.append(next_date)
return datesAnd by the way it seems like a nice opportunity to use yield, if you wanted to.
def date_count2(start, end, day_of_month=1):
yield start
next_date = start.replace(day=day_of_month)
if day_of_month > start.day:
yield next_date
while next_date < end.replace(day=day_of_month):
next_date += relativedelta(next_date, months=+1)
yield next_dateAnother possibility - discard the first value if it is earlier than the start date:
def date_count(start, end, day_of_month=1):
dates = [start.replace(day=day_of_month)]
while dates[-1] start:
return [start] + dates
else:
return [start] + dates[1:]Or use a list comprehension to iterate over the number of months between start and end.
def date_count(start, end, day_of_month=1):
round_start = start.replace(day=day_of_month)
gap = end.year * 12 + end.month - start.year * 12 - start.month + 1
return [start] + [round_start + relativedelta(round_start, months=i)
for i in range(day_of_month <= start.day, gap)]Finally, I don't know dateutil but it seems you can use
rrule:from dateutil import rrule
def date_count(start, end, day_of_month=1):
yield start
for date in rrule(MONTHLY,
dtstart=start.replace(day=day_of_month),
until=end.replace(day=day_of_month)):
if date > start:
yield dateCode Snippets
def date_count(start, end, day_of_month=1):
dates = [start]
next_date = start.replace(day=day_of_month)
if day_of_month > start.day:
dates.append(next_date)
while next_date < end.replace(day=day_of_month):
next_date += relativedelta(next_date, months=+1)
dates.append(next_date)
return datesdef date_count2(start, end, day_of_month=1):
yield start
next_date = start.replace(day=day_of_month)
if day_of_month > start.day:
yield next_date
while next_date < end.replace(day=day_of_month):
next_date += relativedelta(next_date, months=+1)
yield next_datedef date_count(start, end, day_of_month=1):
dates = [start.replace(day=day_of_month)]
while dates[-1] < end.replace(day=day_of_month):
dates.append(dates[-1] + relativedelta(dates[-1], months=+1))
if dates[0] > start:
return [start] + dates
else:
return [start] + dates[1:]def date_count(start, end, day_of_month=1):
round_start = start.replace(day=day_of_month)
gap = end.year * 12 + end.month - start.year * 12 - start.month + 1
return [start] + [round_start + relativedelta(round_start, months=i)
for i in range(day_of_month <= start.day, gap)]from dateutil import rrule
def date_count(start, end, day_of_month=1):
yield start
for date in rrule(MONTHLY,
dtstart=start.replace(day=day_of_month),
until=end.replace(day=day_of_month)):
if date > start:
yield dateContext
StackExchange Code Review Q#19903, answer score: 3
Revisions (0)
No revisions yet.