Get (year,month) for the last X months

Question:

I got a very simple thing to to in python:
I need a list of tuples (year,month) for the last x months starting (and including) from today. So, for x=10 and today(July 2011), the command should output:

[(2011, 7), (2011, 6), (2011, 5), (2011, 4), (2011, 3), 
(2011, 2), (2011, 1), (2010, 12), (2010, 11), (2010, 10)]

Only the default datetime implementation of python should be used. I came up with the following solution:

import datetime
[(d.year, d.month) for d in [datetime.date.today()-datetime.timedelta(weeks=4*i) for i in range(0,10)]]

This solution outputs the correct solution for my test cases but I’m not comfortable with this solution: It assumes that a month has four weeks and this is simply not true. I could replace the weeks=4 with days=30 which would make a better solution but it is still not correct.

The other solution which came to my mind is to use simple maths and subtract 1 from a months counter and if the month-counter is 0, subtract 1 from a year counter. The problem with this solution: It requires more code and isn’t very readable either.

So how can this be done correctly?

Asked By: theomega

||

Answers:

If you create a function to do the date maths, it gets almost as nice as your original implementation:

def next_month(this_year, this_month):
  if this_month == 0: 
    return (this_year - 1, 12)
  else:
    return (this_year, this_month - 1)

this_month = datetime.date.today().month()
this_year = datetime.date.today().year()
for m in range(0, 10):
  yield (this_year, this_month)
  this_year, this_month = next_month(this_year, this_month)
Answered By: rafalotufo

Update: Adding a timedelta version anyway, as it looks prettier 🙂

def get_years_months(start_date, months):
    for i in range(months):
        yield (start_date.year, start_date.month)
        start_date -= datetime.timedelta(days=calendar.monthrange(start_date.year, start_date.month)[1])

You don’t need to work with timedelta since you only need year and month, which is fixed.

def get_years_months(my_date, num_months):
    cur_month = my_date.month
    cur_year = my_date.year

    result = []
    for i in range(num_months):
        if cur_month == 0:
            cur_month = 12
            cur_year -= 1
        result.append((cur_year, cur_month))
        cur_month -= 1

    return result

if __name__ == "__main__":
    import datetime
    result = get_years_months(datetime.date.today(), 10)
    print result
Answered By: Botond Béres

Neatest would be to use integer division (//) and modulus (%) functions, representing the month by the number of months since year 0:

months = year * 12 + month - 1 # Months since year 0 minus 1
tuples = [((months - i) // 12, (months - i) % 12 + 1) for i in range(10)]

The - 1 in the months expression is required to get the correct answer when we add 1 to the result of the modulus function later to get 1-indexing (i.e. months go from 1 to 12 rather than 0 to 11).

Or you might want to create a generator:

def year_month_tuples(year, month):
    months = year * 12 + month - 1 # -1 to reflect 1-indexing
    while True:
        yield (months // 12, months % 12 + 1) # +1 to reflect 1-indexing
        months -= 1 # next time we want the previous month

Which could be used as:

>>> tuples = year_month_tuples(2011, 7)
>>> [tuples.next() for i in range(10)]
Answered By: cwb

I don’t see it documented anywhere, but time.mktime will “roll over” into the correct year when given out-of-range, including negative, month values:

x = 10
now = time.localtime()
print([time.localtime(time.mktime((now.tm_year, now.tm_mon - n, 1, 0, 0, 0, 0, 0, 0)))[:2] for n in range(x)])
Answered By: slowdog

Using relativedelta

import datetime
from dateutil.relativedelta import relativedelta

def get_last_months(start_date, months):
    for i in range(months):
        yield (start_date.year,start_date.month)
        start_date += relativedelta(months = -1)

>>> X = 10       
>>> [i for i in get_last_months(datetime.datetime.today(), X)]
>>> [(2013, 2), (2013, 1), (2012, 12), (2012, 11), (2012, 10), (2012, 9), (2012, 8), (2012, 7), (2012, 6), (2012, 5)]
Answered By: trinchet

if you want to do it without datetime libraries, you can convert to months since year 0 and then convert back

end_year = 2014
end_month = 5
start_year = 2013
start_month = 7

print list = [(a/12,a % 12+1) for a in range(12*end_year+end_month-1,12*start_year+start_month-2,-1)]

python 3 (// instead of /):

list = [(a//12,a % 12+1) for a in range(12*end_year+end_month-1,12*start_year+start_month-2,-1)]
print(list)

[(2014, 5),
(2014, 4),
(2014, 3),
(2014, 2),
(2014, 1),
(2013, 12),
(2013, 11),
(2013, 10),
(2013, 9),
(2013, 8),
(2013, 7)]

Answered By: cperlmutter

Or you can define a function to get the last month, and then print the months (
it’s a bit rudimentary)

def last_month(year_month):#format YYYY-MM
    aux = year_month.split('-')
    m = int(aux[1])
    y = int(aux[0])

    if m-1 == 0:
        return str(y-1)+"-12"
    else:
        return str(y)+"-"+str(m-1)

def print_last_month(ran, year_month= str(datetime.datetime.today().year)+'-'+str(datetime.datetime.today().month)):
    i = 1 
    if ran != 10:
        print( last_month(year_month) )
        print_last_month(i+1, year_month= last_month(year_month))
Answered By: fsalazar_sch
    def list_last_year_month(self):
    last_day_of_prev_month = date.today()
    number_of_years = self.number_of_years
    time_list = collections.defaultdict(list)
    for y in range(number_of_years+1):
        for m in range(13):
            last_day_of_prev_month = last_day_of_prev_month.replace(day=1) - timedelta(days=1)
            last_month = str(last_day_of_prev_month.month)
            last_year = str(last_day_of_prev_month.year)
            time_list[last_year].append(last_month)
    return time_list
Answered By: Hanish Madan

Simple solution using datetime and relativedelta functions. This returns the past dates by subtracting the number of months(input). This function will return the full date and using the below functions it is possible to get the year and month separately.

from datetime import date
from dateutil.relativedelta import relativedelta

def get_past_date(number_of_months):
        return date.today() - relativedelta(months=number_of_months)

to get the year from the date

def get_year_from_the_date(date):
        return date.year

to get the month from the date

def get_month_from_the_date(date):
        return date.month
Answered By: Nathindu Himansha
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.