Jordan Dimov
Jordan Dimov

Reputation: 1308

Annotate list of months with year

I have a list of several consecutive months, always including the current month. For example, as I write this it is April 2020, so a valid list could be any of the following:

example1 = ["January", "February", "March", "April", "May"]
example2 = ["December", "January", "February", "March", "April"]
example3 = ["April", "May", "June", "July", "August", "September", "October", "November", "December", "January", "February"]

What's the best way to write a Python function which, given a list like this, would return a corresponding list of years, pivoting around the current month being the current year. For example:

f(example1) = [2020, 2020, 2020, 2020, 2020]
f(example2) = [2019, 2020, 2020, 2020, 2020]
f(example3) = [2020, 2020, 2020, 2020, 2020, 2020, 2020, 2020, 2020, 2021, 2021]

The best I can think of is partition the original list by the current month, then loop in each direction while also keeping track of the year, then put the two resulting lists together. But this is way too much looping. Is there a more elegant / faster solution?

Upvotes: 3

Views: 510

Answers (2)

JvdV
JvdV

Reputation: 75870

Well, as a Python beginner I have been puzzling a little bit. I think the logic would be:

  • find the index of the month you are working in currently, e.g.: April
  • set this to the current year
  • loop through the rest of the list using if...elif... structure to generate values according to which side of the current month we are.

I made use of from dateutil.relativedelta import relativedelta as per this older post on SO.

import datetime as dt
from dateutil.relativedelta import relativedelta

def get_years_for(months):
    today = dt.date.today()
    try:
        month_index = months.index(today.strftime("%B"))
    except ValueError:
        return []

    current_month = today.month
    current_year = today.year
    day1 = dt.date(current_year, current_month, 1)

    years = {month_index: current_year}

    for idx, month in enumerate(months):
        if idx < month_index:
            years[idx] = (day1 - relativedelta(months=month_index - idx)).year
        elif idx > month_index:
            years[idx] = (day1 + relativedelta(months=idx - month_index)).year

    return [years[i] for i in sorted(years)]

I'm sure this can be done neater, but I liked the challenge as I'm new to this =)


example1 = ["January", "February", "March", "April", "May"]
[2020, 2020, 2020, 2020, 2020]

example2 = ["December", "January", "February", "March", "April"]
[2019, 2020, 2020, 2020, 2020]

example3 = ["April", "May", "June", "July", "August", "September", "October", "November", "December", "January", "February"]
[2020, 2020, 2020, 2020, 2020, 2020, 2020, 2020, 2020, 2021, 2021]

example4 = ["November", "December", "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "January", "February"]
[2019, 2019, 2020, 2020, 2020, 2020, 2020, 2020, 2020, 2020, 2020, 2020, 2020, 2020, 2021, 2021]

All this has been done with the assumption the current month, e.g. April, only occurs once in your list (as per your explanation). Even then, it will still work as long as we can safely make the assumption that the first index of April is your starting position.

Hope it helps.

Upvotes: 2

Marcus
Marcus

Reputation: 320

First get the month as a number, then you can iterrate over the list. All months before the target will be in the current or previous year while the months after the target will be in the current or next year.

The name_to_num function came from this answer

from datetime import datetime
import calendar

name_to_num = {name: num for num, name in enumerate(calendar.month_name) if num}

def f(months):
    curr_month = datetime.now().strftime("%B")  # Get month name.
    curr_year = datetime.now().year
    curr_month_idx = name_to_num[curr_month]

    years = []
    seen_curr_month = False
    for month in months:
        month_idx = name_to_num[month]

        # Set seen to True once the terget month is reached.
        if month_idx == curr_month_idx:
            seen_curr_month = True

        # Target has not been seen 
        if not seen_curr_month:
            # But the months are grater than target then its the previous year.
            if month_idx > curr_month_idx:
                years.append(curr_year-1)
            # If they are smaller or equal to target then its the current year.
            else:
                years.append(curr_year)

        # All years from now on will be greater or equal to current. 
        else:
            # If the month is greater than current its the same year.
            if month_idx >= curr_month_idx:
                years.append(curr_year)
            # Otherwise its the next year.
            else:
                years.append(curr_year+1)

    return years

example1 = ["January", "February", "March", "April", "May"]
example2 = ["December", "January", "February", "March", "April"]
example3 = ["April", "May", "June", "July", "August", "September", "October", "November", "December", "January", "February"]

print(f(example1)) # [2020, 2020, 2020, 2020, 2020]
print(f(example2)) # [2019, 2020, 2020, 2020, 2020]
print(f(example3)) # [2020, 2020, 2020, 2020, 2020, 2020, 2020, 2020, 2020, 2021, 2021]


Upvotes: 0

Related Questions