2025-03-07 03:16:37 +00:00
|
|
|
package utils
|
|
|
|
|
|
2026-07-16 22:20:35 -04:00
|
|
|
import (
|
|
|
|
|
"fmt"
|
|
|
|
|
"regexp"
|
|
|
|
|
"strconv"
|
|
|
|
|
"strings"
|
|
|
|
|
"time"
|
|
|
|
|
)
|
2025-03-07 03:16:37 +00:00
|
|
|
|
|
|
|
|
func TimeNewest(times ...time.Time) time.Time {
|
|
|
|
|
newest := time.Time{}
|
|
|
|
|
for _, t := range times {
|
|
|
|
|
if t.After(newest) {
|
|
|
|
|
newest = t
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return newest
|
|
|
|
|
}
|
2026-07-16 22:20:35 -04:00
|
|
|
|
|
|
|
|
var durationDayWeekRe = regexp.MustCompile(`-?\d+(?:\.\d+)?[dw]`)
|
|
|
|
|
|
|
|
|
|
// ParseDuration is time.ParseDuration extended with d (24h) and w (168h) units.
|
|
|
|
|
// Negative durations are rejected.
|
|
|
|
|
func ParseDuration(s string) (time.Duration, error) {
|
|
|
|
|
expanded := durationDayWeekRe.ReplaceAllStringFunc(s, func(match string) string {
|
|
|
|
|
value, err := strconv.ParseFloat(match[:len(match)-1], 64)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return match
|
|
|
|
|
}
|
|
|
|
|
hours := value * 24
|
|
|
|
|
if match[len(match)-1] == 'w' {
|
|
|
|
|
hours = value * 24 * 7
|
|
|
|
|
}
|
|
|
|
|
return strconv.FormatFloat(hours, 'f', -1, 64) + "h"
|
|
|
|
|
})
|
|
|
|
|
d, err := time.ParseDuration(expanded)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return 0, fmt.Errorf("invalid duration %q: %w", s, err)
|
|
|
|
|
}
|
|
|
|
|
if d < 0 {
|
|
|
|
|
return 0, fmt.Errorf("negative duration not allowed: %q", s)
|
|
|
|
|
}
|
|
|
|
|
return d, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// FormatDuration renders whole w/d multiples with those units, falling back to
|
|
|
|
|
// time.Duration.String for the sub-day remainder, so ParseDuration round-trips.
|
|
|
|
|
func FormatDuration(d time.Duration) string {
|
|
|
|
|
if d < 24*time.Hour {
|
|
|
|
|
return formatSubDay(d)
|
|
|
|
|
}
|
|
|
|
|
var b strings.Builder
|
|
|
|
|
weekDuration := 7 * 24 * time.Hour
|
|
|
|
|
if weeks := d / weekDuration; weeks > 0 {
|
|
|
|
|
b.WriteString(strconv.Itoa(int(weeks)) + "w")
|
|
|
|
|
d %= weekDuration
|
|
|
|
|
}
|
|
|
|
|
dayDuration := 24 * time.Hour
|
|
|
|
|
if days := d / dayDuration; days > 0 {
|
|
|
|
|
b.WriteString(strconv.Itoa(int(days)) + "d")
|
|
|
|
|
d %= dayDuration
|
|
|
|
|
}
|
|
|
|
|
if d > 0 {
|
|
|
|
|
b.WriteString(formatSubDay(d))
|
|
|
|
|
}
|
|
|
|
|
return b.String()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func formatSubDay(d time.Duration) string {
|
|
|
|
|
if d >= time.Hour && d%time.Hour == 0 {
|
|
|
|
|
return strconv.Itoa(int(d/time.Hour)) + "h"
|
|
|
|
|
}
|
|
|
|
|
return d.String()
|
|
|
|
|
}
|