| 458 | } |
| 459 | |
| 460 | func (p *ScheduleParser) parseMonthlyBase() (string, error) { |
| 461 | scheduleLog.Printf("Parsing monthly schedule: token_count=%d", len(p.tokens)) |
| 462 | if len(p.tokens) < 3 || p.tokens[1] != "on" { |
| 463 | return "", errors.New("monthly schedule requires 'on <day>'") |
| 464 | } |
| 465 | |
| 466 | day := p.tokens[2] |
| 467 | dayNum, err := strconv.Atoi(day) |
| 468 | if err != nil || dayNum < 1 || dayNum > 31 { |
| 469 | return "", fmt.Errorf("invalid day of month '%s', must be 1-31", day) |
| 470 | } |
| 471 | if len(p.tokens) > 3 { |
| 472 | return "", fmt.Errorf("'monthly on <day> at <time>' syntax is not supported. Use standard cron syntax for monthly schedules (e.g., '0 9 %s * *' for the %sth at 9am)", day, day) |
| 473 | } |
| 474 | return "", fmt.Errorf("'monthly on <day>' syntax is not supported. Use standard cron syntax for monthly schedules (e.g., '0 0 %s * *' for the %sth at midnight)", day, day) |
| 475 | } |
| 476 | |
| 477 | // extractTime extracts the time specification from tokens starting at startPos |
| 478 | // Returns the time string (HH:MM, midnight, or noon) with optional UTC offset |