Skip to content

Commit

Permalink
Fix corner cases (#460)
Browse files Browse the repository at this point in the history
* Invalid Date tests for `addX`, `setX` and `subX`

* Invalid Date tests for `getX`

* Invalid Date tests for `startOfX`, `endOfX` and `lastDayOfX`

* Invalid Date tests for `isSameX`

* Invalid Date tests for `differenceInX`

* `isX(new Date(NaN))` tests

* `isAfter`, `isBefore` and `isEqual` Invalid Date tests

* Interval functions Invalid Date tests

* `compareAsc` and `compareDesc` Invalid Date tests

* `closestTo`, `closestIndexTo`, `max` and `min` Invalid Date tests

* Test `toDate` if `options.additionalDigits` is not 0, 1, 2 or undefined

* Test `parse` if baseDate is `Invalid Date`

* `distanceInWords` and `distanceInWordsStrict` invalid values tests

* Invalid `options.weekStartsOn` tests

* Invalid value implementation fixes

* `parse` implementation fix

* `weekStartsOn` implementation fix

* Rename `partialMethod` to `roundingMethod`

* `distanceInWordsStrict` implementation fix

* Rebuild typings

* `toDate` implementation fix

* Add CHANGELOG.md entry

* Add missing tests

* Make interval functions throw RangeError

* Fix @throws docs messages

* Add `@throws {RangeError}` to all functions docs

* Add `@param [options.additionalDigits]` to all functions docs

* Add `options.additionalDigits` tests to all functions

* Complete corner cases changelog entry

* Add missing `$ExpectedMistake`
  • Loading branch information
leshakoss committed Apr 14, 2017
1 parent 246a23d commit 09e44d6
Show file tree
Hide file tree
Showing 671 changed files with 4,081 additions and 886 deletions.
43 changes: 40 additions & 3 deletions CHANGELOG.md
Expand Up @@ -187,8 +187,9 @@ This change log follows the format documented in [Keep a CHANGELOG].
```

Also these functions now accept an object with `start` and `end` properties
instead of two arguments as an interval. All these functions, as before,
throw an exception if the start of the interval is after its end.
instead of two arguments as an interval. All these functions
throw `RangeError` if the start of the interval is after its end
or if any date in interval is `Invalid Date`.

```javascript
// Before v2.0.0
Expand Down Expand Up @@ -311,7 +312,43 @@ This change log follows the format documented in [Keep a CHANGELOG].

We introduce this change to make *date-fns* consistent with ECMAScript behavior
that try to coerce arguments to the expected type
(which is also the case with other *date-fns* functions).
(which is also the case with other *date-fns* functions).

- **BREAKING**: `partialMethod` option in `distanceInWordsStrict` is renamed to `roundingMethod`.

```javascript
// Before v2.0.0
var options = {partialMethod: 'ceil'}
// v2.0.0 onward
var options = {roundingMethod: 'ceil'}

var result = distanceInWordsStrict(
new Date(1986, 3, 4, 10, 32, 0),
new Date(1986, 3, 4, 10, 33, 1),
options
)
```

- **BREAKING**: functions now throw `RangeError` if optional values passed to `options`
are not `undefined` or have expected values.
This change is introduced for consistency with ECMAScript standard library which does the same.
See [docs/Options.js](https://github.com/date-fns/date-fns/blob/master/docs/Options.js)

- **BREAKING**: all functions now handle arguments by following rules:

- as before, arguments expected to be `Date` are converted to `Date` using *date-fns'* `toDate` function;
- arguments expected to be numbers are converted to numbers using JavaScript's `Number` function;
- arguments expected to be strings arguments are converted to strings using JavaScript's `String` function.

If any of resulting arguments is invalid (i.e. `NaN` for numbers and `Invalid Date` for dates),
an invalid value will be returned:

- `false` for functions that return booleans (expect `isValid`);
- `Invalid Date` for functions that return dates;
- `NaN` for functions that return numbers;
- and `String('Invalid Date')` for functions that return strings.

See tests and PR [#460](https://github.com/date-fns/date-fns/pull/460) for exact behavior.

- Every function now has `options` as the last argument which is passed to all its dependencies
for consistency and future features.
Expand Down
16 changes: 13 additions & 3 deletions docs/Options.js
Expand Up @@ -6,9 +6,9 @@
* An object passed as the last optional argument to all functions.
*
* @typedef {Object} Options
* @property {Number} [weekStartsOn=0] - the index of the first day of the week (0 - Sunday).
* @property {0|1|2|3|4|5|6} [weekStartsOn=0] - the index of the first day of the week (0 - Sunday).
* Used by `differenceInCalendarWeeks`, `endOfWeek`, `isSameWeek`,
* `isThisWeek`, `lastDayOfWeek`, `setDay`, and `startOfWeek`
* `lastDayOfWeek`, `parse`, `setDay`, and `startOfWeek`
* @property {0|1|2} [additionalDigits=2] - the additional number of digits in the extended year format.
* Used by all functions that take String as Date-like argument.
* Internally, passed to `toDate` to specify which way to convert extended year formatted String to Date.
Expand All @@ -22,9 +22,19 @@
* If true, the result will indicate if the second date is earlier or later than the first
* @property {'s'|'m'|'h'|'d'|'M'|'Y'} [unit] - used by `distanceInWordsStrict`.
* If specified, will force a unit
* @property {'floor'|'ceil'|'round'} [partialMethod='floor'] - used by `distanceInWordsStrict`.
* @property {'floor'|'ceil'|'round'} [roundingMethod='floor'] - used by `distanceInWordsStrict`.
* Specifies, which way to round partial units
*
* @throws {RangeError} `options.additionalDigits` must be 0, 1 or 2.
* Thrown by **all** functions
* @throws {RangeError} `options.weekStartsOn` must be between 0 and 6.
* Thrown by `differenceInCalendarWeeks`, `endOfWeek`, `isSameWeek`,
* `lastDayOfWeek`, `parse`, `setDay`, and `startOfWeek`.
* @throws {RangeError} `options.roundingMethod` must be 'floor', 'ceil' or 'round'.
* Thrown by `distanceInWordsStrict`
* @throws {RangeError} `options.unit` must be 's', 'm', 'h', 'd', 'M' or 'Y'.
* Thrown by `distanceInWordsStrict`
*
* @example
* // For 15 December 12345 AD, represent the start of the week in Esperanto,
* // if the first day of the week is Monday:
Expand Down
2 changes: 2 additions & 0 deletions src/addDays/index.js
Expand Up @@ -11,7 +11,9 @@ import toDate from '../toDate/index.js'
* @param {Date|String|Number} date - the date to be changed
* @param {Number} amount - the amount of days to be added
* @param {Options} [options] - the object with options. See [Options]{@link docs/Options}
* @param {0|1|2} [options.additionalDigits=2] - passed to `toDate`. See [toDate]{@link docs/toDate}
* @returns {Date} the new date with the days added
* @throws {RangeError} `options.additionalDigits` must be 0, 1 or 2
*
* @example
* // Add 10 days to 1 September 2014:
Expand Down
4 changes: 2 additions & 2 deletions src/addDays/index.js.flow
Expand Up @@ -7,13 +7,13 @@ type Interval = {
}

type Options = {
weekStartsOn?: number,
weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6,
additionalDigits?: 0 | 1 | 2,
locale?: Locale,
includeSeconds?: boolean,
addSuffix?: boolean,
unit?: 's' | 'm' | 'h' | 'd' | 'M' | 'Y',
partialMethod?: 'floor' | 'ceil' | 'round'
roundingMethod?: 'floor' | 'ceil' | 'round'
}

type Locale = {
Expand Down
16 changes: 16 additions & 0 deletions src/addDays/test.js
Expand Up @@ -31,4 +31,20 @@ describe('addDays', function () {
addDays(date, 11)
assert.deepEqual(date, new Date(2014, 8 /* Sep */, 1))
})

it('returns `Invalid Date` if the given date is invalid', function () {
var result = addDays(new Date(NaN), 10)
assert(result instanceof Date && isNaN(result))
})

it('returns `Invalid Date` if the given amount is NaN', function () {
var result = addDays(new Date(2014, 8 /* Sep */, 1), NaN)
assert(result instanceof Date && isNaN(result))
})

it('throws `RangeError` if `options.additionalDigits` is not convertable to 0, 1, 2 or undefined', function () {
// $ExpectedMistake
var block = addDays.bind(null, new Date(2014, 8 /* Sep */, 1), 10, {additionalDigits: NaN})
assert.throws(block, RangeError)
})
})
2 changes: 2 additions & 0 deletions src/addHours/index.js
Expand Up @@ -13,7 +13,9 @@ var MILLISECONDS_IN_HOUR = 3600000
* @param {Date|String|Number} date - the date to be changed
* @param {Number} amount - the amount of hours to be added
* @param {Options} [options] - the object with options. See [Options]{@link docs/Options}
* @param {0|1|2} [options.additionalDigits=2] - passed to `toDate`. See [toDate]{@link docs/toDate}
* @returns {Date} the new date with the hours added
* @throws {RangeError} `options.additionalDigits` must be 0, 1 or 2
*
* @example
* // Add 2 hours to 10 July 2014 23:00:00:
Expand Down
4 changes: 2 additions & 2 deletions src/addHours/index.js.flow
Expand Up @@ -7,13 +7,13 @@ type Interval = {
}

type Options = {
weekStartsOn?: number,
weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6,
additionalDigits?: 0 | 1 | 2,
locale?: Locale,
includeSeconds?: boolean,
addSuffix?: boolean,
unit?: 's' | 'm' | 'h' | 'd' | 'M' | 'Y',
partialMethod?: 'floor' | 'ceil' | 'round'
roundingMethod?: 'floor' | 'ceil' | 'round'
}

type Locale = {
Expand Down
16 changes: 16 additions & 0 deletions src/addHours/test.js
Expand Up @@ -35,4 +35,20 @@ describe('addHours', function () {
addHours(date, 10)
assert.deepEqual(date, new Date(2014, 6 /* Jul */, 10, 23, 0))
})

it('returns `Invalid Date` if the given date is invalid', function () {
var result = addHours(new Date(NaN), 2)
assert(result instanceof Date && isNaN(result))
})

it('returns `Invalid Date` if the given amount is NaN', function () {
var result = addHours(new Date(2014, 6 /* Jul */, 10, 23, 0), NaN)
assert(result instanceof Date && isNaN(result))
})

it('throws `RangeError` if `options.additionalDigits` is not convertable to 0, 1, 2 or undefined', function () {
// $ExpectedMistake
var block = addHours.bind(null, new Date(2014, 6 /* Jul */, 10, 23, 0), 2, {additionalDigits: NaN})
assert.throws(block, RangeError)
})
})
2 changes: 2 additions & 0 deletions src/addISOYears/index.js
Expand Up @@ -14,7 +14,9 @@ import setISOYear from '../setISOYear/index.js'
* @param {Date|String|Number} date - the date to be changed
* @param {Number} amount - the amount of ISO week-numbering years to be added
* @param {Options} [options] - the object with options. See [Options]{@link docs/Options}
* @param {0|1|2} [options.additionalDigits=2] - passed to `toDate`. See [toDate]{@link docs/toDate}
* @returns {Date} the new date with the ISO week-numbering years added
* @throws {RangeError} `options.additionalDigits` must be 0, 1 or 2
*
* @example
* // Add 5 ISO week-numbering years to 2 July 2010:
Expand Down
4 changes: 2 additions & 2 deletions src/addISOYears/index.js.flow
Expand Up @@ -7,13 +7,13 @@ type Interval = {
}

type Options = {
weekStartsOn?: number,
weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6,
additionalDigits?: 0 | 1 | 2,
locale?: Locale,
includeSeconds?: boolean,
addSuffix?: boolean,
unit?: 's' | 'm' | 'h' | 'd' | 'M' | 'Y',
partialMethod?: 'floor' | 'ceil' | 'round'
roundingMethod?: 'floor' | 'ceil' | 'round'
}

type Locale = {
Expand Down
16 changes: 16 additions & 0 deletions src/addISOYears/test.js
Expand Up @@ -42,4 +42,20 @@ describe('addISOYears', function () {
var result = addISOYears(initialDate, 5)
assert.deepEqual(result, expectedResult)
})

it('returns `Invalid Date` if the given date is invalid', function () {
var result = addISOYears(new Date(NaN), 5)
assert(result instanceof Date && isNaN(result))
})

it('returns `Invalid Date` if the given amount is NaN', function () {
var result = addISOYears(new Date(2010, 6 /* Jul */, 2), NaN)
assert(result instanceof Date && isNaN(result))
})

it('throws `RangeError` if `options.additionalDigits` is not convertable to 0, 1, 2 or undefined', function () {
// $ExpectedMistake
var block = addISOYears.bind(null, new Date(2010, 6 /* Jul */, 2), 5, {additionalDigits: NaN})
assert.throws(block, RangeError)
})
})
2 changes: 2 additions & 0 deletions src/addMilliseconds/index.js
Expand Up @@ -11,7 +11,9 @@ import toDate from '../toDate/index.js'
* @param {Date|String|Number} date - the date to be changed
* @param {Number} amount - the amount of milliseconds to be added
* @param {Options} [options] - the object with options. See [Options]{@link docs/Options}
* @param {0|1|2} [options.additionalDigits=2] - passed to `toDate`. See [toDate]{@link docs/toDate}
* @returns {Date} the new date with the milliseconds added
* @throws {RangeError} `options.additionalDigits` must be 0, 1 or 2
*
* @example
* // Add 750 milliseconds to 10 July 2014 12:45:30.000:
Expand Down
4 changes: 2 additions & 2 deletions src/addMilliseconds/index.js.flow
Expand Up @@ -7,13 +7,13 @@ type Interval = {
}

