Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Interval Type (ISO 8601) #267

Open
meftunca opened this issue Sep 2, 2024 · 0 comments
Open

Interval Type (ISO 8601) #267

meftunca opened this issue Sep 2, 2024 · 0 comments
Assignees

Comments

@meftunca
Copy link

meftunca commented Sep 2, 2024

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)),
	}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants