mirror of
https://github.com/MontFerret/ferret.git
synced 2025-11-27 22:08:15 +02:00
added DATE_ADD, DATE_SUBTRACT functions (#165)
This commit is contained in:
@@ -2,6 +2,8 @@ package core
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
//revive:disable-next-line redefines-builtin-id
|
//revive:disable-next-line redefines-builtin-id
|
||||||
@@ -39,6 +41,8 @@ func (t Type) String() string {
|
|||||||
return typestr[t]
|
return typestr[t]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Value represents an interface of
|
||||||
|
// any type that needs to be used during runtime
|
||||||
type Value interface {
|
type Value interface {
|
||||||
json.Marshaler
|
json.Marshaler
|
||||||
Type() Type
|
Type() Type
|
||||||
@@ -49,10 +53,15 @@ type Value interface {
|
|||||||
Copy() Value
|
Copy() Value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsTypeOf return true when value's type
|
||||||
|
// is equal to check type.
|
||||||
|
// Returns false, otherwise.
|
||||||
func IsTypeOf(value Value, check Type) bool {
|
func IsTypeOf(value Value, check Type) bool {
|
||||||
return value.Type() == check
|
return value.Type() == check
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ValidateType checks the match of
|
||||||
|
// value's type and required types.
|
||||||
func ValidateType(value Value, required ...Type) error {
|
func ValidateType(value Value, required ...Type) error {
|
||||||
var valid bool
|
var valid bool
|
||||||
ct := value.Type()
|
ct := value.Type()
|
||||||
@@ -70,3 +79,26 @@ func ValidateType(value Value, required ...Type) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PairValueType is a supporting
|
||||||
|
// structure that used in validateValueTypePairs.
|
||||||
|
type PairValueType struct {
|
||||||
|
Value Value
|
||||||
|
Types []Type
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateValueTypePairs validate pairs of
|
||||||
|
// Values and Types.
|
||||||
|
// Returns error when type didn't match
|
||||||
|
func ValidateValueTypePairs(pairs ...PairValueType) error {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
for idx, pair := range pairs {
|
||||||
|
err = ValidateType(pair.Value, pair.Types...)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Errorf("pair %d: %v", idx, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
121
pkg/stdlib/datetime/add_subtract.go
Normal file
121
pkg/stdlib/datetime/add_subtract.go
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
package datetime
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
sliceDateTime = []core.Type{core.DateTimeType}
|
||||||
|
sliceIntType = []core.Type{core.IntType}
|
||||||
|
sliceStringType = []core.Type{core.StringType}
|
||||||
|
|
||||||
|
emptyDateTime values.DateTime
|
||||||
|
emptyInt values.Int
|
||||||
|
emptyString values.String
|
||||||
|
)
|
||||||
|
|
||||||
|
// DateAdd add amount given in unit to date.
|
||||||
|
// @params date (DateTime) - source date.
|
||||||
|
// @params amount (Int) - amount of units
|
||||||
|
// @params unit (String) - unit.
|
||||||
|
// @return (DateTime) - calculated date.
|
||||||
|
// The following units are available:
|
||||||
|
// * y, year, year
|
||||||
|
// * m, month, months
|
||||||
|
// * w, week, weeks
|
||||||
|
// * d, day, days
|
||||||
|
// * h, hour, hours
|
||||||
|
// * i, minute, minutes
|
||||||
|
// * s, second, seconds
|
||||||
|
// * f, millisecond, milliseconds
|
||||||
|
func DateAdd(_ context.Context, args ...core.Value) (core.Value, error) {
|
||||||
|
date, amount, unit, err := getArgs(args)
|
||||||
|
if err != nil {
|
||||||
|
return values.None, err
|
||||||
|
}
|
||||||
|
|
||||||
|
dt, err := addUnit(date, int(amount), unit.String())
|
||||||
|
if err != nil {
|
||||||
|
return values.None, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return dt, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DateSubtract subtract amount given in unit to date.
|
||||||
|
// @params date (DateTime) - source date.
|
||||||
|
// @params amount (Int) - amount of units
|
||||||
|
// @params unit (String) - unit.
|
||||||
|
// @return (DateTime) - calculated date.
|
||||||
|
// The following units are available:
|
||||||
|
// * y, year, year
|
||||||
|
// * m, month, months
|
||||||
|
// * w, week, weeks
|
||||||
|
// * d, day, days
|
||||||
|
// * h, hour, hours
|
||||||
|
// * i, minute, minutes
|
||||||
|
// * s, second, seconds
|
||||||
|
// * f, millisecond, milliseconds
|
||||||
|
func DateSubtract(_ context.Context, args ...core.Value) (core.Value, error) {
|
||||||
|
date, amount, unit, err := getArgs(args)
|
||||||
|
if err != nil {
|
||||||
|
return values.None, err
|
||||||
|
}
|
||||||
|
|
||||||
|
dt, err := addUnit(date, -1*int(amount), unit.String())
|
||||||
|
if err != nil {
|
||||||
|
return values.None, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return dt, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getArgs(args []core.Value) (values.DateTime, values.Int, values.String, error) {
|
||||||
|
err := core.ValidateArgs(args, 3, 3)
|
||||||
|
if err != nil {
|
||||||
|
return emptyDateTime, emptyInt, emptyString, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = core.ValidateValueTypePairs(
|
||||||
|
core.PairValueType{Value: args[0], Types: sliceDateTime},
|
||||||
|
core.PairValueType{Value: args[1], Types: sliceIntType},
|
||||||
|
core.PairValueType{Value: args[2], Types: sliceStringType},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return emptyDateTime, emptyInt, emptyString, err
|
||||||
|
}
|
||||||
|
|
||||||
|
date := args[0].(values.DateTime)
|
||||||
|
amount := args[1].(values.Int)
|
||||||
|
unit := args[2].(values.String)
|
||||||
|
|
||||||
|
return date, amount, unit, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func addUnit(dt values.DateTime, amount int, unit string) (values.DateTime, error) {
|
||||||
|
switch strings.ToLower(unit) {
|
||||||
|
case "y", "year", "years":
|
||||||
|
return values.NewDateTime(dt.AddDate(amount*1, 0, 0)), nil
|
||||||
|
case "m", "month", "months":
|
||||||
|
return values.NewDateTime(dt.AddDate(0, amount*1, 0)), nil
|
||||||
|
case "w", "week", "weeks":
|
||||||
|
return values.NewDateTime(dt.AddDate(0, 0, amount*7)), nil
|
||||||
|
case "d", "day", "days":
|
||||||
|
return values.NewDateTime(dt.AddDate(0, 0, amount*1)), nil
|
||||||
|
case "h", "hour", "hours":
|
||||||
|
return values.NewDateTime(dt.Add(time.Hour)), nil
|
||||||
|
case "i", "minute", "minutes":
|
||||||
|
return values.NewDateTime(dt.Add(time.Minute)), nil
|
||||||
|
case "s", "second", "seconds":
|
||||||
|
return values.NewDateTime(dt.Add(time.Second)), nil
|
||||||
|
case "f", "millisecond", "milliseconds":
|
||||||
|
return values.NewDateTime(dt.Add(time.Millisecond)), nil
|
||||||
|
}
|
||||||
|
return values.DateTime{}, errors.Errorf("no such unit '%s'", unit)
|
||||||
|
}
|
||||||
222
pkg/stdlib/datetime/add_subtract_test.go
Normal file
222
pkg/stdlib/datetime/add_subtract_test.go
Normal file
@@ -0,0 +1,222 @@
|
|||||||
|
package datetime_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||||
|
|
||||||
|
"github.com/MontFerret/ferret/pkg/stdlib/datetime"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
utcLoc, _ = time.LoadLocation("UTC")
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDateAdd(t *testing.T) {
|
||||||
|
tcs := []*testCase{
|
||||||
|
&testCase{
|
||||||
|
Name: "When more than 3 arguments",
|
||||||
|
Expected: values.None,
|
||||||
|
Args: []core.Value{
|
||||||
|
values.NewInt(0),
|
||||||
|
values.NewInt(0),
|
||||||
|
values.NewInt(0),
|
||||||
|
values.NewInt(0),
|
||||||
|
},
|
||||||
|
ShouldErr: true,
|
||||||
|
},
|
||||||
|
&testCase{
|
||||||
|
Name: "When less than 3 arguments",
|
||||||
|
Expected: values.None,
|
||||||
|
Args: []core.Value{
|
||||||
|
values.NewInt(0),
|
||||||
|
},
|
||||||
|
ShouldErr: true,
|
||||||
|
},
|
||||||
|
&testCase{
|
||||||
|
Name: "When incorrect arguments",
|
||||||
|
Expected: values.None,
|
||||||
|
Args: []core.Value{
|
||||||
|
values.NewString("bla-bla"),
|
||||||
|
values.NewInt(0),
|
||||||
|
values.NewString("be-be"),
|
||||||
|
},
|
||||||
|
ShouldErr: true,
|
||||||
|
},
|
||||||
|
&testCase{
|
||||||
|
Name: "When wrong unit given",
|
||||||
|
Expected: values.None,
|
||||||
|
Args: []core.Value{
|
||||||
|
mustLayoutDt("2006-01-02", "1999-02-07"),
|
||||||
|
values.NewInt(5),
|
||||||
|
values.NewString("not_exist"),
|
||||||
|
},
|
||||||
|
ShouldErr: true,
|
||||||
|
},
|
||||||
|
&testCase{
|
||||||
|
Name: "When argument have correct types",
|
||||||
|
Expected: func() core.Value {
|
||||||
|
expected, _ := datetime.DateAdd(
|
||||||
|
context.Background(),
|
||||||
|
mustDefaultLayoutDt("1999-02-07T15:04:05Z"),
|
||||||
|
values.NewInt(1),
|
||||||
|
values.NewString("day"),
|
||||||
|
)
|
||||||
|
return expected
|
||||||
|
}(),
|
||||||
|
Args: []core.Value{
|
||||||
|
mustDefaultLayoutDt("1999-02-07T15:04:05Z"),
|
||||||
|
values.NewInt(1),
|
||||||
|
values.NewString("day"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&testCase{
|
||||||
|
Name: "-1 day",
|
||||||
|
Expected: mustDefaultLayoutDt("1999-02-06T15:04:05Z"),
|
||||||
|
Args: []core.Value{
|
||||||
|
mustDefaultLayoutDt("1999-02-07T15:04:05Z"),
|
||||||
|
values.NewInt(-1),
|
||||||
|
values.NewString("day"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&testCase{
|
||||||
|
Name: "+3 months",
|
||||||
|
Expected: mustDefaultLayoutDt("1999-05-07T15:04:05Z"),
|
||||||
|
Args: []core.Value{
|
||||||
|
mustDefaultLayoutDt("1999-02-07T15:04:05Z"),
|
||||||
|
values.NewInt(3),
|
||||||
|
values.NewString("months"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&testCase{
|
||||||
|
Name: "+5 years",
|
||||||
|
Expected: mustLayoutDt("2006-01-02", "2004-02-07"),
|
||||||
|
Args: []core.Value{
|
||||||
|
mustLayoutDt("2006-01-02", "1999-02-07"),
|
||||||
|
values.NewInt(5),
|
||||||
|
values.NewString("y"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&testCase{
|
||||||
|
Name: "1999 minus 2000 years",
|
||||||
|
Expected: values.NewDateTime(
|
||||||
|
time.Date(-1, 2, 7, 0, 0, 0, 0, utcLoc),
|
||||||
|
),
|
||||||
|
Args: []core.Value{
|
||||||
|
mustLayoutDt("2006-01-02", "1999-02-07"),
|
||||||
|
values.NewInt(-2000),
|
||||||
|
values.NewString("year"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tcs {
|
||||||
|
tc.Do(t, datetime.DateAdd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDateSubtract(t *testing.T) {
|
||||||
|
tcs := []*testCase{
|
||||||
|
&testCase{
|
||||||
|
Name: "When more than 3 arguments",
|
||||||
|
Expected: values.None,
|
||||||
|
Args: []core.Value{
|
||||||
|
values.NewInt(0),
|
||||||
|
values.NewInt(0),
|
||||||
|
values.NewInt(0),
|
||||||
|
values.NewInt(0),
|
||||||
|
},
|
||||||
|
ShouldErr: true,
|
||||||
|
},
|
||||||
|
&testCase{
|
||||||
|
Name: "When less than 3 arguments",
|
||||||
|
Expected: values.None,
|
||||||
|
Args: []core.Value{
|
||||||
|
values.NewInt(0),
|
||||||
|
},
|
||||||
|
ShouldErr: true,
|
||||||
|
},
|
||||||
|
&testCase{
|
||||||
|
Name: "When incorrect arguments",
|
||||||
|
Expected: values.None,
|
||||||
|
Args: []core.Value{
|
||||||
|
values.NewString("bla-bla"),
|
||||||
|
values.NewInt(0),
|
||||||
|
values.NewString("be-be"),
|
||||||
|
},
|
||||||
|
ShouldErr: true,
|
||||||
|
},
|
||||||
|
&testCase{
|
||||||
|
Name: "When wrong unit given",
|
||||||
|
Expected: values.None,
|
||||||
|
Args: []core.Value{
|
||||||
|
mustLayoutDt("2006-01-02", "1999-02-07"),
|
||||||
|
values.NewInt(5),
|
||||||
|
values.NewString("not_exist"),
|
||||||
|
},
|
||||||
|
ShouldErr: true,
|
||||||
|
},
|
||||||
|
&testCase{
|
||||||
|
Name: "When argument have correct types",
|
||||||
|
Expected: func() core.Value {
|
||||||
|
expected, _ := datetime.DateSubtract(
|
||||||
|
context.Background(),
|
||||||
|
mustDefaultLayoutDt("1999-02-07T15:04:05Z"),
|
||||||
|
values.NewInt(1),
|
||||||
|
values.NewString("day"),
|
||||||
|
)
|
||||||
|
return expected
|
||||||
|
}(),
|
||||||
|
Args: []core.Value{
|
||||||
|
mustDefaultLayoutDt("1999-02-07T15:04:05Z"),
|
||||||
|
values.NewInt(1),
|
||||||
|
values.NewString("day"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&testCase{
|
||||||
|
Name: "-1 day",
|
||||||
|
Expected: mustDefaultLayoutDt("1999-02-08T15:04:05Z"),
|
||||||
|
Args: []core.Value{
|
||||||
|
mustDefaultLayoutDt("1999-02-07T15:04:05Z"),
|
||||||
|
values.NewInt(-1),
|
||||||
|
values.NewString("day"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&testCase{
|
||||||
|
Name: "+3 months",
|
||||||
|
Expected: mustDefaultLayoutDt("1999-02-07T15:04:05Z"),
|
||||||
|
Args: []core.Value{
|
||||||
|
mustDefaultLayoutDt("1999-05-07T15:04:05Z"),
|
||||||
|
values.NewInt(3),
|
||||||
|
values.NewString("months"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&testCase{
|
||||||
|
Name: "+5 years",
|
||||||
|
Expected: mustLayoutDt("2006-01-02", "1994-02-07"),
|
||||||
|
Args: []core.Value{
|
||||||
|
mustLayoutDt("2006-01-02", "1999-02-07"),
|
||||||
|
values.NewInt(5),
|
||||||
|
values.NewString("y"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&testCase{
|
||||||
|
Name: "1999 minus 2000 years",
|
||||||
|
Expected: values.NewDateTime(
|
||||||
|
time.Date(-1, 2, 7, 0, 0, 0, 0, utcLoc),
|
||||||
|
),
|
||||||
|
Args: []core.Value{
|
||||||
|
mustLayoutDt("2006-01-02", "1999-02-07"),
|
||||||
|
values.NewInt(2000),
|
||||||
|
values.NewString("year"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tcs {
|
||||||
|
tc.Do(t, datetime.DateSubtract)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,5 +19,7 @@ func NewLib() map[string]core.Function {
|
|||||||
"DATE_QUARTER": DateQuarter,
|
"DATE_QUARTER": DateQuarter,
|
||||||
"DATE_DAYS_IN_MONTH": DateDaysInMonth,
|
"DATE_DAYS_IN_MONTH": DateDaysInMonth,
|
||||||
"DATE_FORMAT": DateFormat,
|
"DATE_FORMAT": DateFormat,
|
||||||
|
"DATE_ADD": DateAdd,
|
||||||
|
"DATE_SUBTRACT": DateSubtract,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user