Skip to content
Browse files

gearing up for 1.3.1 release

  • Loading branch information...
1 parent 8e7c1da commit d0fb722b13936069bb557919c5b6a20ca72a631e @andrewplummer committed Sep 8, 2012
Showing with 16,711 additions and 14 deletions.
  1. +1 −3 lib/date.js
  2. +1,247 −0 release/1.3.1/precompiled/development/array.js
  3. +355 −0 release/1.3.1/precompiled/development/core.js
  4. +2,164 −0 release/1.3.1/precompiled/development/date.js
  5. +952 −0 release/1.3.1/precompiled/development/date_locales.js
  6. +195 −0 release/1.3.1/precompiled/development/date_ranges.js
  7. +443 −0 release/1.3.1/precompiled/development/es5.js
  8. +224 −0 release/1.3.1/precompiled/development/function.js
  9. +409 −0 release/1.3.1/precompiled/development/inflections.js
  10. +383 −0 release/1.3.1/precompiled/development/language.js
  11. +422 −0 release/1.3.1/precompiled/development/number.js
  12. +348 −0 release/1.3.1/precompiled/development/object.js
  13. +92 −0 release/1.3.1/precompiled/development/regexp.js
  14. +871 −0 release/1.3.1/precompiled/development/string.js
  15. +16 −0 release/1.3.1/precompiled/minified/array.js
  16. +9 −0 release/1.3.1/precompiled/minified/core.js
  17. +42 −0 release/1.3.1/precompiled/minified/date.js
  18. +38 −0 release/1.3.1/precompiled/minified/date_locales.js
  19. +3 −0 release/1.3.1/precompiled/minified/date_ranges.js
  20. +8 −0 release/1.3.1/precompiled/minified/es5.js
  21. +4 −0 release/1.3.1/precompiled/minified/function.js
  22. +11 −0 release/1.3.1/precompiled/minified/inflections.js
  23. +19 −0 release/1.3.1/precompiled/minified/language.js
  24. +5 −0 release/1.3.1/precompiled/minified/number.js
  25. +6 −0 release/1.3.1/precompiled/minified/object.js
  26. +2 −0 release/1.3.1/precompiled/minified/regexp.js
  27. +12 −0 release/1.3.1/precompiled/minified/string.js
  28. +3 −0 release/1.3.1/precompiled/readme.txt
  29. +8,114 −0 release/1.3.1/sugar-1.3.1-full.development.js
  30. +183 −0 release/1.3.1/sugar-1.3.1-full.min.js
  31. +115 −0 release/1.3.1/sugar-1.3.1.min.js
  32. +1 −1 release/sugar-edge.js
  33. +6 −7 script/jsmin/externs.js
  34. +2 −1 unit_tests/full.html
  35. +2 −1 unit_tests/minified/default.html
  36. +4 −1 unit_tests/minified/full.html
