Skip to content

Commit

Permalink
feat(#8985): add more date diff XPath functions (#8949)
Browse files Browse the repository at this point in the history
Co-authored-by: Anro Swart <anro.swart@westerncape.gov.za>
Co-authored-by: Joshua Kuestersteffen <jkuester@kuester7.com>
  • Loading branch information
3 people committed Apr 3, 2024
1 parent 79e4268 commit 0700001
Show file tree
Hide file tree
Showing 5 changed files with 258 additions and 81 deletions.
21 changes: 21 additions & 0 deletions tests/e2e/cht-form/default/dates.wdio-spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
const mockConfig = require('../mock-config');
const commonEnketoPage = require('@page-objects/default/enketo/common-enketo.wdio.page');

describe('cht-form web component - Dates', () => {

it('functions calculate difference between two dates', async () => {
await mockConfig.loadForm('default', 'test', 'dates');

const dateA = '2021-01-01';
const dateB = '2025-11-25';

await commonEnketoPage.setDateValue('Date A', dateA);
await commonEnketoPage.setDateValue('Date B', dateB);
const [{ fields }] = await mockConfig.submitForm();

expect(fields.diff_in_days).to.equal('1789');
expect(fields.diff_in_weeks).to.equal('255');
expect(fields.diff_in_months).to.equal('58');
expect(fields.diff_in_years).to.equal('4');
});
});
Binary file added tests/e2e/cht-form/default/forms/dates.xlsx
Binary file not shown.
51 changes: 51 additions & 0 deletions tests/e2e/cht-form/default/forms/dates.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?xml version="1.0"?>
<h:html xmlns="http://www.w3.org/2002/xforms" xmlns:ev="http://www.w3.org/2001/xml-events" xmlns:h="http://www.w3.org/1999/xhtml" xmlns:jr="http://openrosa.org/javarosa" xmlns:orx="http://openrosa.org/xforms" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<h:head>
<h:title>Dates</h:title>
<model>
<itext>
<translation lang="en">
<text id="/dates/group/date_a:label">
<value>Date A</value>
</text>
<text id="/dates/group/date_b:label">
<value>Date B</value>
</text>
</translation>
</itext>
<instance>
<dates delimiter="#" id="dates" prefix="J1!dates!" version="2024-04-02 00:00:00">
<group>
<date_a/>
<date_b/>
</group>
<diff_in_days/>
<diff_in_weeks/>
<diff_in_months/>
<diff_in_years/>
<meta tag="hidden">
<instanceID/>
</meta>
</dates>
</instance>
<instance id="contact-summary"/>
<bind nodeset="/dates/group/date_a" type="date"/>
<bind nodeset="/dates/group/date_b" type="date"/>
<bind calculate="cht:difference-in-days( /dates/group/date_a , /dates/group/date_b )" nodeset="/dates/diff_in_days" type="string"/>
<bind calculate="cht:difference-in-weeks( /dates/group/date_a , /dates/group/date_b )" nodeset="/dates/diff_in_weeks" type="string"/>
<bind calculate="cht:difference-in-months( /dates/group/date_a , /dates/group/date_b )" nodeset="/dates/diff_in_months" type="string"/>
<bind calculate="cht:difference-in-years( /dates/group/date_a , /dates/group/date_b )" nodeset="/dates/diff_in_years" type="string"/>
<bind calculate="concat('uuid:', uuid())" nodeset="/dates/meta/instanceID" readonly="true()" type="string"/>
</model>
</h:head>
<h:body class="pages">
<group appearance="field-list" ref="/dates/group">
<input ref="/dates/group/date_a">
<label ref="jr:itext('/dates/group/date_a:label')"/>
</input>
<input ref="/dates/group/date_b">
<label ref="jr:itext('/dates/group/date_b:label')"/>
</input>
</group>
</h:body>
</h:html>
70 changes: 41 additions & 29 deletions webapp/src/js/enketo/medic-xpath-extensions.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
const RAW_NUMBER = /^(-?[0-9]+)(\.[0-9]+)?$/;
const DATE_STRING = /^\d\d\d\d-\d{1,2}-\d{1,2}(?:T\d\d:\d\d:\d\d\.?\d?\d?(?:Z|[+-]\d\d:\d\d)|.*)?$/;
const XPR = {
number: v => ({ t: 'num', v }),
string: v => ({ t: 'str', v }),
number: v => ({ t: 'num', v }),
string: v => ({ t: 'str', v }),
date: v => ({ t: 'date', v }),
};
const MOMENT_KEYS = {
YEARS: 'years',
MONTHS: 'months',
WEEKS: 'weeks',
DAYS: 'days',
HOURS: 'hours',
MINUTES: 'minutes',
};

let zscoreUtil;
let toBikramSambat;
Expand All @@ -16,7 +24,7 @@ const isObject = (value) => {
return value !== null && (type === 'object' || type === 'function');
};

const getValue = function(resultObject) {
const getValue = function (resultObject) {
if (!isObject(resultObject) || !resultObject.t) {
return resultObject;
}
Expand All @@ -29,7 +37,7 @@ const getValue = function(resultObject) {
return resultObject.v;
};

const toISOLocalString = function(date) {
const toISOLocalString = function (date) {
if (date.toString() === 'Invalid Date') {
return date.toString();
}
Expand All @@ -41,9 +49,9 @@ const toISOLocalString = function(date) {
return dt;
};

const getTimezoneOffsetAsTime = function(date) {
const pad2 = function(x) {
return ( x < 10 ) ? '0' + x : x;
const getTimezoneOffsetAsTime = function (date) {
const pad2 = function (x) {
return (x < 10) ? '0' + x : x;
};

if (date.toString() === 'Invalid Date') {
Expand Down Expand Up @@ -132,31 +140,41 @@ const addDate = function (date, years, months, days, hours, minutes) {
}
const moment = asMoment(date);
[
[years, 'years'],
[months, 'months'],
[days, 'days'],
[hours, 'hours'],
[minutes, 'minutes'],
[years, MOMENT_KEYS.YEARS],
[months, MOMENT_KEYS.MONTHS],
[days, MOMENT_KEYS.DAYS],
[hours, MOMENT_KEYS.HOURS],
[minutes, MOMENT_KEYS.MINUTES],
].filter(([value]) => value)
.map(([value, name]) => ([ +asString(value), name ]))
.map(([value, name]) => ([+asString(value), name]))
.filter(([value]) => value)
.forEach(([value, name]) => moment.add(value, name));
return XPR.date(moment.toDate());
};

const dateDiff = function (startDateObj, endDateObj, key) {
const startDate = asMoment(startDateObj);
const endDate = asMoment(endDateObj);
if (!startDate.isValid() || !endDate.isValid()) {
return XPR.string('');
}

return XPR.number(endDate.diff(startDate, key));
};

module.exports = {
getTimezoneOffsetAsTime: getTimezoneOffsetAsTime,
toISOLocalString: toISOLocalString,
init: function(_zscoreUtil, _toBikramSambat, _moment, _chtScriptApi) {
init: function (_zscoreUtil, _toBikramSambat, _moment, _chtScriptApi) {
zscoreUtil = _zscoreUtil;
toBikramSambat = _toBikramSambat;
moment = _moment;
chtScriptApi = _chtScriptApi;
},
func: {
'add-date': addDate,
'z-score': function() {
const args = Array.from(arguments).map(function(arg) {
'z-score': function () {
const args = Array.from(arguments).map(function (arg) {
return getValue(arg);
});
const result = zscoreUtil.apply(null, args);
Expand All @@ -167,18 +185,12 @@ module.exports = {
},
'to-bikram-sambat': convertToBikramSambat,
'parse-timestamp-to-date': parseTimestampToDate, // Function name convention of XForm
'difference-in-months': function(d1, d2) {
const d1Moment = asMoment(d1);
const d2Moment = asMoment(d2);

if (!d1Moment.isValid() || !d2Moment.isValid()) {
return XPR.string('');
}

const months = d2Moment.diff(d1Moment, 'months');
return XPR.number(months);
},
'cht:extension-lib': function() {
'cht:difference-in-years': (d1, d2) => dateDiff(d1, d2, MOMENT_KEYS.YEARS),
'cht:difference-in-months': (d1, d2) => dateDiff(d1, d2, MOMENT_KEYS.MONTHS),
'difference-in-months': (d1, d2) => dateDiff(d1, d2, MOMENT_KEYS.MONTHS), // To be deprecated
'cht:difference-in-weeks': (d1, d2) => dateDiff(d1, d2, MOMENT_KEYS.WEEKS),
'cht:difference-in-days': (d1, d2) => dateDiff(d1, d2, MOMENT_KEYS.DAYS),
'cht:extension-lib': function () {
const args = Array.from(arguments);
const firstArg = args.shift();
const libId = firstArg && firstArg.v;
Expand All @@ -190,7 +202,7 @@ module.exports = {
}
},
process: {
toExternalResult: function(r) {
toExternalResult: function (r) {
if (r.t === 'date') {
return {
resultType: XPathResult.STRING_TYPE,
Expand Down
Loading

0 comments on commit 0700001

Please sign in to comment.