From edcd275c914bf6a5da1b231c8afa79609046d7e7 Mon Sep 17 00:00:00 2001 From: Adam Shaw Date: Sun, 2 Feb 2014 20:52:23 -0800 Subject: [PATCH] improvements and bugfixes to FullCalendar's MomentJS extensions notably we have no side effects on the Moment prototype anymore. we subclass --- src/moment-ext.js | 166 +++++++++++++++++++------------- src/util.js | 5 +- tests/automated/moment-ambig.js | 4 + 3 files changed, 107 insertions(+), 68 deletions(-) diff --git a/src/moment-ext.js b/src/moment-ext.js index 094c230..c3dc2ad 100644 --- a/src/moment-ext.js +++ b/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