View
4 lib/date.js
@@ -568,9 +568,7 @@
function getExtendedDate(f, localeCode, prefer, forceUTC) {
var d = new date(), relative = false, baseLocalization, loc, format, set, unit, weekday, num, tmp, after;
- if(forceUTC) {
- d.utc();
- }
+ d.utc(forceUTC);
if(isDate(f)) {
d = f.clone();
View
1,247 release/1.3.1/precompiled/development/array.js
@@ -0,0 +1,1247 @@
+
+
+ /***
+ * @package Array
+ * @dependency core
+ * @description Array manipulation and traversal, "fuzzy matching" against elements, alphanumeric sorting and collation, enumerable methods on Object.
+ *
+ ***/
+
+
+ function multiMatch(el, match, scope, params) {
+ var result = true;
+ if(el === match) {
+ // Match strictly equal values up front.
+ return true;
+ } else if(isRegExp(match) && isString(el)) {
+ // Match against a regexp
+ return regexp(match).test(el);
+ } else if(isFunction(match)) {
+ // Match against a filtering function
+ return match.apply(scope, params);
+ } else if(isObject(match) && isObjectPrimitive(el)) {
+ // Match against a hash or array.
+ iterateOverObject(match, function(key, value) {
+ if(!multiMatch(el[key], match[key], scope, [el[key], el])) {
+ result = false;
+ }
+ });
+ return result;
+ } else {
+ return isEqual(el, match);
+ }
+ }
+
+ function transformArgument(el, map, context, mapArgs) {
+ if(isUndefined(map)) {
+ return el;
+ } else if(isFunction(map)) {
+ return map.apply(context, mapArgs || []);
+ } else if(isFunction(el[map])) {
+ return el[map].call(el);
+ } else {
+ return el[map];
+ }
+ }
+
+ // Basic array internal methods
+
+ function arrayEach(arr, fn, startIndex, loop) {
+ var length, index, i;
+ if(startIndex < 0) startIndex = arr.length + startIndex;
+ i = isNaN(startIndex) ? 0 : startIndex;
+ length = loop === true ? arr.length + i : arr.length;
+ while(i < length) {
+ index = i % arr.length;
+ if(!(index in arr)) {
+ return iterateOverSparseArray(arr, fn, i, loop);
+ } else if(fn.call(arr, arr[index], index, arr) === false) {
+ break;
+ }
+ i++;
+ }
+ }
+
+ function iterateOverSparseArray(arr, fn, fromIndex, loop) {
+ var indexes = [], i;
+ for(i in arr) {
+ if(isArrayIndex(arr, i) && i >= fromIndex) {
+ indexes.push(parseInt(i));
+ }
+ }
+ indexes.sort().each(function(index) {
+ return fn.call(arr, arr[index], index, arr);
+ });
+ return arr;
+ }
+
+ function isArrayIndex(arr, i) {
+ return i in arr && toUInt32(i) == i && i != 0xffffffff;
+ }
+
+ function toUInt32(i) {
+ return i >>> 0;
+ }
+
+ function arrayFind(arr, f, startIndex, loop, returnIndex) {
+ var result, index;
+ arrayEach(arr, function(el, i, arr) {
+ if(multiMatch(el, f, arr, [el, i, arr])) {
+ result = el;
+ index = i;
+ return false;
+ }
+ }, startIndex, loop);
+ return returnIndex ? index : result;
+ }
+
+ function arrayUnique(arr, map) {
+ var result = [], o = {}, transformed;
+ arrayEach(arr, function(el, i) {
+ transformed = map ? transformArgument(el, map, arr, [el, i, arr]) : el;
+ if(!checkForElementInHashAndSet(o, transformed)) {
+ result.push(el);
+ }
+ })
+ return result;
+ }
+
+ function arrayIntersect(arr1, arr2, subtract) {
+ var result = [], o = {};
+ arr2.each(function(el) {
+ checkForElementInHashAndSet(o, el);
+ });
+ arr1.each(function(el) {
+ var stringified = stringify(el),
+ isReference = !objectIsMatchedByValue(el);
+ // Add the result to the array if:
+ // 1. We're subtracting intersections or it doesn't already exist in the result and
+ // 2. It exists in the compared array and we're adding, or it doesn't exist and we're removing.
+ if(elementExistsInHash(o, stringified, el, isReference) != subtract) {
+ discardElementFromHash(o, stringified, el, isReference);
+ result.push(el);
+ }
+ });
+ return result;
+ }
+
+ function arrayFlatten(arr, level, current) {
+ level = level || Infinity;
+ current = current || 0;
+ var result = [];
+ arrayEach(arr, function(el) {
+ if(isArray(el) && current < level) {
+ result = result.concat(arrayFlatten(el, level, current + 1));
+ } else {
+ result.push(el);
+ }
+ });
+ return result;
+ }
+
+ function flatArguments(args) {
+ var result = [];
+ multiArgs(args, function(arg) {
+ result = result.concat(arg);
+ });
+ return result;
+ }
+
+ function elementExistsInHash(hash, key, element, isReference) {
+ var exists = key in hash;
+ if(isReference) {
+ if(!hash[key]) {
+ hash[key] = [];
+ }
+ exists = hash[key].indexOf(element) !== -1;
+ }
+ return exists;
+ }
+
+ function checkForElementInHashAndSet(hash, element) {
+ var stringified = stringify(element),
+ isReference = !objectIsMatchedByValue(element),
+ exists = elementExistsInHash(hash, stringified, element, isReference);
+ if(isReference) {
+ hash[stringified].push(element);
+ } else {
+ hash[stringified] = element;
+ }
+ return exists;
+ }
+
+ function discardElementFromHash(hash, key, element, isReference) {
+ var arr, i = 0;
+ if(isReference) {
+ arr = hash[key];
+ while(i < arr.length) {
+ if(arr[i] === element) {
+ arr.splice(i, 1);
+ } else {
+ i += 1;
+ }
+ }
+ } else {
+ delete hash[key];
+ }
+ }
+
+ // Support methods
+
+ function getMinOrMax(obj, map, which, all) {
+ var edge,
+ result = [],
+ max = which === 'max',
+ min = which === 'min',
+ isArray = Array.isArray(obj);
+ iterateOverObject(obj, function(key) {
+ var el = obj[key];
+ var test = transformArgument(el, map, obj, isArray ? [el, parseInt(key), obj] : []);
+ if(test === edge) {
+ result.push(el);
+ } else if(isUndefined(edge) || (max && test > edge) || (min && test < edge)) {
+ result = [el];
+ edge = test;
+ }
+ });
+ if(!isArray) result = arrayFlatten(result, 1);
+ return all ? result : result[0];
+ }
+
+
+ // Alphanumeric collation helpers
+
+ function collateStrings(a, b) {
+ var aValue, bValue, aChar, bChar, aEquiv, bEquiv, index = 0, tiebreaker = 0;
+ a = getCollationReadyString(a);
+ b = getCollationReadyString(b);
+ do {
+ aChar = getCollationCharacter(a, index);
+ bChar = getCollationCharacter(b, index);
+ aValue = getCollationValue(aChar);
+ bValue = getCollationValue(bChar);
+ if(aValue === -1 || bValue === -1) {
+ aValue = a.charCodeAt(index) || null;
+ bValue = b.charCodeAt(index) || null;
+ }
+ aEquiv = aChar !== a.charAt(index);
+ bEquiv = bChar !== b.charAt(index);
+ if(aEquiv !== bEquiv && tiebreaker === 0) {
+ tiebreaker = aEquiv - bEquiv;
+ }
+ index += 1;
+ } while(aValue != null && bValue != null && aValue === bValue);
+ if(aValue === bValue) return tiebreaker;
+ return aValue < bValue ? -1 : 1;
+ }
+
+ function getCollationReadyString(str) {
+ if(array[AlphanumericSortIgnoreCase]) {
+ str = str.toLowerCase();
+ }
+ return str.replace(array[AlphanumericSortIgnore], '');
+ }
+
+ function getCollationCharacter(str, index) {
+ var chr = str.charAt(index), eq = array[AlphanumericSortEquivalents] || {};
+ return eq[chr] || chr;
+ }
+
+ function getCollationValue(chr) {
+ var order = array[AlphanumericSortOrder];
+ if(!chr) {
+ return null;
+ } else {
+ return order.indexOf(chr);
+ }
+ }
+
+ var AlphanumericSortOrder = 'AlphanumericSortOrder';
+ var AlphanumericSortIgnore = 'AlphanumericSortIgnore';
+ var AlphanumericSortIgnoreCase = 'AlphanumericSortIgnoreCase';
+ var AlphanumericSortEquivalents = 'AlphanumericSortEquivalents';
+
+
+
+ /***
+ * @method every(<f>, [scope])
+ * @returns Boolean
+ * @short Returns true if all elements in the array match <f>.
+ * @extra [scope] is the %this% object. In addition to providing this method for browsers that don't support it natively, this enhanced method also directly accepts strings, numbers, deep objects, and arrays for <f>. %all% is provided an alias.
+ * @example
+ *
+ + ['a','a','a'].every(function(n) {
+ * return n == 'a';
+ * });
+ * ['a','a','a'].every('a') -> true
+ * [{a:2},{a:2}].every({a:2}) -> true
+ *
+ ***
+ * @method some(<f>, [scope])
+ * @returns Boolean
+ * @short Returns true if any element in the array matches <f>.
+ * @extra [scope] is the %this% object. In addition to providing this method for browsers that don't support it natively, this enhanced method also directly accepts strings, numbers, deep objects, and arrays for <f>. %any% is provided as aliases.
+ * @example
+ *
+ + ['a','b','c'].some(function(n) {
+ * return n == 'a';
+ * });
+ + ['a','b','c'].some(function(n) {
+ * return n == 'd';
+ * });
+ * ['a','b','c'].some('a') -> true
+ * [{a:2},{b:5}].some({a:2}) -> true
+ *
+ ***
+ * @method map(<map>, [scope])
+ * @returns Array
+ * @short Maps the array to another array containing the values that are the result of calling <map> on each element.
+ * @extra [scope] is the %this% object. In addition to providing this method for browsers that don't support it natively, this enhanced method also directly accepts a string, which is a shortcut for a function that gets that property (or invokes a function) on each element.
+ * @example
+ *
+ + [1,2,3].map(function(n) {
+ * return n * 3;
+ * }); -> [3,6,9]
+ * ['one','two','three'].map(function(n) {
+ * return n.length;
+ * }); -> [3,3,5]
+ * ['one','two','three'].map('length') -> [3,3,5]
+ *
+ ***
+ * @method filter(<f>, [scope])
+ * @returns Array
+ * @short Returns any elements in the array that match <f>.
+ * @extra [scope] is the %this% object. In addition to providing this method for browsers that don't support it natively, this enhanced method also directly accepts strings, numbers, deep objects, and arrays for <f>.
+ * @example
+ *
+ + [1,2,3].filter(function(n) {
+ * return n > 1;
+ * });
+ * [1,2,2,4].filter(2) -> 2
+ *
+ ***/
+ function buildEnhancements() {
+ var callbackCheck = function() { var a = arguments; return a.length > 0 && !isFunction(a[0]); };
+ extendSimilar(array, true, callbackCheck, 'map,every,all,some,any,none,filter', function(methods, name) {
+ methods[name] = function(f) {
+ return this[name](function(el, index) {
+ if(name === 'map') {
+ return transformArgument(el, f, this, [el, index, this]);
+ } else {
+ return multiMatch(el, f, this, [el, index, this]);
+ }
+ });
+ }
+ });
+ }
+
+ function buildAlphanumericSort() {
+ var order = 'AÁÀÂÃĄBCĆČÇDĎÐEÉÈĚÊËĘFGĞHıIÍÌİÎÏJKLŁMNŃŇÑOÓÒÔPQRŘSŚŠŞTŤUÚÙŮÛÜVWXYÝZŹŻŽÞÆŒØÕÅÄÖ';
+ var equiv = 'AÁÀÂÃÄ,CÇ,EÉÈÊË,IÍÌİÎÏ,OÓÒÔÕÖ,Sß,UÚÙÛÜ';
+ array[AlphanumericSortOrder] = order.split('').map(function(str) {
+ return str + str.toLowerCase();
+ }).join('');
+ var equivalents = {};
+ arrayEach(equiv.split(','), function(set) {
+ var equivalent = set.charAt(0);
+ arrayEach(set.slice(1).split(''), function(chr) {
+ equivalents[chr] = equivalent;
+ equivalents[chr.toLowerCase()] = equivalent.toLowerCase();
+ });
+ });
+ array[AlphanumericSortIgnoreCase] = true;
+ array[AlphanumericSortEquivalents] = equivalents;
+ }
+
+ extend(array, false, false, {
+
+ /***
+ *
+ * @method Array.create(<obj1>, <obj2>, ...)
+ * @returns Array
+ * @short Alternate array constructor.
+ * @extra This method will create a single array by calling %concat% on all arguments passed. In addition to ensuring that an unknown variable is in a single, flat array (the standard constructor will create nested arrays, this one will not), it is also a useful shorthand to convert a function's arguments object into a standard array.
+ * @example
+ *
+ * Array.create('one', true, 3) -> ['one', true, 3]
+ * Array.create(['one', true, 3]) -> ['one', true, 3]
+ + Array.create(function(n) {
+ * return arguments;
+ * }('howdy', 'doody'));
+ *
+ ***/
+ 'create': function() {
+ var result = []
+ multiArgs(arguments, function(a) {
+ if(isObjectPrimitive(a)) {
+ result = result.concat(array.prototype.slice.call(a));
+ } else {
+ result.push(a);
+ }
+ });
+ return result;
+ }
+
+ });
+
+ extend(array, true, false, {
+
+ /***
+ * @method find(<f>, [index] = 0, [loop] = false)
+ * @returns Mixed
+ * @short Returns the first element that matches <f>.
+ * @extra <f> will match a string, number, array, object, or alternately test against a function or regex. Starts at [index], and will continue once from index = 0 if [loop] is true.
+ * @example
+ *
+ + [{a:1,b:2},{a:1,b:3},{a:1,b:4}].find(function(n) {
+ * return n['a'] == 1;
+ * }); -> {a:1,b:3}
+ * ['cuba','japan','canada'].find(/^c/, 2) -> 'canada'
+ *
+ ***/
+ 'find': function(f, index, loop) {
+ return arrayFind(this, f, index, loop);
+ },
+
+ /***
+ * @method findAll(<f>, [index] = 0, [loop] = false)
+ * @returns Array
+ * @short Returns all elements that match <f>.
+ * @extra <f> will match a string, number, array, object, or alternately test against a function or regex. Starts at [index], and will continue once from index = 0 if [loop] is true.
+ * @example
+ *
+ + [{a:1,b:2},{a:1,b:3},{a:2,b:4}].findAll(function(n) {
+ * return n['a'] == 1;
+ * }); -> [{a:1,b:3},{a:1,b:4}]
+ * ['cuba','japan','canada'].findAll(/^c/) -> 'cuba','canada'
+ * ['cuba','japan','canada'].findAll(/^c/, 2) -> 'canada'
+ *
+ ***/
+ 'findAll': function(f, index, loop) {
+ var result = [];
+ arrayEach(this, function(el, i, arr) {
+ if(multiMatch(el, f, arr, [el, i, arr])) {
+ result.push(el);
+ }
+ }, index, loop);
+ return result;
+ },
+
+ /***
+ * @method findIndex(<f>, [startIndex] = 0, [loop] = false)
+ * @returns Number
+ * @short Returns the index of the first element that matches <f> or -1 if not found.
+ * @extra This method has a few notable differences to native %indexOf%. Although <f> will similarly match a primitive such as a string or number, it will also match deep objects and arrays that are not equal by reference (%===%). Additionally, if a function is passed it will be run as a matching function (similar to the behavior of %Array#filter%) rather than attempting to find that function itself by reference in the array. Finally, a regexp will be matched against elements in the array, presumed to be strings. Starts at [index], and will continue once from index = 0 if [loop] is true.
+ * @example
+ *
+ + [1,2,3,4].findIndex(3); -> 2
+ + [1,2,3,4].findIndex(function(n) {
+ * return n % 2 == 0;
+ * }); -> 1
+ + ['one','two','three'].findIndex(/th/); -> 2
+ *
+ ***/
+ 'findIndex': function(f, startIndex, loop) {
+ var index = arrayFind(this, f, startIndex, loop, true);
+ return isUndefined(index) ? -1 : index;
+ },
+
+ /***
+ * @method count(<f>)
+ * @returns Number
+ * @short Counts all elements in the array that match <f>.
+ * @extra <f> will match a string, number, array, object, or alternately test against a function or regex.
+ * @example
+ *
+ * [1,2,3,1].count(1) -> 2
+ * ['a','b','c'].count(/b/) -> 1
+ + [{a:1},{b:2}].count(function(n) {
+ * return n['a'] > 1;
+ * }); -> 0
+ *
+ ***/
+ 'count': function(f) {
+ if(isUndefined(f)) return this.length;
+ return this.findAll(f).length;
+ },
+
+ /***
+ * @method removeAt(<start>, [end])
+ * @returns Array
+ * @short Removes element at <start>. If [end] is specified, removes the range between <start> and [end]. This method will change the array! If you don't intend the array to be changed use %clone% first.
+ * @example
+ *
+ * ['a','b','c'].removeAt(0) -> ['b','c']
+ * [1,2,3,4].removeAt(1, 3) -> [1]
+ *
+ ***/
+ 'removeAt': function(start, end) {
+ if(isUndefined(start)) return this;
+ if(isUndefined(end)) end = start;
+ for(var i = 0; i <= (end - start); i++) {
+ this.splice(start, 1);
+ }
+ return this;
+ },
+
+ /***
+ * @method include(<el>, [index])
+ * @returns Array
+ * @short Adds <el> to the array.
+ * @extra This is a non-destructive alias for %add%. It will not change the original array.
+ * @example
+ *
+ * [1,2,3,4].include(5) -> [1,2,3,4,5]
+ * [1,2,3,4].include(8, 1) -> [1,8,2,3,4]
+ * [1,2,3,4].include([5,6,7]) -> [1,2,3,4,5,6,7]
+ *
+ ***/
+ 'include': function(el, index) {
+ return this.clone().add(el, index);
+ },
+
+ /***
+ * @method exclude([f1], [f2], ...)
+ * @returns Array
+ * @short Removes any element in the array that matches [f1], [f2], etc.
+ * @extra This is a non-destructive alias for %remove%. It will not change the original array.
+ * @example
+ *
+ * [1,2,3].exclude(3) -> [1,2]
+ * ['a','b','c'].exclude(/b/) -> ['a','c']
+ + [{a:1},{b:2}].exclude(function(n) {
+ * return n['a'] == 1;
+ * }); -> [{b:2}]
+ *
+ ***/
+ 'exclude': function() {
+ return array.prototype.remove.apply(this.clone(), arguments);
+ },
+
+ /***
+ * @method clone()
+ * @returns Array
+ * @short Clones the array.
+ * @example
+ *
+ * [1,2,3].clone() -> [1,2,3]
+ *
+ ***/
+ 'clone': function() {
+ return simpleMerge([], this);
+ },
+
+ /***
+ * @method unique([map] = null)
+ * @returns Array
+ * @short Removes all duplicate elements in the array.
+ * @extra [map] may be a function mapping the value to be uniqued on or a string acting as a shortcut. This is most commonly used when you have a key that ensures the object's uniqueness, and don't need to check all fields. This method will also correctly operate on arrays of objects.
+ * @example
+ *
+ * [1,2,2,3].unique() -> [1,2,3]
+ * [{foo:'bar'},{foo:'bar'}].unique() -> [{foo:'bar'}]
+ + [{foo:'bar'},{foo:'bar'}].unique(function(obj){
+ * return obj.foo;
+ * }); -> [{foo:'bar'}]
+ * [{foo:'bar'},{foo:'bar'}].unique('foo') -> [{foo:'bar'}]
+ *
+ ***/
+ 'unique': function(map) {
+ return arrayUnique(this, map);
+ },
+
+ /***
+ * @method flatten([limit] = Infinity)
+ * @returns Array
+ * @short Returns a flattened, one-dimensional copy of the array.
+ * @extra You can optionally specify a [limit], which will only flatten that depth.
+ * @example
+ *
+ * [[1], 2, [3]].flatten() -> [1,2,3]
+ * [['a'],[],'b','c'].flatten() -> ['a','b','c']
+ *
+ ***/
+ 'flatten': function(limit) {
+ return arrayFlatten(this, limit);
+ },
+
+ /***
+ * @method union([a1], [a2], ...)
+ * @returns Array
+ * @short Returns an array containing all elements in all arrays with duplicates removed.
+ * @extra This method will also correctly operate on arrays of objects.
+ * @example
+ *
+ * [1,3,5].union([5,7,9]) -> [1,3,5,7,9]
+ * ['a','b'].union(['b','c']) -> ['a','b','c']
+ *
+ ***/
+ 'union': function() {
+ return arrayUnique(this.concat(flatArguments(arguments)));
+ },
+
+ /***
+ * @method intersect([a1], [a2], ...)
+ * @returns Array
+ * @short Returns an array containing the elements all arrays have in common.
+ * @extra This method will also correctly operate on arrays of objects.
+ * @example
+ *
+ * [1,3,5].intersect([5,7,9]) -> [5]
+ * ['a','b'].intersect('b','c') -> ['b']
+ *
+ ***/
+ 'intersect': function() {
+ return arrayIntersect(this, flatArguments(arguments), false);
+ },
+
+ /***
+ * @method subtract([a1], [a2], ...)
+ * @returns Array
+ * @short Subtracts from the array all elements in [a1], [a2], etc.
+ * @extra This method will also correctly operate on arrays of objects.
+ * @example
+ *
+ * [1,3,5].subtract([5,7,9]) -> [1,3]
+ * [1,3,5].subtract([3],[5]) -> [1]
+ * ['a','b'].subtract('b','c') -> ['a']
+ *
+ ***/
+ 'subtract': function(a) {
+ return arrayIntersect(this, flatArguments(arguments), true);
+ },
+
+ /***
+ * @method at(<index>, [loop] = true)
+ * @returns Mixed
+ * @short Gets the element(s) at a given index.
+ * @extra When [loop] is true, overshooting the end of the array (or the beginning) will begin counting from the other end. As an alternate syntax, passing multiple indexes will get the elements at those indexes.
+ * @example
+ *
+ * [1,2,3].at(0) -> 1
+ * [1,2,3].at(2) -> 3
+ * [1,2,3].at(4) -> 2
+ * [1,2,3].at(4, false) -> null
+ * [1,2,3].at(-1) -> 3
+ * [1,2,3].at(0,1) -> [1,2]
+ *
+ ***/
+ 'at': function() {
+ return entryAtIndex(this, arguments);
+ },
+
+ /***
+ * @method first([num] = 1)
+ * @returns Mixed
+ * @short Returns the first element(s) in the array.
+ * @extra When <num> is passed, returns the first <num> elements in the array.
+ * @example
+ *
+ * [1,2,3].first() -> 1
+ * [1,2,3].first(2) -> [1,2]
+ *
+ ***/
+ 'first': function(num) {
+ if(isUndefined(num)) return this[0];
+ if(num < 0) num = 0;
+ return this.slice(0, num);
+ },
+
+ /***
+ * @method last([num] = 1)
+ * @returns Mixed
+ * @short Returns the last element(s) in the array.
+ * @extra When <num> is passed, returns the last <num> elements in the array.
+ * @example
+ *
+ * [1,2,3].last() -> 3
+ * [1,2,3].last(2) -> [2,3]
+ *
+ ***/
+ 'last': function(num) {
+ if(isUndefined(num)) return this[this.length - 1];
+ var start = this.length - num < 0 ? 0 : this.length - num;
+ return this.slice(start);
+ },
+
+ /***
+ * @method from(<index>)
+ * @returns Array
+ * @short Returns a slice of the array from <index>.
+ * @example
+ *
+ * [1,2,3].from(1) -> [2,3]
+ * [1,2,3].from(2) -> [3]
+ *
+ ***/
+ 'from': function(num) {
+ return this.slice(num);
+ },
+
+ /***
+ * @method to(<index>)
+ * @returns Array
+ * @short Returns a slice of the array up to <index>.
+ * @example
+ *
+ * [1,2,3].to(1) -> [1]
+ * [1,2,3].to(2) -> [1,2]
+ *
+ ***/
+ 'to': function(num) {
+ if(isUndefined(num)) num = this.length;
+ return this.slice(0, num);
+ },
+
+ /***
+ * @method min([map], [all] = false)
+ * @returns Mixed
+ * @short Returns the element in the array with the lowest value.
+ * @extra [map] may be a function mapping the value to be checked or a string acting as a shortcut. If [all] is true, will return all min values in an array.
+ * @example
+ *
+ * [1,2,3].min() -> 1
+ * ['fee','fo','fum'].min('length') -> 'fo'
+ * ['fee','fo','fum'].min('length', true) -> ['fo']
+ + ['fee','fo','fum'].min(function(n) {
+ * return n.length;
+ * }); -> ['fo']
+ + [{a:3,a:2}].min(function(n) {
+ * return n['a'];
+ * }); -> [{a:2}]
+ *
+ ***/
+ 'min': function(map, all) {
+ return getMinOrMax(this, map, 'min', all);
+ },
+
+ /***
+ * @method max([map], [all] = false)
+ * @returns Mixed
+ * @short Returns the element in the array with the greatest value.
+ * @extra [map] may be a function mapping the value to be checked or a string acting as a shortcut. If [all] is true, will return all max values in an array.
+ * @example
+ *
+ * [1,2,3].max() -> 3
+ * ['fee','fo','fum'].max('length') -> 'fee'
+ * ['fee','fo','fum'].max('length', true) -> ['fee']
+ + [{a:3,a:2}].max(function(n) {
+ * return n['a'];
+ * }); -> {a:3}
+ *
+ ***/
+ 'max': function(map, all) {
+ return getMinOrMax(this, map, 'max', all);
+ },
+
+ /***
+ * @method least([map])
+ * @returns Array
+ * @short Returns the elements in the array with the least commonly occuring value.
+ * @extra [map] may be a function mapping the value to be checked or a string acting as a shortcut.
+ * @example
+ *
+ * [3,2,2].least() -> [3]
+ * ['fe','fo','fum'].least('length') -> ['fum']
+ + [{age:35,name:'ken'},{age:12,name:'bob'},{age:12,name:'ted'}].least(function(n) {
+ * return n.age;
+ * }); -> [{age:35,name:'ken'}]
+ *
+ ***/
+ 'least': function(map, all) {
+ return getMinOrMax(this.groupBy.apply(this, [map]), 'length', 'min', all);
+ },
+
+ /***
+ * @method most([map])
+ * @returns Array
+ * @short Returns the elements in the array with the most commonly occuring value.
+ * @extra [map] may be a function mapping the value to be checked or a string acting as a shortcut.
+ * @example
+ *
+ * [3,2,2].most() -> [2]
+ * ['fe','fo','fum'].most('length') -> ['fe','fo']
+ + [{age:35,name:'ken'},{age:12,name:'bob'},{age:12,name:'ted'}].most(function(n) {
+ * return n.age;
+ * }); -> [{age:12,name:'bob'},{age:12,name:'ted'}]
+ *
+ ***/
+ 'most': function(map, all) {
+ return getMinOrMax(this.groupBy.apply(this, [map]), 'length', 'max', all);
+ },
+
+ /***
+ * @method sum([map])
+ * @returns Number
+ * @short Sums all values in the array.
+ * @extra [map] may be a function mapping the value to be summed or a string acting as a shortcut.
+ * @example
+ *
+ * [1,2,2].sum() -> 5
+ + [{age:35},{age:12},{age:12}].sum(function(n) {
+ * return n.age;
+ * }); -> 59
+ * [{age:35},{age:12},{age:12}].sum('age') -> 59
+ *
+ ***/
+ 'sum': function(map) {
+ var arr = map ? this.map(map) : this;
+ return arr.length > 0 ? arr.reduce(function(a,b) { return a + b; }) : 0;
+ },
+
+ /***
+ * @method average([map])
+ * @returns Number
+ * @short Averages all values in the array.
+ * @extra [map] may be a function mapping the value to be averaged or a string acting as a shortcut.
+ * @example
+ *
+ * [1,2,3].average() -> 2
+ + [{age:35},{age:11},{age:11}].average(function(n) {
+ * return n.age;
+ * }); -> 19
+ * [{age:35},{age:11},{age:11}].average('age') -> 19
+ *
+ ***/
+ 'average': function(map) {
+ var arr = map ? this.map(map) : this;
+ return arr.length > 0 ? arr.sum() / arr.length : 0;
+ },
+
+ /***
+ * @method inGroups(<num>, [padding])
+ * @returns Array
+ * @short Groups the array into <num> arrays.
+ * @extra [padding] specifies a value with which to pad the last array so that they are all equal length.
+ * @example
+ *
+ * [1,2,3,4,5,6,7].inGroups(3) -> [ [1,2,3], [4,5,6], [7] ]
+ * [1,2,3,4,5,6,7].inGroups(3, 'none') -> [ [1,2,3], [4,5,6], [7,'none','none'] ]
+ *
+ ***/
+ 'inGroups': function(num, padding) {
+ var pad = arguments.length > 1;
+ var arr = this;
+ var result = [];
+ var divisor = ceil(this.length / num);
+ getRange(0, num - 1, function(i) {
+ var index = i * divisor;
+ var group = arr.slice(index, index + divisor);
+ if(pad && group.length < divisor) {
+ getRange(1, divisor - group.length, function() {
+ group = group.add(padding);
+ });
+ }
+ result.push(group);
+ });
+ return result;
+ },
+
+ /***
+ * @method inGroupsOf(<num>, [padding] = null)
+ * @returns Array
+ * @short Groups the array into arrays of <num> elements each.
+ * @extra [padding] specifies a value with which to pad the last array so that they are all equal length.
+ * @example
+ *
+ * [1,2,3,4,5,6,7].inGroupsOf(4) -> [ [1,2,3,4], [5,6,7] ]
+ * [1,2,3,4,5,6,7].inGroupsOf(4, 'none') -> [ [1,2,3,4], [5,6,7,'none'] ]
+ *
+ ***/
+ 'inGroupsOf': function(num, padding) {
+ var result = [], len = this.length, arr = this, group;
+ if(len === 0 || num === 0) return arr;
+ if(isUndefined(num)) num = 1;
+ if(isUndefined(padding)) padding = null;
+ getRange(0, ceil(len / num) - 1, function(i) {
+ group = arr.slice(num * i, num * i + num);
+ while(group.length < num) {
+ group.push(padding);
+ }
+ result.push(group);
+ });
+ return result;
+ },
+
+ /***
+ * @method isEmpty()
+ * @returns Boolean
+ * @short Returns true if the array is empty.
+ * @extra This is true if the array has a length of zero, or contains only %undefined%, %null%, or %NaN%.
+ * @example
+ *
+ * [].isEmpty() -> true
+ * [null,undefined].isEmpty() -> true
+ *
+ ***/
+ 'isEmpty': function() {
+ return this.compact().length == 0;
+ },
+
+ /***
+ * @method sortBy(<map>, [desc] = false)
+ * @returns Array
+ * @short Sorts the array by <map>.
+ * @extra <map> may be a function, a string acting as a shortcut, or blank (direct comparison of array values). [desc] will sort the array in descending order. When the field being sorted on is a string, the resulting order will be determined by an internal collation algorithm that is optimized for major Western languages, but can be customized. For more information see @array_sorting.
+ * @example
+ *
+ * ['world','a','new'].sortBy('length') -> ['a','new','world']
+ * ['world','a','new'].sortBy('length', true) -> ['world','new','a']
+ + [{age:72},{age:13},{age:18}].sortBy(function(n) {
+ * return n.age;
+ * }); -> [{age:13},{age:18},{age:72}]
+ *
+ ***/
+ 'sortBy': function(map, desc) {
+ var arr = this.clone();
+ arr.sort(function(a, b) {
+ var aProperty, bProperty, comp;
+ aProperty = transformArgument(a, map, arr, [a]);
+ bProperty = transformArgument(b, map, arr, [b]);
+ if(isString(aProperty) && isString(bProperty)) {
+ comp = collateStrings(aProperty, bProperty);
+ } else if(aProperty < bProperty) {
+ comp = -1;
+ } else if(aProperty > bProperty) {
+ comp = 1;
+ } else {
+ comp = 0;
+ }
+ return comp * (desc ? -1 : 1);
+ });
+ return arr;
+ },
+
+ /***
+ * @method randomize()
+ * @returns Array
+ * @short Returns a copy of the array with the elements randomized.
+ * @extra Uses Fisher-Yates algorithm.
+ * @example
+ *
+ * [1,2,3,4].randomize() -> [?,?,?,?]
+ *
+ ***/
+ 'randomize': function() {
+ var a = this.concat();
+ for(var j, x, i = a.length; i; j = parseInt(math.random() * i), x = a[--i], a[i] = a[j], a[j] = x) {};
+ return a;
+ },
+
+ /***
+ * @method zip([arr1], [arr2], ...)
+ * @returns Array
+ * @short Merges multiple arrays together.
+ * @extra This method "zips up" smaller arrays into one large whose elements are "all elements at index 0", "all elements at index 1", etc. Useful when you have associated data that is split over separated arrays. If the arrays passed have more elements than the original array, they will be discarded. If they have fewer elements, the missing elements will filled with %null%.
+ * @example
+ *
+ * [1,2,3].zip([4,5,6]) -> [[1,2], [3,4], [5,6]]
+ * ['Martin','John'].zip(['Luther','F.'], ['King','Kennedy']) -> [['Martin','Luther','King'], ['John','F.','Kennedy']]
+ *
+ ***/
+ 'zip': function() {
+ var args = multiArgs(arguments);
+ return this.map(function(el, i) {
+ return [el].concat(args.map(function(k) {
+ return (i in k) ? k[i] : null;
+ }));
+ });
+ },
+
+ /***
+ * @method sample([num])
+ * @returns Mixed
+ * @short Returns a random element from the array.
+ * @extra If [num] is passed, will return [num] samples from the array.
+ * @example
+ *
+ * [1,2,3,4,5].sample() -> // Random element
+ * [1,2,3,4,5].sample(3) -> // Array of 3 random elements
+ *
+ ***/
+ 'sample': function(num) {
+ var result = [], arr = this.clone(), index;
+ if(isUndefined(num)) num = 1;
+ while(result.length < num) {
+ index = floor(math.random() * (arr.length - 1));
+ result.push(arr[index]);
+ arr.removeAt(index);
+ if(arr.length == 0) break;
+ }
+ return arguments.length > 0 ? result : result[0];
+ },
+
+ /***
+ * @method each(<fn>, [index] = 0, [loop] = false)
+ * @returns Array
+ * @short Runs <fn> against each element in the array. Enhanced version of %Array#forEach%.
+ * @extra Parameters passed to <fn> are identical to %forEach%, ie. the first parameter is the current element, second parameter is the current index, and third parameter is the array itself. If <fn> returns %false% at any time it will break out of the loop. Once %each% finishes, it will return the array. If [index] is passed, <fn> will begin at that index and work its way to the end. If [loop] is true, it will then start over from the beginning of the array and continue until it reaches [index] - 1.
+ * @example
+ *
+ * [1,2,3,4].each(function(n) {
+ * // Called 4 times: 1, 2, 3, 4
+ * });
+ * [1,2,3,4].each(function(n) {
+ * // Called 4 times: 3, 4, 1, 2
+ * }, 2, true);
+ *
+ ***/
+ 'each': function(fn, index, loop) {
+ arrayEach(this, fn, index, loop);
+ return this;
+ },
+
+ /***
+ * @method add(<el>, [index])
+ * @returns Array
+ * @short Adds <el> to the array.
+ * @extra If [index] is specified, it will add at [index], otherwise adds to the end of the array. %add% behaves like %concat% in that if <el> is an array it will be joined, not inserted. This method will change the array! Use %include% for a non-destructive alias. Also, %insert% is provided as an alias that reads better when using an index.
+ * @example
+ *
+ * [1,2,3,4].add(5) -> [1,2,3,4,5]
+ * [1,2,3,4].add([5,6,7]) -> [1,2,3,4,5,6,7]
+ * [1,2,3,4].insert(8, 1) -> [1,8,2,3,4]
+ *
+ ***/
+ 'add': function(el, index) {
+ if(!isNumber(number(index)) || isNaN(index)) index = this.length;
+ array.prototype.splice.apply(this, [index, 0].concat(el));
+ return this;
+ },
+
+ /***
+ * @method remove([f1], [f2], ...)
+ * @returns Array
+ * @short Removes any element in the array that matches [f1], [f2], etc.
+ * @extra Will match a string, number, array, object, or alternately test against a function or regex. This method will change the array! Use %exclude% for a non-destructive alias.
+ * @example
+ *
+ * [1,2,3].remove(3) -> [1,2]
+ * ['a','b','c'].remove(/b/) -> ['a','c']
+ + [{a:1},{b:2}].remove(function(n) {
+ * return n['a'] == 1;
+ * }); -> [{b:2}]
+ *
+ ***/
+ 'remove': function() {
+ var i, arr = this;
+ multiArgs(arguments, function(f) {
+ i = 0;
+ while(i < arr.length) {
+ if(multiMatch(arr[i], f, arr, [arr[i], i, arr])) {
+ arr.splice(i, 1);
+ } else {
+ i++;
+ }
+ }
+ });
+ return arr;
+ },
+
+ /***
+ * @method compact([all] = false)
+ * @returns Array
+ * @short Removes all instances of %undefined%, %null%, and %NaN% from the array.
+ * @extra If [all] is %true%, all "falsy" elements will be removed. This includes empty strings, 0, and false.
+ * @example
+ *
+ * [1,null,2,undefined,3].compact() -> [1,2,3]
+ * [1,'',2,false,3].compact() -> [1,'',2,false,3]
+ * [1,'',2,false,3].compact(true) -> [1,2,3]
+ *
+ ***/
+ 'compact': function(all) {
+ var result = [];
+ arrayEach(this, function(el, i) {
+ if(isArray(el)) {
+ result.push(el.compact());
+ } else if(all && el) {
+ result.push(el);
+ } else if(!all && el != null && el.valueOf() === el.valueOf()) {
+ result.push(el);
+ }
+ });
+ return result;
+ },
+
+ /***
+ * @method groupBy(<map>, [fn])
+ * @returns Object
+ * @short Groups the array by <map>.
+ * @extra Will return an object with keys equal to the grouped values. <map> may be a mapping function, or a string acting as a shortcut. Optionally calls [fn] for each group.
+ * @example
+ *
+ * ['fee','fi','fum'].groupBy('length') -> { 2: ['fi'], 3: ['fee','fum'] }
+ + [{age:35,name:'ken'},{age:15,name:'bob'}].groupBy(function(n) {
+ * return n.age;
+ * }); -> { 35: [{age:35,name:'ken'}], 15: [{age:15,name:'bob'}] }
+ *
+ ***/
+ 'groupBy': function(map, fn) {
+ var arr = this, result = {}, key;
+ arrayEach(arr, function(el, index) {
+ key = transformArgument(el, map, arr, [el, index, arr]);
+ if(!result[key]) result[key] = [];
+ result[key].push(el);
+ });
+ if(fn) {
+ iterateOverObject(result, fn);
+ }
+ return result;
+ },
+
+ /***
+ * @method none(<f>)
+ * @returns Boolean
+ * @short Returns true if none of the elements in the array match <f>.
+ * @extra <f> will match a string, number, array, object, or alternately test against a function or regex.
+ * @example
+ *
+ * [1,2,3].none(5) -> true
+ * ['a','b','c'].none(/b/) -> false
+ + [{a:1},{b:2}].none(function(n) {
+ * return n['a'] > 1;
+ * }); -> true
+ *
+ ***/
+ 'none': function() {
+ return !this.any.apply(this, arguments);
+ }
+
+
+ });
+
+ // Aliases
+ extend(array, true, false, {
+
+ /***
+ * @method all()
+ * @alias every
+ *
+ ***/
+ 'all': array.prototype.every,
+
+ /*** @method any()
+ * @alias some
+ *
+ ***/
+ 'any': array.prototype.some,
+
+ /***
+ * @method insert()
+ * @alias add
+ *
+ ***/
+ 'insert': array.prototype.add
+
+ });
+
+
+ /***
+ * Object module
+ * Enumerable methods on objects
+ *
+ ***/
+
+ function keysWithCoercion(obj) {
+ if(obj && obj.valueOf) {
+ obj = obj.valueOf();
+ }
+ return object.keys(obj);
+ }
+
+ /***
+ * @method [enumerable](<obj>)
+ * @returns Boolean
+ * @short Enumerable methods in the Array package are also available to the Object class. They will perform their normal operations for every property in <obj>.
+ * @extra In cases where a callback is used, instead of %element, index%, the callback will instead be passed %key, value%. Enumerable methods are also available to extended objects as instance methods.
+ *
+ * @set
+ * map
+ * any
+ * all
+ * none
+ * count
+ * find
+ * findAll
+ * reduce
+ * isEmpty
+ * sum
+ * average
+ * min
+ * max
+ * least
+ * most
+ *
+ * @example
+ *
+ * Object.any({foo:'bar'}, 'bar') -> true
+ * Object.extended({foo:'bar'}).any('bar') -> true
+ * Object.isEmpty({}) -> true
+ + Object.map({ fred: { age: 52 } }, 'age'); -> { fred: 52 }
+ *
+ ***/
+
+ function buildEnumerableMethods(names, mapping) {
+ extendSimilar(object, false, false, names, function(methods, name) {
+ methods[name] = function(obj, arg1, arg2) {
+ var result;
+ result = array.prototype[name].call(keysWithCoercion(obj), function(key) {
+ if(mapping) {
+ return transformArgument(obj[key], arg1, obj, [key, obj[key], obj]);
+ } else {
+ return multiMatch(obj[key], arg1, obj, [key, obj[key], obj]);
+ }
+ }, arg2);
+ if(isArray(result)) {
+ // The method has returned an array of keys so use this array
+ // to build up the resulting object in the form we want it in.
+ result = result.reduce(function(o, key, i) {
+ o[key] = obj[key];
+ return o;
+ }, {});
+ }
+ return result;
+ };
+ });
+ buildObjectInstanceMethods(names, Hash);
+ }
+
+ extend(object, false, false, {
+
+ 'map': function(obj, map) {
+ return keysWithCoercion(obj).reduce(function(result, key) {
+ result[key] = transformArgument(obj[key], map, obj, [key, obj[key], obj]);
+ return result;
+ }, {});
+ },
+
+ 'reduce': function(obj) {
+ var values = keysWithCoercion(obj).map(function(key) {
+ return obj[key];
+ });
+ return values.reduce.apply(values, multiArgs(arguments).slice(1));
+ },
+
+ /***
+ * @method size(<obj>)
+ * @returns Number
+ * @short Returns the number of properties in <obj>.
+ * @extra %size% is available as an instance method on extended objects.
+ * @example
+ *
+ * Object.size({ foo: 'bar' }) -> 1
+ *
+ ***/
+ 'size': function (obj) {
+ return keysWithCoercion(obj).length;
+ }
+
+ });
+
+ buildEnhancements();
+ buildAlphanumericSort();
+ buildEnumerableMethods('each,any,all,none,count,find,findAll,isEmpty');
+ buildEnumerableMethods('sum,average,min,max,least,most', true);
+ buildObjectInstanceMethods('map,reduce,size', Hash);
+
View
355 release/1.3.1/precompiled/development/core.js
@@ -0,0 +1,355 @@
+
+
+ /***
+ * @package Core
+ * @description Internal utility and common methods.
+ ***/
+
+
+ // A few optimizations for Google Closure Compiler will save us a couple kb in the release script.
+ var object = Object, array = Array, regexp = RegExp, date = Date, string = String, number = Number, math = Math, Undefined;
+
+ // The global context
+ var globalContext = typeof global !== 'undefined' ? global : this;
+
+ // defineProperty exists in IE8 but will error when trying to define a property on
+ // native objects. IE8 does not have defineProperies, however, so this check saves a try/catch block.
+ var definePropertySupport = object.defineProperty && object.defineProperties;
+
+ // Class initializers and class helpers
+
+ var ClassNames = 'Array,Boolean,Date,Function,Number,String,RegExp'.split(',');
+
+ var isArray = buildClassCheck(ClassNames[0]);
+ var isBoolean = buildClassCheck(ClassNames[1]);
+ var isDate = buildClassCheck(ClassNames[2]);
+ var isFunction = buildClassCheck(ClassNames[3]);
+ var isNumber = buildClassCheck(ClassNames[4]);
+ var isString = buildClassCheck(ClassNames[5]);
+ var isRegExp = buildClassCheck(ClassNames[6]);
+
+ function buildClassCheck(type) {
+ return function(obj) {
+ return className(obj) === '[object '+type+']';
+ }
+ }
+
+ function className(obj) {
+ return object.prototype.toString.call(obj);
+ }
+
+ function initializeClasses() {
+ initializeClass(object);
+ iterateOverObject(ClassNames, function(i,name) {
+ initializeClass(globalContext[name]);
+ });
+ }
+
+ function initializeClass(klass) {
+ if(klass['SugarMethods']) return;
+ defineProperty(klass, 'SugarMethods', {});
+ extend(klass, false, false, {
+ 'restore': function() {
+ var all = arguments.length === 0, methods = multiArgs(arguments);
+ iterateOverObject(klass['SugarMethods'], function(name, m) {
+ if(all || methods.indexOf(name) > -1) {
+ defineProperty(m.instance ? klass.prototype : klass, name, m.method);
+ }
+ });
+ },
+ 'extend': function(methods, override, instance) {
+ extend(klass, instance !== false, override, methods);
+ }
+ });
+ }
+
+ // Class extending methods
+
+ function extend(klass, instance, override, methods) {
+ var extendee = instance ? klass.prototype : klass, original;
+ initializeClass(klass);
+ iterateOverObject(methods, function(name, method) {
+ original = extendee[name];
+ if(typeof override === 'function') {
+ method = wrapNative(extendee[name], method, override);
+ }
+ if(override !== false || !extendee[name]) {
+ defineProperty(extendee, name, method);
+ }
+ // If the method is internal to Sugar, then store a reference so it can be restored later.
+ klass['SugarMethods'][name] = { instance: instance, method: method, original: original };
+ });
+ }
+
+ function extendSimilar(klass, instance, override, set, fn) {
+ var methods = {};
+ set = isString(set) ? set.split(',') : set;
+ set.forEach(function(name, i) {
+ fn(methods, name, i);
+ });
+ extend(klass, instance, override, methods);
+ }
+
+ function wrapNative(nativeFn, extendedFn, condition) {
+ return function() {
+ if(nativeFn && (condition === true || !condition.apply(this, arguments))) {
+ return nativeFn.apply(this, arguments);
+ } else {
+ return extendedFn.apply(this, arguments);
+ }
+ }
+ }
+
+ function defineProperty(target, name, method) {
+ if(definePropertySupport) {
+ object.defineProperty(target, name, { 'value': method, 'configurable': true, 'enumerable': false, 'writable': true });
+ } else {
+ target[name] = method;
+ }
+ }
+
+
+ // Argument helpers
+
+ function multiArgs(args, fn) {
+ var result = [], i = 0;
+ for(i = 0; i < args.length; i++) {
+ result.push(args[i]);
+ if(fn) fn.call(args, args[i], i);
+ }
+ return result;
+ }
+
+
+ // General helpers
+
+ function isDefined(o) {
+ return o !== Undefined;
+ }
+
+ function isUndefined(o) {
+ return o === Undefined;
+ }
+
+
+ // Object helpers
+
+ function isObjectPrimitive(obj) {
+ // Check for null
+ return obj && typeof obj === 'object';
+ }
+
+ function isObject(obj) {
+ // === on the constructor is not safe across iframes
+ // 'hasOwnProperty' ensures that the object also inherits
+ // from Object, which is false for DOMElements in IE.
+ return !!obj && className(obj) === '[object Object]' && 'hasOwnProperty' in obj;
+ }
+
+ function hasOwnProperty(obj, key) {
+ return object['hasOwnProperty'].call(obj, key);
+ }
+
+ function iterateOverObject(obj, fn) {
+ var key;
+ for(key in obj) {
+ if(!hasOwnProperty(obj, key)) continue;
+ if(fn.call(obj, key, obj[key]) === false) break;
+ }
+ }
+
+ function simpleMerge(target, source) {
+ iterateOverObject(source, function(key) {
+ target[key] = source[key];
+ });
+ return target;
+ }
+
+ // Hash definition
+
+ function Hash(obj) {
+ simpleMerge(this, obj);
+ };
+
+ Hash.prototype.constructor = object;
+
+ // Number helpers
+
+ function getRange(start, stop, fn, step) {
+ var arr = [], i = parseInt(start), down = step < 0;
+ while((!down && i <= stop) || (down && i >= stop)) {
+ arr.push(i);
+ if(fn) fn.call(this, i);
+ i += step || 1;
+ }
+ return arr;
+ }
+
+ function round(val, precision, method) {
+ var fn = math[method || 'round'];
+ var multiplier = math.pow(10, math.abs(precision || 0));
+ if(precision < 0) multiplier = 1 / multiplier;
+ return fn(val * multiplier) / multiplier;
+ }
+
+ function ceil(val, precision) {
+ return round(val, precision, 'ceil');
+ }
+
+ function floor(val, precision) {
+ return round(val, precision, 'floor');
+ }
+
+ function padNumber(num, place, sign, base) {
+ var str = math.abs(num).toString(base || 10);
+ str = repeatString(place - str.replace(/\.\d+/, '').length, '0') + str;
+ if(sign || num < 0) {
+ str = (num < 0 ? '-' : '+') + str;
+ }
+ return str;
+ }
+
+ function getOrdinalizedSuffix(num) {
+ if(num >= 11 && num <= 13) {
+ return 'th';
+ } else {
+ switch(num % 10) {
+ case 1: return 'st';
+ case 2: return 'nd';
+ case 3: return 'rd';
+ default: return 'th';
+ }
+ }
+ }
+
+
+ // String helpers
+
+ // WhiteSpace/LineTerminator as defined in ES5.1 plus Unicode characters in the Space, Separator category.
+ function getTrimmableCharacters() {
+ return '\u0009\u000A\u000B\u000C\u000D\u0020\u00A0\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u2028\u2029\u3000\uFEFF';
+ }
+
+ function repeatString(times, str) {
+ return array(math.max(0, isDefined(times) ? times : 1) + 1).join(str || '');
+ }
+
+
+ // RegExp helpers
+
+ function getRegExpFlags(reg, add) {
+ var flags = reg.toString().match(/[^/]*$/)[0];
+ if(add) {
+ flags = (flags + add).split('').sort().join('').replace(/([gimy])\1+/g, '$1');
+ }
+ return flags;
+ }
+
+ function escapeRegExp(str) {
+ if(!isString(str)) str = string(str);
+ return str.replace(/([\\/'*+?|()\[\]{}.^$])/g,'\\$1');
+ }
+
+
+ // Specialized helpers
+
+
+ // Used by Array#unique and Object.equal
+
+ function stringify(thing, stack) {
+ var type = typeof thing,
+ thingIsObject,
+ thingIsArray,
+ klass, value,
+ arr, key, i;
+
+ // Return quickly if string to save cycles
+ if(type === 'string') return thing;
+
+ klass = object.prototype.toString.call(thing)
+ thingIsObject = isObject(thing);
+ thingIsArray = klass === '[object Array]';
+
+ if(thing != null && thingIsObject || thingIsArray) {
+ // This method for checking for cyclic structures was egregiously stolen from
+ // the ingenious method by @kitcambridge from the Underscore script:
+ // https://github.com/documentcloud/underscore/issues/240
+ if(!stack) stack = [];
+ // Allowing a step into the structure before triggering this
+ // script to save cycles on standard JSON structures and also to
+ // try as hard as possible to catch basic properties that may have
+ // been modified.
+ if(stack.length > 1) {
+ i = stack.length;
+ while (i--) {
+ if (stack[i] === thing) {
+ return 'CYC';
+ }
+ }
+ }
+ stack.push(thing);
+ value = string(thing.constructor);
+ arr = thingIsArray ? thing : object.keys(thing).sort();
+ for(i = 0; i < arr.length; i++) {
+ key = thingIsArray ? i : arr[i];
+ value += key + stringify(thing[key], stack);
+ }
+ stack.pop();
+ } else if(1 / thing === -Infinity) {
+ value = '-0';
+ } else {
+ value = string(thing && thing.valueOf ? thing.valueOf() : thing);
+ }
+ return type + klass + value;
+ }
+
+ function isEqual(a, b) {
+ if(objectIsMatchedByValue(a) && objectIsMatchedByValue(b)) {
+ return stringify(a) === stringify(b);
+ } else {
+ return a === b;
+ }
+ }
+
+ function objectIsMatchedByValue(obj) {
+ var klass = className(obj);
+ return klass === '[object Date]' ||
+ klass === '[object Array]' ||
+ klass === '[object String]' ||
+ klass === '[object Number]' ||
+ klass === '[object RegExp]' ||
+ klass === '[object Boolean]' ||
+ klass === '[object Arguments]' ||
+ isObject(obj);
+ }
+
+
+ // Used by Array#at and String#at
+
+ function entryAtIndex(arr, args, str) {
+ var result = [], length = arr.length, loop = args[args.length - 1] !== false, r;
+ multiArgs(args, function(index) {
+ if(isBoolean(index)) return false;
+ if(loop) {
+ index = index % length;
+ if(index < 0) index = length + index;
+ }
+ r = str ? arr.charAt(index) || '' : arr[index];
+ result.push(r);
+ });
+ return result.length < 2 ? result[0] : result;
+ }
+
+
+ // Object class methods implemented as instance methods
+
+ function buildObjectInstanceMethods(set, target) {
+ extendSimilar(target, true, false, set, function(methods, name) {
+ methods[name + (name === 'equal' ? 's' : '')] = function() {
+ return object[name].apply(null, [this].concat(multiArgs(arguments)));
+ }
+ });
+ }
+
+ initializeClasses();
+
View
2,164 release/1.3.1/precompiled/development/date.js
@@ -0,0 +1,2164 @@
+
+ /***
+ * @package Date
+ * @dependency core
+ * @description Date parsing and formatting, relative formats like "1 minute ago", Number methods like "daysAgo", localization support with default English locale definition.
+ *
+ ***/
+
+ var English;
+ var CurrentLocalization;
+
+ var TimeFormat = ['ampm','hour','minute','second','ampm','utc','offset_sign','offset_hours','offset_minutes','ampm']
+ var FloatReg = '\\d{1,2}(?:[,.]\\d+)?';
+ var RequiredTime = '({t})?\\s*('+FloatReg+')(?:{h}('+FloatReg+')?{m}(?::?('+FloatReg+'){s})?\\s*(?:({t})|(Z)|(?:([+-])(\\d{2,2})(?::?(\\d{2,2}))?)?)?|\\s*({t}))';
+
+ var KanjiDigits = '〇一二三四五六七八九十百千万';
+ var FullWidthDigits = '0123456789';
+ var AsianDigitMap = {};
+ var AsianDigitReg;
+
+ var DateArgumentUnits;
+ var DateUnitsReversed;
+ var CoreDateFormats = [];
+
+ var DateOutputFormats = [
+ {
+ token: 'f{1,4}|ms|milliseconds',
+ format: function(d) {
+ return callDateGet(d, 'Milliseconds');
+ }
+ },
+ {
+ token: 'ss?|seconds',
+ format: function(d, len) {
+ return callDateGet(d, 'Seconds');
+ }
+ },
+ {
+ token: 'mm?|minutes',
+ format: function(d, len) {
+ return callDateGet(d, 'Minutes');
+ }
+ },
+ {
+ token: 'hh?|hours|12hr',
+ format: function(d) {
+ return getShortHour(d);
+ }
+ },
+ {
+ token: 'HH?|24hr',
+ format: function(d) {
+ return callDateGet(d, 'Hours');
+ }
+ },
+ {
+ token: 'dd?|date|day',
+ format: function(d) {
+ return callDateGet(d, 'Date');
+ }
+ },
+ {
+ token: 'dow|weekday',
+ word: true,
+ format: function(d, loc, n, t) {
+ var dow = callDateGet(d, 'Day');
+ return loc['weekdays'][dow + (n - 1) * 7];
+ }
+ },
+ {
+ token: 'MM?',
+ format: function(d) {
+ return callDateGet(d, 'Month') + 1;
+ }
+ },
+ {
+ token: 'mon|month',
+ word: true,
+ format: function(d, loc, n, len) {
+ var month = callDateGet(d, 'Month');
+ return loc['months'][month + (n - 1) * 12];
+ }
+ },
+ {
+ token: 'y{2,4}|year',
+ format: function(d) {
+ return callDateGet(d, 'FullYear');
+ }
+ },
+ {
+ token: '[Tt]{1,2}',
+ format: function(d, loc, n, format) {
+ var hours = callDateGet(d, 'Hours');
+ var str = loc['ampm'][floor(hours / 12)];
+ if(format.length === 1) str = str.slice(0,1);
+ if(format.slice(0,1) === 'T') str = str.toUpperCase();
+ return str;
+ }
+ },
+ {
+ token: 'z{1,4}|tz|timezone',
+ text: true,
+ format: function(d, loc, n, format) {
+ var tz = d.getUTCOffset();
+ if(format == 'z' || format == 'zz') {
+ tz = tz.replace(/(\d{2})(\d{2})/, function(f,h,m) {
+ return padNumber(h, format.length);
+ });
+ }
+ return tz;
+ }
+ },
+ {
+ token: 'iso(tz|timezone)',
+ format: function(d) {
+ return d.getUTCOffset(true);
+ }
+ },
+ {
+ token: 'ord',
+ format: function(d) {
+ var date = callDateGet(d, 'Date');
+ return date + getOrdinalizedSuffix(date);
+ }
+ }
+ ];
+
+ var DateUnits = [
+ {
+ unit: 'year',
+ method: 'FullYear',
+ ambiguous: true,
+ multiplier: function(d) {
+ var adjust = d ? (d.isLeapYear() ? 1 : 0) : 0.25;
+ return (365 + adjust) * 24 * 60 * 60 * 1000;
+ }
+ },
+ {
+ unit: 'month',
+ method: 'Month',
+ ambiguous: true,
+ multiplier: function(d, ms) {
+ var days = 30.4375, inMonth;
+ if(d) {
+ inMonth = d.daysInMonth();
+ if(ms <= inMonth.days()) {
+ days = inMonth;
+ }
+ }
+ return days * 24 * 60 * 60 * 1000;
+ }
+ },
+ {
+ unit: 'week',
+ method: 'Week',
+ multiplier: function() {
+ return 7 * 24 * 60 * 60 * 1000;
+ }
+ },
+ {
+ unit: 'day',
+ method: 'Date',
+ ambiguous: true,
+ multiplier: function() {
+ return 24 * 60 * 60 * 1000;
+ }
+ },
+ {
+ unit: 'hour',
+ method: 'Hours',
+ multiplier: function() {
+ return 60 * 60 * 1000;
+ }
+ },
+ {
+ unit: 'minute',
+ method: 'Minutes',
+ multiplier: function() {
+ return 60 * 1000;
+ }
+ },
+ {
+ unit: 'second',
+ method: 'Seconds',
+ multiplier: function() {
+ return 1000;
+ }
+ },
+ {
+ unit: 'millisecond',
+ method: 'Milliseconds',
+ multiplier: function() {
+ return 1;
+ }
+ }
+ ];
+
+
+
+
+ // Date Localization
+
+ var Localizations = {};
+
+ // Localization object
+
+ function Localization(l) {
+ simpleMerge(this, l);
+ this.compiledFormats = CoreDateFormats.concat();
+ }
+
+ Localization.prototype = {
+
+ getMonth: function(n) {
+ if(isNumber(n)) {
+ return n - 1;
+ } else {
+ return this['months'].indexOf(n) % 12;
+ }
+ },
+
+ getWeekday: function(n) {
+ return this['weekdays'].indexOf(n) % 7;
+ },
+
+ getNumber: function(n) {
+ var i;
+ if(isNumber(n)) {
+ return n;
+ } else if(n && (i = this['numbers'].indexOf(n)) !== -1) {
+ return (i + 1) % 10;
+ } else {
+ return 1;
+ }
+ },
+
+ getNumericDate: function(n) {
+ var self = this;
+ return n.replace(regexp(this['num'], 'g'), function(d) {
+ var num = self.getNumber(d);
+ return num || '';
+ });
+ },
+
+ getEnglishUnit: function(n) {
+ return English['units'][this['units'].indexOf(n) % 8];
+ },
+
+ relative: function(adu) {
+ return this.convertAdjustedToFormat(adu, adu[2] > 0 ? 'future' : 'past');
+ },
+
+ getDuration: function(ms) {
+ return this.convertAdjustedToFormat(getAdjustedUnit(ms), 'duration');
+ },
+
+ hasVariant: function(code) {
+ code = code || this.code;
+ return code === 'en' || code === 'en-US' ? true : this['variant'];
+ },
+
+ matchAM: function(str) {
+ return str === this['ampm'][0];
+ },
+
+ matchPM: function(str) {
+ return str === this['ampm'][1];
+ },
+
+ convertAdjustedToFormat: function(adu, format) {
+ var num = adu[0], u = adu[1], ms = adu[2], sign, unit, last, mult;
+ if(this['code'] == 'ru') {
+ last = num.toString().slice(-1);
+ switch(true) {
+ case last == 1: mult = 1; break;
+ case last >= 2 && last <= 4: mult = 2; break;
+ default: mult = 3;
+ }
+ } else {
+ mult = this['plural'] && num > 1 ? 1 : 0;
+ }
+ unit = this['units'][mult * 8 + u] || this['units'][u];
+ if(this['capitalizeUnit']) unit = simpleCapitalize(unit);
+ sign = this['modifiers'].filter(function(m) { return m.name == 'sign' && m.value == (ms > 0 ? 1 : -1); })[0];
+ return this[format].replace(/\{(.*?)\}/g, function(full, match) {
+ switch(match) {
+ case 'num': return num;
+ case 'unit': return unit;
+ case 'sign': return sign.src;
+ }
+ });
+ },
+
+ getFormats: function() {
+ return this.cachedFormat ? [this.cachedFormat].concat(this.compiledFormats) : this.compiledFormats;
+ },
+
+ addFormat: function(src, allowsTime, match, variant, iso) {
+ var to = match || [], loc = this, time, timeMarkers, lastIsNumeral;
+
+ src = src.replace(/\s+/g, '[-,. ]*');
+ src = src.replace(/\{([^,]+?)\}/g, function(all, k) {
+ var opt = k.match(/\?$/), slice = k.match(/(\d)(?:-(\d))?/), nc = k.match(/^\d+$/), key = k.replace(/[^a-z]+$/, ''), value, arr;
+ if(nc) {
+ value = loc['optionals'][nc[0]];
+ } else if(loc[key]) {
+ value = loc[key];
+ } else if(loc[key + 's']) {
+ value = loc[key + 's'];
+ if(slice) {
+ // Can't use filter here as Prototype hijacks the method and doesn't
+ // pass an index, so use a simple loop instead!
+ arr = [];
+ value.forEach(function(m, i) {
+ var mod = i % (loc['units'] ? 8 : value.length);
+ if(mod >= slice[1] && mod <= (slice[2] || slice[1])) {
+ arr.push(m);
+ }
+ });
+ value = arr;
+ }
+ value = arrayToAlternates(value);
+ }
+ if(nc) {
+ return '(?:' + value + ')?';
+ } else {
+ if(!match) {
+ to.push(key);
+ }
+ return '(' + value + ')' + (opt ? '?' : '');
+ }
+ });
+ if(allowsTime) {
+ time = prepareTime(RequiredTime, loc, iso);
+ timeMarkers = ['t','[\\s\\u3000]'].concat(loc['timeMarker']);
+ lastIsNumeral = src.match(/\\d\{\d,\d\}\)+\??$/);
+ addDateInputFormat(loc, '(?:' + time + ')[,\\s\\u3000]+?' + src, TimeFormat.concat(to), variant);
+ addDateInputFormat(loc, src + '(?:[,\\s]*(?:' + timeMarkers.join('|') + (lastIsNumeral ? '+' : '*') +')' + time + ')?', to.concat(TimeFormat), variant);
+ } else {
+ addDateInputFormat(loc, src, to, variant);
+ }
+ }
+
+ };
+
+
+ // Localization helpers
+
+ function getLocalization(localeCode, fallback) {
+ var loc;
+ if(!isString(localeCode)) localeCode = '';
+ loc = Localizations[localeCode] || Localizations[localeCode.slice(0,2)];
+ if(fallback === false && !loc) {
+ throw new Error('Invalid locale.');
+ }
+ return loc || CurrentLocalization;
+ }
+
+ function setLocalization(localeCode, set) {
+ var loc, canAbbreviate;
+
+ function initializeField(name) {
+ var val = loc[name];
+ if(isString(val)) {
+ loc[name] = val.split(',');
+ } else if(!val) {
+ loc[name] = [];
+ }
+ }
+
+ function eachAlternate(str, fn) {
+ str = str.split('+').map(function(split) {
+ return split.replace(/(.+):(.+)$/, function(full, base, suffixes) {
+ return suffixes.split('|').map(function(suffix) {
+ return base + suffix;
+ }).join('|');
+ });
+ }).join('|');
+ return str.split('|').forEach(fn);
+ }
+
+ function setArray(name, abbreviate, multiple) {
+ var arr = [];
+ loc[name].forEach(function(full, i) {
+ if(abbreviate) {
+ full += '+' + full.slice(0,3);
+ }
+ eachAlternate(full, function(day, j) {
+ arr[j * multiple + i] = day.toLowerCase();
+ });
+ });
+ loc[name] = arr;
+ }
+
+ function getDigit(start, stop, allowNumbers) {
+ var str = '\\d{' + start + ',' + stop + '}';
+ if(allowNumbers) str += '|(?:' + arrayToAlternates(loc['numbers']) + ')+';
+ return str;
+ }
+
+ function getNum() {
+ var arr = ['\\d+'].concat(loc['articles']);
+ if(loc['numbers']) arr = arr.concat(loc['numbers']);
+ return arrayToAlternates(arr);
+ }
+
+ function setModifiers() {
+ var arr = [];
+ loc.modifiersByName = {};
+ loc['modifiers'].forEach(function(modifier) {
+ var name = modifier.name;
+ eachAlternate(modifier.src, function(t) {
+ var locEntry = loc[name];
+ loc.modifiersByName[t] = modifier;
+ arr.push({ name: name, src: t, value: modifier.value });
+ loc[name] = locEntry ? locEntry + '|' + t : t;
+ });
+ });
+ loc['day'] += '|' + arrayToAlternates(loc['weekdays']);
+ loc['modifiers'] = arr;
+ }
+
+ // Initialize the locale
+ loc = new Localization(set);
+ initializeField('modifiers');
+ 'months,weekdays,units,numbers,articles,optionals,timeMarker,ampm,timeSuffixes,dateParse,timeParse'.split(',').forEach(initializeField);
+
+ canAbbreviate = !loc['monthSuffix'];
+
+ setArray('months', canAbbreviate, 12);
+ setArray('weekdays', canAbbreviate, 7);
+ setArray('units', false, 8);
+ setArray('numbers', false, 10);
+
+ loc['code'] = localeCode;
+ loc['date'] = getDigit(1,2, loc['digitDate']);
+ loc['year'] = getDigit(4,4);
+ loc['num'] = getNum();
+
+ setModifiers();
+
+ if(loc['monthSuffix']) {
+ loc['month'] = getDigit(1,2);
+ loc['months'] = getRange(1, 12).map(function(n) { return n + loc['monthSuffix']; });
+ }
+ loc['full_month'] = getDigit(1,2) + '|' + arrayToAlternates(loc['months']);
+
+ // The order of these formats is very important. Order is reversed so formats that come
+ // later will take precedence over formats that come before. This generally means that
+ // more specific formats should come later, however, the {year} format should come before
+ // {day}, as 2011 needs to be parsed as a year (2011) and not date (20) + hours (11)
+
+ // If the locale has time suffixes then add a time only format for that locale
+ // that is separate from the core English-based one.
+ if(loc['timeSuffixes'].length > 0) {
+ loc.addFormat(prepareTime(RequiredTime, loc), false, TimeFormat)
+ }
+
+ loc.addFormat('{day}', true);
+ loc.addFormat('{month}' + (loc['monthSuffix'] || ''));
+ loc.addFormat('{year}' + (loc['yearSuffix'] || ''));
+
+ loc['timeParse'].forEach(function(src) {
+ loc.addFormat(src, true);
+ });
+
+ loc['dateParse'].forEach(function(src) {
+ loc.addFormat(src);
+ });
+
+ return Localizations[localeCode] = loc;
+ }
+
+
+ // General helpers
+
+ function addDateInputFormat(locale, format, match, variant) {
+ locale.compiledFormats.unshift({
+ variant: variant,
+ locale: locale,
+ reg: regexp('^' + format + '$', 'i'),
+ to: match
+ });
+ }
+
+ function simpleCapitalize(str) {
+ return str.slice(0,1).toUpperCase() + str.slice(1);
+ }
+
+ function arrayToAlternates(arr) {
+ return arr.filter(function(el) {
+ return !!el;
+ }).join('|');
+ }
+
+ // Date argument helpers
+
+ function collectDateArguments(args, allowDuration) {
+ var obj, arr;
+ if(isObject(args[0])) {
+ return args;
+ } else if (isNumber(args[0]) && !isNumber(args[1])) {
+ return [args[0]];
+ } else if (isString(args[0]) && allowDuration) {
+ return [getDateParamsFromString(args[0]), args[1]];
+ }
+ obj = {};
+ DateArgumentUnits.forEach(function(u,i) {
+ obj[u.unit] = args[i];
+ });
+ return [obj];
+ }
+
+ function getDateParamsFromString(str, num) {
+ var params = {};
+ match = str.match(/^(\d+)?\s?(\w+?)s?$/i);
+ if(match) {
+ if(isUndefined(num)) {
+ num = parseInt(match[1]) || 1;
+ }
+ params[match[2].toLowerCase()] = num;
+ }
+ return params;
+ }
+
+ // Date parsing helpers
+
+ function getFormatMatch(match, arr) {
+ var obj = {}, value, num;
+ arr.forEach(function(key, i) {
+ value = match[i + 1];
+ if(isUndefined(value) || value === '') return;
+ if(key === 'year') obj.yearAsString = value;
+ num = parseFloat(value.replace(/,/, '.'));
+ obj[key] = !isNaN(num) ? num : value.toLowerCase();
+ });
+ return obj;
+ }
+
+ function cleanDateInput(str) {
+ str = str.trim().replace(/^(just )?now|\.+$/i, '');
+ return convertAsianDigits(str);
+ }
+
+ function convertAsianDigits(str) {
+ return str.replace(AsianDigitReg, function(full, disallowed, match) {
+ var sum = 0, place = 1, lastWasHolder, lastHolder;
+ if(disallowed) return full;
+ match.split('').reverse().forEach(function(letter) {
+ var value = AsianDigitMap[letter], holder = value > 9;
+ if(holder) {
+ if(lastWasHolder) sum += place;
+ place *= value / (lastHolder || 1);
+ lastHolder = value;
+ } else {
+ if(lastWasHolder === false) {
+ place *= 10;
+ }
+ sum += place * value;
+ }
+ lastWasHolder = holder;
+ });
+ if(lastWasHolder) sum += place;
+ return sum;
+ });
+ }
+
+ function getExtendedDate(f, localeCode, prefer, forceUTC) {
+ var d = new date(), relative = false, baseLocalization, loc, format, set, unit, weekday, num, tmp, after;
+
+ d.utc(forceUTC);
+
+ if(isDate(f)) {
+ d = f.clone();
+ } else if(isNumber(f)) {
+ d = new date(f);
+ } else if(isObject(f)) {
+ d.set(f, true);
+ set = f;
+ } else if(isString(f)) {
+
+ // The act of getting the localization will pre-initialize
+ // if it is missing and add the required formats.
+ baseLocalization = getLocalization(localeCode);
+
+ // Clean the input and convert Kanji based numerals if they exist.
+ f = cleanDateInput(f);
+
+ if(baseLocalization) {
+ iterateOverObject(baseLocalization.getFormats(), function(i, dif) {
+ var match = f.match(dif.reg);
+ if(match) {
+ format = dif;
+ loc = format.locale;
+ set = getFormatMatch(match, format.to, loc);
+
+ if(set['utc']) {
+ d.utc();
+ }
+
+ loc.cachedFormat = format;
+
+ if(set.timestamp) {
+ set = set.timestamp;
+ return false;
+ }
+
+ // If there's a variant (crazy Endian American format), swap the month and day.
+ if(format.variant && !isString(set['month']) && (isString(set['date']) || baseLocalization.hasVariant(localeCode))) {
+ tmp = set['month'];
+ set['month'] = set['date'];
+ set['date'] = tmp;
+ }
+
+ // If the year is 2 digits then get the implied century.
+ if(set['year'] && set.yearAsString.length === 2) {
+ set['year'] = getYearFromAbbreviation(set['year']);
+ }
+
+ // Set the month which may be localized.
+ if(set['month']) {
+ set['month'] = loc.getMonth(set['month']);
+ if(set['shift'] && !set['unit']) set['unit'] = loc['units'][7];
+ }
+
+ // If there is both a weekday and a date, the date takes precedence.
+ if(set['weekday'] && set['date']) {
+ delete set['weekday'];
+ // Otherwise set a localized weekday.
+ } else if(set['weekday']) {
+ set['weekday'] = loc.getWeekday(set['weekday']);
+ if(set['shift'] && !set['unit']) set['unit'] = loc['units'][5];
+ }
+
+ // Relative day localizations such as "today" and "tomorrow".
+ if(set['day'] && (tmp = loc.modifiersByName[set['day']])) {
+ set['day'] = tmp.value;
+ d.reset();
+ relative = true;
+ // If the day is a weekday, then set that instead.
+ } else if(set['day'] && (weekday = loc.getWeekday(set['day'])) > -1) {
+ delete set['day'];
+ if(set['num'] && set['month']) {
+ // If we have "the 2nd tuesday of June", set the day to the beginning of the month, then
+ // look ahead to set the weekday after all other properties have been set. The weekday needs
+ // to be set after the actual set because it requires overriding the "prefer" argument which
+ // could unintentionally send the year into the future, past, etc.
+ after = function() {
+ var w = d.getWeekday();
+ d.setWeekday((7 * (set['num'] - 1)) + (w > weekday ? weekday + 7 : weekday));
+ }
+ set['day'] = 1;
+ } else {
+ set['weekday'] = weekday;
+ }
+ }
+
+ if(set['date'] && !isNumber(set['date'])) {
+ set['date'] = loc.getNumericDate(set['date']);
+ }
+
+ // If the time is 1pm-11pm advance the time by 12 hours.
+ if(loc.matchPM(set['ampm']) && set['hour'] < 12) {
+ set['hour'] += 12;
+ } else if(loc.matchAM(set['ampm']) && set['hour'] === 12) {
+ set['hour'] = 0;
+ }
+
+ // Adjust for timezone offset
+ if('offset_hours' in set || 'offset_minutes' in set) {
+ d.utc();
+ set['offset_minutes'] = set['offset_minutes'] || 0;
+ set['offset_minutes'] += set['offset_hours'] * 60;
+ if(set['offset_sign'] === '-') {
+ set['offset_minutes'] *= -1;
+ }
+ set['minute'] -= set['offset_minutes'];
+ }
+
+ // Date has a unit like "days", "months", etc. are all relative to the current date.
+ if(set['unit']) {
+ relative = true;
+ num = loc.getNumber(set['num']);
+ unit = loc.getEnglishUnit(set['unit']);
+
+ // Shift and unit, ie "next month", "last week", etc.
+ if(set['shift'] || set['edge']) {
+ num *= (tmp = loc.modifiersByName[set['shift']]) ? tmp.value : 0;
+
+ // Relative month and static date: "the 15th of last month"
+ if(unit === 'month' && isDefined(set['date'])) {
+ d.set({ 'day': set['date'] }, true);
+ delete set['date'];
+ }
+