Skip to content

Commit e8c568f

Browse files
Merge pull request #32 in LFOR/fhirpath.js from bugfix/LF-2047/date-time-arithmetic to master
* commit '5541394f5e97b79e85d821a2907ce55caccd5615': Updated Node.js version Fixed issues found during review Fixed issues found during review Fixed typo Fix date/time arithmetic
2 parents e98476f + 5541394 commit e8c568f

File tree

6 files changed

+177
-114
lines changed

6 files changed

+177
-114
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
This log documents significant changes for each release. This project follows
44
[Semantic Versioning](http://semver.org/).
55

6+
## [2.10.2] - 2021-12-02
7+
### Fixed
8+
- Date/Time arithmetic: "@2016 + 365 days" should equal "@2017".
9+
610
## [2.10.1] - 2021-10-25
711
### Fixed
812
- toDecimal() function should return an empty collection for non-convertible string

bashrc.fhirpath

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# The following is the standard bashrc file for the
22
# development team for this repository.
33

4-
NODE=node-v10.14.1
4+
NODE=node-v14.16.1-linux-x64
55
#
66
# Set path
77
PATH=~/${NODE}/bin:/bin:/usr/local/bin:/usr/bin:/usr/sbin:/sbin:/etc

package-lock.json

+11-10
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "fhirpath",
3-
"version": "2.10.1",
3+
"version": "2.10.2",
44
"description": "A FHIRPath engine",
55
"main": "src/fhirpath.js",
66
"dependencies": {

src/types.js

+85-100
Original file line numberDiff line numberDiff line change
@@ -248,33 +248,32 @@ FP_Quantity._yearMonthConversionFactor = {
248248
};
249249

250250
/**
251-
* Defines a map from FHIRPath time units to UCUM.
251+
* Defines a map from time units that are supported for arithmetic (including
252+
* some UCUM time based units) to FHIRPath time units.
252253
*/
253-
FP_Quantity.timeUnitsToUCUM = {
254-
'years': "'a'",
255-
'months': "'mo'",
256-
'weeks': "'wk'",
257-
'days': "'d'",
258-
'hours': "'h'",
259-
'minutes': "'min'",
260-
'seconds': "'s'",
261-
'milliseconds': "'ms'",
262-
'year': "'a'",
263-
'month': "'mo'",
264-
'week': "'wk'",
265-
'day': "'d'",
266-
'hour': "'h'",
267-
'minute': "'min'",
268-
'second': "'s'",
269-
'millisecond': "'ms'",
270-
"'a'": "'a'",
271-
"'mo'": "'mo'",
272-
"'wk'": "'wk'",
273-
"'d'": "'d'",
274-
"'h'": "'h'",
275-
"'min'": "'min'",
276-
"'s'": "'s'",
277-
"'ms'": "'ms'"
254+
FP_Quantity.arithmeticDurationUnits = {
255+
'years': "year",
256+
'months': "month",
257+
'weeks': "week",
258+
'days': "day",
259+
'hours': "hour",
260+
'minutes': "minute",
261+
'seconds': "second",
262+
'milliseconds': "millisecond",
263+
'year': "year",
264+
'month': "month",
265+
'week': "week",
266+
'day': "day",
267+
'hour': "hour",
268+
'minute': "minute",
269+
'second': "second",
270+
'millisecond': "millisecond",
271+
"'wk'": "week",
272+
"'d'": "day",
273+
"'h'": "hour",
274+
"'min'": "minute",
275+
"'s'": "second",
276+
"'ms'": "millisecond"
278277
};
279278

280279
/**
@@ -301,20 +300,6 @@ FP_Quantity.mapTimeUnitsToUCUMCode = Object.keys(FP_Quantity.mapUCUMCodeToTimeUn
301300
return res;
302301
}, {});
303302

304-
/**
305-
* A map of the UCUM units that must be paired with integer values when doing
306-
* arithmetic.
307-
*/
308-
FP_Quantity.integerUnits = {
309-
"'a'": true,
310-
"'mo'": true,
311-
"'wk'": true,
312-
"'d'": true,
313-
"'h'": true,
314-
"'min'": true
315-
};
316-
317-
318303
class FP_TimeBase extends FP_Type {
319304
constructor(timeStr) {
320305
super();
@@ -327,45 +312,46 @@ class FP_TimeBase extends FP_Type {
327312
* FHIRPath specification for supported units.
328313
*/
329314
plus(timeQuantity) {
330-
var unit = timeQuantity.unit;
331-
var ucumUnit = FP_Quantity.timeUnitsToUCUM[unit];
332-
if (!ucumUnit) {
333-
throw new Error('For date/time arithmetic, the unit of the quantity '+
334-
'must be a recognized time-based unit');
315+
const unit = timeQuantity.unit;
316+
let timeUnit = FP_Quantity.arithmeticDurationUnits[unit];
317+
if (!timeUnit) {
318+
throw new Error('For date/time arithmetic, the unit of the quantity ' +
319+
'must be one of the following time-based units: ' +
320+
Object.keys(FP_Quantity.arithmeticDurationUnits));
335321
}
336-
var cls = this.constructor;
337-
var unitPrecision = cls._ucumToDatePrecision[ucumUnit];
322+
const cls = this.constructor;
323+
const unitPrecision = cls._timeUnitToDatePrecision[timeUnit];
338324
if (unitPrecision === undefined) {
339-
throw new Error('Unsupported unit for +. The unit should be one of '+
340-
Object.keys(cls._ucumToDatePrecision).join(', ') + '.');
325+
throw new Error('Unsupported unit for +. The unit should be one of ' +
326+
Object.keys(cls._timeUnitToDatePrecision).join(', ') + '.');
341327
}
342-
var isIntUnit = FP_Quantity.integerUnits[ucumUnit];
343-
var qVal = timeQuantity.value;
344-
if (isIntUnit && !Number.isInteger(qVal)) {
345-
throw new Error('When adding a quantity of unit '+unit+' to a date/time,'+
346-
' the value must be an integer.');
328+
let qVal = timeQuantity.value;
329+
const isTime = (cls === FP_Time);
330+
331+
// From the FHIRPath specification: "For precisions above seconds, the
332+
// decimal portion of the time-valued quantity is ignored, since date/time
333+
// arithmetic above seconds is performed with calendar duration semantics."
334+
if (isTime ? unitPrecision < 2 : unitPrecision < 5) {
335+
qVal = Math.trunc(qVal);
347336
}
348337

349338
// If the precision of the time quantity is higher than the precision of the
350339
// date, we need to convert the time quantity to the precision of the date.
351340
if (this._getPrecision() < unitPrecision) {
352-
var unquotedUnit = ucumUnit.slice(1, ucumUnit.length-1);
353-
var neededUnit = cls._datePrecisionToUnquotedUcum[
341+
const neededUnit = cls._datePrecisionToTimeUnit[
354342
this._getPrecision()];
355-
var convResult = ucumUtils.convertUnitTo(unquotedUnit, qVal, neededUnit);
356-
if (convResult.status != 'succeeded') {
357-
throw new Error(convResult.msg.join("\n"));
343+
if (neededUnit !== 'second') {
344+
const newQuantity = FP_Quantity.convUnitTo(timeUnit, qVal, neededUnit);
345+
timeUnit = newQuantity.unit;
346+
qVal = Math.trunc(newQuantity.value);
358347
}
359-
ucumUnit = "'"+neededUnit+"'";
360-
qVal = Math.floor(convResult.toVal);
361348
}
362-
var newDate = FP_TimeBase.timeUnitToAddFn[ucumUnit](this._getDateObj(), qVal);
349+
const newDate = FP_TimeBase.timeUnitToAddFn[timeUnit](this._getDateObj(), qVal);
363350
// newDate is a Date. We need to make a string with the correct precision.
364-
var isTime = (cls === FP_Time);
365-
var precision = this._getPrecision();
351+
let precision = this._getPrecision();
366352
if (isTime)
367353
precision += 3; // based on dateTimeRE, not timeRE
368-
var newDateStr = FP_DateTime.isoDateTime(newDate, precision);
354+
let newDateStr = FP_DateTime.isoDateTime(newDate, precision);
369355
if (isTime) {
370356
// FP_Time just needs the time part of the string
371357
newDateStr = newDateStr.slice(newDateStr.indexOf('T') + 1);
@@ -604,18 +590,18 @@ class FP_TimeBase extends FP_Type {
604590
}
605591

606592
/**
607-
* A map from a UCUM time based unit to a function used to add that quantity to
608-
* a date/time.
593+
* A map from a FHIRPath time units to a function used to add that
594+
* quantity to a date/time.
609595
*/
610596
FP_TimeBase.timeUnitToAddFn = {
611-
"'a'": require('date-fns/add_years'),
612-
"'mo'": require('date-fns/add_months'),
613-
"'wk'": require('date-fns/add_weeks'),
614-
"'d'": require('date-fns/add_days'),
615-
"'h'": require('date-fns/add_hours'),
616-
"'min'": require('date-fns/add_minutes'),
617-
"'s'": require('date-fns/add_seconds'),
618-
"'ms'": require('date-fns/add_milliseconds')
597+
"year": require('date-fns/add_years'),
598+
"month": require('date-fns/add_months'),
599+
"week": require('date-fns/add_weeks'),
600+
"day": require('date-fns/add_days'),
601+
"hour": require('date-fns/add_hours'),
602+
"minute": require('date-fns/add_minutes'),
603+
"second": require('date-fns/add_seconds'),
604+
"millisecond": require('date-fns/add_milliseconds')
619605
};
620606

621607

@@ -733,25 +719,25 @@ FP_DateTime.checkString = function(str) {
733719
};
734720

735721
/**
736-
* A map from UCUM units (in quotation marks, which is the FHIRPath syntax for
737-
* UCUM) to the internal DateTime "precision" number.
722+
* A map from FHIRPath time units to the internal DateTime "precision" number.
738723
*/
739-
FP_DateTime._ucumToDatePrecision = {
740-
"'a'": 0,
741-
"'mo'": 1,
742-
"'wk'": 2, // wk is just 7*d
743-
"'d'": 2,
744-
"'h'": 3,
745-
"'min'": 4,
746-
"'s'": 5,
747-
"'ms'": 6
724+
FP_DateTime._timeUnitToDatePrecision = {
725+
"year": 0,
726+
"month": 1,
727+
"week": 2, // wk is just 7*d
728+
"day": 2,
729+
"hour": 3,
730+
"minute": 4,
731+
"second": 5,
732+
"millisecond": 6
748733
};
749734

750735
/**
751-
* The inverse of _ucumToDatePrecision, except with unquoted UCUM units.
736+
* The inverse of _timeUnitToDatePrecision.
752737
*/
753-
FP_DateTime._datePrecisionToUnquotedUcum = ["a", "mo", "d", "h", "min", "s",
754-
"ms"];
738+
FP_DateTime._datePrecisionToTimeUnit = [
739+
"year", "month", "day", "hour", "minute", "second", "millisecond"
740+
];
755741

756742

757743

@@ -857,20 +843,19 @@ FP_Time.checkString = function(str) {
857843
};
858844

859845
/**
860-
* A map from UCUM units (in quotation marks, which is the FHIRPath syntax for
861-
* UCUM) to the internal DateTime "precision" number.
846+
* A map from FHIRPath time units to the internal DateTime "precision" number.
862847
*/
863-
FP_Time._ucumToDatePrecision = {
864-
"'h'": 0,
865-
"'min'": 1,
866-
"'s'": 2,
867-
"'ms'": 3
848+
FP_Time._timeUnitToDatePrecision = {
849+
"hour": 0,
850+
"minute": 1,
851+
"second": 2,
852+
"millisecond": 3
868853
};
869854

870855
/**
871-
* The inverse of _ucumToDatePrecision, except with unquoted UCUM units.
856+
* The inverse of _timeUnitToDatePrecision.
872857
*/
873-
FP_Time._datePrecisionToUnquotedUcum = ["h", "min", "s", "ms"];
858+
FP_Time._datePrecisionToTimeUnit = ["hour", "minute", "second", "millisecond"];
874859

875860

876861
/**
@@ -920,8 +905,8 @@ FP_DateTime.isoDateTime = function(date, precision) {
920905
if (precision > 3) {
921906
rtn += ':' + formatNum(date.getMinutes());
922907
if (precision > 4) {
923-
rtn += ':' + formatNum(date.getSeconds());
924-
if (precision > 5)
908+
rtn += ':' + formatNum(date.getSeconds() );
909+
if (date.getMilliseconds())
925910
rtn += '.' + formatNum(date.getMilliseconds(), 3);
926911
}
927912
}

0 commit comments

Comments
 (0)