9
\$\begingroup\$

I am looking for a cleaner, more pythonic way to get a list of the name of the months starting with the current month and ending 12 months later.

For example, it's December so my list should be

['Dec', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov']

Here is how I'm currently doing it:

from datetime import datetime

currentMonth = datetime.now().month

if (currentMonth == 1):
    theList = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
elif (currentMonth == 2):
    theList = ['Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec', 'Jan']
elif (currentMonth == 3):
    theList = ['Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec', 'Jan', 'Feb']
elif (currentMonth == 4):
    theList = ['Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec', 'Jan', 'Feb', 'Mar']
elif (currentMonth == 5):
    theList = ['May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec', 'Jan', 'Feb', 'Mar', 'Apr']
elif (currentMonth == 6):
    theList = ['Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec', 'Jan', 'Feb', 'Mar', 'Apr', 'May']
elif (currentMonth == 7):
    theList = ['Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun']
elif (currentMonth == 8):
    theList = ['Aug', 'Sep', 'Oct', 'Nov', 'Dec', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul']
elif (currentMonth == 9):
    theList = ['Sep', 'Oct', 'Nov', 'Dec', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug']
elif (currentMonth == 10):
    theList = ['Oct', 'Nov', 'Dec', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep']
elif (currentMonth == 11):
    theList = ['Nov', 'Dec', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct']
elif (currentMonth == 12):
    theList = ['Dec', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov']

print(theList)

Ideas?

\$\endgroup\$
16
\$\begingroup\$

Instead of hard-coding month names - delegate all the work to respective libraries.

I would suggest 2 approaches:

  • calendar.month_abbr

    from datetime import datetime
    from calendar import month_abbr
    
    def get_forward_month_list():
        month = datetime.now().month   # current month number
        return [month_abbr[(month % 12 + i) or month] for i in range(12)]
    
  • dateutil.relativedelta.relativedelta

    from datetime import datetime, timedelta
    from dateutil.relativedelta import relativedelta
    
    def get_forward_month_list():
        now = datetime.now()
        return [(now + relativedelta(months=i)).strftime('%b') for i in range(12)]
    

Both approaches will return the expected/needed list of month names:

print(get_forward_month_list())
['Dec', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov']
\$\endgroup\$
  • 2
    \$\begingroup\$ Awesome. I thought there was a way to do it utilizing a library. Thank you. \$\endgroup\$ – Christopher Dec 9 '19 at 18:26
  • 9
    \$\begingroup\$ To avoid modulos and ors and complicated expressions for skipping the empty [0] I would use return month_abbr[month:] + month_abbr[1:month] instead \$\endgroup\$ – njzk2 Dec 10 '19 at 3:23
  • 2
    \$\begingroup\$ @Christopher said "I thought there was a way to do it utilizing a library." The only thing the library did was provide the month_abbr list. It's the same as your first definition of theList, so, in terms of coding, all the library did was save a few characters of typing. — Thinking at a higher level though, libraries guarantee getting the same abbreviations as everyone else, and that should the world decide to make changes to the official abbreviation list (e.g. add a new month) your code will handle it with no changes on your part. And if a user prefers French, that's what they'll see. \$\endgroup\$ – Ray Butterworth Dec 10 '19 at 14:35
  • 1
    \$\begingroup\$ @njzk2 Absolutely. I've been staring at (month % 12 + i) or month for far too long. I still don't get why the naive solution (month + i) % 12 wouldn't work? What am I missing? \$\endgroup\$ – JollyJoker Dec 10 '19 at 15:22
  • 1
    \$\begingroup\$ @njzk2 — Another good reason for not using the %12 method is that it makes an assumption about the number of elements in a list obtained from a library. In this case, I doubt a thirteenth month will be added anytime soon, but as a general principle, it's not good to make assumptions about what something else provides. Even if the library were something that the program's author maintains, it's still not good to use hard-wired numbers. Changing the library would require changing this code. 0, 1, and many are the only good numbers to use when programming. \$\endgroup\$ – Ray Butterworth Dec 10 '19 at 16:39
9
\$\begingroup\$

First of call, theList is not a good name for that variable. You can be much more specific in your case! I'd suggest months.

Also, there is no need to have all of those lists hard-coded in your script. Use a single instance of that list, and then create modified versions as you need them.

Edit: I was absolutely sure that there has to be a Python library that has the names already covered, but failed to find it. RomanPerekhrest beat me here and correctly identified calendar.month_abbr as the way to go.

A list comprehension like below could to the trick:

months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
current_month = datetime.now().month - 1
print([months[(current_month + i) % 12] for i in range(12)])

Or you can use slicing to get it even more comfortable:

months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
current_month = datetime.now().month - 1
print(months[current_month:]+months[:current_month])

If performance ever should become a concern, you could create a look-up using a dictionary beforehand, so that the computation does not have to be repeated:

MONTHS_TO_COME = {i+1: months[i:]+months[:i] for i in range(12)}

# ... other code ...

month = datetime.now().month
print(MONTHS_TO_COME[month])

Oh, and maybe have a look at the "official" Style Guide for Python Code, aka PEP 8, for some hints on idiomatic formatting commonly found in Python code (e.g. lower_case_with_underscores in variable names).

\$\endgroup\$
7
\$\begingroup\$

Shamelessly ripping off @RomanPerekhrest 's answer. You can't rotate a list in Python but you can a deque, and the collections module always needs more love:

from datetime import datetime
from calendar import month_abbr
from collections import deque

def get_forward_months():
    months = deque(month_abbr[1:])
    months.rotate(1-datetime.now().month)
    return months
\$\endgroup\$
  • 1
    \$\begingroup\$ I don't think you need the list comprehension. months = deque(month_abbr[1:]) should work, shouldn't it? \$\endgroup\$ – Matt M. Dec 10 '19 at 14:46
  • \$\begingroup\$ @MattM. Yes, even better, thanks. \$\endgroup\$ – richardb Dec 10 '19 at 15:31
3
\$\begingroup\$

I'd recommend using the built-in strftime rather than defining your own list of strings:

import datetime

date = datetime.date.today()
months = []
for _ in range (12):
    months.append(date.strftime("%b"))
    date = date.replace(month=(date.month % 12 + 1))

Because someone will always complain when you can't find a way to do a loop like this as a list comprehension, here's a way to do it in a single line of code with a cheesy timedelta (this doesn't work for arbitrarily long sequences because 30 days isn't exactly one month, but for your use case the rounding errors won't add up enough to matter):

months = [(datetime.date.today().replace(day=15) + datetime.timedelta(days=30*n)).strftime("%b") for n in range(12)]

although to make this readable I think you'd want to break it up a little:

months = [
    (datetime.date.today().replace(day=15) + 
     datetime.timedelta(days=30*n)).strftime("%b") 
     for n in range(12)
]

IMO the for loop version is cleaner. :)

\$\endgroup\$
2
\$\begingroup\$

You can work with a single list theList = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']

What you want to do is to start with a certain index and then add 12 elements to a list while you have index%12. So you can do something like

from datetime import datetime

theList = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
month = datetime.now().month
newList = list()
for i in range(12):
    newList.append(theList[(month-1+i)%12])

print(newList)
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.