# 'Python: get all months in range?

I want to get all months between now and August 2010, as a list formatted like this:

```
['2010-08-01', '2010-09-01', .... , '2016-02-01']
```

Right now this is what I have:

```
months = []
for y in range(2010, 2016):
for m in range(1, 13):
if (y == 2010) and m < 8:
continue
if (y == 2016) and m > 2:
continue
month = '%s-%s-01' % (y, ('0%s' % (m)) if m < 10 else m)
months.append(month)
```

What would be a better way to do this?

## Solution 1:^{[1]}

`dateutil.relativedelta`

is handy here.

I've left the formatting out as an exercise.

```
from dateutil.relativedelta import relativedelta
import datetime
result = []
today = datetime.date.today()
current = datetime.date(2010, 8, 1)
while current <= today:
result.append(current)
current += relativedelta(months=1)
```

## Solution 2:^{[2]}

I had a look at the `dateutil`

documentation. Turns out it provides an even more convenient way than using `dateutil.relativedelta`

: recurrence rules (examples)

For the task at hand, it's as easy as

```
from dateutil.rrule import *
from datetime import date
months = map(
date.isoformat,
rrule(MONTHLY, dtstart=date(2010, 8, 1), until=date.today())
)
```

# The fine print

Note that we're cheating a little bit, here. The elements `dateutil.rrule.rrule`

produces are of type `datetime.datetime`

, even if we pass `dtstart`

and `until`

of type `datetime.date`

, as we do above. I let `map`

feed them to `date`

's `isoformat`

function, which just turns out to convert them to strings as if it were just dates without any time-of-day information.

Therefore, the seemingly equivalent list comprehension

```
[day.isoformat()
for day in rrule(MONTHLY, dtstart=date(2010, 8, 1), until=date.today())]
```

would return a list like

```
['2010-08-01T00:00:00',
'2010-09-01T00:00:00',
'2010-10-01T00:00:00',
'2010-11-01T00:00:00',
?
'2015-12-01T00:00:00',
'2016-01-01T00:00:00',
'2016-02-01T00:00:00']
```

Thus, if we want to use a list comprehension instead of `map`

, we have to do something like

```
[dt.date().isoformat()
for dt in rrule(MONTHLY, dtstart=date(2010, 8, 1), until=date.today())]
```

## Solution 3:^{[3]}

use `datetime`

and `timedelta`

standard Python's modules - without installing any new libraries

```
from datetime import datetime, timedelta
now = datetime(datetime.now().year, datetime.now().month, 1)
ctr = datetime(2010, 8, 1)
list = [ctr.strftime('%Y-%m-%d')]
while ctr <= now:
ctr += timedelta(days=32)
list.append( datetime(ctr.year, ctr.month, 1).strftime('%Y-%m-%d') )
```

I'm adding `32`

days to enter new month every time (longest months has `31`

days)

## Solution 4:^{[4]}

It's seems like there's a very simple and clean way to do this by generating a list of dates and subsetting to take only the first day of each month, as shown in the example below.

```
import datetime
import pandas as pd
start_date = datetime.date(2010,8,1)
end_date = datetime.date(2016,2,1)
date_range = pd.date_range(start_date, end_date)
date_range = date_range[date_range.day==1]
print(date_range)
```

## Solution 5:^{[5]}

You could reduce the number of `if`

statements to two lines instead of four lines because having a second `if`

statement that does the same thing with the previous `if`

statement is a bit redundant.

```
if (y == 2010 and m < 8) or (y == 2016 and m > 2):
continue
```

## Solution 6:^{[6]}

I don't know whether it's *better*, but an approach like the following might be considered more 'pythonic':

```
months = [
'{}-{:0>2}-01'.format(year, month)
for year in xrange(2010, 2016 + 1)
for month in xrange(1, 12 + 1)
if not (year <= 2010 and month < 8 or year >= 2016 and month > 2)
]
```

The main differences here are:

- As we want the iteration(s) to produce a list, use a list comprehension instead of aggregating list elements in a
`for`

