a1284075aa
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>
112 lines
3.0 KiB
Go
112 lines
3.0 KiB
Go
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
|
|
}
|