type Options = {
weekStartsOn?: number,
weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6,
additionalDigits?: 0 | 1 | 2,
locale?: Locale,
includeSeconds?: boolean,
addSuffix?: boolean,
unit?: 's' | 'm' | 'h' | 'd' | 'M' | 'Y',
partialMethod?: 'floor' | 'ceil' | 'round'
roundingMethod?: 'floor' | 'ceil' | 'round'
}

type Locale = {
Expand Down
16 changes: 16 additions & 0 deletions src/addMilliseconds/test.js
Expand Up @@ -35,4 +35,20 @@ describe('addMilliseconds', function () {
addMilliseconds(date, 250)
assert.deepEqual(date, new Date(2014, 6 /* Jul */, 10, 12, 45, 30, 0))
})

it('returns `Invalid Date` if the given date is invalid', function () {
var result = addMilliseconds(new Date(NaN), 750)
assert(result instanceof Date && isNaN(result))
})

it('returns `Invalid Date` if the given amount is NaN', function () {
var result = addMilliseconds(new Date(2014, 6 /* Jul */, 10, 12, 45, 30, 0), NaN)
assert(result instanceof Date && isNaN(result))
})

it('throws `RangeError` if `options.additionalDigits` is not convertable to 0, 1, 2 or undefined', function () {
// $ExpectedMistake
var block = addMilliseconds.bind(null, new Date(2014, 6 /* Jul */, 10, 12, 45, 30, 0), 750, {additionalDigits: NaN})
assert.throws(block, RangeError)
})
})
2 changes: 2 additions & 0 deletions src/addMinutes/index.js
Expand Up @@ -13,7 +13,9 @@ var MILLISECONDS_IN_MINUTE = 60000
* @param {Date|String|Number} date - the date to be changed
* @param {Number} amount - the amount of minutes to be added
* @param {Options} [options] - the object with options. See [Options]{@link docs/Options}
* @param {0|1|2} [options.additionalDigits=2] - passed to `toDate`. See [toDate]{@link docs/toDate}
* @returns {Date} the new date with the minutes added
* @throws {RangeError} `options.additionalDigits` must be 0, 1 or 2
*
* @example
* // Add 30 minutes to 10 July 2014 12:00:00:
Expand Down
4 changes: 2 additions & 2 deletions src/addMinutes/index.js.flow
Expand Up @@ -7,13 +7,13 @@ type Interval = {
}

