1
0
mirror of https://github.com/MontFerret/ferret.git synced 2025-01-16 03:21:03 +02:00

added DATE_ADD, DATE_SUBTRACT functions (#165)

This commit is contained in:
3timeslazy 2018-11-07 21:52:25 +03:00 committed by Tim Voronov
parent 528b60df24
commit 64ca68d930
4 changed files with 377 additions and 0 deletions

View File

@ -2,6 +2,8 @@ package core
import (
"encoding/json"
"github.com/pkg/errors"
)
//revive:disable-next-line redefines-builtin-id
@ -39,6 +41,8 @@ func (t Type) String() string {
return typestr[t]
}
// Value represents an interface of
// any type that needs to be used during runtime
type Value interface {
json.Marshaler
Type() Type
@ -49,10 +53,15 @@ type Value interface {
Copy() Value
}
// IsTypeOf return true when value's type
// is equal to check type.
// Returns false, otherwise.
func IsTypeOf(value Value, check Type) bool {
return value.Type() == check
}
// ValidateType checks the match of
// value's type and required types.
func ValidateType(value Value, required ...Type) error {
var valid bool
ct := value.Type()
@ -70,3 +79,26 @@ func ValidateType(value Value, required ...Type) error {
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
}

View 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)
}

View 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)
}
}

View File

@ -19,5 +19,7 @@ func NewLib() map[string]core.Function {
"DATE_QUARTER": DateQuarter,
"DATE_DAYS_IN_MONTH": DateDaysInMonth,
"DATE_FORMAT": DateFormat,
"DATE_ADD": DateAdd,
"DATE_SUBTRACT": DateSubtract,
}
}