diff --git a/feature_flags_test.go b/feature_flags_test.go index 8959d16..547952f 100644 --- a/feature_flags_test.go +++ b/feature_flags_test.go @@ -5,6 +5,7 @@ import ( "net/http" "net/http/httptest" "strings" + "time" "testing" ) @@ -224,6 +225,58 @@ func TestMatchPropertyContains(t *testing.T) { } } +func TestMatchPropertyDate(t *testing.T) { + shouldMatchA := []interface{}{"2022-04-30T00:00:00+00:00", "2022-04-30T00:00:00+02:00", time.Date(2022, 3, 20, 20, 34, 58, 651387237, time.UTC)} + shouldNotMatchA := NewProperties().Set("key", "2022-05-30T00:00:00+00:00") + propertyA := Property{ + Key: "key", + Value: "2022-05-01", + Operator: "is_date_before", + } + for _, val := range shouldMatchA { + isMatch, err := matchProperty(propertyA, NewProperties().Set("key", val)) + if err != nil { + t.Error(err) + } + + if !isMatch { + t.Error(("Value is not a match")) + } + } + isMatchA, errA := matchProperty(propertyA, shouldNotMatchA) + if errA != nil { + t.Error(errA) + } + if isMatchA { + t.Error("Value is not a match") + } + + shouldMatchB := []interface{}{"2022-05-30T00:00:00+00:00", time.Date(2022, 5, 30, 20, 34, 58, 651387237, time.UTC)} + shouldNotMatchB := NewProperties().Set("key", "2022-04-29T00:00:00+00:00") + propertyB := Property{ + Key: "key", + Value: "2022-05-01T00:00:00+00:00", + Operator: "is_date_after", + } + for _, val := range shouldMatchB { + isMatch, err := matchProperty(propertyB, NewProperties().Set("key", val)) + if err != nil { + t.Error(err) + } + + if !isMatch { + t.Error(("Value is not a match")) + } + } + isMatchB, errB := matchProperty(propertyB, shouldNotMatchB) + if errB != nil { + t.Error(errB) + } + if isMatchB { + t.Error("Value is not a match") + } +} + func TestFlagPersonProperty(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { diff --git a/featureflags.go b/featureflags.go index a433ef9..5ffbd1a 100644 --- a/featureflags.go +++ b/featureflags.go @@ -508,10 +508,39 @@ func matchProperty(property Property, properties Properties) (bool, error) { return overrideValueOrderable <= valueOrderable, nil } + if (operator == "is_date_before") || (operator == "is_date_after") { + valueDate, valueDateErr := convertToDateTime(value) + if valueDateErr != nil { + return false, valueDateErr + } + overrideDate, err := convertToDateTime(override_value) + if err != nil { + return false, err + } + if operator == "is_date_before" { + return overrideDate.Before(valueDate), nil + } + return overrideDate.After(valueDate), nil + } + return false, nil } +func convertToDateTime(value interface{}) (time.Time, error) { + if valueDate, ok := value.(time.Time); ok { + return valueDate, nil + } else if valueString, ok := value.(string); ok { + stringToDate, err := time.Parse(time.RFC3339, valueString) + if err == nil { + return stringToDate, nil + } + return time.Now(), &InconclusiveMatchError{fmt.Sprintf("Value %d is not in a valid ISO8601 string format", value)} + } else { + return time.Now(), &InconclusiveMatchError{fmt.Sprintf("Value %d must be in string or date format", value)} + } +} + func validateOrderable(firstValue interface{}, secondValue interface{}) (float64, float64, error) { convertedFirstValue, err := interfaceToFloat(firstValue)