From: Iskren Chernev Date: Mon, 21 Sep 2015 02:58:36 +0000 (-0700) Subject: Ensure special handling of invalid moments in all moment methods X-Git-Tag: 2.11.0~72^2~1 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=d4a1694777aa37fd2a61550b7f205711857c7841;p=thirdparty%2Fmoment.git Ensure special handling of invalid moments in all moment methods --- diff --git a/src/lib/create/from-anything.js b/src/lib/create/from-anything.js index 5a69dc55b..b3897027f 100644 --- a/src/lib/create/from-anything.js +++ b/src/lib/create/from-anything.js @@ -6,6 +6,7 @@ import { Moment, isMoment } from '../moment/constructor'; import { getLocale } from '../locale/locales'; import { hooks } from '../utils/hooks'; import checkOverflow from './check-overflow'; +import { isValid } from './valid'; import { configFromStringAndArray } from './from-string-and-array'; import { configFromStringAndFormat } from './from-string-and-format'; @@ -50,6 +51,10 @@ export function prepareConfig (config) { configFromInput(config); } + if (!isValid(config)) { + config._d = null; + } + return config; } diff --git a/src/lib/duration/create.js b/src/lib/duration/create.js index 15f05dead..9d1ea4eb4 100644 --- a/src/lib/duration/create.js +++ b/src/lib/duration/create.js @@ -100,6 +100,10 @@ function positiveMomentsDifference(base, other) { function momentsDifference(base, other) { var res; + if (!(base.isValid() && other.isValid())) { + return {milliseconds: 0, months: 0}; + } + other = cloneWithOffset(other, base); if (base.isBefore(other)) { res = positiveMomentsDifference(base, other); diff --git a/src/lib/moment/compare.js b/src/lib/moment/compare.js index b6634737a..88a7932d9 100644 --- a/src/lib/moment/compare.js +++ b/src/lib/moment/compare.js @@ -3,26 +3,28 @@ import { normalizeUnits } from '../units/aliases'; import { createLocal } from '../create/local'; export function isAfter (input, units) { - var inputMs; + var localInput = isMoment(input) ? input : createLocal(input); + if (!(this.isValid() && localInput.isValid())) { + return false; + } units = normalizeUnits(typeof units !== 'undefined' ? units : 'millisecond'); if (units === 'millisecond') { - input = isMoment(input) ? input : createLocal(input); - return +this > +input; + return +this > +localInput; } else { - inputMs = isMoment(input) ? +input : +createLocal(input); - return inputMs < +this.clone().startOf(units); + return +localInput < +this.clone().startOf(units); } } export function isBefore (input, units) { - var inputMs; + var localInput = isMoment(input) ? input : createLocal(input); + if (!(this.isValid() && localInput.isValid())) { + return false; + } units = normalizeUnits(typeof units !== 'undefined' ? units : 'millisecond'); if (units === 'millisecond') { - input = isMoment(input) ? input : createLocal(input); - return +this < +input; + return +this < +localInput; } else { - inputMs = isMoment(input) ? +input : +createLocal(input); - return +this.clone().endOf(units) < inputMs; + return +this.clone().endOf(units) < +localInput; } } @@ -31,13 +33,16 @@ export function isBetween (from, to, units) { } export function isSame (input, units) { - var inputMs; + var localInput = isMoment(input) ? input : createLocal(input), + inputMs; + if (!(this.isValid() && localInput.isValid())) { + return false; + } units = normalizeUnits(units || 'millisecond'); if (units === 'millisecond') { - input = isMoment(input) ? input : createLocal(input); - return +this === +input; + return +this === +localInput; } else { - inputMs = +createLocal(input); + inputMs = +localInput; return +(this.clone().startOf(units)) <= inputMs && inputMs <= +(this.clone().endOf(units)); } } diff --git a/src/lib/moment/diff.js b/src/lib/moment/diff.js index 2c16d1dc7..695fe2012 100644 --- a/src/lib/moment/diff.js +++ b/src/lib/moment/diff.js @@ -3,10 +3,22 @@ import { cloneWithOffset } from '../units/offset'; import { normalizeUnits } from '../units/aliases'; export function diff (input, units, asFloat) { - var that = cloneWithOffset(input, this), - zoneDelta = (that.utcOffset() - this.utcOffset()) * 6e4, + var that, + zoneDelta, delta, output; + if (!this.isValid()) { + return NaN; + } + + that = cloneWithOffset(input, this); + + if (!that.isValid()) { + return NaN; + } + + zoneDelta = (that.utcOffset() - this.utcOffset()) * 6e4; + units = normalizeUnits(units); if (units === 'year' || units === 'month' || units === 'quarter') { diff --git a/src/lib/moment/from.js b/src/lib/moment/from.js index c6a5449ff..4fbd03e43 100644 --- a/src/lib/moment/from.js +++ b/src/lib/moment/from.js @@ -1,11 +1,15 @@ import { createDuration } from '../duration/create'; import { createLocal } from '../create/local'; +import { isMoment } from '../moment/constructor'; export function from (time, withoutSuffix) { - if (!this.isValid()) { + if (this.isValid() && + ((isMoment(time) && time.isValid()) || + createLocal(time).isValid())) { + return createDuration({to: this, from: time}).locale(this.locale()).humanize(!withoutSuffix); + } else { return this.localeData().invalidDate(); } - return createDuration({to: this, from: time}).locale(this.locale()).humanize(!withoutSuffix); } export function fromNow (withoutSuffix) { diff --git a/src/lib/moment/min-max.js b/src/lib/moment/min-max.js index 912cbfe71..e355883c8 100644 --- a/src/lib/moment/min-max.js +++ b/src/lib/moment/min-max.js @@ -1,12 +1,17 @@ import { deprecate } from '../utils/deprecate'; import isArray from '../utils/is-array'; import { createLocal } from '../create/local'; +import { createInvalid } from '../create/valid'; export var prototypeMin = deprecate( 'moment().min is deprecated, use moment.min instead. https://github.com/moment/moment/issues/1548', function () { var other = createLocal.apply(null, arguments); - return other < this ? this : other; + if (this.isValid() && other.isValid()) { + return other < this ? this : other; + } else { + return createInvalid(); + } } ); @@ -14,7 +19,11 @@ export var prototypeMax = deprecate( 'moment().max is deprecated, use moment.max instead. https://github.com/moment/moment/issues/1548', function () { var other = createLocal.apply(null, arguments); - return other > this ? this : other; + if (this.isValid() && other.isValid()) { + return other > this ? this : other; + } else { + return createInvalid(); + } } ); diff --git a/src/lib/moment/prototype.js b/src/lib/moment/prototype.js index d17f743ad..85c7e8217 100644 --- a/src/lib/moment/prototype.js +++ b/src/lib/moment/prototype.js @@ -14,7 +14,7 @@ import { getSet } from './get-set'; import { locale, localeData, lang } from './locale'; import { prototypeMin, prototypeMax } from './min-max'; import { startOf, endOf } from './start-end-of'; -import { valueOf, toDate, toArray, toObject, unix } from './to-type'; +import { valueOf, toDate, toArray, toObject, toJSON, unix } from './to-type'; import { isValid, parsingFlags, invalidAt } from './valid'; proto.add = add; @@ -47,7 +47,7 @@ proto.toArray = toArray; proto.toObject = toObject; proto.toDate = toDate; proto.toISOString = toISOString; -proto.toJSON = toISOString; +proto.toJSON = toJSON; proto.toString = toString; proto.unix = unix; proto.valueOf = valueOf; diff --git a/src/lib/moment/to-type.js b/src/lib/moment/to-type.js index 3008d95e3..b6787afe6 100644 --- a/src/lib/moment/to-type.js +++ b/src/lib/moment/to-type.js @@ -27,3 +27,8 @@ export function toObject () { milliseconds: m.milliseconds() }; } + +export function toJSON () { + // JSON.stringify(new Date(NaN)) === 'null' + return this.isValid() ? this.toISOString() : 'null'; +} diff --git a/src/lib/moment/to.js b/src/lib/moment/to.js index d9eccfde7..df29f9157 100644 --- a/src/lib/moment/to.js +++ b/src/lib/moment/to.js @@ -2,10 +2,13 @@ import { createDuration } from '../duration/create'; import { createLocal } from '../create/local'; export function to (time, withoutSuffix) { - if (!this.isValid()) { + if (this.isValid() && + ((isMoment(time) && time.isValid()) || + createLocal(time).isValid())) { + return createDuration({from: this, to: time}).locale(this.locale()).humanize(!withoutSuffix); + } else { return this.localeData().invalidDate(); } - return createDuration({from: this, to: time}).locale(this.locale()).humanize(!withoutSuffix); } export function toNow (withoutSuffix) { diff --git a/src/lib/units/day-of-week.js b/src/lib/units/day-of-week.js index a82f12630..5f5b74bab 100644 --- a/src/lib/units/day-of-week.js +++ b/src/lib/units/day-of-week.js @@ -112,6 +112,9 @@ export function localeWeekdaysParse (weekdayName) { // MOMENTS export function getSetDayOfWeek (input) { + if (!this.isValid()) { + return input != null ? this : NaN; + } var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay(); if (input != null) { input = parseWeekday(input, this.localeData()); @@ -122,11 +125,17 @@ export function getSetDayOfWeek (input) { } export function getSetLocaleDayOfWeek (input) { + if (!this.isValid()) { + return input != null ? this : NaN; + } var weekday = (this.day() + 7 - this.localeData()._week.dow) % 7; return input == null ? weekday : this.add(input - weekday, 'd'); } export function getSetISODayOfWeek (input) { + if (!this.isValid()) { + return input != null ? this : NaN; + } // behaves the same as moment#day except // as a getter, returns 7 instead of 0 (1-7 range instead of 0-6) // as a setter, sunday should belong to the previous week. diff --git a/src/lib/units/offset.js b/src/lib/units/offset.js index 33288c0bf..5d0bf5206 100644 --- a/src/lib/units/offset.js +++ b/src/lib/units/offset.js @@ -97,6 +97,9 @@ hooks.updateOffset = function () {}; export function getSetOffset (input, keepLocalTime) { var offset = this._offset || 0, localAdjust; + if (!this.isValid()) { + return input != null ? this : NaN; + } if (input != null) { if (typeof input === 'string') { input = offsetFromString(input); @@ -167,6 +170,9 @@ export function setOffsetToParsedOffset () { } export function hasAlignedHourOffset (input) { + if (!this.isValid()) { + return false; + } input = input ? createLocal(input).utcOffset() : 0; return (this.utcOffset() - input) % 60 === 0; @@ -201,13 +207,13 @@ export function isDaylightSavingTimeShifted () { } export function isLocal () { - return !this._isUTC; + return this.isValid() ? !this._isUTC : false; } export function isUtcOffset () { - return this._isUTC; + return this.isValid() ? this._isUTC : false; } export function isUtc () { - return this._isUTC && this._offset === 0; + return this.isValid() ? this._isUTC && this._offset === 0 : false; } diff --git a/src/test/moment/invalid.js b/src/test/moment/invalid.js index 599a6ddc9..779835413 100644 --- a/src/test/moment/invalid.js +++ b/src/test/moment/invalid.js @@ -25,3 +25,155 @@ test('invalid with custom flag', function (assert) { assert.equal(m.parsingFlags().tooBusyWith, 'reiculating splines'); assert.ok(isNaN(m.valueOf())); }); + +test('invalid operations', function (assert) { + var invalids = [ + moment.invalid(), + moment('xyz', 'l'), + moment('2015-01-35', 'YYYY-MM-DD'), + moment('2015-01-25 a', 'YYYY-MM-DD', true), + ], + i, + invalid, + valid = moment(); + + for (i = 0; i < invalids.length; ++i) { + invalid = invalids[i]; + + assert.ok(!invalid.clone().add(5, 'hours').isValid(), 'invalid.add is invalid'); + assert.equal(invalid.calendar(), 'Invalid date', 'invalid.calendar is \'Invalid date\''); + assert.ok(!invalid.clone().isValid(), 'invalid.clone is invalid'); + assert.ok(isNaN(invalid.diff(valid)), 'invalid.diff(valid) is NaN'); + assert.ok(isNaN(valid.diff(invalid)), 'valid.diff(invalid) is NaN'); + assert.ok(isNaN(invalid.diff(invalid)), 'invalid.diff(invalid) is NaN'); + assert.ok(!invalid.clone().endOf('month').isValid(), 'invalid.endOf is invalid'); + assert.equal(invalid.format(), 'Invalid date', 'invalid.format is \'Invalid date\''); + assert.equal(invalid.from(), 'Invalid date'); + assert.equal(invalid.from(valid), 'Invalid date'); + assert.equal(valid.from(invalid), 'Invalid date'); + assert.equal(invalid.fromNow(), 'Invalid date'); + assert.equal(invalid.to(), 'Invalid date'); + assert.equal(invalid.to(valid), 'Invalid date'); + assert.equal(valid.to(invalid), 'Invalid date'); + assert.equal(invalid.toNow(), 'Invalid date'); + assert.ok(isNaN(invalid.get('year')), 'invalid.get is NaN'); + // TODO invalidAt + assert.ok(!invalid.isAfter(valid)); + assert.ok(!valid.isAfter(invalid)); + assert.ok(!invalid.isAfter(invalid)); + assert.ok(!invalid.isBefore(valid)); + assert.ok(!valid.isBefore(invalid)); + assert.ok(!invalid.isBefore(invalid)); + assert.ok(!invalid.isBetween(valid, valid)); + assert.ok(!valid.isBetween(invalid, valid)); + assert.ok(!valid.isBetween(valid, invalid)); + assert.ok(!invalid.isSame(invalid)); + assert.ok(!invalid.isSame(valid)); + assert.ok(!valid.isSame(invalid)); + assert.ok(!invalid.isValid()); + assert.equal(invalid.locale(), 'en'); + assert.equal(invalid.localeData()._abbr, 'en'); + assert.ok(!invalid.clone().max(valid).isValid()); + assert.ok(!valid.clone().max(invalid).isValid()); + assert.ok(!invalid.clone().max(invalid).isValid()); + assert.ok(!invalid.clone().min(valid).isValid()); + assert.ok(!valid.clone().min(invalid).isValid()); + assert.ok(!invalid.clone().min(invalid).isValid()); + assert.ok(!moment.min(invalid, valid).isValid()); + assert.ok(!moment.min(valid, invalid).isValid()); + assert.ok(!moment.max(invalid, valid).isValid()); + assert.ok(!moment.max(valid, invalid).isValid()); + assert.ok(!invalid.clone().set('year', 2005).isValid()); + assert.ok(!invalid.clone().startOf('month').isValid()); + + assert.ok(!invalid.clone().subtract(5, 'days').isValid()); + assert.deepEqual(invalid.toArray(), [NaN, NaN, NaN, NaN, NaN, NaN, NaN]); + assert.deepEqual(invalid.toObject(), { + years: NaN, + months: NaN, + date: NaN, + hours: NaN, + minutes: NaN, + seconds: NaN, + milliseconds: NaN, + }); + assert.ok(moment.isDate(invalid.toDate())); + assert.ok(isNaN(invalid.toDate().valueOf())); + assert.equal(invalid.toJSON(), 'null'); + assert.equal(invalid.toString(), 'Invalid date'); + assert.ok(isNaN(invalid.unix())); + assert.ok(isNaN(invalid.valueOf())); + + assert.ok(isNaN(invalid.year())); + assert.ok(isNaN(invalid.weekYear())); + assert.ok(isNaN(invalid.isoWeekYear())); + assert.ok(isNaN(invalid.quarter())); + assert.ok(isNaN(invalid.quarters())); + assert.ok(isNaN(invalid.month())); + assert.ok(isNaN(invalid.daysInMonth())); + assert.ok(isNaN(invalid.week())); + assert.ok(isNaN(invalid.weeks())); + assert.ok(isNaN(invalid.isoWeek())); + assert.ok(isNaN(invalid.isoWeeks())); + assert.ok(isNaN(invalid.weeksInYear())); + assert.ok(isNaN(invalid.isoWeeksInYear())); + assert.ok(isNaN(invalid.date())); + assert.ok(isNaN(invalid.day())); + assert.ok(isNaN(invalid.days())); + assert.ok(isNaN(invalid.weekday())); + assert.ok(isNaN(invalid.isoWeekday())); + assert.ok(isNaN(invalid.dayOfYear())); + assert.ok(isNaN(invalid.hour())); + assert.ok(isNaN(invalid.hours())); + assert.ok(isNaN(invalid.minute())); + assert.ok(isNaN(invalid.minutes())); + assert.ok(isNaN(invalid.second())); + assert.ok(isNaN(invalid.seconds())); + assert.ok(isNaN(invalid.millisecond())); + assert.ok(isNaN(invalid.milliseconds())); + assert.ok(isNaN(invalid.utcOffset())); + + assert.ok(!invalid.clone().year(2001).isValid()); + assert.ok(!invalid.clone().weekYear(2001).isValid()); + assert.ok(!invalid.clone().isoWeekYear(2001).isValid()); + assert.ok(!invalid.clone().quarter(1).isValid()); + assert.ok(!invalid.clone().quarters(1).isValid()); + assert.ok(!invalid.clone().month(1).isValid()); + assert.ok(!invalid.clone().week(1).isValid()); + assert.ok(!invalid.clone().weeks(1).isValid()); + assert.ok(!invalid.clone().isoWeek(1).isValid()); + assert.ok(!invalid.clone().isoWeeks(1).isValid()); + assert.ok(!invalid.clone().date(1).isValid()); + assert.ok(!invalid.clone().day(1).isValid()); + assert.ok(!invalid.clone().days(1).isValid()); + assert.ok(!invalid.clone().weekday(1).isValid()); + assert.ok(!invalid.clone().isoWeekday(1).isValid()); + assert.ok(!invalid.clone().dayOfYear(1).isValid()); + assert.ok(!invalid.clone().hour(1).isValid()); + assert.ok(!invalid.clone().hours(1).isValid()); + assert.ok(!invalid.clone().minute(1).isValid()); + assert.ok(!invalid.clone().minutes(1).isValid()); + assert.ok(!invalid.clone().second(1).isValid()); + assert.ok(!invalid.clone().seconds(1).isValid()); + assert.ok(!invalid.clone().millisecond(1).isValid()); + assert.ok(!invalid.clone().milliseconds(1).isValid()); + assert.ok(!invalid.clone().utcOffset(1).isValid()); + + assert.ok(!invalid.clone().utc().isValid()); + assert.ok(!invalid.clone().local().isValid()); + assert.ok(!invalid.clone().parseZone('05:30').isValid()); + assert.ok(!invalid.hasAlignedHourOffset()); + assert.ok(!invalid.isDST()); + assert.ok(!invalid.isDSTShifted()); + assert.ok(!invalid.isLocal()); + assert.ok(!invalid.isUtcOffset()); + assert.ok(!invalid.isUtc()); + assert.ok(!invalid.isUTC()); + + assert.ok(!invalid.isLeapYear()); + + assert.equal(moment.duration({from: invalid, to: valid}).asMilliseconds(), 0); + assert.equal(moment.duration({from: valid, to: invalid}).asMilliseconds(), 0); + assert.equal(moment.duration({from: invalid, to: invalid}).asMilliseconds(), 0); + } +});