diff --git a/flytekit/core/schedule.py b/flytekit/core/schedule.py index d9bb621cf0..98c7929cad 100644 --- a/flytekit/core/schedule.py +++ b/flytekit/core/schedule.py @@ -142,12 +142,12 @@ def _validate_expression(cron_expression: str): def _validate_schedule(schedule: str): if schedule.lower() not in CronSchedule._VALID_CRON_ALIASES: try: - croniter.croniter(schedule) - except Exception: - raise ValueError( - "Schedule is invalid. It must be set to either a cron alias or valid cron expression." - f" Provided schedule: {schedule}" - ) + # Validate the cron expression + cron = croniter.croniter(schedule) + # Try to get the next occurrence to validate the schedule + cron.get_next(datetime.datetime) + except Exception as e: + raise ValueError(f"Schedule is invalid. Provided schedule: {schedule} Error: {str(e)}") @staticmethod def _validate_offset(offset: str): diff --git a/tests/flytekit/unit/core/test_schedule.py b/tests/flytekit/unit/core/test_schedule.py index c66c93d646..bcb6ad03fa 100644 --- a/tests/flytekit/unit/core/test_schedule.py +++ b/tests/flytekit/unit/core/test_schedule.py @@ -153,3 +153,35 @@ def quadruple(a: int) -> int: assert lp.schedule == _schedule_models.Schedule( "kickoff_input", rate=_schedule_models.Schedule.FixedRate(12, _schedule_models.Schedule.FixedRateUnit.HOUR) ) + + +@pytest.mark.parametrize( + "invalid_schedule", + [ + "0 0 31 2 *", # February 31st (does not exist) + "0 0 30 2 *", # February 30th (does not exist) + "0 0 31 4 *", # April 31st (does not exist) + "0 0 31 6 *", # June 31st (does not exist) + ], +) +def test_cron_invalid_date_combinations(invalid_schedule): + """Test that CronSchedule rejects invalid date combinations like 31st of February.""" + with pytest.raises(ValueError, match="Schedule is invalid."): + CronSchedule(schedule=invalid_schedule) + + +@pytest.mark.parametrize( + "valid_schedule", + [ + "0 0 28 2 *", # February 28th (always valid) + "0 0 29 2 *", # February 29th (valid in leap years - handled by croniter) + "0 0 30 4 *", # April 30th (valid) + "0 0 31 1 *", # January 31st (valid) + "0 0 31 3 *", # March 31st (valid) + ], +) +def test_cron_valid_date_combinations(valid_schedule): + """Test that CronSchedule accepts valid date combinations.""" + # These should not raise any exceptions + obj = CronSchedule(schedule=valid_schedule) + assert obj.cron_schedule.schedule == valid_schedule