diff --git a/smoothschedule/schedule/management/commands/seed_holidays.py b/smoothschedule/schedule/management/commands/seed_holidays.py new file mode 100644 index 0000000..cb23ecd --- /dev/null +++ b/smoothschedule/schedule/management/commands/seed_holidays.py @@ -0,0 +1,201 @@ +"""Management command to seed US holidays.""" +from django.core.management.base import BaseCommand + +from schedule.models import Holiday + + +US_HOLIDAYS = [ + # Fixed date holidays + { + "code": "new_years_day", + "name": "New Year's Day", + "country": "US", + "holiday_type": Holiday.Type.FIXED, + "month": 1, + "day": 1, + }, + { + "code": "independence_day", + "name": "Independence Day", + "country": "US", + "holiday_type": Holiday.Type.FIXED, + "month": 7, + "day": 4, + }, + { + "code": "veterans_day", + "name": "Veterans Day", + "country": "US", + "holiday_type": Holiday.Type.FIXED, + "month": 11, + "day": 11, + }, + { + "code": "christmas_eve", + "name": "Christmas Eve", + "country": "US", + "holiday_type": Holiday.Type.FIXED, + "month": 12, + "day": 24, + }, + { + "code": "christmas_day", + "name": "Christmas Day", + "country": "US", + "holiday_type": Holiday.Type.FIXED, + "month": 12, + "day": 25, + }, + { + "code": "new_years_eve", + "name": "New Year's Eve", + "country": "US", + "holiday_type": Holiday.Type.FIXED, + "month": 12, + "day": 31, + }, + # Floating holidays (Nth weekday of month) + { + "code": "mlk_day", + "name": "Martin Luther King Jr. Day", + "country": "US", + "holiday_type": Holiday.Type.FLOATING, + "month": 1, + "week_of_month": 3, # 3rd Monday of January + "day_of_week": 0, # Monday + }, + { + "code": "presidents_day", + "name": "Presidents Day", + "country": "US", + "holiday_type": Holiday.Type.FLOATING, + "month": 2, + "week_of_month": 3, # 3rd Monday of February + "day_of_week": 0, # Monday + }, + { + "code": "memorial_day", + "name": "Memorial Day", + "country": "US", + "holiday_type": Holiday.Type.FLOATING, + "month": 5, + "week_of_month": 5, # Last Monday of May (5 = last) + "day_of_week": 0, # Monday + }, + { + "code": "juneteenth", + "name": "Juneteenth", + "country": "US", + "holiday_type": Holiday.Type.FIXED, + "month": 6, + "day": 19, + }, + { + "code": "labor_day", + "name": "Labor Day", + "country": "US", + "holiday_type": Holiday.Type.FLOATING, + "month": 9, + "week_of_month": 1, # 1st Monday of September + "day_of_week": 0, # Monday + }, + { + "code": "columbus_day", + "name": "Columbus Day", + "country": "US", + "holiday_type": Holiday.Type.FLOATING, + "month": 10, + "week_of_month": 2, # 2nd Monday of October + "day_of_week": 0, # Monday + }, + { + "code": "thanksgiving", + "name": "Thanksgiving Day", + "country": "US", + "holiday_type": Holiday.Type.FLOATING, + "month": 11, + "week_of_month": 4, # 4th Thursday of November + "day_of_week": 3, # Thursday + }, + { + "code": "thanksgiving_friday", + "name": "Day After Thanksgiving", + "country": "US", + "holiday_type": Holiday.Type.FLOATING, + "month": 11, + "week_of_month": 4, # 4th Friday of November (day after Thanksgiving) + "day_of_week": 4, # Friday + }, + # Calculated holidays (Easter-based) + { + "code": "easter", + "name": "Easter Sunday", + "country": "US", + "holiday_type": Holiday.Type.CALCULATED, + "calculation_rule": "easter", + }, + { + "code": "good_friday", + "name": "Good Friday", + "country": "US", + "holiday_type": Holiday.Type.CALCULATED, + "calculation_rule": "easter-2", # 2 days before Easter + }, +] + + +class Command(BaseCommand): + """Seed US holidays into the database.""" + + help = "Seed US holidays for the time blocking system" + + def add_arguments(self, parser): + parser.add_argument( + "--clear", + action="store_true", + help="Clear existing holidays before seeding", + ) + + def handle(self, *args, **options): + """Create or update holiday records.""" + if options["clear"]: + deleted_count, _ = Holiday.objects.filter(country="US").delete() + self.stdout.write( + self.style.WARNING(f"Deleted {deleted_count} existing US holidays") + ) + + created_count = 0 + updated_count = 0 + + for holiday_data in US_HOLIDAYS: + code = holiday_data["code"] + holiday, created = Holiday.objects.update_or_create( + code=code, + defaults=holiday_data, + ) + if created: + created_count += 1 + self.stdout.write(f" Created: {holiday.name}") + else: + updated_count += 1 + self.stdout.write(f" Updated: {holiday.name}") + + self.stdout.write( + self.style.SUCCESS( + f"\nDone! Created {created_count}, updated {updated_count} holidays." + ) + ) + + # Show sample dates for the current year + from datetime import date + current_year = date.today().year + self.stdout.write(f"\nHoliday dates for {current_year}:") + for holiday in Holiday.objects.filter(country="US", is_active=True).order_by("month", "day"): + try: + holiday_date = holiday.get_date_for_year(current_year) + if holiday_date: + self.stdout.write(f" {holiday.name}: {holiday_date.strftime('%B %d, %Y')}") + except Exception as e: + self.stdout.write( + self.style.ERROR(f" {holiday.name}: Error calculating date - {e}") + )