Răsfoiți Sursa

improvements and bugfixes to FullCalendar's MomentJS extensions

notably we have no side effects on the Moment prototype anymore. we subclass
Adam Shaw 12 ani în urmă
părinte
comite
edcd275c91
3 a modificat fișierele cu 106 adăugiri și 67 ștergeri
  1. 98 66
      src/moment-ext.js
  2. 4 1
      src/util.js
  3. 4 0
      tests/automated/moment-ambig.js

+ 98 - 66
src/moment-ext.js

@@ -3,7 +3,7 @@ var ambigTimeRegex = /^\s*\d{4}-(?:(\d\d-\d\d)|(W\d\d$)|(W\d\d-\d)|(\d\d\d))$/;
 var ambigZoneRegex = /^\s*\d{4}-(?:(\d\d-\d\d)|(W\d\d$)|(W\d\d-\d)|(\d\d\d))((T| )(\d\d(:\d\d(:\d\d(\.\d+)?)?)?)?)?$/;
 
 
-// MOMENT: creating
+// Creating
 // -------------------------------------------------------------------------------------------------
 
 // Creates a moment in the local timezone, similar to the vanilla moment(...) constructor,
@@ -26,25 +26,13 @@ fc.moment.parseZone = function() {
 	return makeMoment(arguments, true, true);
 };
 
