Skip to content

Commit 464d1c4

Browse files
committed
Modified date time parsing with enhanced timezone parsing and timezone retention
1 parent 0a0c6d9 commit 464d1c4

File tree

13 files changed

+880
-796
lines changed

13 files changed

+880
-796
lines changed

.eslintrc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"no-shadow":"off",
88
"consistent-return": ["error", { "treatUndefinedAsUnspecified": true }],
99
"no-nested-ternary":"off",
10-
"no-useless-escape":"off"
10+
"no-useless-escape":"off",
11+
"no-prototype-builtins":"off"
1112
}
1213
}

dist/feel.js

Lines changed: 592 additions & 581 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

grammar/feel.pegjs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,8 @@ NamePart
6565
}
6666

6767
Name
68-
= !ReservedWord head:NameStart tail:(__ (!ReservedWord) __ NamePart)*
68+
= "time zone"
69+
/ !ReservedWord head:NameStart tail:(__ (!ReservedWord) __ NamePart)*
6970
{
7071
return new ast.NameNode(buildName(head,tail,0),location());
7172
}

src/feel.pegjs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,8 @@ NamePart
126126
}
127127

128128
Name
129-
= !ReservedWord head:NameStart tail:(__ (!ReservedWord) __ NamePart)*
129+
= "time zone"
130+
/ !ReservedWord head:NameStart tail:(__ (!ReservedWord) __ NamePart)*
130131
{
131132
return new ast.NameNode(buildName(head,tail,0),location());
132133
}

test/comparision-expression/feel-comparision-expression.build.spec.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,7 @@ describe(chalk.blue('Comparision expression ast parsing test'), function() {
274274
});
275275

