github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/kbtime/long_duration.go (about) 1 package kbtime 2 3 import ( 4 "fmt" 5 "regexp" 6 "strconv" 7 "strings" 8 "time" 9 ) 10 11 // see go/src/time/format.go `var unitMap = map[string]int64` 12 var durationRxp = regexp.MustCompile(`^([0-9]+)\s?(([nuµμm]?s)|[mhdDMyY])$`) 13 14 // There is an ambiguity with 'm' being minutes and 'M' being months. So just 15 // don't allow units like 'd' for days - expect uppercase units for all of the 16 // "longer durations". 17 18 // AddLongDuration parses time duration from `duration` argument and adds to 19 // time in `now`, returning the resulting time. The duration format is similar 20 // to `time` package duration format, with the following changes: 21 // - additional duration units are supported: 22 // - 'D' for days, 23 // - 'M' for months, 24 // - 'Y' for years, 25 // - fractional numbers are *not* supported, 26 // - negative numbers are *not* supported, 27 // - whitespace at the beginning and end of duration string is ignored, 28 // - optionally there can be one whitespace character between the number 29 // and unit. 30 // 31 // Long durations are handled using Time.AddDate function, which works by 32 // adding given number of years, months, and days to tval. It normalizes its 33 // result, for example, adding one month to October 31 yields December 1, the 34 // normalized form for November 31. 35 // 36 // Examples: 37 // 38 // `AddLongDuration(time.Now(), "1000 Y")` 39 // `AddLongDuration(time.Now(), "7 D")` 40 // `AddLongDuration(then, "1 M")` 41 func AddLongDuration(tval time.Time, duration string) (ret time.Time, err error) { 42 duration = strings.TrimSpace(duration) 43 44 parsed := durationRxp.FindStringSubmatch(duration) 45 if parsed == nil { 46 return ret, fmt.Errorf("bad duration format %q", duration) 47 } 48 49 amount, err := strconv.ParseInt(parsed[1], 10, 32) 50 if err != nil { 51 return ret, fmt.Errorf("failed to parse number: %w", err) 52 } 53 54 unit := parsed[2] 55 switch unit { 56 case "ns", "us", "µs", "μs", "ms", "s", "m", "h": 57 dur, err := time.ParseDuration(fmt.Sprintf("%d%s", amount, unit)) 58 if err != nil { 59 return ret, err 60 } 61 return tval.Add(dur), nil 62 case "d": 63 return ret, fmt.Errorf("use 'D' unit for days instead of '%s'", unit) 64 case "D": // day 65 return tval.AddDate(0, 0, int(amount)), nil 66 // 'm' is minute, handled by time.ParseDuration. 67 case "M": // month 68 return tval.AddDate(0, int(amount), 0), nil 69 case "y": 70 return ret, fmt.Errorf("use 'Y' unit for years instead of '%s'", unit) 71 case "Y": // year 72 return tval.AddDate(int(amount), 0, 0), nil 73 default: 74 return ret, fmt.Errorf("unhandled unit %q", unit) 75 } 76 }