-function FCMoment(config) {
-	extend(this, config);
-}
-
-FCMoment.prototype = createObject(moment.fn);
-
-FCMoment.prototype.clone = function() {
-	return makeMoment([ this ]);
-};
-
 // when parseZone==true, if can't figure it out, fall back to parseUTC
 function makeMoment(args, parseUTC, parseZone) {
-	var isSingleArg = args.length == 1;
-	var isSingleMoment = isSingleArg && moment.isMoment(args[0]);
-	var isSingleString = isSingleArg && typeof args[0] === 'string';
-	var isSingleArray = isSingleArg && $.isArray(args[0]);
-	var isSingleNativeDate = isSingleArg && isNativeDate(args[0]);
-	var isAmbigTime = isSingleString && ambigTimeRegex.test(args[0]);
-	var isAmbigZone = isAmbigTime || isSingleArray || isSingleString && ambigZoneRegex.test(args[0]);
+	var isSingleString = args.length == 1 && typeof args[0] === 'string';
+	var isAmbigTime = isSingleString && ambigTimeRegex.test(args[0]); // an ISO8601 string without a time part
+	var isAmbigZone = isAmbigTime || // ambiguously-zoned moments are always ambiguously-timed too
+		$.isArray(args[0]) || // we consider y/m/d/h/m/s arrays to be lacking a zone
+		isSingleString && ambigZoneRegex.test(args[0]); // an ISO8601 string without a zone part
 	var mom;
 
 	if (parseUTC || parseZone || isAmbigTime) {
@@ -54,7 +42,7 @@ function makeMoment(args, parseUTC, parseZone) {
 		mom = moment.apply(null, args);
 	}
 
-	if (isSingleMoment) {
+	if (moment.isMoment(args[0])) {
 		transferAmbigs(args[0], mom);
 	}
 
@@ -70,7 +58,7 @@ function makeMoment(args, parseUTC, parseZone) {
 		else if (isSingleString) {
 			mom.zone(args[0]); // if fails, will set it to 0, which it already was
 		}
-		else if (isSingleNativeDate || args[0] === undefined) {
+		else if (isNativeDate(args[0]) || args[0] === undefined) {
 			// native Date object?
 			// specified with no arguments?
 			// then consider the moment to be local
@@ -81,40 +69,24 @@ function makeMoment(args, parseUTC, parseZone) {
 	return new FCMoment(mom);
 }
 
-// transfers our internal _ambig properties from one moment to another
-function transferAmbigs(src, dest) {
-	if (src._ambigTime) {
-		dest._ambigTime = true;
-	}
-	else if (dest._ambigTime) {
-		delete dest._ambigTime;
-	}
-
-	if (src._ambigZone) {
-		dest._ambigZone = true;
-	}
-	else if (dest._ambigZone) {
-		delete dest._ambigZone;
-	}
+// our subclass of Moment.
+// accepts an object with the internal Moment properties that should be copied over to
+// this object (most likely another Moment object).
+function FCMoment(config) {
+	extend(this, config);
 }
 
-function makeMomentAs(input, model) {
-	var output = fc.moment(input);
-	if (model._ambigTime) {
-		output.stripTime();
-	}
-	else if (model._ambigZone) {
-		output.stripZone();
-	}
-	return output;
-}
+// chain the prototype to Moment's
+FCMoment.prototype = createObject(moment.fn);
 
+FCMoment.prototype.clone = function() {
+	return makeMoment([ this ]);
+};
 
 
-// MOMENT: time-of-day
+// Time-of-day
 // -------------------------------------------------------------------------------------------------
 
-
 // GETTER
 // Returns a Duration with the hours/minutes/seconds/ms values of the moment.
 // If the moment has an ambiguous time, a duration of 00:00 will be returned.
@@ -123,7 +95,7 @@ function makeMomentAs(input, model) {
 // You can supply a Duration, a Moment, or a Duration-like argument.
 // When setting the time, and the moment has an ambiguous time, it then becomes unambiguous.
 FCMoment.prototype.time = function(time) {
-	if (time === undefined) { // getter
+	if (time == null) { // getter
 		return moment.duration({
 			hours: this.hours(),
 			minutes: this.minutes(),
@@ -152,17 +124,21 @@ FCMoment.prototype.time = function(time) {
 FCMoment.prototype.stripTime = function() {
 	var a = this.toArray(); // year,month,date,hours,minutes,seconds as an array
 
+	// set the internal UTC flag
+	moment.fn.utc.call(this); // call the original method, because we don't want to affect _ambigZone
+
 	this._ambigTime = true;
 	this._ambigZone = true; // if ambiguous time, also ambiguous timezone offset
 
-	return this.utc()
-		.year(a[0])
+	this.year(a[0])
 		.month(a[1])
 		.date(a[2])
 		.hours(0)
 		.minutes(0)
 		.seconds(0)
 		.milliseconds(0);
+
+	return this; // for chaining
 };
 
 // Returns if the moment has a non-ambiguous time (boolean)
@@ -171,7 +147,7 @@ FCMoment.prototype.hasTime = function() {
 };
 
 
-// MOMENT: timezone offset
+// Timezone
 // -------------------------------------------------------------------------------------------------
 
 // Converts the moment to UTC, stripping out its timezone offset, but preserving its
@@ -180,16 +156,20 @@ FCMoment.prototype.hasTime = function() {
 FCMoment.prototype.stripZone = function() {
 	var a = this.toArray(); // year,month,date,hours,minutes,seconds as an array
 
+	// set the internal UTC flag
+	moment.fn.utc.call(this); // call the original method, because we don't want to affect _ambigZone
+
 	this._ambigZone = true;
 
-	return this.utc()
-		.year(a[0])
+	this.year(a[0])
 		.month(a[1])
 		.date(a[2])
 		.hours(a[3])
 		.minutes(a[4])
 		.seconds(a[5])
 		.milliseconds(a[6]);
+
+	return this; // for chaining
 };
 
 // Returns of the moment has a non-ambiguous timezone offset (boolean)
@@ -197,25 +177,30 @@ FCMoment.prototype.hasZone = function() {
 	return !this._ambigZone;
 };
 
+// this method implicitly marks a zone
 FCMoment.prototype.zone = function(tzo) {
-	if (tzo != undefined) {
-		this._ambigZone = false;
+	if (tzo != null) {
+		delete this._ambigZone;
 	}
 	return moment.fn.zone.apply(this, arguments);
 };
 
+// this method implicitly marks a zone.
+// we don't need this, because .local internally calls .zone, but we don't want to depend on that.
 FCMoment.prototype.local = function() {
-	this._ambigZone = false;
+	delete this._ambigZone;
 	return moment.fn.local.apply(this, arguments);
 };
 
+// this method implicitly marks a zone.
+// we don't need this, because .utc internally calls .zone, but we don't want to depend on that.
 FCMoment.prototype.utc = function() {
-	this._ambigZone = false;
+	delete this._ambigZone;
 	return moment.fn.utc.apply(this, arguments);
 };
 
 
-// MOMENT: formatting mods
+// Formatting
 // -------------------------------------------------------------------------------------------------
 
 FCMoment.prototype.format = function() {
@@ -242,23 +227,70 @@ FCMoment.prototype.toISOString = function() {
 };
 
 
-// MOMENT: misc utils
+// Querying
 // -------------------------------------------------------------------------------------------------
 
 // Is the moment within the specified range? `end` is exclusive.
 FCMoment.prototype.isWithin = function(start, end) {
-	return this >= makeMomentAs(start, this) && this < makeMomentAs(end, this);
+	var a = commonlyAmbiguate([ this, start, end ]);
+	return a[0] >= a[1] && a[0] < a[2];
 };
 
+// Make these query methods work with ambiguous moments
 $.each([
 	'isBefore',
-	'isAfter'
+	'isAfter',
+	'isSame'
 ], function(i, methodName) {
 	FCMoment.prototype[methodName] = function(input, units) {
-		moment.fn[methodName].call(
-			this,
-			makeMomentAs(input, this),
-			units
-		);
+		var a = commonlyAmbiguate([ this, input ]);
+		return moment.fn[methodName].call(a[0], a[1], units);
 	};
 });
+
+
+// Misc Internals
+// -------------------------------------------------------------------------------------------------
+
+// transfers our internal _ambig properties from one moment to another
+function transferAmbigs(src, dest) {
+	if (src._ambigTime) {
+		dest._ambigTime = true;
+	}
+	else if (dest._ambigTime) {
+		delete dest._ambigTime;
+	}
+
+	if (src._ambigZone) {
+		dest._ambigZone = true;
+	}
+	else if (dest._ambigZone) {
+		delete dest._ambigZone;
+	}
+}
+
+// given an array of moment-like inputs, return a parallel array w/ moments similarly ambiguated.
+// for example, of one moment has ambig time, but not others, all moments will have their time stripped.
+function commonlyAmbiguate(inputs) {
+	var outputs = [];
+	var anyAmbigTime = false;
+	var anyAmbigZone = false;
+	var i;
+
+	for (i=0; i<inputs.length; i++) {
+		outputs.push(fc.moment(inputs[i]));
+		anyAmbigTime = anyAmbigTime || outputs[i]._ambigTime;
+		anyAmbigZone = anyAmbigZone || outputs[i]._ambigZone;
+	}
+
+	for (i=0; i<outputs.length; i++) {
+		if (anyAmbigTime) {
+			outputs[i].stripTime();
+		}
+		else if (anyAmbigZone) {
+			outputs[i].stripZone();
+		}
+	}
+
+	return outputs;
+}

+ 4 - 1
src/util.js

@@ -3,12 +3,15 @@ fc.applyAll = applyAll;
 
 
 
-function createObject(proto) { // like Object.create
+// Create an object that has the given prototype.
+// Just like Object.create
+function createObject(proto) {
 	var f = function() {};
 	f.prototype = proto;
 	return new f();
 }
 
+// copy specifically-owned (non-protoype) properties of `b` onto `a`
 function extend(a, b) {
 	for (var i in b) {
 		if (b.hasOwnProperty(i)) {

+ 4 - 0
tests/automated/moment-ambig.js

@@ -269,17 +269,21 @@ describe('unambiguous moment', function() {
 	it('can be made ambiguously-zoned via stripZone', function() {
 		var mom = $.fullCalendar.moment('2014-06-08T10:00:00-0700');
 		expect(mom.hasZone()).toBe(true);
+		expect(mom.hasTime()).toBe(true);
 		mom.stripZone();
 		expect(mom.format()).toBe('2014-06-08T10:00:00');
 		expect(mom.hasZone()).toBe(false);
+		expect(mom.hasTime()).toBe(true);
 	});
 
 	it('can be made ambigously-timed via stripTime', function() {
 		var mom = $.fullCalendar.moment('2014-06-08T10:00:00-0700');
 		expect(mom.hasTime()).toBe(true);
+		expect(mom.hasZone()).toBe(true);
 		mom.stripTime();
 		expect(mom.format()).toBe('2014-06-08');
 		expect(mom.hasTime()).toBe(false);
+		expect(mom.hasZone()).toBe(false);
 	});
 
 });