type Options = {
weekStartsOn?: number,
weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6,
additionalDigits?: 0 | 1 | 2,
locale?: Locale,
includeSeconds?: boolean,
addSuffix?: boolean,
unit?: 's' | 'm' | 'h' | 'd' | 'M' | 'Y',
partialMethod?: 'floor' | 'ceil' | 'round'
roundingMethod?: 'floor' | 'ceil' | 'round'
}

type Locale = {
Expand Down
16 changes: 16 additions & 0 deletions src/addMinutes/test.js
Expand Up @@ -35,4 +35,20 @@ describe('addMinutes', function () {
addMinutes(date, 25)
assert.deepEqual(date, new Date(2014, 6 /* Jul */, 10, 12, 0))
})

it('returns `Invalid Date` if the given date is invalid', function () {
var result = addMinutes(new Date(NaN), 30)
assert(result instanceof Date && isNaN(result))
})

it('returns `Invalid Date` if the given amount is NaN', function () {
var result = addMinutes(new Date(2014, 6 /* Jul */, 10, 12, 0), NaN)
assert(result instanceof Date && isNaN(result))
})

it('throws `RangeError` if `options.additionalDigits` is not convertable to 0, 1, 2 or undefined', function () {
// $ExpectedMistake
var block = addMinutes.bind(null, new Date(2014, 6 /* Jul */, 10, 12, 0), 30, {additionalDigits: NaN})
assert.throws(block, RangeError)
})
})
2 changes: 2 additions & 0 deletions src/addMonths/index.js
Expand Up @@ -12,7 +12,9 @@ import getDaysInMonth from '../getDaysInMonth/index.js'
* @param {Date|String|Number} date - the date to be changed
* @param {Number} amount - the amount of months to be added
* @param {Options} [options] - the object with options. See [Options]{@link docs/Options}
* @param {0|1|2} [options.additionalDigits=2] - passed to `toDate`. See [toDate]{@link docs/toDate}
* @returns {Date} the new date with the months added
* @throws {RangeError} `options.additionalDigits` must be 0, 1 or 2
*
* @example
* // Add 5 months to 1 September 2014:
Expand Down
4 changes: 2 additions & 2 deletions src/addMonths/index.js.flow
Expand Up @@ -7,13 +7,13 @@ type Interval = {
}

