diff options
Diffstat (limited to 'date-fns/src/getOverlappingDaysInIntervals')
4 files changed, 405 insertions, 0 deletions
diff --git a/date-fns/src/getOverlappingDaysInIntervals/index.d.ts b/date-fns/src/getOverlappingDaysInIntervals/index.d.ts new file mode 100644 index 0000000..e309ff9 --- /dev/null +++ b/date-fns/src/getOverlappingDaysInIntervals/index.d.ts @@ -0,0 +1,4 @@ +// This file is generated automatically by `scripts/build/typings.js`. Please, don't change it. + +import { getOverlappingDaysInIntervals } from 'date-fns' +export default getOverlappingDaysInIntervals diff --git a/date-fns/src/getOverlappingDaysInIntervals/index.js.flow b/date-fns/src/getOverlappingDaysInIntervals/index.js.flow new file mode 100644 index 0000000..9c0f272 --- /dev/null +++ b/date-fns/src/getOverlappingDaysInIntervals/index.js.flow @@ -0,0 +1,55 @@ +// @flow +// This file is generated automatically by `scripts/build/typings.js`. Please, don't change it. + +export type Interval = { + start: Date | number, + end: Date | number, +} + +export type Locale = { + code?: string, + formatDistance?: (...args: Array<any>) => any, + formatRelative?: (...args: Array<any>) => any, + localize?: { + ordinalNumber: (...args: Array<any>) => any, + era: (...args: Array<any>) => any, + quarter: (...args: Array<any>) => any, + month: (...args: Array<any>) => any, + day: (...args: Array<any>) => any, + dayPeriod: (...args: Array<any>) => any, + }, + formatLong?: { + date: (...args: Array<any>) => any, + time: (...args: Array<any>) => any, + dateTime: (...args: Array<any>) => any, + }, + match?: { + ordinalNumber: (...args: Array<any>) => any, + era: (...args: Array<any>) => any, + quarter: (...args: Array<any>) => any, + month: (...args: Array<any>) => any, + day: (...args: Array<any>) => any, + dayPeriod: (...args: Array<any>) => any, + }, + options?: { + weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6, + firstWeekContainsDate?: 1 | 2 | 3 | 4 | 5 | 6 | 7, + }, +} + +export type Duration = { + years?: number, + months?: number, + weeks?: number, + days?: number, + hours?: number, + minutes?: number, + seconds?: number, +} + +export type Day = 0 | 1 | 2 | 3 | 4 | 5 | 6 + +declare module.exports: ( + intervalLeft: Interval, + intervalRight: Interval +) => number diff --git a/date-fns/src/getOverlappingDaysInIntervals/index.ts b/date-fns/src/getOverlappingDaysInIntervals/index.ts new file mode 100644 index 0000000..aa5dc63 --- /dev/null +++ b/date-fns/src/getOverlappingDaysInIntervals/index.ts @@ -0,0 +1,106 @@ +import toDate from '../toDate/index' +import requiredArgs from '../_lib/requiredArgs/index' +import { Interval } from '../types' + +const MILLISECONDS_IN_DAY = 24 * 60 * 60 * 1000 + +/** + * @name getOverlappingDaysInIntervals + * @category Interval Helpers + * @summary Get the number of days that overlap in two time intervals + * + * @description + * Get the number of days that overlap in two time intervals + * + * ### v2.0.0 breaking changes: + * + * - [Changes that are common for the whole library](https://github.com/date-fns/date-fns/blob/master/docs/upgradeGuide.md#Common-Changes). + * + * - The function was renamed from `getOverlappingDaysInRanges` to `getOverlappingDaysInIntervals`. + * This change was made to mirror the use of the word "interval" in standard ISO 8601:2004 terminology: + * + * ``` + * 2.1.3 + * time interval + * part of the time axis limited by two instants + * ``` + * + * Also, this function now accepts an object with `start` and `end` properties + * instead of two arguments as an interval. + * This function now throws `RangeError` if the start of the interval is after its end + * or if any date in the interval is `Invalid Date`. + * + * ```javascript + * // Before v2.0.0 + * + * getOverlappingDaysInRanges( + * new Date(2014, 0, 10), new Date(2014, 0, 20), + * new Date(2014, 0, 17), new Date(2014, 0, 21) + * ) + * + * // v2.0.0 onward + * + * getOverlappingDaysInIntervals( + * { start: new Date(2014, 0, 10), end: new Date(2014, 0, 20) }, + * { start: new Date(2014, 0, 17), end: new Date(2014, 0, 21) } + * ) + * ``` + * + * @param {Interval} intervalLeft - the first interval to compare. See [Interval]{@link docs/Interval} + * @param {Interval} intervalRight - the second interval to compare. See [Interval]{@link docs/Interval} + * @returns {Number} the number of days that overlap in two time intervals + * @throws {TypeError} 2 arguments required + * @throws {RangeError} The start of an interval cannot be after its end + * @throws {RangeError} Date in interval cannot be `Invalid Date` + * + * @example + * // For overlapping time intervals adds 1 for each started overlapping day: + * getOverlappingDaysInIntervals( + * { start: new Date(2014, 0, 10), end: new Date(2014, 0, 20) }, + * { start: new Date(2014, 0, 17), end: new Date(2014, 0, 21) } + * ) + * //=> 3 + * + * @example + * // For non-overlapping time intervals returns 0: + * getOverlappingDaysInIntervals( + * { start: new Date(2014, 0, 10), end: new Date(2014, 0, 20) }, + * { start: new Date(2014, 0, 21), end: new Date(2014, 0, 22) } + * ) + * //=> 0 + */ + +export default function getOverlappingDaysInIntervals( + dirtyIntervalLeft: Interval, + dirtyIntervalRight: Interval +): number { + requiredArgs(2, arguments) + + const intervalLeft = dirtyIntervalLeft || {} + const intervalRight = dirtyIntervalRight || {} + const leftStartTime = toDate(intervalLeft.start).getTime() + const leftEndTime = toDate(intervalLeft.end).getTime() + const rightStartTime = toDate(intervalRight.start).getTime() + const rightEndTime = toDate(intervalRight.end).getTime() + + // Throw an exception if start date is after end date or if any date is `Invalid Date` + if (!(leftStartTime <= leftEndTime && rightStartTime <= rightEndTime)) { + throw new RangeError('Invalid interval') + } + + const isOverlapping = + leftStartTime < rightEndTime && rightStartTime < leftEndTime + + if (!isOverlapping) { + return 0 + } + + const overlapStartDate = + rightStartTime < leftStartTime ? leftStartTime : rightStartTime + + const overlapEndDate = rightEndTime > leftEndTime ? leftEndTime : rightEndTime + + const differenceInMs = overlapEndDate - overlapStartDate + + return Math.ceil(differenceInMs / MILLISECONDS_IN_DAY) +} diff --git a/date-fns/src/getOverlappingDaysInIntervals/test.ts b/date-fns/src/getOverlappingDaysInIntervals/test.ts new file mode 100644 index 0000000..b39672f --- /dev/null +++ b/date-fns/src/getOverlappingDaysInIntervals/test.ts @@ -0,0 +1,240 @@ +// @flow +/* eslint-env mocha */ + +import assert from 'power-assert' +import getOverlappingDaysInIntervals from '.' + +describe('getOverlappingDaysInIntervals', function() { + const initialIntervalStart = new Date(2016, 10, 10, 13, 0, 0) + const initialIntervalEnd = new Date(2016, 11, 3, 15, 0, 0) + + describe("when the time intervals don't overlap", function() { + it('returns 0 for a valid non overlapping interval before another interval', function() { + const earlierIntervalStart = new Date(2016, 9, 25) + const earlierIntervalEnd = new Date(2016, 10, 9) + + const numOverlappingDays = getOverlappingDaysInIntervals( + { start: initialIntervalStart, end: initialIntervalEnd }, + { start: earlierIntervalStart, end: earlierIntervalEnd } + ) + assert(numOverlappingDays === 0) + }) + + it('returns 0 for a valid non overlapping interval after another interval', function() { + const laterIntervalStart = new Date(2016, 11, 4) + const laterIntervalEnd = new Date(2016, 11, 9) + + const numOverlappingDays = getOverlappingDaysInIntervals( + { start: initialIntervalStart, end: initialIntervalEnd }, + { start: laterIntervalStart, end: laterIntervalEnd } + ) + assert(numOverlappingDays === 0) + }) + + it('returns 0 for a non overlapping same-day interval', function() { + const sameDayIntervalStart = new Date(2016, 11, 4, 9, 0, 0) + const sameDayIntervalEnd = new Date(2016, 11, 4, 18, 0, 0) + + const numOverlappingDays = getOverlappingDaysInIntervals( + { start: initialIntervalStart, end: initialIntervalEnd }, + { start: sameDayIntervalStart, end: sameDayIntervalEnd } + ) + assert(numOverlappingDays === 0) + }) + + it('returns 0 for an interval differing by a few hours', function() { + const oneDayOverlappingIntervalStart = new Date(2016, 11, 3, 18, 0, 0) + const oneDayOverlappingIntervalEnd = new Date(2016, 11, 14, 13, 0, 0) + + const numOverlappingDays = getOverlappingDaysInIntervals( + { start: initialIntervalStart, end: initialIntervalEnd }, + { + start: oneDayOverlappingIntervalStart, + end: oneDayOverlappingIntervalEnd + } + ) + assert(numOverlappingDays === 0) + }) + + it("returns 0 for an interval with the same startDateTime as the initial time intervals's endDateTime", function() { + const oneDayOverlapIntervalStart = new Date(2016, 11, 3, 15, 0, 0) + const oneDayOverlapIntervalEnd = new Date(2016, 11, 14, 13, 0, 0) + + const numOverlappingDays = getOverlappingDaysInIntervals( + { start: initialIntervalStart, end: initialIntervalEnd }, + { start: oneDayOverlapIntervalStart, end: oneDayOverlapIntervalEnd } + ) + assert(numOverlappingDays === 0) + }) + + it("returns 0 for an interval with the same endDateTime as the initial time interval's startDateTime", function() { + const oneDayOverlapIntervalStart = new Date(2016, 10, 3, 15, 0, 0) + const oneDayOverlapIntervalEnd = new Date(2016, 10, 10, 13, 0, 0) + + const numOverlappingDays = getOverlappingDaysInIntervals( + { start: initialIntervalStart, end: initialIntervalEnd }, + { start: oneDayOverlapIntervalStart, end: oneDayOverlapIntervalEnd } + ) + assert(numOverlappingDays === 0) + }) + }) + + describe('when the time intervals overlap', function() { + it('rounds up the result to include each started overlapping day', function() { + const includedIntervalStart = new Date(2016, 10, 14, 9, 0, 0) + const includedIntervalEnd = new Date(2016, 10, 15, 18, 0, 0) + + const numOverlappingDays = getOverlappingDaysInIntervals( + { start: initialIntervalStart, end: initialIntervalEnd }, + { start: includedIntervalStart, end: includedIntervalEnd } + ) + assert(numOverlappingDays === 2) + }) + + it('returns the correct value for an interval included within another interval', function() { + const includedIntervalStart = new Date(2016, 10, 14) + const includedIntervalEnd = new Date(2016, 10, 15) + + const numOverlappingDays = getOverlappingDaysInIntervals( + { start: initialIntervalStart, end: initialIntervalEnd }, + { start: includedIntervalStart, end: includedIntervalEnd } + ) + assert(numOverlappingDays === 1) + }) + + it('returns the correct value for an interval overlapping at the end', function() { + const endOverlappingIntervalStart = new Date(2016, 10, 5) + const endOverlappingIntervalEnd = new Date(2016, 10, 14) + + const numOverlappingDays = getOverlappingDaysInIntervals( + { start: initialIntervalStart, end: initialIntervalEnd }, + { start: endOverlappingIntervalStart, end: endOverlappingIntervalEnd } + ) + assert(numOverlappingDays === 4) + }) + + it('returns the correct value for an interval overlapping at the beginning', function() { + const startOverlappingIntervalStart = new Date(2016, 10, 20) + const startOverlappingIntervalEnd = new Date(2016, 11, 14) + + const numOverlappingDays = getOverlappingDaysInIntervals( + { start: initialIntervalStart, end: initialIntervalEnd }, + { + start: startOverlappingIntervalStart, + end: startOverlappingIntervalEnd + } + ) + assert(numOverlappingDays === 14) + }) + + it('returns the correct value for an interval including another interval', function() { + const includingIntervalStart = new Date(2016, 10, 5) + const includingIntervalEnd = new Date(2016, 11, 15) + + const numOverlappingDays = getOverlappingDaysInIntervals( + { start: initialIntervalStart, end: initialIntervalEnd }, + { start: includingIntervalStart, end: includingIntervalEnd } + ) + assert(numOverlappingDays === 24) + }) + }) + + it('accepts a timestamp', function() { + const initialIntervalStart = new Date(2016, 10, 10, 13, 0, 0).getTime() + const initialIntervalEnd = new Date(2016, 11, 3, 15, 0, 0).getTime() + + const endOverlappingIntervalStart = new Date(2016, 10, 5).getTime() + const endOverlappingIntervalEnd = new Date(2016, 10, 14).getTime() + + const numOverlappingDays = getOverlappingDaysInIntervals( + { start: initialIntervalStart, end: initialIntervalEnd }, + { start: endOverlappingIntervalStart, end: endOverlappingIntervalEnd } + ) + assert(numOverlappingDays === 4) + }) + + it('throws an exception if the start date of the initial time interval is after the end date', function() { + const block = getOverlappingDaysInIntervals.bind( + null, + { start: new Date(2016, 10, 7), end: new Date(2016, 10, 3) }, + { start: new Date(2016, 10, 5), end: new Date(2016, 10, 15) } + ) + assert.throws(block, RangeError) + }) + + it('throws an exception if the start date of the compared time interval is after the end date', function() { + const block = getOverlappingDaysInIntervals.bind( + null, + { start: new Date(2016, 10, 3), end: new Date(2016, 10, 7) }, + { start: new Date(2016, 10, 15), end: new Date(2016, 10, 5) } + ) + assert.throws(block, RangeError) + }) + + it('throws an exception if the initial interval is undefined', function() { + const block = getOverlappingDaysInIntervals.bind( + null, + // $ExpectedMistake + //@ts-expect-error + undefined, + { start: new Date(2016, 10, 5), end: new Date(2016, 10, 15) } + ) + assert.throws(block, RangeError) + }) + + it('throws an exception if the compared interval is undefined', function() { + const block = getOverlappingDaysInIntervals.bind( + null, + { start: new Date(2016, 10, 3), end: new Date(2016, 10, 7) }, + // $ExpectedMistake + //@ts-expect-error + undefined + ) + assert.throws(block, RangeError) + }) + + describe('one of the dates is `Invalid Date`', function() { + it('throws an exception if the start date of the initial time interval is `Invalid Date`', function() { + const block = getOverlappingDaysInIntervals.bind( + null, + { start: new Date(NaN), end: new Date(2016, 10, 3) }, + { start: new Date(2016, 10, 5), end: new Date(2016, 10, 15) } + ) + assert.throws(block, RangeError) + }) + + it('throws an exception if the end date of the initial time interval is `Invalid Date`', function() { + const block = getOverlappingDaysInIntervals.bind( + null, + { start: new Date(2016, 10, 3), end: new Date(NaN) }, + { start: new Date(2016, 10, 5), end: new Date(2016, 10, 15) } + ) + assert.throws(block, RangeError) + }) + + it('throws an exception if the start date of the compared time interval is `Invalid Date`', function() { + const block = getOverlappingDaysInIntervals.bind( + null, + { start: new Date(2016, 10, 3), end: new Date(2016, 10, 7) }, + { start: new Date(NaN), end: new Date(2016, 10, 5) } + ) + assert.throws(block, RangeError) + }) + + it('throws an exception if the end date of the compared time interval is `Invalid Date`', function() { + const block = getOverlappingDaysInIntervals.bind( + null, + { start: new Date(2016, 10, 3), end: new Date(2016, 10, 7) }, + { start: new Date(2016, 10, 5), end: new Date(NaN) } + ) + assert.throws(block, RangeError) + }) + }) + + it('throws TypeError exception if passed less than 2 arguments', function() { + assert.throws(getOverlappingDaysInIntervals.bind(null), TypeError) + // $ExpectedMistake + //@ts-expect-error + assert.throws(getOverlappingDaysInIntervals.bind(null, 1), TypeError) + }) +}) |