package domain import ( "database/sql/driver" "encoding/json" "fmt" "strconv" "strings" "time" ) type Duration struct { time.Duration } func (d Duration) MarshalJSON() ([]byte, error) { return json.Marshal(d.Duration.String()) } func (d *Duration) UnmarshalJSON(b []byte) error { var s string if err := json.Unmarshal(b, &s); err != nil { return err } duration, err := time.ParseDuration(s) if err != nil { return err } d.Duration = duration return nil } func (d Duration) Value() (driver.Value, error) { return int64(d.Duration), nil } func (d *Duration) Scan(value interface{}) error { if value == nil { d.Duration = 0 return nil } switch v := value.(type) { case int64: d.Duration = time.Duration(v) case string: duration, err := time.ParseDuration(v) if err != nil { return err } d.Duration = duration case []uint8: // Handle PostgreSQL interval format (e.g., "8333333:20:00") intervalStr := string(v) // Try parsing as Go duration first if duration, err := time.ParseDuration(intervalStr); err == nil { d.Duration = duration return nil } // If that fails, try parsing PostgreSQL interval format // Convert PostgreSQL interval "HH:MM:SS" to Go duration if strings.Contains(intervalStr, ":") { parts := strings.Split(intervalStr, ":") if len(parts) >= 2 { // Parse hours, minutes, seconds format var hours, minutes, seconds int64 var err error // Handle the case where we might have days as well (e.g., "8333333:20:00") // or just hours:minutes:seconds switch len(parts) { case 2: // MM:SS minutes, err = parseNumber(parts[0]) if err != nil { return fmt.Errorf("cannot parse minutes from interval: %s", intervalStr) } seconds, err = parseNumber(parts[1]) if err != nil { return fmt.Errorf("cannot parse seconds from interval: %s", intervalStr) } case 3: // HH:MM:SS hours, err = parseNumber(parts[0]) if err != nil { return fmt.Errorf("cannot parse hours from interval: %s", intervalStr) } minutes, err = parseNumber(parts[1]) if err != nil { return fmt.Errorf("cannot parse minutes from interval: %s", intervalStr) } seconds, err = parseNumber(parts[2]) if err != nil { return fmt.Errorf("cannot parse seconds from interval: %s", intervalStr) } default: return fmt.Errorf("unsupported interval format: %s", intervalStr) } // Convert to duration totalSeconds := hours*3600 + minutes*60 + seconds d.Duration = time.Duration(totalSeconds) * time.Second return nil } } return fmt.Errorf("cannot parse PostgreSQL interval format: %s", intervalStr) default: return fmt.Errorf("cannot scan %T into Duration", value) } return nil } func ParseDuration(s string) (Duration, error) { if s == "" { return Duration{}, fmt.Errorf("empty duration string") } s = strings.TrimSpace(s) duration, err := time.ParseDuration(s) if err != nil { return Duration{}, fmt.Errorf("failed to parse duration '%s': %v", s, err) } return Duration{Duration: duration}, nil } func (d Duration) String() string { return d.Duration.String() } func (d Duration) Seconds() float64 { return d.Duration.Seconds() } func (d Duration) Minutes() float64 { return d.Duration.Minutes() } func (d Duration) Hours() float64 { return d.Duration.Hours() } // Helper function to parse number from string, handling potential whitespace func parseNumber(s string) (int64, error) { s = strings.TrimSpace(s) return strconv.ParseInt(s, 10, 64) }