Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 104 additions & 25 deletions pkg/study/studyengine/expressions.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"fmt"
"log/slog"
"math/rand"
"reflect"
"strconv"
"strings"
"time"
Expand Down Expand Up @@ -161,12 +160,16 @@ func ExpressionEval(expression studyTypes.Expression, evalCtx EvalContext) (val
// Other
case "timestampWithOffset":
val, err = evalCtx.timestampWithOffset(expression)
case "timestampDiff":
val, err = evalCtx.timestampDiff(expression)
case "getTsForNextStartOfMonth":
val, err = evalCtx.getTsForNextStartOfMonth(expression)
case "getISOWeekForTs":
val, err = evalCtx.getISOWeekForTs(expression)
case "getTsForNextISOWeek":
val, err = evalCtx.getTsForNextISOWeek(expression)
case "getTsForStartOfISOWeek":
val, err = evalCtx.getTsForStartOfISOWeek(expression)
case "dateToStr":
val, err = evalCtx.dateToStr(expression)
case "parseValueAsNum":
Expand Down Expand Up @@ -1648,7 +1651,7 @@ func (ctx EvalContext) sum(exp studyTypes.Expression) (t float64, err error) {
case float64:
t = t + v
default:
slog.Error("unexpected type during expression eval", slog.Int("index", idx), slog.String("expression", exp.Name), slog.String("type", reflect.TypeOf(arg).String()))
slog.Error("unexpected type during expression eval", slog.Int("index", idx), slog.String("expression", exp.Name), slog.String("type", fmt.Sprintf("%T", arg)))
}

}
Expand All @@ -1664,10 +1667,10 @@ func (ctx EvalContext) neg(exp studyTypes.Expression) (val float64, err error) {
if err != nil {
return val, err
}
if reflect.TypeOf(arg).Kind() != reflect.Float64 {
v, ok := arg.(float64)
if !ok {
return val, errors.New("argument 1 should be resolved as type number (float64)")
}
v := arg.(float64)
val = -1 * v
return
}
Expand All @@ -1681,28 +1684,57 @@ func (ctx EvalContext) timestampWithOffset(exp studyTypes.Expression) (t float64
if err1 != nil {
return t, err1
}
if reflect.TypeOf(arg1).Kind() != reflect.Float64 {
arg1Float, ok := arg1.(float64)
if !ok {
return t, errors.New("argument 1 should be resolved as type number (float64)")
}
delta := int64(arg1.(float64))
delta := int64(arg1Float)

referenceTime := Now().Unix()
if len(exp.Data) == 2 {
arg2, err2 := ctx.ExpressionArgResolver(exp.Data[1])
if err2 != nil {
return t, err2
}
if reflect.TypeOf(arg2).Kind() != reflect.Float64 {
arg2Float, ok := arg2.(float64)
if !ok {
return t, errors.New("argument 2 should be resolved as type number (float64)")
}

referenceTime = int64(arg2.(float64))
referenceTime = int64(arg2Float)
}

t = float64(referenceTime + delta)
return
}

func (ctx EvalContext) timestampDiff(exp studyTypes.Expression) (t float64, err error) {
if len(exp.Data) != 2 {
return t, errors.New("should have exactly two arguments")
}

arg1, err1 := ctx.ExpressionArgResolver(exp.Data[0])
if err1 != nil {
return t, err1
}
laterTimestamp, ok := arg1.(float64)
if !ok {
return t, errors.New("argument 1 should be resolved as type number (float64)")
}

arg2, err2 := ctx.ExpressionArgResolver(exp.Data[1])
if err2 != nil {
return t, err2
}
earlierTimestamp, ok := arg2.(float64)
if !ok {
return t, errors.New("argument 2 should be resolved as type number (float64)")
}

t = laterTimestamp - earlierTimestamp
return
}

func (ctx EvalContext) getTsForNextStartOfMonth(exp studyTypes.Expression) (t float64, err error) {
if len(exp.Data) != 1 && len(exp.Data) != 2 {
return t, errors.New("should have one or two arguments")
Expand Down Expand Up @@ -1762,11 +1794,12 @@ func (ctx EvalContext) getTsForNextStartOfMonth(exp studyTypes.Expression) (t fl
if err2 != nil {
return t, err2
}
if reflect.TypeOf(arg2).Kind() != reflect.Float64 {
arg2Float, ok := arg2.(float64)
if !ok {
return t, errors.New("argument 2 should be resolved as type number (float64)")
}

referenceTime = time.Unix(int64(arg2.(float64)), 0)
referenceTime = time.Unix(int64(arg2Float), 0)
}

// Get the first day of the next occurrence of the specified month
Expand Down Expand Up @@ -1801,11 +1834,12 @@ func (ctx EvalContext) getTsForNextISOWeek(exp studyTypes.Expression) (t float64
if err1 != nil {
return t, err1
}
if reflect.TypeOf(arg1).Kind() != reflect.Float64 {
arg1Float, ok := arg1.(float64)
if !ok {
return t, errors.New("argument 1 should be resolved as type number (float64)")
}

ISOWeek := int64(arg1.(float64))
ISOWeek := int64(arg1Float)

if ISOWeek < 1 || ISOWeek > 53 {
return t, errors.New("argument 1 should be between 1 and 53")
Expand All @@ -1817,11 +1851,12 @@ func (ctx EvalContext) getTsForNextISOWeek(exp studyTypes.Expression) (t float64
if err2 != nil {
return t, err2
}
if reflect.TypeOf(arg2).Kind() != reflect.Float64 {
arg2Float, ok := arg2.(float64)
if !ok {
return t, errors.New("argument 2 should be resolved as type number (float64)")
}

referenceTime = time.Unix(int64(arg2.(float64)), 0)
referenceTime = time.Unix(int64(arg2Float), 0)
}

for {
Expand All @@ -1832,7 +1867,47 @@ func (ctx EvalContext) getTsForNextISOWeek(exp studyTypes.Expression) (t float64
referenceTime = referenceTime.AddDate(0, 0, 1)
}

startOfWeek := referenceTime.AddDate(0, 0, -int(referenceTime.Weekday())+1)
weekday := int(referenceTime.Weekday())
if weekday == 0 {
// time.Sunday is 0, but for ISO weeks Sunday is the 7th day
weekday = 7
}

startOfWeek := referenceTime.AddDate(0, 0, -weekday+1)
startOfWeek = time.Date(startOfWeek.Year(), startOfWeek.Month(), startOfWeek.Day(), 0, 0, 0, 0, startOfWeek.Location())

t = float64(startOfWeek.Unix())
return
}

func (ctx EvalContext) getTsForStartOfISOWeek(exp studyTypes.Expression) (t float64, err error) {
if len(exp.Data) != 0 && len(exp.Data) != 1 {
return t, errors.New("should have zero or one argument")
}

referenceTime := Now()
if len(exp.Data) == 1 {
arg1, err1 := ctx.ExpressionArgResolver(exp.Data[0])
if err1 != nil {
return t, err1
}
arg1Float, ok := arg1.(float64)
if !ok {
return t, errors.New("argument 1 should be resolved as type number (float64)")
}

referenceTime = time.Unix(int64(arg1Float), 0)
}

weekday := int(referenceTime.Weekday())
if weekday == 0 {
// time.Sunday is 0, but for ISO weeks Sunday is the 7th day
weekday = 7
}

startOfWeek := referenceTime.AddDate(0, 0, -weekday+1)
startOfWeek = time.Date(startOfWeek.Year(), startOfWeek.Month(), startOfWeek.Day(), 0, 0, 0, 0, startOfWeek.Location())

t = float64(startOfWeek.Unix())
return
}
Expand All @@ -1846,11 +1921,12 @@ func (ctx EvalContext) getISOWeekForTs(exp studyTypes.Expression) (t float64, er
if err1 != nil {
return t, err1
}
if reflect.TypeOf(arg1).Kind() != reflect.Float64 {
arg1Float, ok := arg1.(float64)
if !ok {
return t, errors.New("argument 1 should be resolved as type number (float64)")
}

ts := int64(arg1.(float64))
ts := int64(arg1Float)
_, iw := time.Unix(ts, 0).ISOWeek()
t = float64(iw)
return
Expand Down Expand Up @@ -1905,15 +1981,16 @@ func (ctx EvalContext) parseValueAsNum(exp studyTypes.Expression) (val float64,
return val, err
}

if reflect.TypeOf(arg1).Kind() == reflect.Float64 {
return arg1.(float64), nil
if v, ok := arg1.(float64); ok {
return v, nil
}

if reflect.TypeOf(arg1).Kind() != reflect.String {
strVal, ok := arg1.(string)
if !ok {
return val, errors.New("argument 1 should be resolved as type string")
}

val, err = strconv.ParseFloat(arg1.(string), 64)
val, err = strconv.ParseFloat(strVal, 64)

return
}
Expand All @@ -1927,19 +2004,21 @@ func (ctx EvalContext) generateRandomNumber(exp studyTypes.Expression) (val floa
if err != nil {
return val, err
}
if reflect.TypeOf(arg1).Kind() != reflect.Float64 {
arg1Float, ok := arg1.(float64)
if !ok {
return val, errors.New("argument 1 should be resolved as type number (float64)")
}
min := int(arg1.(float64))
min := int(arg1Float)

arg2, err := ctx.ExpressionArgResolver(exp.Data[1])
if err != nil {
return val, err
}
if reflect.TypeOf(arg2).Kind() != reflect.Float64 {
arg2Float, ok := arg2.(float64)
if !ok {
return val, errors.New("argument 2 should be resolved as type number (float64)")
}
max := int(arg2.(float64))
max := int(arg2Float)

rand.Seed(time.Now().UnixNano())
randomVal := rand.Intn(max-min+1) + min
Expand Down
Loading