loop. - Instead of explicitly making a distinction between numbers below 10 and numbers 10 and above, use the capabilities of the format specification mini-language for the
`.format()`

method of`str`

to specify- a field width (the
`2`

in the`{:0>2}`

place holder) - right-alignment within the field (the
`>`

in the`{:0>2}`

place holder) - zero-padding (the
`0`

in the`{:0>2}`

place holder)

- a field width (the
`xrange`

instead of`range`

returns a generator instead of a list, so that the iteration values can be produced as they're being consumed and don't have to be held in memory. (Doesn't matter for ranges this small, but it's a good idea to get used to this in Python 2.)**Note**: In Python 3, there is no`xrange`

and the`range`

function already returns a generator instead of a list.- Make the
`+ 1`

for the upper bounds explicit. This makes it easier for human readers of the code to recognize that we want to specify an inclusive bound to a method (`range`

or`xrange`

) that treats the upper bound as exclusive. Otherwise, they might wonder what's the deal with the number 13.

## Solution 7:^{[7]}

I got another way using datetime, timedelta and calender:

```
from calendar import monthrange
from datetime import datetime, timedelta
def monthdelta(d1, d2):
delta = 0
while True:
mdays = monthrange(d1.year, d1.month)[1]
d1 += timedelta(days=mdays)
if d1 <= d2:
delta += 1
else:
break
return delta
start_date = datetime(2016, 1, 1)
end_date = datetime(2016, 12, 1)
num_months = [i-12 if i>12 else i for i in range(start_date.month, monthdelta(start_date, end_date)+start_date.month+1)]
monthly_daterange = [datetime(start_date.year,i, start_date.day, start_date.hour) for i in num_months]
```

## Solution 8:^{[8]}

A different approach that **doesn't require any additional libraries, nor nested or while loops**. Simply convert your dates into an absolute number of months from some reference point (it can be any date really, but for simplicity we can use 1st January 0001). For example

```
a=datetime.date(2010,2,5)
abs_months = a.year * 12 + a.month
```

Once you have a number representing the month you are in you can simply use `range`

to loop over the months, and then convert back:

**Solution to the generalized problem:**

```
import datetime
def range_of_months(start_date, end_date):
months = []
for i in range(start_date.year * 12 + start_date.month, end_date.year*12+end_date.month + 1)
months.append(datetime.date((i-13) // 12 + 1, (i-1) % 12 + 1, 1))
return months
```

**Additional Notes/explanation:**

Here `//`

divides rounding down to the nearest whole number, and `% 12`

gives the remainder when divided by 12, e.g. `13 % 12`

is `1`

.

(Note also that in the above `date.year *12 + date.month`

does not give the number of months since the 1st of January 0001. For example if `date = datetime.datetime(1,1,1)`

, then `date.year * 12 + date.month`

gives `13`

. If I wanted to do the actual number of months I would need to subtract 1 from the year and month, but that would just make the calculations more complicated. All that matters is that we have a consistent way to convert to and from some integer representation of what month we are in.)

## Solution 9:^{[9]}

fresh pythonic one-liner from me

```
from dateutil.relativedelta import relativedelta
import datetime
[(start_date + relativedelta(months=+m)).isoformat()
for m in range(0, relativedelta(start_date, end_date).months+1)]
```

## Solution 10:^{[10]}

In case you don't have any months duplicates and they are in correct order you can get what you want with this.

```
from datetime import date, timedelta
first = date.today()
last = first + timedelta(weeks=20)
date_format = "%Y-%m"
results = []
while last >= first:
results.append(last.strftime(date_format))
last -= timedelta(days=last.day)
```

## 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 | Ani |

Solution 2 | |

Solution 3 | |

Solution 4 | MatAff |

Solution 5 | Sean Francis N. Ballais |

Solution 6 | das-g |

Solution 7 | dl.meteo |

Solution 8 | |

Solution 9 | alex9311 |

Solution 10 | parfeniukink |