package cli import ( "fmt" "regexp" "strconv" "strings" "time" ) // parseDuration parses duration strings. Supports standard Go duration format // (e.g., "3h30m", "1h45m30s") as well as extended units: // - d: days (e.g., "30d", "7d") // - w: weeks (e.g., "2w", "4w") // - mo: months (30 days) (e.g., "6mo", "1mo") // - y: years (365 days) (e.g., "1y", "2y") // // Can combine units: "1y6mo", "2w3d", "1d12h30m" func parseDuration(s string) (time.Duration, error) { // First try standard Go duration parsing if d, err := time.ParseDuration(s); err == nil { return d, nil } // Extended duration parsing // Check for negative values if strings.HasPrefix(strings.TrimSpace(s), "-") { return 0, fmt.Errorf("negative durations are not supported") } // Pattern matches: number + unit, repeated re := regexp.MustCompile(`(\d+(?:\.\d+)?)\s*([a-zA-Z]+)`) matches := re.FindAllStringSubmatch(s, -1) if len(matches) == 0 { return 0, fmt.Errorf("invalid duration format: %q", s) } var total time.Duration for _, match := range matches { valueStr := match[1] unit := strings.ToLower(match[2]) value, err := strconv.ParseFloat(valueStr, 64) if err != nil { return 0, fmt.Errorf("invalid number %q: %w", valueStr, err) } var d time.Duration switch unit { // Standard time units case "ns", "nanosecond", "nanoseconds": d = time.Duration(value) case "us", "µs", "microsecond", "microseconds": d = time.Duration(value * float64(time.Microsecond)) case "ms", "millisecond", "milliseconds": d = time.Duration(value * float64(time.Millisecond)) case "s", "sec", "second", "seconds": d = time.Duration(value * float64(time.Second)) case "m", "min", "minute", "minutes": d = time.Duration(value * float64(time.Minute)) case "h", "hr", "hour", "hours": d = time.Duration(value * float64(time.Hour)) // Extended units case "d", "day", "days": d = time.Duration(value * float64(24*time.Hour)) case "w", "week", "weeks": d = time.Duration(value * float64(7*24*time.Hour)) case "mo", "month", "months": // Using 30 days as approximation d = time.Duration(value * float64(30*24*time.Hour)) case "y", "year", "years": // Using 365 days as approximation d = time.Duration(value * float64(365*24*time.Hour)) default: // Try parsing as standard Go duration unit testStr := fmt.Sprintf("1%s", unit) if _, err := time.ParseDuration(testStr); err == nil { // It's a valid Go duration unit, parse the full value fullStr := fmt.Sprintf("%g%s", value, unit) if d, err = time.ParseDuration(fullStr); err != nil { return 0, fmt.Errorf("invalid duration %q: %w", fullStr, err) } } else { return 0, fmt.Errorf("unknown time unit %q", unit) } } total += d } return total, nil }