diff --git a/export.go b/export.go index bb0c13b5..b33cba9c 100644 --- a/export.go +++ b/export.go @@ -288,6 +288,10 @@ var ( // The following options are recognized: host, port, user, database, password. CreateClientDSN = edgedb.CreateClientDSN + // DurationFromNanoseconds creates a Duration represented as microseconds + // from a [time.Duration] represented as nanoseconds. + DurationFromNanoseconds = edgedbtypes.DurationFromNanoseconds + // NewDateDuration returns a new DateDuration NewDateDuration = edgedbtypes.NewDateDuration diff --git a/internal/edgedbtypes/datetime.go b/internal/edgedbtypes/datetime.go index 210053b2..0c30902b 100644 --- a/internal/edgedbtypes/datetime.go +++ b/internal/edgedbtypes/datetime.go @@ -660,6 +660,26 @@ func (d Duration) String() string { return strings.Join(buf, "") } +// AsNanoseconds returns [time.Duration] represented as nanoseconds, +// after transforming from Duration microsecond representation. +// Returns an error if the Duration is too long and would cause an overflow of +// the internal int64 representation. +func (d Duration) AsNanoseconds() (time.Duration, error) { + if int64(d) > math.MaxInt64/int64(time.Microsecond) || + int64(d) < math.MinInt64/int64(time.Microsecond) { + return time.Duration(0), fmt.Errorf( + "Duration is too large to be represented as nanoseconds", + ) + } + return time.Duration(d) * time.Microsecond, nil +} + +// DurationFromNanoseconds creates a Duration represented as microseconds +// from a [time.Duration] represented as nanoseconds. +func DurationFromNanoseconds(d time.Duration) Duration { + return Duration(math.RoundToEven(float64(d) / 1e3)) +} + // NewOptionalDuration is a convenience function for creating an // OptionalDuration with its value set to v. func NewOptionalDuration(v Duration) OptionalDuration { diff --git a/internal/edgedbtypes/datetime_test.go b/internal/edgedbtypes/datetime_test.go index bfbbea9b..e41d8a00 100644 --- a/internal/edgedbtypes/datetime_test.go +++ b/internal/edgedbtypes/datetime_test.go @@ -19,6 +19,7 @@ package edgedbtypes import ( "encoding/json" "fmt" + "math" "strings" "testing" "time" @@ -758,6 +759,50 @@ func TestUnmarshalDuration(t *testing.T) { } } +func TestAsNanosecondsDuration(t *testing.T) { + var durationTruncMicroseconds = func(i int64) time.Duration { + return time.Duration(time.Duration(i).Microseconds() * 1000) + } + + cases := []struct { + input Duration + mustFail bool + expected time.Duration + }{ + {Duration(math.MaxInt64), true, time.Duration(0)}, + {Duration(math.MaxInt64 / 100), true, time.Duration(0)}, + {Duration(math.MaxInt64/1000 + 1), true, time.Duration(0)}, + // Maximum possible value: + {Duration(math.MaxInt64 / 1000), false, + durationTruncMicroseconds(math.MaxInt64)}, + // Some arbitrary value within range + {Duration(math.MaxInt64 / 1452), false, + durationTruncMicroseconds(math.MaxInt64 / 1452 * 1000)}, + {Duration(0), false, time.Duration(0)}, + {Duration(math.MinInt64), true, time.Duration(0)}, + {Duration(math.MinInt64 / 100), true, time.Duration(0)}, + {Duration(math.MinInt64/1000 - 1), true, time.Duration(0)}, + // Minimum possible value + {Duration(math.MinInt64 / 1000), false, + durationTruncMicroseconds(math.MinInt64)}, + // Some arbitrary value within range + {Duration(math.MinInt64 / 6946), false, + durationTruncMicroseconds(math.MinInt64 / 6946 * 1000)}, + } + + for _, c := range cases { + t.Run(c.input.String(), func(t *testing.T) { + d, err := c.input.AsNanoseconds() + if c.mustFail { + require.Error(t, err) + } else { + require.NoError(t, err) + } + assert.Equal(t, c.expected, d) + }) + } +} + func TestMarshalOptionalDuration(t *testing.T) { cases := []struct { input OptionalDuration diff --git a/rstdocs/types.rst b/rstdocs/types.rst index 24e33732..9ef3a09a 100644 --- a/rstdocs/types.rst +++ b/rstdocs/types.rst @@ -75,6 +75,19 @@ as an int64 microsecond count. type Duration int64 +*function* DurationFromNanoseconds +.................................. + +.. code-block:: go + + func DurationFromNanoseconds(d time.Duration) Duration + +DurationFromNanoseconds creates a Duration represented as microseconds +from a `time.Duration `_ represented as nanoseconds. + + + + *function* ParseDuration ........................ @@ -87,6 +100,21 @@ ParseDuration parses an EdgeDB duration string. +*method* AsNanoseconds +...................... + +.. code-block:: go + + func (d Duration) AsNanoseconds() (time.Duration, error) + +AsNanoseconds returns `time.Duration `_ represented as nanoseconds, +after transforming from Duration microsecond representation. +Returns an error if the Duration is too long and would cause an overflow of +the internal int64 representation. + + + + *method* String ...............