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}")
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
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))
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}")
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
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))