diff --git a/README.md b/README.md index 7ad4e49..c19d56b 100644 --- a/README.md +++ b/README.md @@ -249,6 +249,13 @@ dt.isHoliday('middle-america', {some: 'stuff'}); +## Typedefs + +
+
DiffBusinessConfig
+
+
+ ## DateTime ⇐ [DateTime](#DateTime) @@ -264,6 +271,7 @@ dt.isHoliday('middle-america', {some: 'stuff'}); * [.isBusinessDay()](#DateTime+isBusinessDay) ⇒ boolean * [.plusBusiness([days])](#DateTime+plusBusiness) ⇒ [DateTime](#DateTime) * [.minusBusiness([days])](#DateTime+minusBusiness) ⇒ [DateTime](#DateTime) + * [.diffBusiness(targetDate, config)](#DateTime+diffBusiness) ⇒ number @@ -362,6 +370,18 @@ Subtracts business days to an existing DateTime instance. | --- | --- | --- | --- | | [days] | number | 1 | The number of business days to subtract. | + + +### dateTime.diffBusiness(targetDate, config) ⇒ number +Returns the difference in business days. + +**Kind**: instance method of [DateTime](#DateTime) + +| Param | Type | +| --- | --- | +| targetDate | [DateTime](#DateTime) | +| config | [DiffBusinessConfig](#DiffBusinessConfig) | + ## getEasterMonthAndDay(year) ⇒ Array.<number> @@ -374,3 +394,14 @@ Returns the month and day of Easter for a given year. | --- | --- | | year | number | + + +## DiffBusinessConfig +**Kind**: global typedef +**Properties** + +| Name | Type | Default | Description | +| --- | --- | --- | --- | +| [includeEndDate] | boolean | false | include the end date in the calculation | +| [relative] | boolean | false | signs the return value as negative if end date is in the past | + diff --git a/src/index.js b/src/index.js index 1aaf0ed..5f040e1 100644 --- a/src/index.js +++ b/src/index.js @@ -146,4 +146,50 @@ DateTime.prototype.minusBusiness = function({ days = ONE_DAY } = {}) { return this.plusBusiness({ days: -days }); }; +/** + * @typedef DiffBusinessConfig + * @property {boolean} [includeEndDate=false] - include the end date in the calculation + * @property {boolean} [relative=false] - signs the return value as negative if end date is in the past + */ + +/** + * Returns the difference in business days. + * @param {DateTime} targetDate + * @param {DiffBusinessConfig} config + * @returns {number} + */ +DateTime.prototype.diffBusiness = function( + targetDate, + { includeEndDate = false, relative = false } = {} +) { + let dt = this; + let start = dt < targetDate ? dt : targetDate; + let end = dt < targetDate ? targetDate : dt; + let daysDiff = Number( + includeEndDate && end.isBusinessDay() && !end.isHoliday() + ); + let isSameDay = + dt.hasSame(targetDate, 'day') && + dt.hasSame(targetDate, 'month') && + dt.hasSame(targetDate, 'year'); + + if (isSameDay) { + return daysDiff; + } + + while (start < end) { + if (start.isBusinessDay() && !start.isHoliday()) { + daysDiff += 1; + } + + start = start.plus({ days: 1 }); + } + + if (relative) { + return dt <= targetDate ? daysDiff : -daysDiff; + } + + return daysDiff; +}; + export { DateTime }; diff --git a/src/index.test.js b/src/index.test.js index d3d86ad..0d28855 100644 --- a/src/index.test.js +++ b/src/index.test.js @@ -324,3 +324,60 @@ describe('time zone is carried over after a business-day operation', () => { expect(nyPlusTwo.zoneName).toBe('America/New_York'); }); }); + +describe('diffBusiness()', () => { + // Pick a start date we know is a valid business day + const defaultStartDate = DateTime.fromISO('2021-05-03'); + + it('knows two identical DateTimes have a business day diff of 0', () => { + const targetDate = defaultStartDate.startOf('day').plus({ hours: 2 }); + + expect(defaultStartDate.diffBusiness(targetDate)).toEqual(0); + }); + + it('knows there are 3 business days between two dates that are 3 business days apart', () => { + const myCompanyTakesNoHolidays = []; + const startDate = defaultStartDate.startOf('day'); + const futureDate = startDate.plusBusiness({ days: 3 }); + const pastDate = startDate.minusBusiness({ days: 3 }); + + startDate.setupBusiness({ + holidayMatchers: myCompanyTakesNoHolidays, + }); + + expect(startDate.diffBusiness(futureDate)).toEqual(3); + expect(startDate.diffBusiness(pastDate)).toEqual(3); + }); + + it('knows diff is negative for the past and positive for the future if relative is specified', () => { + const myCompanyTakesNoHolidays = []; + const startDate = defaultStartDate.startOf('day'); + const futureDate = startDate.plusBusiness({ days: 3 }); + const pastDate = startDate.minusBusiness({ days: 3 }); + + startDate.setupBusiness({ + holidayMatchers: myCompanyTakesNoHolidays, + }); + + const config = { relative: true }; + + expect(startDate.diffBusiness(futureDate, config)).toEqual(3); + expect(startDate.diffBusiness(pastDate, config)).toEqual(-3); + }); + + it('knows there are 4 business days between two dates that are 3 business days apart but include the end date', () => { + const myCompanyTakesNoHolidays = []; + const startDate = defaultStartDate.startOf('day'); + const futureDate = startDate.plusBusiness({ days: 3 }); + const pastDate = startDate.minusBusiness({ days: 3 }); + + startDate.setupBusiness({ + holidayMatchers: myCompanyTakesNoHolidays, + }); + + const config = { includeEndDate: true }; + + expect(startDate.diffBusiness(futureDate, config)).toEqual(4); + expect(startDate.diffBusiness(pastDate, config)).toEqual(4); + }); +});