package clocktime import ( "database/sql/driver" "encoding/json" "fmt" "io" "time" ) // ClockTime represents a time of day without a date component in 24-hour format. // It is used to represent times in a way that is independent of any specific date. type ClockTime struct { Hour int `json:"hour"` Minute int `json:"minute"` Second int `json:"second"` } // NewClockTime creates a new ClockTime instance. func NewClockTime(hour, minute, second int) ClockTime { return ClockTime{ Hour: hour, Minute: minute, Second: second, } } // ClockTimeFromString parses a ClockTime from a string in the format "HH:MM:SS" (Subset of RFC 8601). func ClockTimeFromString(s string) (ClockTime, error) { var hour, minute, second int n, err := fmt.Sscanf(s, "%d:%d:%d", &hour, &minute, &second) if err != nil || n != 3 { return ClockTime{}, fmt.Errorf("invalid time format: %s", s) } if hour < 0 || hour > 23 || minute < 0 || minute > 59 || second < 0 || second > 59 { return ClockTime{}, fmt.Errorf("time out of range: %s", s) } return NewClockTime(hour, minute, second), nil } // ClockTimeFromTime converts a time.Time to a ClockTime. func ClockTimeFromTime(t time.Time) ClockTime { return NewClockTime(t.Hour(), t.Minute(), t.Second()) } // String returns the string representation of the ClockTime in "HH:MM:SS" format. func (t ClockTime) String() string { return fmt.Sprintf("%02d:%02d:%02d", t.Hour, t.Minute, t.Second) } // Time returns a time.Time representation of the ClockTime. // It uses a fixed date (January 1, 1970) to create a time.Time object. func (t ClockTime) Time() time.Time { return time.Date(1970, 1, 1, t.Hour, t.Minute, t.Second, 0, time.UTC) } // MarshalJSON implements the json.Marshaler interface for ClockTime. func (t ClockTime) MarshalJSON() ([]byte, error) { return json.Marshal(t.String()) } // UnmarshalJSON implements the json.Unmarshaler interface for ClockTime. func (t *ClockTime) UnmarshalJSON(data []byte) error { var timeString string if err := json.Unmarshal(data, &timeString); err != nil { return err } parsedTime, err := ClockTimeFromString(timeString) if err != nil { return err } *t = parsedTime return nil } // MarshalGQL implements the graphql.Marshaler interface for ClockTime. func (t ClockTime) MarshalGQL(w io.Writer) { fmt.Fprint(w, t.String()) } // UnmarshalGQL implements the graphql.Unmarshaler interface for ClockTime. 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) } parsedTime, err := ClockTimeFromString(str) if err != nil { return err } *t = parsedTime return nil } // Value implements the database/sql/driver.Valuer interface for ClockTime. // Marshals the ClockTime to a time.Time for database storage. func (t ClockTime) Value() (driver.Value, error) { return t.Time(), nil } // Scan implements the database/sql.Scanner interface for ClockTime. func (t *ClockTime) Scan(value any) error { if value == nil { *t = ClockTime{} return nil } rawTime, ok := value.(time.Time) if !ok { return fmt.Errorf("ClockTime must be a time.Time, got %T", value) } *t = ClockTimeFromTime(rawTime) return nil }