Describe the feature
The feature request is to add support for interval types in GORM, a popular ORM library for Go. This includes the ability to define, query, and manipulate interval data types directly within GORM models. Interval types are commonly used in databases to represent periods of time, and supporting them natively in GORM would enhance its functionality for time-based data handling.
Motivation
The motivation behind this feature is to provide a more comprehensive and efficient way to handle time intervals within GORM. Many applications, especially those involving scheduling, event management, or time-series data, require the use of interval types. By integrating support for interval types directly into GORM, developers can streamline their code, reduce the need for custom solutions, and improve the overall performance and reliability of their applications.
Below I share an example that can be integrated
package types
import (
"database/sql/driver"
"errors"
"fmt"
"strconv"
"strings"
"time"
)
// TimeInterval Holds duration/interval information received from PostgreSQL,
// supports each format except verbose. JSON marshals to a clock format "HH:MM:SS".
// Uses a Valid property so it can be nullable
type TimeInterval struct {
Valid bool
Years uint16
Months uint8
Days uint8
Hours uint8
Minutes uint8
Seconds uint8
}
// Value Implements driver.Value
func (t TimeInterval) Value() (driver.Value, error) {
return fmt.Sprintf("P%dY%dM%dDT%02dH%02dM%02dS", t.Years, t.Months, t.Days, t.Hours, t.Minutes, t.Seconds), nil
}
// Scan Implements sql.Scanner
func (t *TimeInterval) Scan(src interface{}) error {
bytes, ok := src.([]byte)
if !ok {
if srcStr, ok := src.(string); ok {
bytes = []byte(srcStr)
} else {
//Probably nil
t.Valid = false
t.Years = 0
t.Months = 0
t.Days = 0
t.Hours = 0
t.Minutes = 0
t.Seconds = 0
return nil
}
}
str := strings.ToUpper(string(bytes))
if len(str) == 0 {
return errors.New("received bytes for TimeInterval but string ended up empty")
}
if str[0] != 'P' {
return errors.New("invalid ISO 8601 format")
}
datePortion := str[1:strings.Index(str, "T")]
timePortion := str[strings.Index(str, "T")+1:]
// Parse date portion
for _, part := range strings.Split(datePortion, "") {
if len(part) > 0 {
value, err := strconv.ParseUint(part[:len(part)-1], 10, 32)
if err != nil {
return err
}
switch part[len(part)-1] {
case 'Y':
t.Years = uint16(value)
case 'M':
t.Months = uint8(value)
case 'D':
t.Days = uint8(value)
}
}
}
// Parse time portion
for _, part := range strings.Split(timePortion, "") {
if len(part) > 0 {
value, err := strconv.ParseUint(part[:len(part)-1], 10, 32)
if err != nil {
return err
}
switch part[len(part)-1] {
case 'H':
t.Hours = uint8(value)
case 'M':
t.Minutes = uint8(value)
case 'S':
t.Seconds = uint8(value)
}
}
}
t.Valid = true
return nil
}
// MarshalJSON Marshals JSON
func (t TimeInterval) MarshalJSON() ([]byte, error) {
if t.Valid {
return []byte(fmt.Sprintf("\"P%dY%dM%dDT%02dH%02dM%02dS\"", t.Years, t.Months, t.Days, t.Hours, t.Minutes, t.Seconds)), nil
}
return []byte("null"), nil
}
// ToSeconds Returns the culminative seconds of this interval
func (t TimeInterval) ToSeconds() uint {
return uint(t.Years*365*24*60*60) + uint(t.Months*30*24*60*60) + uint(t.Days*24*60*60) + uint(t.Hours*60*60) + uint(t.Minutes*60) + uint(t.Seconds)
}
// UnmarshalJSON Implements JSON marshalling
func (t *TimeInterval) UnmarshalJSON(data []byte) error {
str := string(data)
if str[0] != '"' {
if str == "null" {
t.Valid = false
return nil
}
return fmt.Errorf("expected TimeInterval to be a string, received %s instead", str)
}
str = str[1 : len(str)-1]
if len(str) == 0 {
t.Years = 0
t.Months = 0
t.Days = 0
t.Hours = 0
t.Minutes = 0
t.Seconds = 0
t.Valid = false
return nil
}
if str[0] != 'P' {
return errors.New("invalid ISO 8601 format")
}
datePortion := str[1:strings.Index(str, "T")]
timePortion := str[strings.Index(str, "T")+1:]
// Parse date portion
for _, part := range strings.Split(datePortion, "") {
if len(part) > 0 {
value, err := strconv.ParseUint(part[:len(part)-1], 10, 32)
if err != nil {
return err
}
switch part[len(part)-1] {
case 'Y':
t.Years = uint16(value)
case 'M':
t.Months = uint8(value)
case 'D':
t.Days = uint8(value)
}
}
}
// Parse time portion
for _, part := range strings.Split(timePortion, "") {
if len(part) > 0 {
value, err := strconv.ParseUint(part[:len(part)-1], 10, 32)
if err != nil {
return err
}
switch part[len(part)-1] {
case 'H':
t.Hours = uint8(value)
case 'M':
t.Minutes = uint8(value)
case 'S':
t.Seconds = uint8(value)
}
}
}
t.Valid = true
return nil
}
// NewTimeIntervalFromDuration Converts a duration, into a TimeInterval object
func NewTimeIntervalFromDuration(d time.Duration) TimeInterval {
hrs := uint8(d.Hours() / 24)
mins := uint8((d.Hours() - float64(hrs*24)) * 60)
secs := uint8((d.Minutes() - float64(mins*60)) * 60)
return TimeInterval{
Valid: true,
Years: 0,
Months: 0,
Days: hrs,
Hours: mins,
Minutes: secs,
Seconds: uint8(d.Seconds() - float64(secs*60)),
}
}
Describe the feature The feature request is to add support for interval types in GORM, a popular ORM library for Go. This includes the ability to define, query, and manipulate interval data types directly within GORM models. Interval types are commonly used in databases to represent periods of time, and supporting them natively in GORM would enhance its functionality for time-based data handling.
Motivation The motivation behind this feature is to provide a more comprehensive and efficient way to handle time intervals within GORM. Many applications, especially those involving scheduling, event management, or time-series data, require the use of interval types. By integrating support for interval types directly into GORM, developers can streamline their code, reduce the need for custom solutions, and improve the overall performance and reliability of their applications.
Below I share an example that can be integrated