// Package timeutil provides JSON-serializable time utilities. package timeutil import ( "encoding/json" "errors" "time" ) // Duration is a wrapper around time.Duration that supports JSON marshaling/unmarshaling. // // When marshaling to JSON, it outputs the duration as a string using time.Duration.String(). // When unmarshaling from JSON, it accepts both: // - String values that can be parsed by time.ParseDuration (e.g., "30s", "5m", "1h30m") // - Numeric values that represent nanoseconds as a float64 // // This makes it compatible with configuration files and APIs that need to represent // durations in a human-readable format. // // Example usage: // // type Config struct { // Timeout timeutil.Duration `json:"timeout"` // } // // // JSON: {"timeout": "30s"} // // or: {"timeout": 30000000000} type Duration struct { time.Duration } // MarshalJSON implements json.Marshaler. // It marshals the duration as a string using time.Duration.String(). func (d Duration) MarshalJSON() ([]byte, error) { return json.Marshal(time.Duration(d.Duration).String()) } // UnmarshalJSON implements json.Unmarshaler. // It accepts both string values (parsed via time.ParseDuration) and // numeric values (interpreted as nanoseconds). func (d *Duration) UnmarshalJSON(b []byte) error { var v any if err := json.Unmarshal(b, &v); err != nil { return err } switch value := v.(type) { case float64: *d = Duration{time.Duration(value)} return nil case string: tmp, err := time.ParseDuration(value) if err != nil { return err } *d = Duration{tmp} return nil default: return errors.New("invalid duration") } }