type Options = {
weekStartsOn?: number,
weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6,
additionalDigits?: 0 | 1 | 2,
locale?: Locale,
includeSeconds?: boolean,
addSuffix?: boolean,
unit?: 's' | 'm' | 'h' | 'd' | 'M' | 'Y',
partialMethod?: 'floor' | 'ceil' | 'round'
roundingMethod?: 'floor' | 'ceil' | 'round'
}

type Locale = {
Expand Down
16 changes: 16 additions & 0 deletions src/addMonths/test.js
Expand Up @@ -48,4 +48,20 @@ describe('addMonths', function () {
var result = addMonths(initialDate, 1)
assert.deepEqual(result, expectedResult)
})

it('returns `Invalid Date` if the given date is invalid', function () {
var result = addMonths(new Date(NaN), 5)
assert(result instanceof Date && isNaN(result))
})

it('returns `Invalid Date` if the given amount is NaN', function () {
var result = addMonths(new Date(2014, 8 /* Sep */, 1), NaN)
assert(result instanceof Date && isNaN(result))
})

it('throws `RangeError` if `options.additionalDigits` is not convertable to 0, 1, 2 or undefined', function () {
// $ExpectedMistake
var block = addMonths.bind(null, new Date(2014, 8 /* Sep */, 1), 5, {additionalDigits: NaN})
assert.throws(block, RangeError)
})
})
2 changes: 2 additions & 0 deletions src/addQuarters/index.js
Expand Up @@ -11,7 +11,9 @@ import addMonths from '../addMonths/index.js'
* @param {Date|String|Number} date - the date to be changed
* @param {Number} amount - the amount of quarters to be added
* @param {Options} [options] - the object with options. See [Options]{@link docs/Options}
* @param {0|1|2} [options.additionalDigits=2] - passed to `toDate`. See [toDate]{@link docs/toDate}
* @returns {Date} the new date with the quarters added
* @throws {RangeError} `options.additionalDigits` must be 0, 1 or 2
*
* @example
* // Add 1 quarter to 1 September 2014:
Expand Down
4 changes: 2 additions & 2 deletions src/addQuarters/index.js.flow
Expand Up @@ -7,13 +7,13 @@ type Interval = {
}

type Options = {
weekStartsOn?: number,
weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6,
additionalDigits?: 0 | 1 | 2,
locale?: Locale,
includeSeconds?: boolean,
addSuffix?: boolean,
unit?: 's' | 'm' | 'h' | 'd' | 'M' | 'Y',
partialMethod?: 'floor' | 'ceil' | 'round'
roundingMethod?: 'floor' | 'ceil' | 'round'
}

type Locale = {
Expand Down

0 comments on commit 09e44d6

Please sign in to comment.