add ISODuration type for ISO8061 duration compatibility

This commit is contained in:
2025-12-11 20:25:29 -08:00
parent 6ecb12c98c
commit a84cfde045
4 changed files with 140 additions and 0 deletions
+99
View File
@@ -0,0 +1,99 @@
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
}