EdgeVerve / feel

Expression Language for creating and executing business rules in decision table based on DMN 1.1 specification for conformance level 3
MIT License
93 stars 48 forks source link

date("2018-07-31") - duration("P1M") fails #13

Closed a-hegerath closed 3 years ago

a-hegerath commented 6 years ago

When evaluating the expression

date("2018-07-31") - duration("P1M") 

the following error occurs:

Error: Invalid date. Parsing error while attempting to create date from parts
      at date (utils\built-in-functions\date-time-functions\date.js:83:13)
      at operatorMap.-._.curry (utils\helper\fn-generator.js:272:28)
      at apply (node_modules\lodash\lodash.js:496:27)

This is because of the attempt to create the date "2018-06-31", which of course does not exist. It is not sufficient to just reduce the month value, since for the resulting month the day (in this case, the 31st) may not exist.

The same type of error exists for adding months instead of subtracting them. Likely the same error exists if subtracting or adding months from a date and time instead of a date (I have not verified that).

Here is a test that reveals the error:

  it('should subtract months from last day of month correctly', (done) => {
    const text = 'date("2018-07-31") - duration("P1M") = date("2018-06-30")';
    const parsedGrammar = FEEL.parse(text);
    parsedGrammar.build().then(result => {
      expect(result).to.be.true;
      done();
    }).catch(err => done(err));
  });
deostroll commented 6 years ago

I did something to this resolve this, but I hit a road block. Please check the patch I furnish at the end of this post

What I observed is that the expression: date("2018-07-31") - duration("P1M") yields a date_and_time object, where as the expression: date("2018-06-30") yields a date object. Hence an equality comparison would fail due to type incompatibility.

But I guess the correct way to express this is date(date("2018-07-31") - duration("P1M")) = date("2018-06-30")

@pragyandas can you confirm this...

Patch:

diff --git a/utils/helper/fn-generator.js b/utils/helper/fn-generator.js
index c5c03e9..1e8f561 100644
--- a/utils/helper/fn-generator.js
+++ b/utils/helper/fn-generator.js
@@ -6,6 +6,7 @@
 */

 const Big = require('big.js');
+const moment = require('moment-timezone');
 const _ = require('lodash');
 const { valueT, valueInverseT, valueDT, valueInverseDT, valueDTD, valueInverseDTD, valueYMD, valueInverseYMD } = require('./value');
 const { date, time, 'date and time': dateandtime } = require('../built-in-functions');
@@ -267,9 +268,20 @@ const operatorMap = {
       } else if (x.isDtd && y.isDtd) {
         return valueInverseDTD(valueDTD(x) - valueDTD(y));
       } else if ((x.isDateTime || x.isDate) && y.isYmd) {
+        let { years , months } = y;
+        let  monthOverflow = Math.floor(months/12);
+        if(monthOverflow > 0) {
+          years += monthOverflow;
+          months -= monthOverflow * 12;
+        }
         // return dateandtime(date(x.year - (y.years + Math.floor((x.month - y.months) / 12)), (x.month - y.months) - (Math.floor((x.month - y.months) / 12) * 12), x.day), time(x));
         // fix for https://github.com/EdgeVerve/feel/issues/11 by a-hegerath
-        return dateandtime(date((x.year - y.years) + Math.floor((x.month - y.months) / 12), (x.month - y.months) - (Math.floor((x.month - y.months) / 12) * 12), x.day), time(x));
+        // return dateandtime(date((x.year - y.years) + Math.floor((x.month - y.months) / 12), (x.month - y.months) - (Math.floor((x.month - y.months) / 12) * 12), x.day), time(x));
+        // dateandtime(date(x.year - (y.years + Math.floor((x.month - y.months) / 12)), (x.month - y.months) - (Math.floor((x.month - y.months) / 12) * 12), x.day), time(x));
+        // return dateandtime(date(x.year - years, (x.month - months) - 1, x.day), time(x));
+        var clone = moment(x);
+        clone.add(-years, 'years').add(-months, 'months')
+        return dateandtime(date(clone.year(), clone.month(), clone.date()), time(clone.format()));
       } else if (x.isYmd && (y.isDateTime || y.isDate)) {
         throw new Error(`${x.type} - ${y.type} : operation unsupported for one or more operands types`);
       } else if ((x.isDateTime || x.isDate) && y.isDtd) {