开发者

Get (year,month) for the last X months

I got a very simple thing to to in python: I need a list of tuples (year,month) for th开发者_StackOverflowe 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?


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)])


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)]


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)]


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


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)


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)]


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))


    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


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
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