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) } // IsZero checks if the ClockTime is zero (00:00:00) or nil. func (t *ClockTime) IsZero() bool { return t == nil || (t.Hour == 0 && t.Minute == 0 && t.Second == 0) } // Equal checks if two ClockTime instances are equal. func (t ClockTime) Equal(other ClockTime) bool { return t.Hour == other.Hour && t.Minute == other.Minute && t.Second == other.Second } // Before checks if the ClockTime is before another ClockTime. func (t ClockTime) Before(other ClockTime) bool { if t.Hour != other.Hour { return t.Hour < other.Hour } if t.Minute != other.Minute { return t.Minute < other.Minute } return t.Second < other.Second } // After checks if the ClockTime is after another ClockTime. func (t ClockTime) After(other ClockTime) bool { if t.Hour != other.Hour { return t.Hour > other.Hour } if t.Minute != other.Minute { return t.Minute > other.Minute } return t.Second > other.Second } // 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 byte slice for database storage. func (t ClockTime) Value() (driver.Value, error) { return []byte(t.String()), nil } // Scan implements the database/sql.Scanner interface for ClockTime. // Supports scanning from time.Time or []byte. 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: parsedTime, err := ClockTimeFromString(string(v)) if err != nil { return fmt.Errorf("failed to parse ClockTime from string: %w", err) } *t = parsedTime default: return fmt.Errorf("ClockTime.Scan: unsupported type %T", value) } return nil }