javajbossdroolsdrools-guvnordrools-fusion

Compare dates in drools


My rule file as below,

import com.springapp.mvc.model.Person; 
dialect "java"
rule "4" 
    when
        $person:Person(((date > "20-Jan-2015") && (date < "20-Dec-2015")) && (call_count >= "299"))
    then
        System.out.println("Beep");
end

I added following person object and fire rules as below,

Person person = new Person();
person.date = "20-Feb-2015";
person.call_count = 400;
kSession.insert(person);
int fires = kSession.fireAllRules();

But it didn't print "Beep". I think conditions are not matched but I can't understand why this happen. How can I compare dates in drools?

My actual rule set,

package Customer_Loyalty_Categorization;
import com.springapp.mvc.model.Person; 
dialect "java"

rule "4" 
    when
        $person:Person(((date > "10-Nov-2015") && (date < "10-Dec-2015")) && (call_count >= "299"))
        $person:Person(((date > "10-Nov-2015")&&(date < "30-Dec-2015")) && (call_count >= "299"))
    then
        System.out.println("Point rule runs.");
        $person.points = ($person.call_count)*0.2;
end

rule "6" 
    when
        $person:Person(call_count >= "599")
    then
        System.out.println("Category rule runs.");
        $person.setCategory('PLATINUM');
end

And after changing type of the date variable of the person I got following exception,

java.lang.RuntimeException: Unable to Analyse Expression date > "20-Nov-2015":
[Error: Comparison operation requires compatible types. Found class java.util.Date and class java.lang.String]
[Near : {... date > "20-Nov-2015" ....}]
                    ^
[Line: 8, Column: 8] : [Rule name='4']

Unable to Analyse Expression date < "20-Dec-2015":
[Error: Comparison operation requires compatible types. Found class java.util.Date and class java.lang.String]
[Near : {... date < "20-Dec-2015" ....}]
                    ^
[Line: 8, Column: 8] : [Rule name='4']

Unable to Analyse Expression date > "01-Jan-2015":
[Error: Comparison operation requires compatible types. Found class java.util.Date and class java.lang.String]
[Near : {... date > "01-Jan-2015" ....}]
                    ^
[Line: 40, Column: 8] : [Rule name='1']

Unable to Analyse Expression date < "07-Jan-2015":
[Error: Comparison operation requires compatible types. Found class java.util.Date and class java.lang.String]
[Near : {... date < "07-Jan-2015" ....}]
                    ^
[Line: 40, Column: 8] : [Rule name='1']

Unable to Analyse Expression date > "01-Jan-2015":
[Error: Comparison operation requires compatible types. Found class java.util.Date and class java.lang.String]
[Near : {... date > "01-Jan-2015" ....}]
                    ^
[Line: 48, Column: 8] : [Rule name='2']

Unable to Analyse Expression date < "07-Jan-2015":
[Error: Comparison operation requires compatible types. Found class java.util.Date and class java.lang.String]
[Near : {... date < "07-Jan-2015" ....}]
                    ^
[Line: 48, Column: 8] : [Rule name='2']

Unable to Analyse Expression date > "05-Jan-2015":
[Error: Comparison operation requires compatible types. Found class java.util.Date and class java.lang.String]
[Near : {... date > "05-Jan-2015" ....}]
                    ^
[Line: 48, Column: 8] : [Rule name='2']

Unable to Analyse Expression date < "10-Jan-2015":
[Error: Comparison operation requires compatible types. Found class java.util.Date and class java.lang.String]
[Near : {... date < "10-Jan-2015" ....}]
                    ^
[Line: 48, Column: 8] : [Rule name='2']

I'm generating rules as set of strings and convert them into knowledgebase using following function,

public void createKnowledgeBase(){
        String ruleSet = loadRuleSet();//generate rules as strings.
        try {
            System.out.println(ruleSet);
            long start = System.currentTimeMillis();
            if(ruleSet!=null){
                KnowledgeBuilder knowledgeBuilder = KnowledgeBuilderFactory.newKnowledgeBuilder();
                Resource myResource = ResourceFactory.newReaderResource(new StringReader(ruleSet));
                knowledgeBuilder.add(myResource, ResourceType.DRL);
                if (knowledgeBuilder.hasErrors()) {
                    throw new RuntimeException(knowledgeBuilder.getErrors().toString());
                }
                knowledgeBase = KnowledgeBaseFactory.newKnowledgeBase();
                knowledgeBase.addKnowledgePackages(knowledgeBuilder.getKnowledgePackages());
            }
            long finish = System.currentTimeMillis();
            System.out.println("Execution time = " + (finish-start) + " milliseconds.");
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }

Solution

  • Apparently you have

    class Person {
        String date;
        // ...
    }
    

    So that

    when
        $person:Person(((date > "20-Jan-2015") ...
    

    results in a string (!) comparison of

    "20-Feb-2015" > "20-Jan-2015" && "20-Feb-2015" < "20-Dec-2015"
    

    which may even work at times, but mostly it wont. You should use

    class Person {
        java.util.Date date;
        // ...
    }
    

    You need to change

    person.date = new Date( 115, 1, 20 ); // or, preferably, parse a string 
    

    but you can leave the rule as it is; Drools will convert a string to a Date value (provided it corresponds to your locale setting).

    Later After some experiments, I find that 6.3.0 (and probably earlier versions) have a rather weird behaviour when compiling comparisons of java.util.Date to String.

    rule x1 when
      Person(date > "10-Jan-2000")              // OK
      Person($date:date, date > "10-Jan-2000")  // OK
      Person($date:date, $date > "10-Jan-2000") // Error (types incompatible)
    

    It is absolutely confusing when a programmer may not rely on the fact that a bound variable behaves like the property to which it is bound.

    Finally: Don't use public fields in your fact classes. Stay with the Java Beans model and declare getters and setters. It turns out that Drools isn't using the automatic conversion from String to java.util.Date when a (public) instance variable itself is accessed due to the lack of a getter.