Looping through ("Spanning across") dates in Python with variable months and years?

Question:

My requirement is to loop through calendar dates in python in 2-day increments capturing every date across multiple months or years without repeating any dates.

I’m sure that sounds confusing so I’ve posted some reference visuals to demonstrate.

I have written Python code that successfully achieves my desired result (also attached), but it is clunky and gross code. I’m wondering if someone has a more elegant solution?

The reason I need this loop is to "span" the main function and capture either 1 whole month, multiple whole months, or even multiple years with multiple whole months (which I set by changing the years and months variables at the top of the code). These variable give me control over what years and months I want to span with the main function.

Example:
First Loop Iteration: start_date = 1/1/2021 end_date = 1/2/2021
Second Loop Iteration: start_date = 1/3/2021 end_date = 1/4/2021
….
Fifteenth Loop Iteration: start_date = 1/29/2021 end_date = 1/30/2021
Sixteenth Loop Iteration: start_date = 1/31/2021 end_date = 1/31/2021
Seventeenth Loop Iteration: start_date = 2/1/2021 end_date = 2/2/2021
Eighteenth Loop Iteration: start_date = 2/3/2021 end_date = 2/4/2021

Visualization of Example of Desired Loop

Here is the code that works to accomplish this task:

# Set date range to loop through
years = [2021, 2022]
months = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
days_in_month = {1: 31, 2: 28, 3: 31, 4: 30, 5: 31,
                 6: 30, 7: 31, 8: 31, 9: 30, 10: 31, 11: 30, 12: 31}
clients = ["client1", "client2"]
# Loop through clients list and date range
combined_records = []
for client in clients:
    for target_year in years:
        for target_month in months:
            start_day = 1
            end_day = 2
            # Even number days in a month
            if days_in_month[target_month] % 2 == 0:
                while end_day <= days_in_month[target_month]:
                    # Arguments for main function: main(client, start_date, end_date)
                    list_of_records = main(client, date(target_year, target_month, start_day),
                                           date(target_year, target_month, end_day))
                    for record in list_of_records:
                        combined_records.append(record)
                    # Increment date range
                    start_day += 2
                    end_day += 2
            # Odd number days in a month
            else:
                while end_day <= 32:
                    if end_day < 32:
                        # Arguments for main function: main(client, start_date, end_date)
                        list_of_records = main(client, date(target_year, target_month, start_day),
                                               date(target_year, target_month, end_day))
                        for record in list_of_records:
                            combined_records.append(record)
                        # Increment date range
                        start_day += 2
                        end_day += 2
                    else:
                        end_day = 31
                        # Arguments for main function: main(client, start_date, end_date)
                        list_of_records = main(client, date(target_year, target_month, start_day),
                                               date(target_year, target_month, end_day))
                        for record in list_of_records:
                            combined_records.append(record)
                        # Increment date range
                        start_day += 2
                        end_day += 2

    print(f"CLIENT COMPLETE: {client}")
Asked By: Zee

||

Answers:

You can use numpy’s datetime64 to create an array of evenly spaced dates and then just iterate through that single array.

dates = np.arange(np.datetime64('2021-01-01'), np.datetime64('2022-12-31'), np.timedelta64(2, 'D'))

for date in dates:
    # code here
Answered By: Michael Cao

You can solve this using a combinations of generators and a helper-function, using only built-ins:

import datetime
from typing import Generator
from pprint import pp

def generate_month(year: int, month: int) -> Generator[datetime.datetime, None, None]:
    """generates a month of datetime-objects"""
    day_variable = datetime.datetime(year=year, month=month, day=1)
    while day_variable.month == month:
        yield day_variable
        day_variable = day_variable + datetime.timedelta(days=1)

def get_day_pairs(year: int, month: int) -> list[list[datetime.datetime, datetime.datetime]]:
    """
        Generates a month of days, and chunks them up.
        Pair of two conscutive days if number of days are even
        otherwise the last day i repeated.
    """
    days = [i for i in generate_month(year=year, month=month)]
    days_chunk = [days[idx: idx+2] for idx in range(0, len(days), 2)]
    if len(days_chunk[-1]) == 1:
        days_chunk[-1].append(days_chunk[-1][0])
    return days_chunk

years = [2021, 2022]
months = [1, 2]

for year in years:
    for month in months:
        pp(get_day_pairs(year=year, month=month))
Answered By: Hampus Larsson
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.