276276
it('Successfully parse and build equality expression using date with multiple args and date with string arg', function(done) {
277-
var text = 'date(2012, 12, 25) = date("2012-12-25")';
277+
var text = 'date(2012, 11, 25) = date("2012-12-25")';
278278
var parsedGrammar = FEEL.parse(text);
279279
parsedGrammar.build().then(result => {
280280
expect(result).to.be.true;

utils/built-in-functions/date-time-functions/add-properties.js

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,38 @@
66
*/
77

88
const addProperties = (obj, props) => {
9+
const child = Object.create(obj);
910
Object.keys(props).forEach((key) => {
1011
const value = props[key];
1112
if (typeof value === 'function') {
12-
obj[key] = value.call(obj); // eslint-disable-line no-param-reassign
13+
Object.defineProperty(child, key, { get: function () { // eslint-disable-line object-shorthand
14+
const proto = Object.getPrototypeOf(this);
15+
return value.call(proto);
16+
},
17+
});
1318
} else {
14-
obj[key] = value; // eslint-disable-line no-param-reassign
19+
Object.defineProperty(child, key, { get: function () { // eslint-disable-line object-shorthand
20+
const proto = Object.getPrototypeOf(this);
21+
return key !== 'type' && proto[value] ? proto[value]() : value;
22+
},
23+
});
1524
}
1625
});
17-
return obj;
26+
27+
const proxy = new Proxy(child, {
28+
get: (target, propKey) => {
29+
const proto = Object.getPrototypeOf(target);
30+
const protoPropValue = proto[propKey];
31+
if (!target.hasOwnProperty(propKey) && typeof protoPropValue === 'function') {
32+
return function (...args) {
33+
return protoPropValue.apply(proto, args);
34+
};
35+
}
36+
return target[propKey];
37+
},
38+
});
39+
40+
return proxy;
1841
};
1942

2043
module.exports = addProperties;

utils/built-in-functions/date-time-functions/date-time.js

Lines changed: 13 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,12 @@ Description : date time string convert "from" to a date and time
2222
e.g. : date and time("2012-12-24T23:59:00") + duration("PT1M") = date and time("2012-12-25T00:00:00")
2323
*/
2424

25-
2625
const moment = require('moment-timezone');
2726
const addProperties = require('./add-properties');
28-
const { defaultTz, date_time_IANA_tz, types, properties } = require('../../helper/meta');
27+
const { time_ISO_8601, date_ISO_8601, date_time_IANA_tz, types, properties } = require('../../helper/meta');
2928

30-
const { year, month, day, hour, minute, second, time_offset, timezone } = properties;
31-
const props = Object.assign({}, { year, month, day, hour, minute, second, time_offset, timezone, type: types.date_and_time, isDateTime: true });
29+
const { year, month, day, hour, minute, second, 'time offset': time_offset, timezone } = properties;
30+
const props = Object.assign({}, { year, month, day, hour, minute, second, 'time offset': time_offset, timezone, type: types.date_and_time, isDateTime: true });
3231

3332
const parseIANATz = (str) => {
3433
const match = str.match(date_time_IANA_tz);
@@ -50,30 +49,29 @@ const parseIANATz = (str) => {
5049
return match;
5150
};
5251

53-
const dateandtime = (...args) => {
52+
const dateAndTime = (...args) => {
5453
let dt;
5554
if (args.length === 1) {
5655
const arg = args[0];
57-
if (typeof arg === 'string') {
56+
const str = arg instanceof Date ? arg.toISOString() : arg;
57+
if (typeof str === 'string') {
5858
try {
59-
dt = arg === '' ? moment() : parseIANATz(arg) || moment(arg);
59+
dt = str === '' ? moment() : parseIANATz(str) || moment.parseZone(str);
6060
} catch (err) {
6161
throw err;
6262
}
63-
} else if (arg instanceof Date) {
64-
dt = moment(arg);
6563
}
6664
if (!dt.isValid()) {
6765
throw new Error('Invalid date_and_time. This is usually caused by an invalid format. Please check the input format');
6866
}
6967
} else if (args.length === 2) {
7068
const [date, time] = args;
7169
if (date && date.isDate && time && time.isTime) {
72-
const { year, month, day } = date;
73-
const { hour, minute, second } = time;
74-
dt = moment.tz({ year, month, day, hour, minute, second }, defaultTz);
70+
const datePart = date.format(date_ISO_8601);
71+
const timePart = time.format(time_ISO_8601);
72+
dt = moment.parseZone(`${datePart}${timePart}`);
7573
if (!dt.isValid()) {
76-
throw new Error('Invalid date_and_time');
74+
throw new Error('Invalid date and time. This is usually caused by input type mismatch.');
7775
}
7876
} else {
7977
throw new Error('Type Mismatch - args specified with date_and_time are expected to be of type date and time respectively. Please check the arguments order or type');
@@ -82,15 +80,7 @@ const dateandtime = (...args) => {
8280
throw new Error('Invalid number of arguments specified with "date_and_time" in-built function');
8381
}
8482

85-
try {
86-
dt = dt.tz(defaultTz);
87-
if (dt.isValid()) {
88-
return addProperties(dt, props);
89-
}
90-
throw new Error('Please check the defaultTz property in the metadata. Possible invalid timezone id encountered');
91-
} catch (err) {
92-
throw err;
93-
}
83+
return addProperties(dt, props);
9484
};
9585

96-
module.exports = { 'date and time': dateandtime };
86+
module.exports = { 'date and time': dateAndTime };

utils/built-in-functions/date-time-functions/date.js

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ e.g. : date(2012, 12, 25) = date("2012-12-25")
3030

3131
const moment = require('moment-timezone');
3232
const addProperties = require('./add-properties');
33-
const { types, properties, UTC, UTCTimePart } = require('../../helper/meta');
33+
const { types, properties, UTC, UTCTimePart, time_ISO_8601, date_ISO_8601 } = require('../../helper/meta');
3434

3535
const { year, month, day } = properties;
3636
const props = Object.assign({}, { year, month, day, type: types.date, isDate: true });
@@ -39,7 +39,7 @@ const isNumber = args => args.reduce((prev, next) => prev && typeof next === 'nu
3939

4040
const parseDate = (str) => {
4141
try {
42-
const d = moment.tz(`${str}${UTCTimePart}`, UTC);
42+
const d = moment.parseZone(`${str}${UTCTimePart}`);
4343
if (d.isValid()) {
4444
return d;
4545
}
@@ -55,16 +55,20 @@ const date = (...args) => {
5555
const arg = args[0];
5656
if (typeof arg === 'string') {
5757
try {
58-
d = arg === '' ? moment.tz(UTC) : parseDate(arg);
58+
d = arg === '' ? moment.parseZone(UTCTimePart, time_ISO_8601) : parseDate(arg);
5959
} catch (err) {
6060
throw err;
6161
}
6262
} else if (typeof arg === 'object') {
6363
if (arg instanceof Date) {
64-
d = moment(arg);
64+
const ISO = arg.toISOString();
65+
const dateTime = moment.parseZone(ISO);
66+
const datePart = dateTime.format(date_ISO_8601);
67+
d = moment.parseZone(`${datePart}${UTCTimePart}`);
6568
} else if (arg.isDateTime) {
66-
const { year, month, day } = arg;
67-
d = moment.tz({ year, month, day, hour: 0, minute: 0, second: 0 }, UTC);
69+
const dateTime = moment.tz(arg.format(), UTC);
70+
const datePart = dateTime.format(date_ISO_8601);
71+
d = moment.parseZone(`${datePart}${UTCTimePart}`);
6872
}
6973
if (!d.isValid()) {
7074
throw new Error('Invalid date. Parsing error while attempting to create date from date and time');
@@ -74,7 +78,7 @@ const date = (...args) => {
7478
}
7579
} else if (args.length === 3 && isNumber(args)) {
7680
const [year, month, day] = args;
77-
d = moment.tz({ year, month: month - 1, day, hour: 0, minute: 0, second: 0 }, UTC);
81+
d = moment.tz({ year, month, day, hour: 0, minute: 0, second: 0 }, UTC);
7882
if (!d.isValid()) {
7983
throw new Error('Invalid date. Parsing error while attempting to create date from parts');
8084
}

utils/built-in-functions/date-time-functions/duration.js

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,12 @@ const addProperties = require('./add-properties');
2828
const { ymd_ISO_8601, dtd_ISO_8601, types, properties } = require('../../helper/meta');
2929

3030
const { years, months, days, hours, minutes, seconds } = properties;
31-
const dtdProps = Object.assign({}, { days, hours, minutes, seconds, type: types.dtd, isDtd: () => true, isDuration: () => true });
32-
const ymdProps = Object.assign({}, { years, months, type: types.ymd, isYmd: () => true, isDuration: () => true });
31+
const dtdProps = Object.assign({}, { days, hours, minutes, seconds, type: types.dtd, isDtd: true, isDuration: true });
32+
const ymdProps = Object.assign({}, { years, months, type: types.ymd, isYmd: true, isDuration: true });
3333

3434
const isDateTime = args => args.reduce((recur, next) => recur && (next.isDateTime || next.isDate), true);
3535

36-
const daysandtimeduration = (...args) => { // eslint-disable-line camelcase
36+
const daysAndTimeDuration = (...args) => {
3737
let dtd;
3838
if (args.length === 1) {
3939
dtd = moment.duration(args[0]);
@@ -52,7 +52,7 @@ const daysandtimeduration = (...args) => { // eslint-disable-line camelcase
5252
}
5353
};
5454

55-
const yearsandmonthsduration = (...args) => { // eslint-disable-line camelcase
55+
const yearsAndMonthsDuration = (...args) => {
5656
let ymd;
5757
if (args.length === 1) {
5858
ymd = moment.duration(args[0]);
@@ -79,13 +79,13 @@ const duration = (arg) => {
7979
if (typeof arg === 'string') {
8080
if (patternMatch(arg, ymd_ISO_8601)) {
8181
try {
82-
return yearsandmonthsduration(arg);
82+
return yearsAndMonthsDuration(arg);
8383
} catch (err) {
8484
throw err;
8585
}
8686
} else if (patternMatch(arg, dtd_ISO_8601)) {
8787
try {
88-
return daysandtimeduration(arg);
88+
return daysAndTimeDuration(arg);
8989
} catch (err) {
9090
throw err;
9191
}
@@ -96,4 +96,4 @@ const duration = (arg) => {
9696
};
9797

9898

99-
module.exports = { duration, 'years and months duration': yearsandmonthsduration, 'days and time duration': daysandtimeduration };
99+
module.exports = { duration, 'years and months duration': yearsAndMonthsDuration, 'days and time duration': daysAndTimeDuration };

utils/built-in-functions/date-time-functions/time.js

Lines changed: 28 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -30,16 +30,16 @@ e.g. : time(“T23:59:00z") = time(23, 59, 0, duration(“PT0H”))
3030

3131
const moment = require('moment-timezone');
3232
const addProperties = require('./add-properties');
33-
const { defaultTz, time_ISO_8601, time_IANA_tz, types, properties } = require('../../helper/meta');
33+
const { time_ISO_8601, time_IANA_tz, types, properties } = require('../../helper/meta');
3434

35-
const { hour, minute, second, time_offset, timezone } = properties;
36-
const props = Object.assign({}, { hour, minute, second, time_offset, timezone, type: types.time, isTime: true });
35+
const { hour, minute, second, 'time offset': time_offset, timezone } = properties;
36+
const props = Object.assign({}, { hour, minute, second, 'time offset': time_offset, timezone, type: types.time, isTime: true });
3737

3838
const isNumber = args => args.reduce((prev, next) => prev && typeof next === 'number', true);
3939

4040
const parseTime = (str) => {
4141
try {
42-
const t = moment(str, time_ISO_8601);
42+
const t = moment.parseZone(str, time_ISO_8601);
4343
if (t.isValid()) {
4444
return t;
4545
}
@@ -49,6 +49,16 @@ const parseTime = (str) => {
4949
}
5050
};
5151

52+
const dtdToOffset = (dtd) => {
53+
if (dtd.isDtd) {
54+
let { hours, minutes } = dtd;
55+
hours = hours < 10 ? `0${hours}` : `${hours}`;
56+
minutes = minutes < 10 ? `0${minutes}` : `${minutes}`;
57+
return `${hours}:${minutes}`;
58+
}
59+
throw new Error('Invalid Type');
60+
};
61+
5262
const parseIANATz = (str) => {
5363
const match = str.match(time_IANA_tz);
5464
if (match) {
@@ -79,14 +89,12 @@ const time = (...args) => {
7989
} catch (err) {
8090
throw err;
8191
}
82-
} else if (typeof arg === 'object' && arg.isDateTime) {
92+
} else if (typeof arg === 'object') {
8393
if (arg instanceof Date) {
84-
t = moment(arg);
94+
t = moment.parseZone(arg.toISOString);
8595
} else if (arg.isDateTime) {
86-
const hour = arg.hour;
87-
const minute = arg.minute;
88-
const second = arg.second;
89-
t = moment.tz({ hour, minute, second }, defaultTz);
96+
const str = arg.format(time_ISO_8601);
97+
t = moment.parseZone(str, time_ISO_8601);
9098
}
9199
if (!t.isValid()) {
92100
throw new Error('Invalid time. Parsing error while attempting to extract time from date and time.');
@@ -97,12 +105,15 @@ const time = (...args) => {
97105
} else if (args.length >= 3 && isNumber(args.slice(0, 3))) {
98106
const [hour, minute, second] = args.slice(0, 3);
99107
t = moment({ hour, minute, second });
100-
const offset = args[3];
101-
if (offset && offset.isDuration) {
102-
// TODO : implement duration to offset conversion
103-
t.utcOffset(offset.value);
104-
} else {
105-
throw new Error('Type Mismatch - the fourth argument in "time" in-built function is expected to be of type "duration"');
108+
const dtd = args[3];
109+
if (dtd) {
110+
try {
111+
const sign = Math.sign < 0 ? '-' : '+';
112+
const offset = `${sign}${dtdToOffset(dtd)}`;
113+
t = moment.parseZone(`${moment({ hour, minute, second }).format('THH:mm:ss')}${offset}`, time_ISO_8601);
114+
} catch (err) {
115+
throw new Error(`${err.message} - the fourth argument in "time" in-built function is expected to be of type "days and time duration"`);
116+
}
106117
}
107118
if (!t.isValid()) {
108119
throw new Error('Invalid time. Parsing error while attempting to create time from parts');
@@ -111,15 +122,7 @@ const time = (...args) => {
111122
throw new Error('Invalid number of arguments specified with "time" in-built function');
112123
}
113124

114-
try {
115-
t = t.tz(defaultTz);
116-
if (t.isValid()) {
117-
return addProperties(t, props);
118-
}
119-
throw new Error('Please check the defaultTz property in the metadata. Possible invalid timezone id encountered');
120-
} catch (err) {
121-
throw err;
122-
}
125+
return addProperties(t, props);
123126
};
124127

125128
module.exports = { time };

0 commit comments

Comments
 (0)