]> git.ipfire.org Git - thirdparty/moment.git/commitdiff
Ensure special handling of invalid moments in all moment methods
authorIskren Chernev <iskren.chernev@gmail.com>
Mon, 21 Sep 2015 02:58:36 +0000 (19:58 -0700)
committerIskren Chernev <iskren.chernev@gmail.com>
Mon, 9 Nov 2015 02:20:52 +0000 (18:20 -0800)
12 files changed:
src/lib/create/from-anything.js
src/lib/duration/create.js
src/lib/moment/compare.js
src/lib/moment/diff.js
src/lib/moment/from.js
src/lib/moment/min-max.js
src/lib/moment/prototype.js
src/lib/moment/to-type.js
src/lib/moment/to.js
src/lib/units/day-of-week.js
src/lib/units/offset.js
src/test/moment/invalid.js

index 5a69dc55b9f0da62062e02a2864422ce05a3a87f..b3897027fa6074beaaf989428867aa1c3bed0931 100644 (file)
@@ -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;
 }
 
index 15f05deadcd361983bf23c08a2f70d332ebd8d7b..9d1ea4eb4043b3fd06fd080905a681a70cb3680b 100644 (file)
@@ -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);
index b6634737a1b880661f5f0b9f3ba2dd501596a2a9..88a7932d99498653c8f27b09c745d7e15b5f2f81 100644 (file)
@@ -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));
     }
 }
index 2c16d1dc75a6bbe9ac19af8a98a0e84ed5b501e2..695fe20124c1c41097754296b2121ce095bc68c2 100644 (file)
@@ -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') {
index c6a5449ffac6670f563b72a7a319bd8b3bb68c37..4fbd03e438b7e2083379085d5ff25efcc8d41db2 100644 (file)
@@ -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) {
index 912cbfe71902c4dc3e7751a9334f5e4d2167a4d3..e355883c8aca5de7b9dad6a7c348889ffad0c030 100644 (file)
@@ -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();
+        }
     }
 );
 
index d17f743addc4bfb7dbfdd8832277f8bd7607be58..85c7e82177d66157a0c42560bba884512b290afc 100644 (file)
@@ -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;
index 3008d95e3499e57f2d7851028adad250fcbc2362..b6787afe6c41c81984bed4193954425745fdc4cb 100644 (file)
@@ -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';
+}
index d9eccfde71b24b64f509efab6b58e9ceb1b6b28e..df29f9157e8482a223368c47db34700a4716f69f 100644 (file)
@@ -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) {
index a82f126309d2fd750455eabad14cafd1a114cca9..5f5b74babe721c7c36de84d55b9db1ee311411a5 100644 (file)
@@ -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.
index 33288c0bf4c89ac25051204558341801351b80b1..5d0bf520632257b84cb5406766ebb5221d0d48fd 100644 (file)
@@ -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;
 }
index 599a6ddc922fec511dcce4e7dd7b5f715ac0a6c1..7798354130064a3b77e925c2b50e1245ba2b23b4 100644 (file)
@@ -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);
+    }
+});