package clocktime import ( "fmt" "time" ) // ClockTime represents a time of day without a date component (24-hour HH:MM:SS). type ClockTime struct { Hour int `json:"hour"` Minute int `json:"minute"` Second int `json:"second"` } // NewClockTime creates a ClockTime after validating component ranges. func NewClockTime(hour, minute, second int) (ClockTime, error) { t := ClockTime{Hour: hour, Minute: minute, Second: second} if err := t.validate(); err != nil { return ClockTime{}, err } return t, nil } // ParseClockTime parses strict HH:MM:SS (RFC 3339 time-of-day subset). func ParseClockTime(s string) (ClockTime, error) { return ClockTimeFromString(s) } // ClockTimeFromString parses a ClockTime from HH:MM:SS. func ClockTimeFromString(s string) (ClockTime, error) { if s == "" { return ClockTime{}, fmt.Errorf("invalid time format: empty string") } parsed, err := time.Parse("15:04:05", s) if err != nil { return ClockTime{}, fmt.Errorf("invalid time format: %s", s) } return ClockTime{Hour: parsed.Hour(), Minute: parsed.Minute(), Second: parsed.Second()}, nil } // ClockTimeFromTime extracts the UTC time-of-day from t (sub-second precision truncated). func ClockTimeFromTime(t time.Time) ClockTime { u := t.UTC() return ClockTime{Hour: u.Hour(), Minute: u.Minute(), Second: u.Second()} } func (t ClockTime) validate() error { if t.Hour < 0 || t.Hour > 23 || t.Minute < 0 || t.Minute > 59 || t.Second < 0 || t.Second > 59 { return fmt.Errorf("time out of range: %s", t.String()) } return nil } // String returns HH:MM:SS with zero padding. func (t ClockTime) String() string { return fmt.Sprintf("%02d:%02d:%02d", t.Hour, t.Minute, t.Second) } // Time returns a UTC time.Time anchored on 1970-01-01. func (t ClockTime) Time() time.Time { return time.Date(1970, 1, 1, t.Hour, t.Minute, t.Second, 0, time.UTC) } // IsMidnight reports whether the time is exactly 00:00:00. func (t ClockTime) IsMidnight() bool { return t.Hour == 0 && t.Minute == 0 && t.Second == 0 } // IsZero reports whether the receiver pointer is nil. For optional fields use *ClockTime; // midnight 00:00:00 is a valid value and is not considered zero. func (t *ClockTime) IsZero() bool { return t == nil } // Equal reports whether two times are the same. func (t ClockTime) Equal(other ClockTime) bool { return t.Hour == other.Hour && t.Minute == other.Minute && t.Second == other.Second } // Compare returns -1, 0, or 1 comparing t to other. func (t ClockTime) Compare(other ClockTime) int { if t.Before(other) { return -1 } if t.After(other) { return 1 } return 0 } // Before reports whether t is before other. 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 reports whether t is after other. 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 }