Files
end a1284075aa fix: strict ClockTime parsing and ISODuration SQL
Validate constructors, strict HH:MM:SS parse, UTC ClockTimeFromTime,
ISODuration Value/Scan, Compare and TextMarshaler, codec.go split.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-29 18:01:08 -07:00

211 lines
4.4 KiB
Go

package clocktime
import (
"database/sql/driver"
"encoding/json"
"fmt"
"io"
"time"
)
func marshalJSONString(s string) ([]byte, error) {
return json.Marshal(s)
}
func unmarshalJSONString(data []byte) (string, error) {
var s string
if err := json.Unmarshal(data, &s); err != nil {
return "", err
}
return s, nil
}
// MarshalJSON encodes ClockTime as a JSON string HH:MM:SS.
func (t ClockTime) MarshalJSON() ([]byte, error) {
return marshalJSONString(t.String())
}
// UnmarshalJSON decodes a JSON string into ClockTime.
func (t *ClockTime) UnmarshalJSON(data []byte) error {
if string(data) == "null" {
*t = ClockTime{}
return nil
}
s, err := unmarshalJSONString(data)
if err != nil {
return err
}
parsed, err := ParseClockTime(s)
if err != nil {
return err
}
*t = parsed
return nil
}
// MarshalText implements encoding.TextMarshaler.
func (t ClockTime) MarshalText() ([]byte, error) {
return []byte(t.String()), nil
}
// UnmarshalText implements encoding.TextUnmarshaler.
func (t *ClockTime) UnmarshalText(text []byte) error {
parsed, err := ParseClockTime(string(text))
if err != nil {
return err
}
*t = parsed
return nil
}
// MarshalGQL implements gqlgen's Marshaler interface.
func (t ClockTime) MarshalGQL(w io.Writer) {
fmt.Fprint(w, "\""+t.String()+"\"")
}
// UnmarshalGQL implements gqlgen's Unmarshaler interface.
func (t *ClockTime) UnmarshalGQL(value any) error {
if value == nil {
*t = ClockTime{}
return nil
}
str, ok := value.(string)
if !ok {
return fmt.Errorf("ClockTime must be a string, got %T", value)
}
parsed, err := ParseClockTime(str)
if err != nil {
return err
}
*t = parsed
return nil
}
// Value implements driver.Valuer for SQL TIME columns as HH:MM:SS bytes.
func (t ClockTime) Value() (driver.Value, error) {
return []byte(t.String()), nil
}
// Scan implements sql.Scanner for time.Time, []byte, and string.
func (t *ClockTime) Scan(value any) error {
if value == nil {
*t = ClockTime{}
return nil
}
switch v := value.(type) {
case time.Time:
*t = ClockTimeFromTime(v)
case []byte:
parsed, err := ParseClockTime(string(v))
if err != nil {
return fmt.Errorf("failed to parse ClockTime: %w", err)
}
*t = parsed
case string:
parsed, err := ParseClockTime(v)
if err != nil {
return fmt.Errorf("failed to parse ClockTime: %w", err)
}
*t = parsed
default:
return fmt.Errorf("ClockTime.Scan: unsupported type %T", value)
}
return nil
}
// MarshalJSON encodes ISODuration as a JSON ISO 8601 string.
func (d ISODuration) MarshalJSON() ([]byte, error) {
return marshalJSONString(d.String())
}
// UnmarshalJSON decodes a JSON ISO 8601 duration string.
func (d *ISODuration) UnmarshalJSON(data []byte) error {
if string(data) == "null" {
*d = ISODuration(0)
return nil
}
s, err := unmarshalJSONString(data)
if err != nil {
return err
}
parsed, err := ParseISODuration(s)
if err != nil {
return err
}
*d = parsed
return nil
}
// MarshalText implements encoding.TextMarshaler.
func (d ISODuration) MarshalText() ([]byte, error) {
return []byte(d.String()), nil
}
// UnmarshalText implements encoding.TextUnmarshaler.
func (d *ISODuration) UnmarshalText(text []byte) error {
parsed, err := ParseISODuration(string(text))
if err != nil {
return err
}
*d = parsed
return nil
}
// MarshalGQL implements gqlgen's Marshaler interface.
func (d ISODuration) MarshalGQL(w io.Writer) {
fmt.Fprint(w, "\""+d.String()+"\"")
}
// UnmarshalGQL implements gqlgen's Unmarshaler interface.
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, got %T", value)
}
parsed, err := ParseISODuration(s)
if err != nil {
return err
}
*d = parsed
return nil
}
// Value stores ISODuration as nanoseconds in the database.
func (d ISODuration) Value() (driver.Value, error) {
return int64(d), nil
}
// Scan reads ISODuration from int64 nanoseconds or an ISO 8601 string.
func (d *ISODuration) Scan(value any) error {
if value == nil {
*d = ISODuration(0)
return nil
}
switch v := value.(type) {
case int64:
*d = ISODuration(v)
case []byte:
parsed, err := ParseISODuration(string(v))
if err != nil {
return err
}
*d = parsed
case string:
parsed, err := ParseISODuration(v)
if err != nil {
return err
}
*d = parsed
default:
return fmt.Errorf("ISODuration.Scan: unsupported type %T", value)
}
return nil
}