cron_parser.go 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. package parser
  2. import (
  3. "fmt"
  4. "regexp"
  5. "strconv"
  6. "strings"
  7. )
  8. // ParseCron converts natural language to cron expression
  9. func ParseCron(input string) (string, error) {
  10. input = strings.TrimSpace(strings.ToLower(input))
  11. // If it already looks like a cron expression, return as-is
  12. if isCronExpression(input) {
  13. return input, nil
  14. }
  15. // "every X minutes"
  16. if strings.HasPrefix(input, "every ") && strings.Contains(input, "minute") {
  17. return parseEveryMinutes(input)
  18. }
  19. // "every hour" or "hourly"
  20. if input == "every hour" || input == "hourly" {
  21. return "0 * * * *", nil
  22. }
  23. // "every day" or "daily"
  24. if input == "every day" || input == "daily" {
  25. return "0 9 * * *", nil // 9am daily
  26. }
  27. // "daily at HH:MM"
  28. if strings.HasPrefix(input, "daily at ") {
  29. return parseDailyAt(input)
  30. }
  31. // "every monday/tuesday/etc"
  32. if strings.HasPrefix(input, "every ") && containsWeekday(input) {
  33. return parseEveryWeekday(input)
  34. }
  35. // "every weekday"
  36. if input == "every weekday" || input == "weekdays" {
  37. return "0 9 * * 1-5", nil // 9am Mon-Fri
  38. }
  39. // "every weekend"
  40. if input == "every weekend" || input == "weekends" {
  41. return "0 9 * * 0,6", nil // 9am Sat-Sun
  42. }
  43. // "monthly"
  44. if input == "monthly" {
  45. return "0 9 1 * *", nil // 9am on 1st of month
  46. }
  47. // "weekly"
  48. if input == "weekly" {
  49. return "0 9 * * 1", nil // 9am every Monday
  50. }
  51. return "", fmt.Errorf("unable to parse cron: %s\n\nSupported formats:\n - Cron: */5 * * * * (every 5 min)\n - Minutes: every 5 minutes, every 30 minutes\n - Hourly: every hour, hourly\n - Daily: daily, daily at 9am, daily at 14:30\n - Weekday: every monday, every friday at 3pm\n - Weekdays: every weekday, weekdays (Mon-Fri at 9am)\n - Weekly: weekly (every Monday at 9am)\n - Monthly: monthly (1st of month at 9am)", input)
  52. }
  53. func parseEveryMinutes(input string) (string, error) {
  54. // "every 5 minutes", "every 30 minutes"
  55. re := regexp.MustCompile(`^every\s+(\d+)\s+minutes?$`)
  56. matches := re.FindStringSubmatch(input)
  57. if len(matches) != 2 {
  58. return "", fmt.Errorf("invalid format: %s (expected: every X minutes)", input)
  59. }
  60. minutes, _ := strconv.Atoi(matches[1])
  61. if minutes <= 0 || minutes > 59 {
  62. return "", fmt.Errorf("minutes must be between 1 and 59")
  63. }
  64. return fmt.Sprintf("*/%d * * * *", minutes), nil
  65. }
  66. func parseDailyAt(input string) (string, error) {
  67. // "daily at 9am", "daily at 14:30"
  68. timeStr := strings.TrimPrefix(input, "daily at ")
  69. timeStr = strings.TrimSpace(timeStr)
  70. hour, minute, err := parseTimeOfDay(timeStr)
  71. if err != nil {
  72. return "", err
  73. }
  74. return fmt.Sprintf("%d %d * * *", minute, hour), nil
  75. }
  76. func parseEveryWeekday(input string) (string, error) {
  77. // "every monday", "every friday at 3pm"
  78. re := regexp.MustCompile(`^every\s+(monday|tuesday|wednesday|thursday|friday|saturday|sunday)(?:\s+at\s+(.+))?$`)
  79. matches := re.FindStringSubmatch(input)
  80. if len(matches) < 2 {
  81. return "", fmt.Errorf("invalid format: %s", input)
  82. }
  83. dayName := matches[1]
  84. timeStr := ""
  85. if len(matches) > 2 {
  86. timeStr = matches[2]
  87. }
  88. // Get weekday number (0=Sunday, 1=Monday, etc.)
  89. weekdayNum := getWeekdayNumber(dayName)
  90. // Default to 9am if no time specified
  91. hour := 9
  92. minute := 0
  93. if timeStr != "" {
  94. var err error
  95. hour, minute, err = parseTimeOfDay(timeStr)
  96. if err != nil {
  97. return "", err
  98. }
  99. }
  100. return fmt.Sprintf("%d %d * * %d", minute, hour, weekdayNum), nil
  101. }
  102. func isCronExpression(input string) bool {
  103. // Basic check for cron pattern (5 fields separated by spaces)
  104. parts := strings.Fields(input)
  105. if len(parts) != 5 {
  106. return false
  107. }
  108. // Check if fields look cron-like
  109. cronPattern := regexp.MustCompile(`^[\d\*\-,/]+$`)
  110. for _, part := range parts {
  111. if !cronPattern.MatchString(part) {
  112. return false
  113. }
  114. }
  115. return true
  116. }
  117. func containsWeekday(input string) bool {
  118. weekdays := []string{"monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"}
  119. for _, day := range weekdays {
  120. if strings.Contains(input, day) {
  121. return true
  122. }
  123. }
  124. return false
  125. }
  126. func getWeekdayNumber(day string) int {
  127. // Cron weekday numbers: 0=Sunday, 1=Monday, ..., 6=Saturday
  128. switch strings.ToLower(day) {
  129. case "sunday":
  130. return 0
  131. case "monday":
  132. return 1
  133. case "tuesday":
  134. return 2
  135. case "wednesday":
  136. return 3
  137. case "thursday":
  138. return 4
  139. case "friday":
  140. return 5
  141. case "saturday":
  142. return 6
  143. default:
  144. return 1
  145. }
  146. }