Phase 3: Implement date format parsers and duration extensions
- Add month name parsing (jan, january, feb, etc.) with year logic - Add day+month parsing (21jan, Jan21, 21January, etc.) in all case variations - Add period boundaries (sod, eod, sow, eow, som, eom, soy, eoy) - Add special keywords (later, someday -> 2150-01-01) - Add duration-as-date offset (2d, 3w, etc. means X from now) - Add named duration aliases (daily, weekly, monthly, yearly) - Enhance ParseRecurrencePattern to support min, sec, hrs and word forms - Implement time-of-day parsing (mon:15:35, tomorrow:0800, 15:35) - Add comprehensive test suite (50+ tests total) - All tests passing
This commit is contained in:
@@ -8,34 +8,74 @@ import (
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// ParseRecurrencePattern converts "1w", "2d", "1m" to time.Duration
|
||||
// ParseRecurrencePattern converts duration strings to time.Duration
|
||||
// Supports: 1d, 2w, 3m, 1y, 5min, 5hrs, 30sec
|
||||
// Also supports: days, weeks, months, years, minutes, hours, seconds
|
||||
func ParseRecurrencePattern(pattern string) (time.Duration, error) {
|
||||
if len(pattern) < 2 {
|
||||
return 0, fmt.Errorf("invalid recurrence pattern: %s", pattern)
|
||||
if len(pattern) == 0 {
|
||||
return 0, fmt.Errorf("empty duration pattern")
|
||||
}
|
||||
|
||||
numStr := pattern[:len(pattern)-1]
|
||||
unit := pattern[len(pattern)-1]
|
||||
// Handle word-based durations (hour, hours, day, days, etc.)
|
||||
wordDurations := map[string]time.Duration{
|
||||
"second": time.Second, "seconds": time.Second, "sec": time.Second,
|
||||
"minute": time.Minute, "minutes": time.Minute, "min": time.Minute,
|
||||
"hour": time.Hour, "hours": time.Hour, "hrs": time.Hour, "hr": time.Hour,
|
||||
"day": 24 * time.Hour, "days": 24 * time.Hour,
|
||||
"week": 7 * 24 * time.Hour, "weeks": 7 * 24 * time.Hour,
|
||||
"month": 30 * 24 * time.Hour, "months": 30 * 24 * time.Hour,
|
||||
"year": 365 * 24 * time.Hour, "years": 365 * 24 * time.Hour,
|
||||
}
|
||||
|
||||
if duration, ok := wordDurations[pattern]; ok {
|
||||
return duration, nil
|
||||
}
|
||||
|
||||
// Find where number ends and unit begins
|
||||
splitIdx := -1
|
||||
for i, ch := range pattern {
|
||||
if (ch < '0' || ch > '9') && ch != '-' {
|
||||
splitIdx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if splitIdx == -1 || splitIdx == 0 {
|
||||
return 0, fmt.Errorf("invalid duration pattern: %s", pattern)
|
||||
}
|
||||
|
||||
numStr := pattern[:splitIdx]
|
||||
unit := pattern[splitIdx:]
|
||||
|
||||
num, err := strconv.Atoi(numStr)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("invalid number in pattern: %s", pattern)
|
||||
}
|
||||
|
||||
switch unit {
|
||||
case 'd':
|
||||
return time.Duration(num) * 24 * time.Hour, nil
|
||||
case 'w':
|
||||
return time.Duration(num) * 7 * 24 * time.Hour, nil
|
||||
case 'm':
|
||||
// Approximate: 30 days
|
||||
return time.Duration(num) * 30 * 24 * time.Hour, nil
|
||||
case 'y':
|
||||
// Approximate: 365 days
|
||||
return time.Duration(num) * 365 * 24 * time.Hour, nil
|
||||
default:
|
||||
return 0, fmt.Errorf("unknown unit: %c (use d/w/m/y)", unit)
|
||||
// Single letter units
|
||||
if len(unit) == 1 {
|
||||
switch unit {
|
||||
case "d":
|
||||
return time.Duration(num) * 24 * time.Hour, nil
|
||||
case "w":
|
||||
return time.Duration(num) * 7 * 24 * time.Hour, nil
|
||||
case "m":
|
||||
// Approximate: 30 days
|
||||
return time.Duration(num) * 30 * 24 * time.Hour, nil
|
||||
case "y":
|
||||
// Approximate: 365 days
|
||||
return time.Duration(num) * 365 * 24 * time.Hour, nil
|
||||
default:
|
||||
return 0, fmt.Errorf("unknown unit: %s (use d/w/m/y)", unit)
|
||||
}
|
||||
}
|
||||
|
||||
// Multi-character units
|
||||
if baseDuration, ok := wordDurations[unit]; ok {
|
||||
return time.Duration(num) * baseDuration, nil
|
||||
}
|
||||
|
||||
return 0, fmt.Errorf("unknown duration unit: %s", unit)
|
||||
}
|
||||
|
||||
// FormatRecurrenceDuration converts time.Duration back to "1w", "2d" format
|
||||
|
||||
Reference in New Issue
Block a user