Receive (year, month) for the last X months

I have a very simple thing for python: I need a list of tuples (year,month) for the last x months, starting (including) from today. So, for x = 10 and today (July 2011), the team 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 version for 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 displays the correct solution for my test cases, but I do not like this solution: it assumes that the month has four weeks, and this is simply not true. I could replace weeks=4 with days=30 , which would make a better solution, but it is still incorrect.

Another solution that occurred to me was to use simple mathematical calculations and subtract 1 from the month counter, and if the month counter is 0, subtract 1 from the year counter. The problem with this solution: it requires more code and is not very readable.

So how can this be done correctly?

+6
source share
7 answers

I don’t see this being documented anywhere, but time.mktime will “flip” to the correct year when it is 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)]) 
+19
source

Neatest will use integer division ( // ) and module ( % ), representing the month by the number of months from 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)] 

- 1 in the months expression it is required to get the correct answer when we add 1 to the result of the module function later to get 1-indexing (i.e. months go from 1 to 12, and not from 0 to 11).

Or you can 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 

What can be used as:

 >>> tuples = year_month_tuples(2011, 7) >>> [tuples.next() for i in range(10)] 
+6
source

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)] 
+4
source

If you create a function for date math, it becomes almost as enjoyable as the 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) 
+2
source

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 do not need to work with timedelta , since you only need the year and month that 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 
+2
source

if you want to do this without datetime libraries you can convert to months from 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)]

+1
source

Or you can define a function to get the last month, and then print the months (this is 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)) 
0
source

Source: https://habr.com/ru/post/891988/


All Articles