use day occurance counter instead of week counter

This commit is contained in:
Fredrick W. Warren 2026-01-02 13:59:52 -05:00
parent 4e571b1b41
commit 12f096c56d

118
main.py
View File

@ -6,31 +6,11 @@ CHURCH CALENDAR CSV GENERATOR
import calendar import calendar
from dataclasses import dataclass from dataclasses import dataclass
from datetime import date, datetime from datetime import date, datetime
from pprint import pprint
import sys import sys
import click import click
from dataclass_csv import DataclassWriter from dataclass_csv import DataclassWriter
from events import EVENT_LIST from events import EVENT_LIST
"""
build a list of months / weeks / day of the month in those weeks
months 1-12, weeks 0-5 days of week 5 6 are the 1st and 2nd of month
MONTHS[1] [0] [ 0 0 0 0 0 1 2 ]
due to how months work
week 0 can have null days (represented by 0's)
weeks 1 - 4 will have all 7 days present
week 5 may have 7 days or nulls
week 6 can have a monday and/or tuesday in them if the 1st falls on a
saturday or sunday in week 0
EVENT_LIST has weeks as 1-6 while MONTHS has weeks as 0-5, offsetting will
be necessary
"""
def initalize_events(event_list): def initalize_events(event_list):
""" convert EVENT_LIST into monthly_events """ """ convert EVENT_LIST into monthly_events """
monthly_events = [[] for _ in range(7)] monthly_events = [[] for _ in range(7)]
@ -38,14 +18,8 @@ def initalize_events(event_list):
monthly_events[event[0]].append(event[1:]) monthly_events[event[0]].append(event[1:])
return monthly_events return monthly_events
def debug_print(data, condition=True, end="\n"): def debug_print(data, condition=True, end="\n"):
"""Conditionally print data using pprint. """Conditionally print data."""
Args:
data: The data to be printed.
condition: A boolean value. If True, the data will be printed.
"""
if condition: if condition:
print(data, end=end) print(data, end=end)
@ -58,7 +32,6 @@ def initalize_year(year: int) -> dict[int, list[list[int]]]:
@dataclass @dataclass
class Event(): class Event():
"""Event for CSV export""" """Event for CSV export"""
# pylint: disable=too-many-instance-attributes
event_name: str event_name: str
venue_name: str venue_name: str
organizer_name: str organizer_name: str
@ -86,20 +59,13 @@ def suffix(day: int) -> str:
result = 'rd' result = 'rd'
return result return result
def sort_events(events: list[Event]) -> list[Event]:
"""sort events"""
events = sorted(events, key=lambda k: (k.start_date, k.start_time))
return events
def create_event(name: str, def create_event(name: str,
location: str, location: str,
categories: str, categories: str,
description: str, description: str,
start, start,
finish) -> None: finish) -> Event:
"""create event""" """create event object"""
# pylint: disable=too-many-arguments
event = Event(name, event = Event(name,
location, location,
"", "",
@ -116,13 +82,11 @@ def create_event(name: str,
"", "",
description) description)
return event return event
# events.append(event)
def write_calendar(events: list[Event], filename) -> None: def write_calendar(events: list[Event], filename) -> None:
"""write calendar to csv file""" """write calendar to csv file"""
with open(filename, "w", encoding="utf-8", newline="") as handle: with open(filename, "w", encoding="utf-8", newline="") as handle:
writer =DataclassWriter(handle, events, Event) writer = DataclassWriter(handle, events, Event)
writer.map("event_name").to("EVENT NAME") writer.map("event_name").to("EVENT NAME")
writer.map("venue_name").to("VENUE NAME") writer.map("venue_name").to("VENUE NAME")
writer.map("organizer_name").to("ORGANIZER NAME") writer.map("organizer_name").to("ORGANIZER NAME")
@ -140,7 +104,6 @@ def write_calendar(events: list[Event], filename) -> None:
writer.map("event_description").to("EVENT DESCRIPTION") writer.map("event_description").to("EVENT DESCRIPTION")
writer.write() writer.write()
def process_day(events, year, month, day, day_events): def process_day(events, year, month, day, day_events):
"""process all events for one day""" """process all events for one day"""
for event_item in day_events: for event_item in day_events:
@ -151,87 +114,78 @@ def process_day(events, year, month, day, day_events):
events.append(event) events.append(event)
return events return events
def process_month(months, monthly_events, year, month, week_of_month, week, events): def process_month(monthly_events, year, month, week, events, weekday_counters):
"""process one months of events""" """process one week of events using weekday occurrence counters"""
for day_of_week, day in enumerate(week): for day_of_week, day in enumerate(week):
if day > 0: if day > 0:
""" # Increment the occurrence count for this specific weekday (0=Mon, 6=Sun)
month, week_of_month, day_of_week, day weekday_counters[day_of_week] += 1
use monthly_events[day_of_week] current_occurrence = weekday_counters[day_of_week]
"""
debug_print(f" {day_of_week}-{day:02d}", DEBUG) debug_print(f" {day_of_week}-{day:02d} (Occurrence: {current_occurrence})", DEBUG)
day_events = [x[1:] for x in monthly_events[day_of_week] if week_of_month + 1 in x[0]]
# Filter events matching this day of week and this specific occurrence (e.g., 2nd Monday)
day_events = [x[1:] for x in monthly_events[day_of_week] if current_occurrence in x[0]]
events = process_day(events, year, month, day, day_events) events = process_day(events, year, month, day, day_events)
debug_print("", DEBUG)
return events return events
def process_months(months, monthly_events, year, month_range): def process_months(months_dict, monthly_events, year, month_range):
"""process full year of months and return list of events""" """process selected months and return list of events"""
events = [] events = []
for month in range(1, 13): for month in range(1, 13):
if month not in month_range: if month not in month_range:
continue continue
debug_print(f"MONTH: {month}", DEBUG) debug_print(f"MONTH: {month}", DEBUG)
for week_of_month, week in enumerate(months[month]):
# Initialize counters for the month to track 1st, 2nd, etc. occurrence of each weekday
weekday_counters = [0] * 7
for week_of_month, week in enumerate(months_dict[month]):
debug_print(f"{week_of_month} {week}", DEBUG) debug_print(f"{week_of_month} {week}", DEBUG)
events = process_month(months, monthly_events, year, month, week_of_month, week, events) events = process_month(monthly_events, year, month, week, events, weekday_counters)
return events return events
def validate_months(ctx, param, value): def validate_months(ctx, param, value):
""" """Validates and processes the --months option."""
Validates and processes the --months option.
Accepts a single number (1-12) or a range (1-3).
Defaults to 1-12 if no value is provided.
"""
if value is None: if value is None:
return list(range(1, 13)) # Default behavior return list(range(1, 13))
parts = value.split('-') parts = value.split('-')
if len(parts) == 1: if len(parts) == 1:
# Handle single month input
try: try:
month = int(parts[0]) month = int(parts[0])
if 1 <= month <= 12: if 1 <= month <= 12:
return [month] return [month]
else: raise click.BadParameter("Month must be between 1 and 12.")
raise click.BadParameter("Month must be between 1 and 12.", param=param)
except ValueError: except ValueError:
raise click.BadParameter("Invalid month format. Please use an integer between 1 and 12.", param=param) raise click.BadParameter("Invalid month format.")
elif len(parts) == 2: elif len(parts) == 2:
# Handle range input
try: try:
start, end = int(parts[0]), int(parts[1]) start, end = int(parts[0]), int(parts[1])
if 1 <= start <= 12 and 1 <= end <= 12 and start <= end: if 1 <= start <= 12 and 1 <= end <= 12 and start <= end:
return list(range(start, end + 1)) return list(range(start, end + 1))
else: raise click.BadParameter("Invalid month range.")
raise click.BadParameter("Invalid month range. Both months must be between 1 and 12, and the start month must be less than or equal to the end month.", param=param)
except ValueError: except ValueError:
raise click.BadParameter("Invalid range format. Please use two integers separated by a hyphen (e.g., 1-3).", param=param) raise click.BadParameter("Invalid range format.")
raise click.BadParameter("Use a single number or a range (e.g., 1-3).")
else:
# Handle invalid format
raise click.BadParameter("Invalid format. Use a single number (e.g., 3) or a range (e.g., 1-3).", param=param)
# @click.argument('filename', type=click.File('w'), default="calendar.csv", required=False)
@click.command() @click.command()
@click.option("--debug", "-d", is_flag=True, default=False, help="Print debug output") @click.option("--debug", "-d", is_flag=True, default=False, help="Print debug output")
@click.option('--year', "-y", type=int, default=date.today().year, help='Specify the year.') @click.option('--year', "-y", type=int, default=date.today().year, help='Specify the year.')
@click.option('--months', '-m', 'month_range', callback=validate_months, default=None, @click.option('--months', '-m', 'month_range', callback=validate_months, default=None,
help='A single month (1-12) or a range (e.g., 1-3). Defaults to all months (1-12).') help='A single month (1-12) or a range (e.g., 1-3).')
@click.argument('filename', default="calendar.csv", required=False) @click.argument('filename', default="calendar.csv", required=False)
def main(debug, year, month_range, filename): def main(debug, year, month_range, filename):
"""Create a .csv file of church events for the year. Modify events.py to change events""" """Create a .csv file of church events for the year."""
global DEBUG global DEBUG
DEBUG = debug DEBUG = debug
months = initalize_year(year) months_dict = initalize_year(year)
monthly_events = initalize_events(EVENT_LIST) monthly_events = initalize_events(EVENT_LIST)
events = process_months(months, monthly_events, year, month_range) events = process_months(months_dict, monthly_events, year, month_range)
# events = sort_events(events)
write_calendar(events, filename) write_calendar(events, filename)
sys.exit(0) sys.exit(0)
if __name__ == "__main__": if __name__ == "__main__":
main() # pylint: disable=E1120 main()