package clocktime import ( "encoding/json" "fmt" "io" "time" "github.com/sosodev/duration" ) // ISODuration wraps a time.Duration to provide custom JSON and SQL // serialization and full ISO8061 compatibility. Note: ISODuration is limited // to the same range as time.Duration despite ISO8061 supporting larger durations. type ISODuration time.Duration // NewISODuration creates a new duration from numeric components. func NewISODuration(hours, minutes, seconds int) ISODuration { return ISODuration( time.Hour*time.Duration(hours) + time.Minute*time.Duration(minutes) + time.Second*time.Duration(seconds), ) } // DurationFromISOString parses an ISO8601 duration string (e.g., "PT1H30M45S") // and returns an ISODuration. func DurationFromISOString(s string) (ISODuration, error) { d, err := duration.Parse(s) if err != nil { return 0, fmt.Errorf("error parsing ISO8061 duration: %w", err) } return ISODuration(d.ToTimeDuration()), nil } // ISODurationFromDuration converts a time.Duration to an ISODuration. func ISODurationFromDuration(d time.Duration) ISODuration { return ISODuration(d) } // String returns the ISO8601 string representation of the ISODuration. func (d ISODuration) String() string { td := time.Duration(d) isoDur := duration.FromTimeDuration(td) return isoDur.String() } // Duration returns the time.Duration representation of the ISODuration. func (d ISODuration) Duration() time.Duration { return time.Duration(d) } // MarshalJSON implements the json.Marshaler interface for ISODuration, // serializing it as an ISO8601 duration string. func (d ISODuration) MarshalJSON() ([]byte, error) { return json.Marshal("\"" + d.String() + "\"") } // UnmarshalJSON implements the json.Unmarshaler interface for ISODuration, // parsing an ISO8601 duration string. func (d *ISODuration) UnmarshalJSON(data []byte) error { var s string if err := json.Unmarshal(data, &s); err != nil { return err } parsedDur, err := DurationFromISOString(s) if err != nil { return err } *d = parsedDur return nil } // MarshalGQL implements the graphql.Marshaler interface for ISODuration, // serializing it as an ISO8601 duration string. func (d ISODuration) MarshalGQL(w io.Writer) { fmt.Fprint(w, "\""+d.String()+"\"") } // UnmarshalGQL implements the graphql.Unmarshaler interface for ISODuration, // parsing an ISO8601 duration string. nil values are treated as zero duration. func (d *ISODuration) UnmarshalGQL(value any) error { if value == nil { *d = ISODuration(0) return nil } s, ok := value.(string) if !ok { return fmt.Errorf("ISODuration must be a string") } parsedDur, err := DurationFromISOString(s) if err != nil { return err } *d = parsedDur return nil }