add ISODuration type for ISO8061 duration compatibility
This commit is contained in:
@@ -1,3 +1,21 @@
|
||||
# clocktime
|
||||
|
||||
clocktime provides a type that holds a calendar-independent time of day value in 24-hour format.
|
||||
|
||||
clocktime provides basic types that extend Go's built-in time.Time to provide time-of-day and duration support.
|
||||
|
||||
## `clocktime.ClockTime
|
||||
|
||||
Holds calendar-independent time-of-day in 24-hour format.
|
||||
|
||||
- Converts to and from `HH:MM:SS` format (subset of ISO8061)
|
||||
- Marshals to `[]byte` containing string `HH:MM:SS` for SQL
|
||||
- Marshals to `string` in format `HH:MM:SS` for JSON & gqlgen
|
||||
|
||||
## `clocktime.Duration`
|
||||
|
||||
Wraps time.Duration with prioritized ISO8061 support and opinionated marshalling.
|
||||
|
||||
- Converts to and from ISO8061 format (utilises [sosodev/duration](https://github.com/sosodev/duration))
|
||||
- Marshals to `uint64` with nanosecond precision. Largest representatable duration is about 290 years, limited by underlying time.Duration type.
|
||||
- Marshals to `string` in ISO8061 format for JSON & gqlgen
|
||||
|
||||
+99
@@ -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
|
||||
}
|
||||
@@ -1,3 +1,14 @@
|
||||
module gitea.auvem.com/go-toolkit/clocktime
|
||||
|
||||
go 1.24.0
|
||||
|
||||
require (
|
||||
github.com/sosodev/duration v1.3.1
|
||||
github.com/stretchr/testify v1.11.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/sosodev/duration v1.3.1 h1:qtHBDMQ6lvMQsL15g4aopM4HEfOaYuhWBw3NPTtlqq4=
|
||||
github.com/sosodev/duration v1.3.1/go.mod h1:RQIBBX0+fMLc/D9+Jb/fwvVmo0eZvDDEERAikUR6SDg=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
Reference in New Issue
Block a user