Reputation: 32031
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?
Upvotes: 12
Views: 23641
Reputation: 94
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
Upvotes: 0
Reputation: 47
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
Upvotes: 0
Reputation: 338
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)]
Upvotes: 1
Reputation: 6206
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)])
Upvotes: 23
Reputation: 455
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))
Upvotes: 0
Reputation: 6933
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)]
Upvotes: 9
Reputation: 925
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)]
Upvotes: 6
Reputation: 16673
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
Upvotes: 5
Reputation: 3932
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)
Upvotes: 2