OLD | NEW |
| (Empty) |
1 /* | |
2 * Sugar Library v1.4.1 | |
3 * | |
4 * Freely distributable and licensed under the MIT-style license. | |
5 * Copyright (c) 2013 Andrew Plummer | |
6 * http://sugarjs.com/ | |
7 * | |
8 * ---------------------------- */ | |
9 (function(){ | |
10 'use strict'; | |
11 | |
12 /*** | |
13 * @package Core | |
14 * @description Internal utility and common methods. | |
15 ***/ | |
16 | |
17 | |
18 // A few optimizations for Google Closure Compiler will save us a couple kb in
the release script. | |
19 var object = Object, array = Array, regexp = RegExp, date = Date, string = Str
ing, number = Number, math = Math, Undefined; | |
20 | |
21 // The global context | |
22 var globalContext = typeof global !== 'undefined' ? global : this; | |
23 | |
24 // Internal toString | |
25 var internalToString = object.prototype.toString; | |
26 | |
27 // Internal hasOwnProperty | |
28 var internalHasOwnProperty = object.prototype.hasOwnProperty; | |
29 | |
30 // defineProperty exists in IE8 but will error when trying to define a propert
y on | |
31 // native objects. IE8 does not have defineProperies, however, so this check s
aves a try/catch block. | |
32 var definePropertySupport = object.defineProperty && object.defineProperties; | |
33 | |
34 // Are regexes type function? | |
35 var regexIsFunction = typeof regexp() === 'function'; | |
36 | |
37 // Do strings have no keys? | |
38 var noKeysInStringObjects = !('0' in new string('a')); | |
39 | |
40 // Type check methods need a way to be accessed dynamically. | |
41 var typeChecks = {}; | |
42 | |
43 // Classes that can be matched by value | |
44 var matchedByValueReg = /^\[object Date|Array|String|Number|RegExp|Boolean|Arg
uments\]$/; | |
45 | |
46 // Class initializers and class helpers | |
47 var ClassNames = 'Boolean,Number,String,Array,Date,RegExp,Function'.split(',')
; | |
48 | |
49 var isBoolean = buildPrimitiveClassCheck('boolean', ClassNames[0]); | |
50 var isNumber = buildPrimitiveClassCheck('number', ClassNames[1]); | |
51 var isString = buildPrimitiveClassCheck('string', ClassNames[2]); | |
52 | |
53 var isArray = buildClassCheck(ClassNames[3]); | |
54 var isDate = buildClassCheck(ClassNames[4]); | |
55 var isRegExp = buildClassCheck(ClassNames[5]); | |
56 | |
57 | |
58 // Wanted to enhance performance here by using simply "typeof" | |
59 // but Firefox has two major issues that make this impossible, | |
60 // one fixed, the other not. Despite being typeof "function" | |
61 // the objects below still report in as [object Function], so | |
62 // we need to perform a full class check here. | |
63 // | |
64 // 1. Regexes can be typeof "function" in FF < 3 | |
65 // https://bugzilla.mozilla.org/show_bug.cgi?id=61911 (fixed) | |
66 // | |
67 // 2. HTMLEmbedElement and HTMLObjectElement are be typeof "function" | |
68 // https://bugzilla.mozilla.org/show_bug.cgi?id=268945 (won't fix) | |
69 // | |
70 var isFunction = buildClassCheck(ClassNames[6]); | |
71 | |
72 function isClass(obj, klass, cached) { | |
73 var k = cached || className(obj); | |
74 return k === '[object '+klass+']'; | |
75 } | |
76 | |
77 function buildClassCheck(klass) { | |
78 var fn = (klass === 'Array' && array.isArray) || function(obj, cached) { | |
79 return isClass(obj, klass, cached); | |
80 }; | |
81 typeChecks[klass] = fn; | |
82 return fn; | |
83 } | |
84 | |
85 function buildPrimitiveClassCheck(type, klass) { | |
86 var fn = function(obj) { | |
87 if(isObjectType(obj)) { | |
88 return isClass(obj, klass); | |
89 } | |
90 return typeof obj === type; | |
91 } | |
92 typeChecks[klass] = fn; | |
93 return fn; | |
94 } | |
95 | |
96 function className(obj) { | |
97 return internalToString.call(obj); | |
98 } | |
99 | |
100 function initializeClasses() { | |
101 initializeClass(object); | |
102 iterateOverObject(ClassNames, function(i,name) { | |
103 initializeClass(globalContext[name]); | |
104 }); | |
105 } | |
106 | |
107 function initializeClass(klass) { | |
108 if(klass['SugarMethods']) return; | |
109 defineProperty(klass, 'SugarMethods', {}); | |
110 extend(klass, false, true, { | |
111 'extend': function(methods, override, instance) { | |
112 extend(klass, instance !== false, override, methods); | |
113 }, | |
114 'sugarRestore': function() { | |
115 return batchMethodExecute(this, klass, arguments, function(target, name,
m) { | |
116 defineProperty(target, name, m.method); | |
117 }); | |
118 }, | |
119 'sugarRevert': function() { | |
120 return batchMethodExecute(this, klass, arguments, function(target, name,
m) { | |
121 if(m['existed']) { | |
122 defineProperty(target, name, m['original']); | |
123 } else { | |
124 delete target[name]; | |
125 } | |
126 }); | |
127 } | |
128 }); | |
129 } | |
130 | |
131 // Class extending methods | |
132 | |
133 function extend(klass, instance, override, methods) { | |
134 var extendee = instance ? klass.prototype : klass; | |
135 initializeClass(klass); | |
136 iterateOverObject(methods, function(name, extendedFn) { | |
137 var nativeFn = extendee[name], | |
138 existed = hasOwnProperty(extendee, name); | |
139 if(isFunction(override) && nativeFn) { | |
140 extendedFn = wrapNative(nativeFn, extendedFn, override); | |
141 } | |
142 if(override !== false || !nativeFn) { | |
143 defineProperty(extendee, name, extendedFn); | |
144 } | |
145 // If the method is internal to Sugar, then | |
146 // store a reference so it can be restored later. | |
147 klass['SugarMethods'][name] = { | |
148 'method': extendedFn, | |
149 'existed': existed, | |
150 'original': nativeFn, | |
151 'instance': instance | |
152 }; | |
153 }); | |
154 } | |
155 | |
156 function extendSimilar(klass, instance, override, set, fn) { | |
157 var methods = {}; | |
158 set = isString(set) ? set.split(',') : set; | |
159 set.forEach(function(name, i) { | |
160 fn(methods, name, i); | |
161 }); | |
162 extend(klass, instance, override, methods); | |
163 } | |
164 | |
165 function batchMethodExecute(target, klass, args, fn) { | |
166 var all = args.length === 0, methods = multiArgs(args), changed = false; | |
167 iterateOverObject(klass['SugarMethods'], function(name, m) { | |
168 if(all || methods.indexOf(name) !== -1) { | |
169 changed = true; | |
170 fn(m['instance'] ? target.prototype : target, name, m); | |
171 } | |
172 }); | |
173 return changed; | |
174 } | |
175 | |
176 function wrapNative(nativeFn, extendedFn, condition) { | |
177 return function(a) { | |
178 return condition.apply(this, arguments) ? | |
179 extendedFn.apply(this, arguments) : | |
180 nativeFn.apply(this, arguments); | |
181 } | |
182 } | |
183 | |
184 function defineProperty(target, name, method) { | |
185 if(definePropertySupport) { | |
186 object.defineProperty(target, name, { | |
187 'value': method, | |
188 'configurable': true, | |
189 'enumerable': false, | |
190 'writable': true | |
191 }); | |
192 } else { | |
193 target[name] = method; | |
194 } | |
195 } | |
196 | |
197 | |
198 // Argument helpers | |
199 | |
200 function multiArgs(args, fn, from) { | |
201 var result = [], i = from || 0, len; | |
202 for(len = args.length; i < len; i++) { | |
203 result.push(args[i]); | |
204 if(fn) fn.call(args, args[i], i); | |
205 } | |
206 return result; | |
207 } | |
208 | |
209 function flattenedArgs(args, fn, from) { | |
210 var arg = args[from || 0]; | |
211 if(isArray(arg)) { | |
212 args = arg; | |
213 from = 0; | |
214 } | |
215 return multiArgs(args, fn, from); | |
216 } | |
217 | |
218 function checkCallback(fn) { | |
219 if(!fn || !fn.call) { | |
220 throw new TypeError('Callback is not callable'); | |
221 } | |
222 } | |
223 | |
224 | |
225 // General helpers | |
226 | |
227 function isDefined(o) { | |
228 return o !== Undefined; | |
229 } | |
230 | |
231 function isUndefined(o) { | |
232 return o === Undefined; | |
233 } | |
234 | |
235 | |
236 // Object helpers | |
237 | |
238 function hasProperty(obj, prop) { | |
239 return !isPrimitiveType(obj) && prop in obj; | |
240 } | |
241 | |
242 function hasOwnProperty(obj, prop) { | |
243 return !!obj && internalHasOwnProperty.call(obj, prop); | |
244 } | |
245 | |
246 function isObjectType(obj) { | |
247 // 1. Check for null | |
248 // 2. Check for regexes in environments where they are "functions". | |
249 return !!obj && (typeof obj === 'object' || (regexIsFunction && isRegExp(obj
))); | |
250 } | |
251 | |
252 function isPrimitiveType(obj) { | |
253 var type = typeof obj; | |
254 return obj == null || type === 'string' || type === 'number' || type === 'bo
olean'; | |
255 } | |
256 | |
257 function isPlainObject(obj, klass) { | |
258 klass = klass || className(obj); | |
259 try { | |
260 // Not own constructor property must be Object | |
261 // This code was borrowed from jQuery.isPlainObject | |
262 if (obj && obj.constructor && | |
263 !hasOwnProperty(obj, 'constructor') && | |
264 !hasOwnProperty(obj.constructor.prototype, 'isPrototypeOf')) { | |
265 return false; | |
266 } | |
267 } catch (e) { | |
268 // IE8,9 Will throw exceptions on certain host objects. | |
269 return false; | |
270 } | |
271 // === on the constructor is not safe across iframes | |
272 // 'hasOwnProperty' ensures that the object also inherits | |
273 // from Object, which is false for DOMElements in IE. | |
274 return !!obj && klass === '[object Object]' && 'hasOwnProperty' in obj; | |
275 } | |
276 | |
277 function iterateOverObject(obj, fn) { | |
278 var key; | |
279 for(key in obj) { | |
280 if(!hasOwnProperty(obj, key)) continue; | |
281 if(fn.call(obj, key, obj[key], obj) === false) break; | |
282 } | |
283 } | |
284 | |
285 function simpleRepeat(n, fn) { | |
286 for(var i = 0; i < n; i++) { | |
287 fn(i); | |
288 } | |
289 } | |
290 | |
291 function simpleMerge(target, source) { | |
292 iterateOverObject(source, function(key) { | |
293 target[key] = source[key]; | |
294 }); | |
295 return target; | |
296 } | |
297 | |
298 // Make primtives types like strings into objects. | |
299 function coercePrimitiveToObject(obj) { | |
300 if(isPrimitiveType(obj)) { | |
301 obj = object(obj); | |
302 } | |
303 if(noKeysInStringObjects && isString(obj)) { | |
304 forceStringCoercion(obj); | |
305 } | |
306 return obj; | |
307 } | |
308 | |
309 // Force strings to have their indexes set in | |
310 // environments that don't do this automatically. | |
311 function forceStringCoercion(obj) { | |
312 var i = 0, chr; | |
313 while(chr = obj.charAt(i)) { | |
314 obj[i++] = chr; | |
315 } | |
316 } | |
317 | |
318 // Hash definition | |
319 | |
320 function Hash(obj) { | |
321 simpleMerge(this, coercePrimitiveToObject(obj)); | |
322 }; | |
323 | |
324 Hash.prototype.constructor = object; | |
325 | |
326 // Math helpers | |
327 | |
328 var abs = math.abs; | |
329 var pow = math.pow; | |
330 var ceil = math.ceil; | |
331 var floor = math.floor; | |
332 var round = math.round; | |
333 var min = math.min; | |
334 var max = math.max; | |
335 | |
336 function withPrecision(val, precision, fn) { | |
337 var multiplier = pow(10, abs(precision || 0)); | |
338 fn = fn || round; | |
339 if(precision < 0) multiplier = 1 / multiplier; | |
340 return fn(val * multiplier) / multiplier; | |
341 } | |
342 | |
343 // Full width number helpers | |
344 | |
345 var HalfWidthZeroCode = 0x30; | |
346 var HalfWidthNineCode = 0x39; | |
347 var FullWidthZeroCode = 0xff10; | |
348 var FullWidthNineCode = 0xff19; | |
349 | |
350 var HalfWidthPeriod = '.'; | |
351 var FullWidthPeriod = '.'; | |
352 var HalfWidthComma = ','; | |
353 | |
354 // Used here and later in the Date package. | |
355 var FullWidthDigits = ''; | |
356 | |
357 var NumberNormalizeMap = {}; | |
358 var NumberNormalizeReg; | |
359 | |
360 function codeIsNumeral(code) { | |
361 return (code >= HalfWidthZeroCode && code <= HalfWidthNineCode) || | |
362 (code >= FullWidthZeroCode && code <= FullWidthNineCode); | |
363 } | |
364 | |
365 function buildNumberHelpers() { | |
366 var digit, i; | |
367 for(i = 0; i <= 9; i++) { | |
368 digit = chr(i + FullWidthZeroCode); | |
369 FullWidthDigits += digit; | |
370 NumberNormalizeMap[digit] = chr(i + HalfWidthZeroCode); | |
371 } | |
372 NumberNormalizeMap[HalfWidthComma] = ''; | |
373 NumberNormalizeMap[FullWidthPeriod] = HalfWidthPeriod; | |
374 // Mapping this to itself to easily be able to easily | |
375 // capture it in stringToNumber to detect decimals later. | |
376 NumberNormalizeMap[HalfWidthPeriod] = HalfWidthPeriod; | |
377 NumberNormalizeReg = regexp('[' + FullWidthDigits + FullWidthPeriod + HalfWi
dthComma + HalfWidthPeriod + ']', 'g'); | |
378 } | |
379 | |
380 // String helpers | |
381 | |
382 function chr(num) { | |
383 return string.fromCharCode(num); | |
384 } | |
385 | |
386 // WhiteSpace/LineTerminator as defined in ES5.1 plus Unicode characters in th
e Space, Separator category. | |
387 function getTrimmableCharacters() { | |
388 return '\u0009\u000A\u000B\u000C\u000D\u0020\u00A0\u1680\u180E\u2000\u2001\u
2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u2028\u2029\u30
00\uFEFF'; | |
389 } | |
390 | |
391 function repeatString(str, num) { | |
392 var result = '', str = str.toString(); | |
393 while (num > 0) { | |
394 if (num & 1) { | |
395 result += str; | |
396 } | |
397 if (num >>= 1) { | |
398 str += str; | |
399 } | |
400 } | |
401 return result; | |
402 } | |
403 | |
404 // Returns taking into account full-width characters, commas, and decimals. | |
405 function stringToNumber(str, base) { | |
406 var sanitized, isDecimal; | |
407 sanitized = str.replace(NumberNormalizeReg, function(chr) { | |
408 var replacement = NumberNormalizeMap[chr]; | |
409 if(replacement === HalfWidthPeriod) { | |
410 isDecimal = true; | |
411 } | |
412 return replacement; | |
413 }); | |
414 return isDecimal ? parseFloat(sanitized) : parseInt(sanitized, base || 10); | |
415 } | |
416 | |
417 | |
418 // Used by Number and Date | |
419 | |
420 function padNumber(num, place, sign, base) { | |
421 var str = abs(num).toString(base || 10); | |
422 str = repeatString('0', place - str.replace(/\.\d+/, '').length) + str; | |
423 if(sign || num < 0) { | |
424 str = (num < 0 ? '-' : '+') + str; | |
425 } | |
426 return str; | |
427 } | |
428 | |
429 function getOrdinalizedSuffix(num) { | |
430 if(num >= 11 && num <= 13) { | |
431 return 'th'; | |
432 } else { | |
433 switch(num % 10) { | |
434 case 1: return 'st'; | |
435 case 2: return 'nd'; | |
436 case 3: return 'rd'; | |
437 default: return 'th'; | |
438 } | |
439 } | |
440 } | |
441 | |
442 | |
443 // RegExp helpers | |
444 | |
445 function getRegExpFlags(reg, add) { | |
446 var flags = ''; | |
447 add = add || ''; | |
448 function checkFlag(prop, flag) { | |
449 if(prop || add.indexOf(flag) > -1) { | |
450 flags += flag; | |
451 } | |
452 } | |
453 checkFlag(reg.multiline, 'm'); | |
454 checkFlag(reg.ignoreCase, 'i'); | |
455 checkFlag(reg.global, 'g'); | |
456 checkFlag(reg.sticky, 'y'); | |
457 return flags; | |
458 } | |
459 | |
460 function escapeRegExp(str) { | |
461 if(!isString(str)) str = string(str); | |
462 return str.replace(/([\\/\'*+?|()\[\]{}.^$])/g,'\\$1'); | |
463 } | |
464 | |
465 | |
466 // Date helpers | |
467 | |
468 function callDateGet(d, method) { | |
469 return d['get' + (d._utc ? 'UTC' : '') + method](); | |
470 } | |
471 | |
472 function callDateSet(d, method, value) { | |
473 return d['set' + (d._utc && method != 'ISOWeek' ? 'UTC' : '') + method](valu
e); | |
474 } | |
475 | |
476 // Used by Array#unique and Object.equal | |
477 | |
478 function stringify(thing, stack) { | |
479 var type = typeof thing, | |
480 thingIsObject, | |
481 thingIsArray, | |
482 klass, value, | |
483 arr, key, i, len; | |
484 | |
485 // Return quickly if string to save cycles | |
486 if(type === 'string') return thing; | |
487 | |
488 klass = internalToString.call(thing) | |
489 thingIsObject = isPlainObject(thing, klass); | |
490 thingIsArray = isArray(thing, klass); | |
491 | |
492 if(thing != null && thingIsObject || thingIsArray) { | |
493 // This method for checking for cyclic structures was egregiously stolen f
rom | |
494 // the ingenious method by @kitcambridge from the Underscore script: | |
495 // https://github.com/documentcloud/underscore/issues/240 | |
496 if(!stack) stack = []; | |
497 // Allowing a step into the structure before triggering this | |
498 // script to save cycles on standard JSON structures and also to | |
499 // try as hard as possible to catch basic properties that may have | |
500 // been modified. | |
501 if(stack.length > 1) { | |
502 i = stack.length; | |
503 while (i--) { | |
504 if (stack[i] === thing) { | |
505 return 'CYC'; | |
506 } | |
507 } | |
508 } | |
509 stack.push(thing); | |
510 value = thing.valueOf() + string(thing.constructor); | |
511 arr = thingIsArray ? thing : object.keys(thing).sort(); | |
512 for(i = 0, len = arr.length; i < len; i++) { | |
513 key = thingIsArray ? i : arr[i]; | |
514 value += key + stringify(thing[key], stack); | |
515 } | |
516 stack.pop(); | |
517 } else if(1 / thing === -Infinity) { | |
518 value = '-0'; | |
519 } else { | |
520 value = string(thing && thing.valueOf ? thing.valueOf() : thing); | |
521 } | |
522 return type + klass + value; | |
523 } | |
524 | |
525 function isEqual(a, b) { | |
526 if(a === b) { | |
527 // Return quickly up front when matching by reference, | |
528 // but be careful about 0 !== -0. | |
529 return a !== 0 || 1 / a === 1 / b; | |
530 } else if(objectIsMatchedByValue(a) && objectIsMatchedByValue(b)) { | |
531 return stringify(a) === stringify(b); | |
532 } | |
533 return false; | |
534 } | |
535 | |
536 function objectIsMatchedByValue(obj) { | |
537 // Only known objects are matched by value. This is notably excluding functi
ons, DOM Elements, and instances of | |
538 // user-created classes. The latter can arguably be matched by value, but di
stinguishing between these and | |
539 // host objects -- which should never be compared by value -- is very tricky
so not dealing with it here. | |
540 var klass = className(obj); | |
541 return matchedByValueReg.test(klass) || isPlainObject(obj, klass); | |
542 } | |
543 | |
544 | |
545 // Used by Array#at and String#at | |
546 | |
547 function getEntriesForIndexes(obj, args, isString) { | |
548 var result, | |
549 length = obj.length, | |
550 argsLen = args.length, | |
551 overshoot = args[argsLen - 1] !== false, | |
552 multiple = argsLen > (overshoot ? 1 : 2); | |
553 if(!multiple) { | |
554 return entryAtIndex(obj, length, args[0], overshoot, isString); | |
555 } | |
556 result = []; | |
557 multiArgs(args, function(index) { | |
558 if(isBoolean(index)) return false; | |
559 result.push(entryAtIndex(obj, length, index, overshoot, isString)); | |
560 }); | |
561 return result; | |
562 } | |
563 | |
564 function entryAtIndex(obj, length, index, overshoot, isString) { | |
565 if(overshoot) { | |
566 index = index % length; | |
567 if(index < 0) index = length + index; | |
568 } | |
569 return isString ? obj.charAt(index) : obj[index]; | |
570 } | |
571 | |
572 | |
573 // Object class methods implemented as instance methods | |
574 | |
575 function buildObjectInstanceMethods(set, target) { | |
576 extendSimilar(target, true, false, set, function(methods, name) { | |
577 methods[name + (name === 'equal' ? 's' : '')] = function() { | |
578 return object[name].apply(null, [this].concat(multiArgs(arguments))); | |
579 } | |
580 }); | |
581 } | |
582 | |
583 initializeClasses(); | |
584 buildNumberHelpers(); | |
585 | |
586 | |
587 /*** | |
588 * @package ES5 | |
589 * @description Shim methods that provide ES5 compatible functionality. This p
ackage can be excluded if you do not require legacy browser support (IE8 and bel
ow). | |
590 * | |
591 ***/ | |
592 | |
593 | |
594 /*** | |
595 * Object module | |
596 * | |
597 ***/ | |
598 | |
599 extend(object, false, false, { | |
600 | |
601 'keys': function(obj) { | |
602 var keys = []; | |
603 if(!isObjectType(obj) && !isRegExp(obj) && !isFunction(obj)) { | |
604 throw new TypeError('Object required'); | |
605 } | |
606 iterateOverObject(obj, function(key, value) { | |
607 keys.push(key); | |
608 }); | |
609 return keys; | |
610 } | |
611 | |
612 }); | |
613 | |
614 | |
615 /*** | |
616 * Array module | |
617 * | |
618 ***/ | |
619 | |
620 // ECMA5 methods | |
621 | |
622 function arrayIndexOf(arr, search, fromIndex, increment) { | |
623 var length = arr.length, | |
624 fromRight = increment == -1, | |
625 start = fromRight ? length - 1 : 0, | |
626 index = toIntegerWithDefault(fromIndex, start); | |
627 if(index < 0) { | |
628 index = length + index; | |
629 } | |
630 if((!fromRight && index < 0) || (fromRight && index >= length)) { | |
631 index = start; | |
632 } | |
633 while((fromRight && index >= 0) || (!fromRight && index < length)) { | |
634 if(arr[index] === search) { | |
635 return index; | |
636 } | |
637 index += increment; | |
638 } | |
639 return -1; | |
640 } | |
641 | |
642 function arrayReduce(arr, fn, initialValue, fromRight) { | |
643 var length = arr.length, count = 0, defined = isDefined(initialValue), resul
t, index; | |
644 checkCallback(fn); | |
645 if(length == 0 && !defined) { | |
646 throw new TypeError('Reduce called on empty array with no initial value'); | |
647 } else if(defined) { | |
648 result = initialValue; | |
649 } else { | |
650 result = arr[fromRight ? length - 1 : count]; | |
651 count++; | |
652 } | |
653 while(count < length) { | |
654 index = fromRight ? length - count - 1 : count; | |
655 if(index in arr) { | |
656 result = fn(result, arr[index], index, arr); | |
657 } | |
658 count++; | |
659 } | |
660 return result; | |
661 } | |
662 | |
663 function toIntegerWithDefault(i, d) { | |
664 if(isNaN(i)) { | |
665 return d; | |
666 } else { | |
667 return parseInt(i >> 0); | |
668 } | |
669 } | |
670 | |
671 function checkFirstArgumentExists(args) { | |
672 if(args.length === 0) { | |
673 throw new TypeError('First argument must be defined'); | |
674 } | |
675 } | |
676 | |
677 | |
678 | |
679 | |
680 extend(array, false, false, { | |
681 | |
682 /*** | |
683 * | |
684 * @method Array.isArray(<obj>) | |
685 * @returns Boolean | |
686 * @short Returns true if <obj> is an Array. | |
687 * @extra This method is provided for browsers that don't support it interna
lly. | |
688 * @example | |
689 * | |
690 * Array.isArray(3) -> false | |
691 * Array.isArray(true) -> false | |
692 * Array.isArray('wasabi') -> false | |
693 * Array.isArray([1,2,3]) -> true | |
694 * | |
695 ***/ | |
696 'isArray': function(obj) { | |
697 return isArray(obj); | |
698 } | |
699 | |
700 }); | |
701 | |
702 | |
703 extend(array, true, false, { | |
704 | |
705 /*** | |
706 * @method every(<f>, [scope]) | |
707 * @returns Boolean | |
708 * @short Returns true if all elements in the array match <f>. | |
709 * @extra [scope] is the %this% object. %all% is provided an alias. In addit
ion to providing this method for browsers that don't support it natively, this m
ethod also implements @array_matching. | |
710 * @example | |
711 * | |
712 + ['a','a','a'].every(function(n) { | |
713 * return n == 'a'; | |
714 * }); | |
715 * ['a','a','a'].every('a') -> true | |
716 * [{a:2},{a:2}].every({a:2}) -> true | |
717 ***/ | |
718 'every': function(fn, scope) { | |
719 var length = this.length, index = 0; | |
720 checkFirstArgumentExists(arguments); | |
721 while(index < length) { | |
722 if(index in this && !fn.call(scope, this[index], index, this)) { | |
723 return false; | |
724 } | |
725 index++; | |
726 } | |
727 return true; | |
728 }, | |
729 | |
730 /*** | |
731 * @method some(<f>, [scope]) | |
732 * @returns Boolean | |
733 * @short Returns true if any element in the array matches <f>. | |
734 * @extra [scope] is the %this% object. %any% is provided as an alias. In ad
dition to providing this method for browsers that don't support it natively, thi
s method also implements @array_matching. | |
735 * @example | |
736 * | |
737 + ['a','b','c'].some(function(n) { | |
738 * return n == 'a'; | |
739 * }); | |
740 + ['a','b','c'].some(function(n) { | |
741 * return n == 'd'; | |
742 * }); | |
743 * ['a','b','c'].some('a') -> true | |
744 * [{a:2},{b:5}].some({a:2}) -> true | |
745 ***/ | |
746 'some': function(fn, scope) { | |
747 var length = this.length, index = 0; | |
748 checkFirstArgumentExists(arguments); | |
749 while(index < length) { | |
750 if(index in this && fn.call(scope, this[index], index, this)) { | |
751 return true; | |
752 } | |
753 index++; | |
754 } | |
755 return false; | |
756 }, | |
757 | |
758 /*** | |
759 * @method map(<map>, [scope]) | |
760 * @returns Array | |
761 * @short Maps the array to another array containing the values that are the
result of calling <map> on each element. | |
762 * @extra [scope] is the %this% object. When <map> is a function, it receive
s three arguments: the current element, the current index, and a reference to th
e array. 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 short
cut for a function that gets that property (or invokes a function) on each eleme
nt. | |
763 * @example | |
764 * | |
765 * [1,2,3].map(function(n) { | |
766 * return n * 3; | |
767 * }); -> [3,6,9] | |
768 * ['one','two','three'].map(function(n) { | |
769 * return n.length; | |
770 * }); -> [3,3,5] | |
771 * ['one','two','three'].map('length') -> [3,3,5] | |
772 * | |
773 ***/ | |
774 'map': function(fn, scope) { | |
775 var scope = arguments[1], length = this.length, index = 0, result = new Ar
ray(length); | |
776 checkFirstArgumentExists(arguments); | |
777 while(index < length) { | |
778 if(index in this) { | |
779 result[index] = fn.call(scope, this[index], index, this); | |
780 } | |
781 index++; | |
782 } | |
783 return result; | |
784 }, | |
785 | |
786 /*** | |
787 * @method filter(<f>, [scope]) | |
788 * @returns Array | |
789 * @short Returns any elements in the array that match <f>. | |
790 * @extra [scope] is the %this% object. In addition to providing this method
for browsers that don't support it natively, this method also implements @array
_matching. | |
791 * @example | |
792 * | |
793 + [1,2,3].filter(function(n) { | |
794 * return n > 1; | |
795 * }); | |
796 * [1,2,2,4].filter(2) -> 2 | |
797 * | |
798 ***/ | |
799 'filter': function(fn) { | |
800 var scope = arguments[1]; | |
801 var length = this.length, index = 0, result = []; | |
802 checkFirstArgumentExists(arguments); | |
803 while(index < length) { | |
804 if(index in this && fn.call(scope, this[index], index, this)) { | |
805 result.push(this[index]); | |
806 } | |
807 index++; | |
808 } | |
809 return result; | |
810 }, | |
811 | |
812 /*** | |
813 * @method indexOf(<search>, [fromIndex]) | |
814 * @returns Number | |
815 * @short Searches the array and returns the first index where <search> occu
rs, or -1 if the element is not found. | |
816 * @extra [fromIndex] is the index from which to begin the search. This meth
od performs a simple strict equality comparison on <search>. It does not support
enhanced functionality such as searching the contents against a regex, callback
, or deep comparison of objects. For such functionality, use the %findIndex% met
hod instead. | |
817 * @example | |
818 * | |
819 * [1,2,3].indexOf(3) -> 1 | |
820 * [1,2,3].indexOf(7) -> -1 | |
821 * | |
822 ***/ | |
823 'indexOf': function(search) { | |
824 var fromIndex = arguments[1]; | |
825 if(isString(this)) return this.indexOf(search, fromIndex); | |
826 return arrayIndexOf(this, search, fromIndex, 1); | |
827 }, | |
828 | |
829 /*** | |
830 * @method lastIndexOf(<search>, [fromIndex]) | |
831 * @returns Number | |
832 * @short Searches the array and returns the last index where <search> occur
s, or -1 if the element is not found. | |
833 * @extra [fromIndex] is the index from which to begin the search. This meth
od performs a simple strict equality comparison on <search>. | |
834 * @example | |
835 * | |
836 * [1,2,1].lastIndexOf(1) -> 2 | |
837 * [1,2,1].lastIndexOf(7) -> -1 | |
838 * | |
839 ***/ | |
840 'lastIndexOf': function(search) { | |
841 var fromIndex = arguments[1]; | |
842 if(isString(this)) return this.lastIndexOf(search, fromIndex); | |
843 return arrayIndexOf(this, search, fromIndex, -1); | |
844 }, | |
845 | |
846 /*** | |
847 * @method forEach([fn], [scope]) | |
848 * @returns Nothing | |
849 * @short Iterates over the array, calling [fn] on each loop. | |
850 * @extra This method is only provided for those browsers that do not suppor
t it natively. [scope] becomes the %this% object. | |
851 * @example | |
852 * | |
853 * ['a','b','c'].forEach(function(a) { | |
854 * // Called 3 times: 'a','b','c' | |
855 * }); | |
856 * | |
857 ***/ | |
858 'forEach': function(fn) { | |
859 var length = this.length, index = 0, scope = arguments[1]; | |
860 checkCallback(fn); | |
861 while(index < length) { | |
862 if(index in this) { | |
863 fn.call(scope, this[index], index, this); | |
864 } | |
865 index++; | |
866 } | |
867 }, | |
868 | |
869 /*** | |
870 * @method reduce(<fn>, [init]) | |
871 * @returns Mixed | |
872 * @short Reduces the array to a single result. | |
873 * @extra If [init] is passed as a starting value, that value will be passed
as the first argument to the callback. The second argument will be the first el
ement in the array. From that point, the result of the callback will then be use
d as the first argument of the next iteration. This is often refered to as "accu
mulation", and [init] is often called an "accumulator". If [init] is not passed,
then <fn> will be called n - 1 times, where n is the length of the array. In th
is case, on the first iteration only, the first argument will be the first eleme
nt of the array, and the second argument will be the second. After that callback
s work as normal, using the result of the previous callback as the first argumen
t of the next. This method is only provided for those browsers that do not suppo
rt it natively. | |
874 * | |
875 * @example | |
876 * | |
877 + [1,2,3,4].reduce(function(a, b) { | |
878 * return a - b; | |
879 * }); | |
880 + [1,2,3,4].reduce(function(a, b) { | |
881 * return a - b; | |
882 * }, 100); | |
883 * | |
884 ***/ | |
885 'reduce': function(fn) { | |
886 return arrayReduce(this, fn, arguments[1]); | |
887 }, | |
888 | |
889 /*** | |
890 * @method reduceRight([fn], [init]) | |
891 * @returns Mixed | |
892 * @short Identical to %Array#reduce%, but operates on the elements in rever
se order. | |
893 * @extra This method is only provided for those browsers that do not suppor
t it natively. | |
894 * | |
895 * | |
896 * | |
897 * | |
898 * @example | |
899 * | |
900 + [1,2,3,4].reduceRight(function(a, b) { | |
901 * return a - b; | |
902 * }); | |
903 * | |
904 ***/ | |
905 'reduceRight': function(fn) { | |
906 return arrayReduce(this, fn, arguments[1], true); | |
907 } | |
908 | |
909 | |
910 }); | |
911 | |
912 | |
913 | |
914 | |
915 /*** | |
916 * String module | |
917 * | |
918 ***/ | |
919 | |
920 | |
921 function buildTrim() { | |
922 var support = getTrimmableCharacters().match(/^\s+$/); | |
923 try { string.prototype.trim.call([1]); } catch(e) { support = false; } | |
924 extend(string, true, !support, { | |
925 | |
926 /*** | |
927 * @method trim[Side]() | |
928 * @returns String | |
929 * @short Removes leading and/or trailing whitespace from the string. | |
930 * @extra Whitespace is defined as line breaks, tabs, and any character in
the "Space, Separator" Unicode category, conforming to the the ES5 spec. The st
andard %trim% method is only added when not fully supported natively. | |
931 * | |
932 * @set | |
933 * trim | |
934 * trimLeft | |
935 * trimRight | |
936 * | |
937 * @example | |
938 * | |
939 * ' wasabi '.trim() -> 'wasabi' | |
940 * ' wasabi '.trimLeft() -> 'wasabi ' | |
941 * ' wasabi '.trimRight() -> ' wasabi' | |
942 * | |
943 ***/ | |
944 'trim': function() { | |
945 return this.toString().trimLeft().trimRight(); | |
946 }, | |
947 | |
948 'trimLeft': function() { | |
949 return this.replace(regexp('^['+getTrimmableCharacters()+']+'), ''); | |
950 }, | |
951 | |
952 'trimRight': function() { | |
953 return this.replace(regexp('['+getTrimmableCharacters()+']+$'), ''); | |
954 } | |
955 }); | |
956 } | |
957 | |
958 | |
959 | |
960 /*** | |
961 * Function module | |
962 * | |
963 ***/ | |
964 | |
965 | |
966 extend(Function, true, false, { | |
967 | |
968 /*** | |
969 * @method bind(<scope>, [arg1], ...) | |
970 * @returns Function | |
971 * @short Binds <scope> as the %this% object for the function when it is cal
led. Also allows currying an unlimited number of parameters. | |
972 * @extra "currying" means setting parameters ([arg1], [arg2], etc.) ahead o
f time so that they are passed when the function is called later. If you pass ad
ditional parameters when the function is actually called, they will be added wil
l be added to the end of the curried parameters. This method is provided for bro
wsers that don't support it internally. | |
973 * @example | |
974 * | |
975 + (function() { | |
976 * return this; | |
977 * }).bind('woof')(); -> returns 'woof'; function is bound with 'woof' as
the this object. | |
978 * (function(a) { | |
979 * return a; | |
980 * }).bind(1, 2)(); -> returns 2; function is bound with 1 as the this o
bject and 2 curried as the first parameter | |
981 * (function(a, b) { | |
982 * return a + b; | |
983 * }).bind(1, 2)(3); -> returns 5; function is bound with 1 as the this o
bject, 2 curied as the first parameter and 3 passed as the second when calling t
he function | |
984 * | |
985 ***/ | |
986 'bind': function(scope) { | |
987 var fn = this, args = multiArgs(arguments, null, 1), bound; | |
988 if(!isFunction(this)) { | |
989 throw new TypeError('Function.prototype.bind called on a non-function'); | |
990 } | |
991 bound = function() { | |
992 return fn.apply(fn.prototype && this instanceof fn ? this : scope, args.
concat(multiArgs(arguments))); | |
993 } | |
994 bound.prototype = this.prototype; | |
995 return bound; | |
996 } | |
997 | |
998 }); | |
999 | |
1000 /*** | |
1001 * Date module | |
1002 * | |
1003 ***/ | |
1004 | |
1005 /*** | |
1006 * @method toISOString() | |
1007 * @returns String | |
1008 * @short Formats the string to ISO8601 format. | |
1009 * @extra This will always format as UTC time. Provided for browsers that do n
ot support this method. | |
1010 * @example | |
1011 * | |
1012 * Date.create().toISOString() -> ex. 2011-07-05 12:24:55.528Z | |
1013 * | |
1014 *** | |
1015 * @method toJSON() | |
1016 * @returns String | |
1017 * @short Returns a JSON representation of the date. | |
1018 * @extra This is effectively an alias for %toISOString%. Will always return t
he date in UTC time. Provided for browsers that do not support this method. | |
1019 * @example | |
1020 * | |
1021 * Date.create().toJSON() -> ex. 2011-07-05 12:24:55.528Z | |
1022 * | |
1023 ***/ | |
1024 | |
1025 extend(date, false, false, { | |
1026 | |
1027 /*** | |
1028 * @method Date.now() | |
1029 * @returns String | |
1030 * @short Returns the number of milliseconds since January 1st, 1970 00:00:0
0 (UTC time). | |
1031 * @extra Provided for browsers that do not support this method. | |
1032 * @example | |
1033 * | |
1034 * Date.now() -> ex. 1311938296231 | |
1035 * | |
1036 ***/ | |
1037 'now': function() { | |
1038 return new date().getTime(); | |
1039 } | |
1040 | |
1041 }); | |
1042 | |
1043 function buildISOString() { | |
1044 var d = new date(date.UTC(1999, 11, 31)), target = '1999-12-31T00:00:00.000Z
'; | |
1045 var support = d.toISOString && d.toISOString() === target; | |
1046 extendSimilar(date, true, !support, 'toISOString,toJSON', function(methods,
name) { | |
1047 methods[name] = function() { | |
1048 return padNumber(this.getUTCFullYear(), 4) + '-' + | |
1049 padNumber(this.getUTCMonth() + 1, 2) + '-' + | |
1050 padNumber(this.getUTCDate(), 2) + 'T' + | |
1051 padNumber(this.getUTCHours(), 2) + ':' + | |
1052 padNumber(this.getUTCMinutes(), 2) + ':' + | |
1053 padNumber(this.getUTCSeconds(), 2) + '.' + | |
1054 padNumber(this.getUTCMilliseconds(), 3) + 'Z'; | |
1055 } | |
1056 }); | |
1057 } | |
1058 | |
1059 // Initialize | |
1060 buildTrim(); | |
1061 buildISOString(); | |
1062 | |
1063 | |
1064 /*** | |
1065 * @package Array | |
1066 * @dependency core | |
1067 * @description Array manipulation and traversal, "fuzzy matching" against ele
ments, alphanumeric sorting and collation, enumerable methods on Object. | |
1068 * | |
1069 ***/ | |
1070 | |
1071 | |
1072 function regexMatcher(reg) { | |
1073 reg = regexp(reg); | |
1074 return function (el) { | |
1075 return reg.test(el); | |
1076 } | |
1077 } | |
1078 | |
1079 function dateMatcher(d) { | |
1080 var ms = d.getTime(); | |
1081 return function (el) { | |
1082 return !!(el && el.getTime) && el.getTime() === ms; | |
1083 } | |
1084 } | |
1085 | |
1086 function functionMatcher(fn) { | |
1087 return function (el, i, arr) { | |
1088 // Return true up front if match by reference | |
1089 return el === fn || fn.call(this, el, i, arr); | |
1090 } | |
1091 } | |
1092 | |
1093 function invertedArgsFunctionMatcher(fn) { | |
1094 return function (value, key, obj) { | |
1095 // Return true up front if match by reference | |
1096 return value === fn || fn.call(obj, key, value, obj); | |
1097 } | |
1098 } | |
1099 | |
1100 function fuzzyMatcher(obj, isObject) { | |
1101 var matchers = {}; | |
1102 return function (el, i, arr) { | |
1103 var key; | |
1104 if(!isObjectType(el)) { | |
1105 return false; | |
1106 } | |
1107 for(key in obj) { | |
1108 matchers[key] = matchers[key] || getMatcher(obj[key], isObject); | |
1109 if(matchers[key].call(arr, el[key], i, arr) === false) { | |
1110 return false; | |
1111 } | |
1112 } | |
1113 return true; | |
1114 } | |
1115 } | |
1116 | |
1117 function defaultMatcher(f) { | |
1118 return function (el) { | |
1119 return el === f || isEqual(el, f); | |
1120 } | |
1121 } | |
1122 | |
1123 function getMatcher(f, isObject) { | |
1124 if(isPrimitiveType(f)) { | |
1125 // Do nothing and fall through to the | |
1126 // default matcher below. | |
1127 } else if(isRegExp(f)) { | |
1128 // Match against a regexp | |
1129 return regexMatcher(f); | |
1130 } else if(isDate(f)) { | |
1131 // Match against a date. isEqual below should also | |
1132 // catch this but matching directly up front for speed. | |
1133 return dateMatcher(f); | |
1134 } else if(isFunction(f)) { | |
1135 // Match against a filtering function | |
1136 if(isObject) { | |
1137 return invertedArgsFunctionMatcher(f); | |
1138 } else { | |
1139 return functionMatcher(f); | |
1140 } | |
1141 } else if(isPlainObject(f)) { | |
1142 // Match against a fuzzy hash or array. | |
1143 return fuzzyMatcher(f, isObject); | |
1144 } | |
1145 // Default is standard isEqual | |
1146 return defaultMatcher(f); | |
1147 } | |
1148 | |
1149 function transformArgument(el, map, context, mapArgs) { | |
1150 if(!map) { | |
1151 return el; | |
1152 } else if(map.apply) { | |
1153 return map.apply(context, mapArgs || []); | |
1154 } else if(isFunction(el[map])) { | |
1155 return el[map].call(el); | |
1156 } else { | |
1157 return el[map]; | |
1158 } | |
1159 } | |
1160 | |
1161 // Basic array internal methods | |
1162 | |
1163 function arrayEach(arr, fn, startIndex, loop) { | |
1164 var index, i, length = +arr.length; | |
1165 if(startIndex < 0) startIndex = arr.length + startIndex; | |
1166 i = isNaN(startIndex) ? 0 : startIndex; | |
1167 if(loop === true) { | |
1168 length += i; | |
1169 } | |
1170 while(i < length) { | |
1171 index = i % arr.length; | |
1172 if(!(index in arr)) { | |
1173 return iterateOverSparseArray(arr, fn, i, loop); | |
1174 } else if(fn.call(arr, arr[index], index, arr) === false) { | |
1175 break; | |
1176 } | |
1177 i++; | |
1178 } | |
1179 } | |
1180 | |
1181 function iterateOverSparseArray(arr, fn, fromIndex, loop) { | |
1182 var indexes = [], i; | |
1183 for(i in arr) { | |
1184 if(isArrayIndex(arr, i) && i >= fromIndex) { | |
1185 indexes.push(parseInt(i)); | |
1186 } | |
1187 } | |
1188 indexes.sort().each(function(index) { | |
1189 return fn.call(arr, arr[index], index, arr); | |
1190 }); | |
1191 return arr; | |
1192 } | |
1193 | |
1194 function isArrayIndex(arr, i) { | |
1195 return i in arr && toUInt32(i) == i && i != 0xffffffff; | |
1196 } | |
1197 | |
1198 function toUInt32(i) { | |
1199 return i >>> 0; | |
1200 } | |
1201 | |
1202 function arrayFind(arr, f, startIndex, loop, returnIndex, context) { | |
1203 var result, index, matcher; | |
1204 if(arr.length > 0) { | |
1205 matcher = getMatcher(f); | |
1206 arrayEach(arr, function(el, i) { | |
1207 if(matcher.call(context, el, i, arr)) { | |
1208 result = el; | |
1209 index = i; | |
1210 return false; | |
1211 } | |
1212 }, startIndex, loop); | |
1213 } | |
1214 return returnIndex ? index : result; | |
1215 } | |
1216 | |
1217 function arrayUnique(arr, map) { | |
1218 var result = [], o = {}, transformed; | |
1219 arrayEach(arr, function(el, i) { | |
1220 transformed = map ? transformArgument(el, map, arr, [el, i, arr]) : el; | |
1221 if(!checkForElementInHashAndSet(o, transformed)) { | |
1222 result.push(el); | |
1223 } | |
1224 }) | |
1225 return result; | |
1226 } | |
1227 | |
1228 function arrayIntersect(arr1, arr2, subtract) { | |
1229 var result = [], o = {}; | |
1230 arr2.each(function(el) { | |
1231 checkForElementInHashAndSet(o, el); | |
1232 }); | |
1233 arr1.each(function(el) { | |
1234 var stringified = stringify(el), | |
1235 isReference = !objectIsMatchedByValue(el); | |
1236 // Add the result to the array if: | |
1237 // 1. We're subtracting intersections or it doesn't already exist in the r
esult and | |
1238 // 2. It exists in the compared array and we're adding, or it doesn't exis
t and we're removing. | |
1239 if(elementExistsInHash(o, stringified, el, isReference) !== subtract) { | |
1240 discardElementFromHash(o, stringified, el, isReference); | |
1241 result.push(el); | |
1242 } | |
1243 }); | |
1244 return result; | |
1245 } | |
1246 | |
1247 function arrayFlatten(arr, level, current) { | |
1248 level = level || Infinity; | |
1249 current = current || 0; | |
1250 var result = []; | |
1251 arrayEach(arr, function(el) { | |
1252 if(isArray(el) && current < level) { | |
1253 result = result.concat(arrayFlatten(el, level, current + 1)); | |
1254 } else { | |
1255 result.push(el); | |
1256 } | |
1257 }); | |
1258 return result; | |
1259 } | |
1260 | |
1261 function isArrayLike(obj) { | |
1262 return hasProperty(obj, 'length') && !isString(obj) && !isPlainObject(obj); | |
1263 } | |
1264 | |
1265 function isArgumentsObject(obj) { | |
1266 // .callee exists on Arguments objects in < IE8 | |
1267 return hasProperty(obj, 'length') && (className(obj) === '[object Arguments]
' || !!obj.callee); | |
1268 } | |
1269 | |
1270 function flatArguments(args) { | |
1271 var result = []; | |
1272 multiArgs(args, function(arg) { | |
1273 result = result.concat(arg); | |
1274 }); | |
1275 return result; | |
1276 } | |
1277 | |
1278 function elementExistsInHash(hash, key, element, isReference) { | |
1279 var exists = key in hash; | |
1280 if(isReference) { | |
1281 if(!hash[key]) { | |
1282 hash[key] = []; | |
1283 } | |
1284 exists = hash[key].indexOf(element) !== -1; | |
1285 } | |
1286 return exists; | |
1287 } | |
1288 | |
1289 function checkForElementInHashAndSet(hash, element) { | |
1290 var stringified = stringify(element), | |
1291 isReference = !objectIsMatchedByValue(element), | |
1292 exists = elementExistsInHash(hash, stringified, element, isReferenc
e); | |
1293 if(isReference) { | |
1294 hash[stringified].push(element); | |
1295 } else { | |
1296 hash[stringified] = element; | |
1297 } | |
1298 return exists; | |
1299 } | |
1300 | |
1301 function discardElementFromHash(hash, key, element, isReference) { | |
1302 var arr, i = 0; | |
1303 if(isReference) { | |
1304 arr = hash[key]; | |
1305 while(i < arr.length) { | |
1306 if(arr[i] === element) { | |
1307 arr.splice(i, 1); | |
1308 } else { | |
1309 i += 1; | |
1310 } | |
1311 } | |
1312 } else { | |
1313 delete hash[key]; | |
1314 } | |
1315 } | |
1316 | |
1317 // Support methods | |
1318 | |
1319 function getMinOrMax(obj, map, which, all) { | |
1320 var el, | |
1321 key, | |
1322 edge, | |
1323 test, | |
1324 result = [], | |
1325 max = which === 'max', | |
1326 min = which === 'min', | |
1327 isArray = array.isArray(obj); | |
1328 for(key in obj) { | |
1329 if(!obj.hasOwnProperty(key)) continue; | |
1330 el = obj[key]; | |
1331 test = transformArgument(el, map, obj, isArray ? [el, parseInt(key), obj]
: []); | |
1332 if(isUndefined(test)) { | |
1333 throw new TypeError('Cannot compare with undefined'); | |
1334 } | |
1335 if(test === edge) { | |
1336 result.push(el); | |
1337 } else if(isUndefined(edge) || (max && test > edge) || (min && test < edge
)) { | |
1338 result = [el]; | |
1339 edge = test; | |
1340 } | |
1341 } | |
1342 if(!isArray) result = arrayFlatten(result, 1); | |
1343 return all ? result : result[0]; | |
1344 } | |
1345 | |
1346 | |
1347 // Alphanumeric collation helpers | |
1348 | |
1349 function collateStrings(a, b) { | |
1350 var aValue, bValue, aChar, bChar, aEquiv, bEquiv, index = 0, tiebreaker = 0; | |
1351 | |
1352 var sortIgnore = array[AlphanumericSortIgnore]; | |
1353 var sortIgnoreCase = array[AlphanumericSortIgnoreCase]; | |
1354 var sortEquivalents = array[AlphanumericSortEquivalents]; | |
1355 var sortOrder = array[AlphanumericSortOrder]; | |
1356 var naturalSort = array[AlphanumericSortNatural]; | |
1357 | |
1358 a = getCollationReadyString(a, sortIgnore, sortIgnoreCase); | |
1359 b = getCollationReadyString(b, sortIgnore, sortIgnoreCase); | |
1360 | |
1361 do { | |
1362 | |
1363 aChar = getCollationCharacter(a, index, sortEquivalents); | |
1364 bChar = getCollationCharacter(b, index, sortEquivalents); | |
1365 aValue = getSortOrderIndex(aChar, sortOrder); | |
1366 bValue = getSortOrderIndex(bChar, sortOrder); | |
1367 | |
1368 if(aValue === -1 || bValue === -1) { | |
1369 aValue = a.charCodeAt(index) || null; | |
1370 bValue = b.charCodeAt(index) || null; | |
1371 if(naturalSort && codeIsNumeral(aValue) && codeIsNumeral(bValue)) { | |
1372 aValue = stringToNumber(a.slice(index)); | |
1373 bValue = stringToNumber(b.slice(index)); | |
1374 } | |
1375 } else { | |
1376 aEquiv = aChar !== a.charAt(index); | |
1377 bEquiv = bChar !== b.charAt(index); | |
1378 if(aEquiv !== bEquiv && tiebreaker === 0) { | |
1379 tiebreaker = aEquiv - bEquiv; | |
1380 } | |
1381 } | |
1382 index += 1; | |
1383 } while(aValue != null && bValue != null && aValue === bValue); | |
1384 if(aValue === bValue) return tiebreaker; | |
1385 return aValue - bValue; | |
1386 } | |
1387 | |
1388 function getCollationReadyString(str, sortIgnore, sortIgnoreCase) { | |
1389 if(!isString(str)) str = string(str); | |
1390 if(sortIgnoreCase) { | |
1391 str = str.toLowerCase(); | |
1392 } | |
1393 if(sortIgnore) { | |
1394 str = str.replace(sortIgnore, ''); | |
1395 } | |
1396 return str; | |
1397 } | |
1398 | |
1399 function getCollationCharacter(str, index, sortEquivalents) { | |
1400 var chr = str.charAt(index); | |
1401 return sortEquivalents[chr] || chr; | |
1402 } | |
1403 | |
1404 function getSortOrderIndex(chr, sortOrder) { | |
1405 if(!chr) { | |
1406 return null; | |
1407 } else { | |
1408 return sortOrder.indexOf(chr); | |
1409 } | |
1410 } | |
1411 | |
1412 var AlphanumericSort = 'AlphanumericSort'; | |
1413 var AlphanumericSortOrder = 'AlphanumericSortOrder'; | |
1414 var AlphanumericSortIgnore = 'AlphanumericSortIgnore'; | |
1415 var AlphanumericSortIgnoreCase = 'AlphanumericSortIgnoreCase'; | |
1416 var AlphanumericSortEquivalents = 'AlphanumericSortEquivalents'; | |
1417 var AlphanumericSortNatural = 'AlphanumericSortNatural'; | |
1418 | |
1419 | |
1420 | |
1421 function buildEnhancements() { | |
1422 var nativeMap = array.prototype.map; | |
1423 var callbackCheck = function() { | |
1424 var args = arguments; | |
1425 return args.length > 0 && !isFunction(args[0]); | |
1426 }; | |
1427 extendSimilar(array, true, callbackCheck, 'every,all,some,filter,any,none,fi
nd,findIndex', function(methods, name) { | |
1428 var nativeFn = array.prototype[name] | |
1429 methods[name] = function(f) { | |
1430 var matcher = getMatcher(f); | |
1431 return nativeFn.call(this, function(el, index) { | |
1432 return matcher(el, index, this); | |
1433 }); | |
1434 } | |
1435 }); | |
1436 extend(array, true, callbackCheck, { | |
1437 'map': function(f) { | |
1438 return nativeMap.call(this, function(el, index) { | |
1439 return transformArgument(el, f, this, [el, index, this]); | |
1440 }); | |
1441 } | |
1442 }); | |
1443 } | |
1444 | |
1445 function buildAlphanumericSort() { | |
1446 var order = 'AÁÀÂÃĄBCĆČÇDĎÐEÉÈĚÊËĘFGĞHıIÍÌİÎÏJKLŁMNŃŇÑOÓÒÔPQRŘSŚŠŞTŤUÚÙŮÛÜVW
XYÝZŹŻŽÞÆŒØÕÅÄÖ'; | |
1447 var equiv = 'AÁÀÂÃÄ,CÇ,EÉÈÊË,IÍÌİÎÏ,OÓÒÔÕÖ,Sß,UÚÙÛÜ'; | |
1448 array[AlphanumericSortOrder] = order.split('').map(function(str) { | |
1449 return str + str.toLowerCase(); | |
1450 }).join(''); | |
1451 var equivalents = {}; | |
1452 arrayEach(equiv.split(','), function(set) { | |
1453 var equivalent = set.charAt(0); | |
1454 arrayEach(set.slice(1).split(''), function(chr) { | |
1455 equivalents[chr] = equivalent; | |
1456 equivalents[chr.toLowerCase()] = equivalent.toLowerCase(); | |
1457 }); | |
1458 }); | |
1459 array[AlphanumericSortNatural] = true; | |
1460 array[AlphanumericSortIgnoreCase] = true; | |
1461 array[AlphanumericSortEquivalents] = equivalents; | |
1462 } | |
1463 | |
1464 extend(array, false, true, { | |
1465 | |
1466 /*** | |
1467 * | |
1468 * @method Array.create(<obj1>, <obj2>, ...) | |
1469 * @returns Array | |
1470 * @short Alternate array constructor. | |
1471 * @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 singl
e, 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 in
to a standard array. | |
1472 * @example | |
1473 * | |
1474 * Array.create('one', true, 3) -> ['one', true, 3] | |
1475 * Array.create(['one', true, 3]) -> ['one', true, 3] | |
1476 + Array.create(function(n) { | |
1477 * return arguments; | |
1478 * }('howdy', 'doody')); | |
1479 * | |
1480 ***/ | |
1481 'create': function() { | |
1482 var result = []; | |
1483 multiArgs(arguments, function(a) { | |
1484 if(isArgumentsObject(a) || isArrayLike(a)) { | |
1485 a = array.prototype.slice.call(a, 0); | |
1486 } | |
1487 result = result.concat(a); | |
1488 }); | |
1489 return result; | |
1490 } | |
1491 | |
1492 }); | |
1493 | |
1494 extend(array, true, false, { | |
1495 | |
1496 /*** | |
1497 * @method find(<f>, [context] = undefined) | |
1498 * @returns Mixed | |
1499 * @short Returns the first element that matches <f>. | |
1500 * @extra [context] is the %this% object if passed. When <f> is a function,
will use native implementation if it exists. <f> will also match a string, numbe
r, array, object, or alternately test against a function or regex. This method i
mplements @array_matching. | |
1501 * @example | |
1502 * | |
1503 + [{a:1,b:2},{a:1,b:3},{a:1,b:4}].find(function(n) { | |
1504 * return n['a'] == 1; | |
1505 * }); -> {a:1,b:3} | |
1506 * ['cuba','japan','canada'].find(/^c/) -> 'cuba' | |
1507 * | |
1508 ***/ | |
1509 'find': function(f, context) { | |
1510 checkCallback(f); | |
1511 return arrayFind(this, f, 0, false, false, context); | |
1512 }, | |
1513 | |
1514 /*** | |
1515 * @method findIndex(<f>, [context] = undefined) | |
1516 * @returns Number | |
1517 * @short Returns the index of the first element that matches <f> or -1 if n
ot found. | |
1518 * @extra [context] is the %this% object if passed. When <f> is a function,
will use native implementation if it exists. <f> will also match a string, numbe
r, array, object, or alternately test against a function or regex. This method i
mplements @array_matching. | |
1519 * | |
1520 * @example | |
1521 * | |
1522 + [1,2,3,4].findIndex(function(n) { | |
1523 * return n % 2 == 0; | |
1524 * }); -> 1 | |
1525 + [1,2,3,4].findIndex(3); -> 2 | |
1526 + ['one','two','three'].findIndex(/t/); -> 1 | |
1527 * | |
1528 ***/ | |
1529 'findIndex': function(f, context) { | |
1530 var index; | |
1531 checkCallback(f); | |
1532 index = arrayFind(this, f, 0, false, true, context); | |
1533 return isUndefined(index) ? -1 : index; | |
1534 } | |
1535 | |
1536 }); | |
1537 | |
1538 extend(array, true, true, { | |
1539 | |
1540 /*** | |
1541 * @method findFrom(<f>, [index] = 0, [loop] = false) | |
1542 * @returns Array | |
1543 * @short Returns any element that matches <f>, beginning from [index]. | |
1544 * @extra <f> will match a string, number, array, object, or alternately tes
t against a function or regex. Will continue from index = 0 if [loop] is true. T
his method implements @array_matching. | |
1545 * @example | |
1546 * | |
1547 * ['cuba','japan','canada'].findFrom(/^c/, 2) -> 'canada' | |
1548 * | |
1549 ***/ | |
1550 'findFrom': function(f, index, loop) { | |
1551 return arrayFind(this, f, index, loop); | |
1552 }, | |
1553 | |
1554 /*** | |
1555 * @method findIndexFrom(<f>, [index] = 0, [loop] = false) | |
1556 * @returns Array | |
1557 * @short Returns the index of any element that matches <f>, beginning from
[index]. | |
1558 * @extra <f> will match a string, number, array, object, or alternately tes
t against a function or regex. Will continue from index = 0 if [loop] is true. T
his method implements @array_matching. | |
1559 * @example | |
1560 * | |
1561 * ['cuba','japan','canada'].findIndexFrom(/^c/, 2) -> 2 | |
1562 * | |
1563 ***/ | |
1564 'findIndexFrom': function(f, index, loop) { | |
1565 var index = arrayFind(this, f, index, loop, true); | |
1566 return isUndefined(index) ? -1 : index; | |
1567 }, | |
1568 | |
1569 /*** | |
1570 * @method findAll(<f>, [index] = 0, [loop] = false) | |
1571 * @returns Array | |
1572 * @short Returns all elements that match <f>. | |
1573 * @extra <f> will match a string, number, array, object, or alternately tes
t against a function or regex. Starts at [index], and will continue once from in
dex = 0 if [loop] is true. This method implements @array_matching. | |
1574 * @example | |
1575 * | |
1576 + [{a:1,b:2},{a:1,b:3},{a:2,b:4}].findAll(function(n) { | |
1577 * return n['a'] == 1; | |
1578 * }); -> [{a:1,b:3},{a:1,b:4}] | |
1579 * ['cuba','japan','canada'].findAll(/^c/) -> 'cuba','canada' | |
1580 * ['cuba','japan','canada'].findAll(/^c/, 2) -> 'canada' | |
1581 * | |
1582 ***/ | |
1583 'findAll': function(f, index, loop) { | |
1584 var result = [], matcher; | |
1585 if(this.length > 0) { | |
1586 matcher = getMatcher(f); | |
1587 arrayEach(this, function(el, i, arr) { | |
1588 if(matcher(el, i, arr)) { | |
1589 result.push(el); | |
1590 } | |
1591 }, index, loop); | |
1592 } | |
1593 return result; | |
1594 }, | |
1595 | |
1596 /*** | |
1597 * @method count(<f>) | |
1598 * @returns Number | |
1599 * @short Counts all elements in the array that match <f>. | |
1600 * @extra <f> will match a string, number, array, object, or alternately tes
t against a function or regex. This method implements @array_matching. | |
1601 * @example | |
1602 * | |
1603 * [1,2,3,1].count(1) -> 2 | |
1604 * ['a','b','c'].count(/b/) -> 1 | |
1605 + [{a:1},{b:2}].count(function(n) { | |
1606 * return n['a'] > 1; | |
1607 * }); -> 0 | |
1608 * | |
1609 ***/ | |
1610 'count': function(f) { | |
1611 if(isUndefined(f)) return this.length; | |
1612 return this.findAll(f).length; | |
1613 }, | |
1614 | |
1615 /*** | |
1616 * @method removeAt(<start>, [end]) | |
1617 * @returns Array | |
1618 * @short Removes element at <start>. If [end] is specified, removes the ran
ge between <start> and [end]. This method will change the array! If you don't in
tend the array to be changed use %clone% first. | |
1619 * @example | |
1620 * | |
1621 * ['a','b','c'].removeAt(0) -> ['b','c'] | |
1622 * [1,2,3,4].removeAt(1, 3) -> [1] | |
1623 * | |
1624 ***/ | |
1625 'removeAt': function(start, end) { | |
1626 if(isUndefined(start)) return this; | |
1627 if(isUndefined(end)) end = start; | |
1628 this.splice(start, end - start + 1); | |
1629 return this; | |
1630 }, | |
1631 | |
1632 /*** | |
1633 * @method include(<el>, [index]) | |
1634 * @returns Array | |
1635 * @short Adds <el> to the array. | |
1636 * @extra This is a non-destructive alias for %add%. It will not change the
original array. | |
1637 * @example | |
1638 * | |
1639 * [1,2,3,4].include(5) -> [1,2,3,4,5] | |
1640 * [1,2,3,4].include(8, 1) -> [1,8,2,3,4] | |
1641 * [1,2,3,4].include([5,6,7]) -> [1,2,3,4,5,6,7] | |
1642 * | |
1643 ***/ | |
1644 'include': function(el, index) { | |
1645 return this.clone().add(el, index); | |
1646 }, | |
1647 | |
1648 /*** | |
1649 * @method exclude([f1], [f2], ...) | |
1650 * @returns Array | |
1651 * @short Removes any element in the array that matches [f1], [f2], etc. | |
1652 * @extra This is a non-destructive alias for %remove%. It will not change t
he original array. This method implements @array_matching. | |
1653 * @example | |
1654 * | |
1655 * [1,2,3].exclude(3) -> [1,2] | |
1656 * ['a','b','c'].exclude(/b/) -> ['a','c'] | |
1657 + [{a:1},{b:2}].exclude(function(n) { | |
1658 * return n['a'] == 1; | |
1659 * }); -> [{b:2}] | |
1660 * | |
1661 ***/ | |
1662 'exclude': function() { | |
1663 return array.prototype.remove.apply(this.clone(), arguments); | |
1664 }, | |
1665 | |
1666 /*** | |
1667 * @method clone() | |
1668 * @returns Array | |
1669 * @short Makes a shallow clone of the array. | |
1670 * @example | |
1671 * | |
1672 * [1,2,3].clone() -> [1,2,3] | |
1673 * | |
1674 ***/ | |
1675 'clone': function() { | |
1676 return simpleMerge([], this); | |
1677 }, | |
1678 | |
1679 /*** | |
1680 * @method unique([map] = null) | |
1681 * @returns Array | |
1682 * @short Removes all duplicate elements in the array. | |
1683 * @extra [map] may be a function mapping the value to be uniqued on or a st
ring acting as a shortcut. This is most commonly used when you have a key that e
nsures the object's uniqueness, and don't need to check all fields. This method
will also correctly operate on arrays of objects. | |
1684 * @example | |
1685 * | |
1686 * [1,2,2,3].unique() -> [1,2,3] | |
1687 * [{foo:'bar'},{foo:'bar'}].unique() -> [{foo:'bar'}] | |
1688 + [{foo:'bar'},{foo:'bar'}].unique(function(obj){ | |
1689 * return obj.foo; | |
1690 * }); -> [{foo:'bar'}] | |
1691 * [{foo:'bar'},{foo:'bar'}].unique('foo') -> [{foo:'bar'}] | |
1692 * | |
1693 ***/ | |
1694 'unique': function(map) { | |
1695 return arrayUnique(this, map); | |
1696 }, | |
1697 | |
1698 /*** | |
1699 * @method flatten([limit] = Infinity) | |
1700 * @returns Array | |
1701 * @short Returns a flattened, one-dimensional copy of the array. | |
1702 * @extra You can optionally specify a [limit], which will only flatten that
depth. | |
1703 * @example | |
1704 * | |
1705 * [[1], 2, [3]].flatten() -> [1,2,3] | |
1706 * [['a'],[],'b','c'].flatten() -> ['a','b','c'] | |
1707 * | |
1708 ***/ | |
1709 'flatten': function(limit) { | |
1710 return arrayFlatten(this, limit); | |
1711 }, | |
1712 | |
1713 /*** | |
1714 * @method union([a1], [a2], ...) | |
1715 * @returns Array | |
1716 * @short Returns an array containing all elements in all arrays with duplic
ates removed. | |
1717 * @extra This method will also correctly operate on arrays of objects. | |
1718 * @example | |
1719 * | |
1720 * [1,3,5].union([5,7,9]) -> [1,3,5,7,9] | |
1721 * ['a','b'].union(['b','c']) -> ['a','b','c'] | |
1722 * | |
1723 ***/ | |
1724 'union': function() { | |
1725 return arrayUnique(this.concat(flatArguments(arguments))); | |
1726 }, | |
1727 | |
1728 /*** | |
1729 * @method intersect([a1], [a2], ...) | |
1730 * @returns Array | |
1731 * @short Returns an array containing the elements all arrays have in common
. | |
1732 * @extra This method will also correctly operate on arrays of objects. | |
1733 * @example | |
1734 * | |
1735 * [1,3,5].intersect([5,7,9]) -> [5] | |
1736 * ['a','b'].intersect('b','c') -> ['b'] | |
1737 * | |
1738 ***/ | |
1739 'intersect': function() { | |
1740 return arrayIntersect(this, flatArguments(arguments), false); | |
1741 }, | |
1742 | |
1743 /*** | |
1744 * @method subtract([a1], [a2], ...) | |
1745 * @returns Array | |
1746 * @short Subtracts from the array all elements in [a1], [a2], etc. | |
1747 * @extra This method will also correctly operate on arrays of objects. | |
1748 * @example | |
1749 * | |
1750 * [1,3,5].subtract([5,7,9]) -> [1,3] | |
1751 * [1,3,5].subtract([3],[5]) -> [1] | |
1752 * ['a','b'].subtract('b','c') -> ['a'] | |
1753 * | |
1754 ***/ | |
1755 'subtract': function(a) { | |
1756 return arrayIntersect(this, flatArguments(arguments), true); | |
1757 }, | |
1758 | |
1759 /*** | |
1760 * @method at(<index>, [loop] = true) | |
1761 * @returns Mixed | |
1762 * @short Gets the element(s) at a given index. | |
1763 * @extra When [loop] is true, overshooting the end of the array (or the beg
inning) will begin counting from the other end. As an alternate syntax, passing
multiple indexes will get the elements at those indexes. | |
1764 * @example | |
1765 * | |
1766 * [1,2,3].at(0) -> 1 | |
1767 * [1,2,3].at(2) -> 3 | |
1768 * [1,2,3].at(4) -> 2 | |
1769 * [1,2,3].at(4, false) -> null | |
1770 * [1,2,3].at(-1) -> 3 | |
1771 * [1,2,3].at(0,1) -> [1,2] | |
1772 * | |
1773 ***/ | |
1774 'at': function() { | |
1775 return getEntriesForIndexes(this, arguments); | |
1776 }, | |
1777 | |
1778 /*** | |
1779 * @method first([num] = 1) | |
1780 * @returns Mixed | |
1781 * @short Returns the first element(s) in the array. | |
1782 * @extra When <num> is passed, returns the first <num> elements in the arra
y. | |
1783 * @example | |
1784 * | |
1785 * [1,2,3].first() -> 1 | |
1786 * [1,2,3].first(2) -> [1,2] | |
1787 * | |
1788 ***/ | |
1789 'first': function(num) { | |
1790 if(isUndefined(num)) return this[0]; | |
1791 if(num < 0) num = 0; | |
1792 return this.slice(0, num); | |
1793 }, | |
1794 | |
1795 /*** | |
1796 * @method last([num] = 1) | |
1797 * @returns Mixed | |
1798 * @short Returns the last element(s) in the array. | |
1799 * @extra When <num> is passed, returns the last <num> elements in the array
. | |
1800 * @example | |
1801 * | |
1802 * [1,2,3].last() -> 3 | |
1803 * [1,2,3].last(2) -> [2,3] | |
1804 * | |
1805 ***/ | |
1806 'last': function(num) { | |
1807 if(isUndefined(num)) return this[this.length - 1]; | |
1808 var start = this.length - num < 0 ? 0 : this.length - num; | |
1809 return this.slice(start); | |
1810 }, | |
1811 | |
1812 /*** | |
1813 * @method from(<index>) | |
1814 * @returns Array | |
1815 * @short Returns a slice of the array from <index>. | |
1816 * @example | |
1817 * | |
1818 * [1,2,3].from(1) -> [2,3] | |
1819 * [1,2,3].from(2) -> [3] | |
1820 * | |
1821 ***/ | |
1822 'from': function(num) { | |
1823 return this.slice(num); | |
1824 }, | |
1825 | |
1826 /*** | |
1827 * @method to(<index>) | |
1828 * @returns Array | |
1829 * @short Returns a slice of the array up to <index>. | |
1830 * @example | |
1831 * | |
1832 * [1,2,3].to(1) -> [1] | |
1833 * [1,2,3].to(2) -> [1,2] | |
1834 * | |
1835 ***/ | |
1836 'to': function(num) { | |
1837 if(isUndefined(num)) num = this.length; | |
1838 return this.slice(0, num); | |
1839 }, | |
1840 | |
1841 /*** | |
1842 * @method min([map], [all] = false) | |
1843 * @returns Mixed | |
1844 * @short Returns the element in the array with the lowest value. | |
1845 * @extra [map] may be a function mapping the value to be checked or a strin
g acting as a shortcut. If [all] is true, will return all min values in an array
. | |
1846 * @example | |
1847 * | |
1848 * [1,2,3].min() -> 1 | |
1849 * ['fee','fo','fum'].min('length') -> 'fo' | |
1850 * ['fee','fo','fum'].min('length', true) -> ['fo'] | |
1851 + ['fee','fo','fum'].min(function(n) { | |
1852 * return n.length; | |
1853 * }); -> ['fo'] | |
1854 + [{a:3,a:2}].min(function(n) { | |
1855 * return n['a']; | |
1856 * }); -> [{a:2}] | |
1857 * | |
1858 ***/ | |
1859 'min': function(map, all) { | |
1860 return getMinOrMax(this, map, 'min', all); | |
1861 }, | |
1862 | |
1863 /*** | |
1864 * @method max([map], [all] = false) | |
1865 * @returns Mixed | |
1866 * @short Returns the element in the array with the greatest value. | |
1867 * @extra [map] may be a function mapping the value to be checked or a strin
g acting as a shortcut. If [all] is true, will return all max values in an array
. | |
1868 * @example | |
1869 * | |
1870 * [1,2,3].max() -> 3 | |
1871 * ['fee','fo','fum'].max('length') -> 'fee' | |
1872 * ['fee','fo','fum'].max('length', true) -> ['fee'] | |
1873 + [{a:3,a:2}].max(function(n) { | |
1874 * return n['a']; | |
1875 * }); -> {a:3} | |
1876 * | |
1877 ***/ | |
1878 'max': function(map, all) { | |
1879 return getMinOrMax(this, map, 'max', all); | |
1880 }, | |
1881 | |
1882 /*** | |
1883 * @method least([map]) | |
1884 * @returns Array | |
1885 * @short Returns the elements in the array with the least commonly occuring
value. | |
1886 * @extra [map] may be a function mapping the value to be checked or a strin
g acting as a shortcut. | |
1887 * @example | |
1888 * | |
1889 * [3,2,2].least() -> [3] | |
1890 * ['fe','fo','fum'].least('length') -> ['fum'] | |
1891 + [{age:35,name:'ken'},{age:12,name:'bob'},{age:12,name:'ted'}].least(fun
ction(n) { | |
1892 * return n.age; | |
1893 * }); -> [{age:35,name:'ken'}] | |
1894 * | |
1895 ***/ | |
1896 'least': function(map, all) { | |
1897 return getMinOrMax(this.groupBy.apply(this, [map]), 'length', 'min', all); | |
1898 }, | |
1899 | |
1900 /*** | |
1901 * @method most([map]) | |
1902 * @returns Array | |
1903 * @short Returns the elements in the array with the most commonly occuring
value. | |
1904 * @extra [map] may be a function mapping the value to be checked or a strin
g acting as a shortcut. | |
1905 * @example | |
1906 * | |
1907 * [3,2,2].most() -> [2] | |
1908 * ['fe','fo','fum'].most('length') -> ['fe','fo'] | |
1909 + [{age:35,name:'ken'},{age:12,name:'bob'},{age:12,name:'ted'}].most(func
tion(n) { | |
1910 * return n.age; | |
1911 * }); -> [{age:12,name:'bob'},{age:12,name:'
ted'}] | |
1912 * | |
1913 ***/ | |
1914 'most': function(map, all) { | |
1915 return getMinOrMax(this.groupBy.apply(this, [map]), 'length', 'max', all); | |
1916 }, | |
1917 | |
1918 /*** | |
1919 * @method sum([map]) | |
1920 * @returns Number | |
1921 * @short Sums all values in the array. | |
1922 * @extra [map] may be a function mapping the value to be summed or a string
acting as a shortcut. | |
1923 * @example | |
1924 * | |
1925 * [1,2,2].sum() -> 5 | |
1926 + [{age:35},{age:12},{age:12}].sum(function(n) { | |
1927 * return n.age; | |
1928 * }); -> 59 | |
1929 * [{age:35},{age:12},{age:12}].sum('age') -> 59 | |
1930 * | |
1931 ***/ | |
1932 'sum': function(map) { | |
1933 var arr = map ? this.map(map) : this; | |
1934 return arr.length > 0 ? arr.reduce(function(a,b) { return a + b; }) : 0; | |
1935 }, | |
1936 | |
1937 /*** | |
1938 * @method average([map]) | |
1939 * @returns Number | |
1940 * @short Gets the mean average for all values in the array. | |
1941 * @extra [map] may be a function mapping the value to be averaged or a stri
ng acting as a shortcut. | |
1942 * @example | |
1943 * | |
1944 * [1,2,3].average() -> 2 | |
1945 + [{age:35},{age:11},{age:11}].average(function(n) { | |
1946 * return n.age; | |
1947 * }); -> 19 | |
1948 * [{age:35},{age:11},{age:11}].average('age') -> 19 | |
1949 * | |
1950 ***/ | |
1951 'average': function(map) { | |
1952 var arr = map ? this.map(map) : this; | |
1953 return arr.length > 0 ? arr.sum() / arr.length : 0; | |
1954 }, | |
1955 | |
1956 /*** | |
1957 * @method inGroups(<num>, [padding]) | |
1958 * @returns Array | |
1959 * @short Groups the array into <num> arrays. | |
1960 * @extra [padding] specifies a value with which to pad the last array so th
at they are all equal length. | |
1961 * @example | |
1962 * | |
1963 * [1,2,3,4,5,6,7].inGroups(3) -> [ [1,2,3], [4,5,6], [7] ] | |
1964 * [1,2,3,4,5,6,7].inGroups(3, 'none') -> [ [1,2,3], [4,5,6], [7,'none','n
one'] ] | |
1965 * | |
1966 ***/ | |
1967 'inGroups': function(num, padding) { | |
1968 var pad = arguments.length > 1; | |
1969 var arr = this; | |
1970 var result = []; | |
1971 var divisor = ceil(this.length / num); | |
1972 simpleRepeat(num, function(i) { | |
1973 var index = i * divisor; | |
1974 var group = arr.slice(index, index + divisor); | |
1975 if(pad && group.length < divisor) { | |
1976 simpleRepeat(divisor - group.length, function() { | |
1977 group = group.add(padding); | |
1978 }); | |
1979 } | |
1980 result.push(group); | |
1981 }); | |
1982 return result; | |
1983 }, | |
1984 | |
1985 /*** | |
1986 * @method inGroupsOf(<num>, [padding] = null) | |
1987 * @returns Array | |
1988 * @short Groups the array into arrays of <num> elements each. | |
1989 * @extra [padding] specifies a value with which to pad the last array so th
at they are all equal length. | |
1990 * @example | |
1991 * | |
1992 * [1,2,3,4,5,6,7].inGroupsOf(4) -> [ [1,2,3,4], [5,6,7] ] | |
1993 * [1,2,3,4,5,6,7].inGroupsOf(4, 'none') -> [ [1,2,3,4], [5,6,7,'none'] ] | |
1994 * | |
1995 ***/ | |
1996 'inGroupsOf': function(num, padding) { | |
1997 var result = [], len = this.length, arr = this, group; | |
1998 if(len === 0 || num === 0) return arr; | |
1999 if(isUndefined(num)) num = 1; | |
2000 if(isUndefined(padding)) padding = null; | |
2001 simpleRepeat(ceil(len / num), function(i) { | |
2002 group = arr.slice(num * i, num * i + num); | |
2003 while(group.length < num) { | |
2004 group.push(padding); | |
2005 } | |
2006 result.push(group); | |
2007 }); | |
2008 return result; | |
2009 }, | |
2010 | |
2011 /*** | |
2012 * @method isEmpty() | |
2013 * @returns Boolean | |
2014 * @short Returns true if the array is empty. | |
2015 * @extra This is true if the array has a length of zero, or contains only %
undefined%, %null%, or %NaN%. | |
2016 * @example | |
2017 * | |
2018 * [].isEmpty() -> true | |
2019 * [null,undefined].isEmpty() -> true | |
2020 * | |
2021 ***/ | |
2022 'isEmpty': function() { | |
2023 return this.compact().length == 0; | |
2024 }, | |
2025 | |
2026 /*** | |
2027 * @method sortBy(<map>, [desc] = false) | |
2028 * @returns Array | |
2029 * @short Sorts the array by <map>. | |
2030 * @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 ord
er. When the field being sorted on is a string, the resulting order will be dete
rmined by an internal collation algorithm that is optimized for major Western la
nguages, but can be customized. For more information see @array_sorting. | |
2031 * @example | |
2032 * | |
2033 * ['world','a','new'].sortBy('length') -> ['a','new','world'] | |
2034 * ['world','a','new'].sortBy('length', true) -> ['world','new','a'] | |
2035 + [{age:72},{age:13},{age:18}].sortBy(function(n) { | |
2036 * return n.age; | |
2037 * }); -> [{age:13},{age:18},{age:7
2}] | |
2038 * | |
2039 ***/ | |
2040 'sortBy': function(map, desc) { | |
2041 var arr = this.clone(); | |
2042 arr.sort(function(a, b) { | |
2043 var aProperty, bProperty, comp; | |
2044 aProperty = transformArgument(a, map, arr, [a]); | |
2045 bProperty = transformArgument(b, map, arr, [b]); | |
2046 if(isString(aProperty) && isString(bProperty)) { | |
2047 comp = collateStrings(aProperty, bProperty); | |
2048 } else if(aProperty < bProperty) { | |
2049 comp = -1; | |
2050 } else if(aProperty > bProperty) { | |
2051 comp = 1; | |
2052 } else { | |
2053 comp = 0; | |
2054 } | |
2055 return comp * (desc ? -1 : 1); | |
2056 }); | |
2057 return arr; | |
2058 }, | |
2059 | |
2060 /*** | |
2061 * @method randomize() | |
2062 * @returns Array | |
2063 * @short Returns a copy of the array with the elements randomized. | |
2064 * @extra Uses Fisher-Yates algorithm. | |
2065 * @example | |
2066 * | |
2067 * [1,2,3,4].randomize() -> [?,?,?,?] | |
2068 * | |
2069 ***/ | |
2070 'randomize': function() { | |
2071 var arr = this.concat(), i = arr.length, j, x; | |
2072 while(i) { | |
2073 j = (math.random() * i) | 0; | |
2074 x = arr[--i]; | |
2075 arr[i] = arr[j]; | |
2076 arr[j] = x; | |
2077 } | |
2078 return arr; | |
2079 }, | |
2080 | |
2081 /*** | |
2082 * @method zip([arr1], [arr2], ...) | |
2083 * @returns Array | |
2084 * @short Merges multiple arrays together. | |
2085 * @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 h
ave more elements than the original array, they will be discarded. If they have
fewer elements, the missing elements will filled with %null%. | |
2086 * @example | |
2087 * | |
2088 * [1,2,3].zip([4,5,6]) -> [[1,2], [
3,4], [5,6]] | |
2089 * ['Martin','John'].zip(['Luther','F.'], ['King','Kennedy']) -> [['Martin
','Luther','King'], ['John','F.','Kennedy']] | |
2090 * | |
2091 ***/ | |
2092 'zip': function() { | |
2093 var args = multiArgs(arguments); | |
2094 return this.map(function(el, i) { | |
2095 return [el].concat(args.map(function(k) { | |
2096 return (i in k) ? k[i] : null; | |
2097 })); | |
2098 }); | |
2099 }, | |
2100 | |
2101 /*** | |
2102 * @method sample([num]) | |
2103 * @returns Mixed | |
2104 * @short Returns a random element from the array. | |
2105 * @extra If [num] is passed, will return [num] samples from the array. | |
2106 * @example | |
2107 * | |
2108 * [1,2,3,4,5].sample() -> // Random element | |
2109 * [1,2,3,4,5].sample(3) -> // Array of 3 random elements | |
2110 * | |
2111 ***/ | |
2112 'sample': function(num) { | |
2113 var arr = this.randomize(); | |
2114 return arguments.length > 0 ? arr.slice(0, num) : arr[0]; | |
2115 }, | |
2116 | |
2117 /*** | |
2118 * @method each(<fn>, [index] = 0, [loop] = false) | |
2119 * @returns Array | |
2120 * @short Runs <fn> against each element in the array. Enhanced version of %
Array#forEach%. | |
2121 * @extra Parameters passed to <fn> are identical to %forEach%, ie. the firs
t parameter is the current element, second parameter is the current index, and t
hird 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. | |
2122 * @example | |
2123 * | |
2124 * [1,2,3,4].each(function(n) { | |
2125 * // Called 4 times: 1, 2, 3, 4 | |
2126 * }); | |
2127 * [1,2,3,4].each(function(n) { | |
2128 * // Called 4 times: 3, 4, 1, 2 | |
2129 * }, 2, true); | |
2130 * | |
2131 ***/ | |
2132 'each': function(fn, index, loop) { | |
2133 arrayEach(this, fn, index, loop); | |
2134 return this; | |
2135 }, | |
2136 | |
2137 /*** | |
2138 * @method add(<el>, [index]) | |
2139 * @returns Array | |
2140 * @short Adds <el> to the array. | |
2141 * @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 i
t 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. | |
2142 * @example | |
2143 * | |
2144 * [1,2,3,4].add(5) -> [1,2,3,4,5] | |
2145 * [1,2,3,4].add([5,6,7]) -> [1,2,3,4,5,6,7] | |
2146 * [1,2,3,4].insert(8, 1) -> [1,8,2,3,4] | |
2147 * | |
2148 ***/ | |
2149 'add': function(el, index) { | |
2150 if(!isNumber(number(index)) || isNaN(index)) index = this.length; | |
2151 array.prototype.splice.apply(this, [index, 0].concat(el)); | |
2152 return this; | |
2153 }, | |
2154 | |
2155 /*** | |
2156 * @method remove([f1], [f2], ...) | |
2157 * @returns Array | |
2158 * @short Removes any element in the array that matches [f1], [f2], etc. | |
2159 * @extra Will match a string, number, array, object, or alternately test ag
ainst a function or regex. This method will change the array! Use %exclude% for
a non-destructive alias. This method implements @array_matching. | |
2160 * @example | |
2161 * | |
2162 * [1,2,3].remove(3) -> [1,2] | |
2163 * ['a','b','c'].remove(/b/) -> ['a','c'] | |
2164 + [{a:1},{b:2}].remove(function(n) { | |
2165 * return n['a'] == 1; | |
2166 * }); -> [{b:2}] | |
2167 * | |
2168 ***/ | |
2169 'remove': function() { | |
2170 var arr = this; | |
2171 multiArgs(arguments, function(f) { | |
2172 var i = 0, matcher = getMatcher(f); | |
2173 while(i < arr.length) { | |
2174 if(matcher(arr[i], i, arr)) { | |
2175 arr.splice(i, 1); | |
2176 } else { | |
2177 i++; | |
2178 } | |
2179 } | |
2180 }); | |
2181 return arr; | |
2182 }, | |
2183 | |
2184 /*** | |
2185 * @method compact([all] = false) | |
2186 * @returns Array | |
2187 * @short Removes all instances of %undefined%, %null%, and %NaN% from the a
rray. | |
2188 * @extra If [all] is %true%, all "falsy" elements will be removed. This inc
ludes empty strings, 0, and false. | |
2189 * @example | |
2190 * | |
2191 * [1,null,2,undefined,3].compact() -> [1,2,3] | |
2192 * [1,'',2,false,3].compact() -> [1,'',2,false,3] | |
2193 * [1,'',2,false,3].compact(true) -> [1,2,3] | |
2194 * | |
2195 ***/ | |
2196 'compact': function(all) { | |
2197 var result = []; | |
2198 arrayEach(this, function(el, i) { | |
2199 if(isArray(el)) { | |
2200 result.push(el.compact()); | |
2201 } else if(all && el) { | |
2202 result.push(el); | |
2203 } else if(!all && el != null && el.valueOf() === el.valueOf()) { | |
2204 result.push(el); | |
2205 } | |
2206 }); | |
2207 return result; | |
2208 }, | |
2209 | |
2210 /*** | |
2211 * @method groupBy(<map>, [fn]) | |
2212 * @returns Object | |
2213 * @short Groups the array by <map>. | |
2214 * @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. | |
2215 * @example | |
2216 * | |
2217 * ['fee','fi','fum'].groupBy('length') -> { 2: ['fi'], 3: ['fee','fum'] } | |
2218 + [{age:35,name:'ken'},{age:15,name:'bob'}].groupBy(function(n) { | |
2219 * return n.age; | |
2220 * }); -> { 35: [{age:35,name:'ken'}], 15
: [{age:15,name:'bob'}] } | |
2221 * | |
2222 ***/ | |
2223 'groupBy': function(map, fn) { | |
2224 var arr = this, result = {}, key; | |
2225 arrayEach(arr, function(el, index) { | |
2226 key = transformArgument(el, map, arr, [el, index, arr]); | |
2227 if(!result[key]) result[key] = []; | |
2228 result[key].push(el); | |
2229 }); | |
2230 if(fn) { | |
2231 iterateOverObject(result, fn); | |
2232 } | |
2233 return result; | |
2234 }, | |
2235 | |
2236 /*** | |
2237 * @method none(<f>) | |
2238 * @returns Boolean | |
2239 * @short Returns true if none of the elements in the array match <f>. | |
2240 * @extra <f> will match a string, number, array, object, or alternately tes
t against a function or regex. This method implements @array_matching. | |
2241 * @example | |
2242 * | |
2243 * [1,2,3].none(5) -> true | |
2244 * ['a','b','c'].none(/b/) -> false | |
2245 + [{a:1},{b:2}].none(function(n) { | |
2246 * return n['a'] > 1; | |
2247 * }); -> true | |
2248 * | |
2249 ***/ | |
2250 'none': function() { | |
2251 return !this.any.apply(this, arguments); | |
2252 } | |
2253 | |
2254 | |
2255 }); | |
2256 | |
2257 | |
2258 // Aliases | |
2259 | |
2260 extend(array, true, true, { | |
2261 | |
2262 /*** | |
2263 * @method all() | |
2264 * @alias every | |
2265 * | |
2266 ***/ | |
2267 'all': array.prototype.every, | |
2268 | |
2269 /*** @method any() | |
2270 * @alias some | |
2271 * | |
2272 ***/ | |
2273 'any': array.prototype.some, | |
2274 | |
2275 /*** | |
2276 * @method insert() | |
2277 * @alias add | |
2278 * | |
2279 ***/ | |
2280 'insert': array.prototype.add | |
2281 | |
2282 }); | |
2283 | |
2284 | |
2285 /*** | |
2286 * Object module | |
2287 * Enumerable methods on objects | |
2288 * | |
2289 ***/ | |
2290 | |
2291 function keysWithObjectCoercion(obj) { | |
2292 return object.keys(coercePrimitiveToObject(obj)); | |
2293 } | |
2294 | |
2295 /*** | |
2296 * @method [enumerable](<obj>) | |
2297 * @returns Boolean | |
2298 * @short Enumerable methods in the Array package are also available to the Ob
ject class. They will perform their normal operations for every property in <obj
>. | |
2299 * @extra In cases where a callback is used, instead of %element, index%, the
callback will instead be passed %key, value%. Enumerable methods are also availa
ble to extended objects as instance methods. | |
2300 * | |
2301 * @set | |
2302 * each | |
2303 * map | |
2304 * any | |
2305 * all | |
2306 * none | |
2307 * count | |
2308 * find | |
2309 * findAll | |
2310 * reduce | |
2311 * isEmpty | |
2312 * sum | |
2313 * average | |
2314 * min | |
2315 * max | |
2316 * least | |
2317 * most | |
2318 * | |
2319 * @example | |
2320 * | |
2321 * Object.any({foo:'bar'}, 'bar') -> true | |
2322 * Object.extended({foo:'bar'}).any('bar') -> true | |
2323 * Object.isEmpty({}) -> true | |
2324 + Object.map({ fred: { age: 52 } }, 'age'); -> { fred: 52 } | |
2325 * | |
2326 ***/ | |
2327 | |
2328 function buildEnumerableMethods(names, mapping) { | |
2329 extendSimilar(object, false, true, names, function(methods, name) { | |
2330 methods[name] = function(obj, arg1, arg2) { | |
2331 var result, coerced = keysWithObjectCoercion(obj), matcher; | |
2332 if(!mapping) { | |
2333 matcher = getMatcher(arg1, true); | |
2334 } | |
2335 result = array.prototype[name].call(coerced, function(key) { | |
2336 var value = obj[key]; | |
2337 if(mapping) { | |
2338 return transformArgument(value, arg1, obj, [key, value, obj]); | |
2339 } else { | |
2340 return matcher(value, key, obj); | |
2341 } | |
2342 }, arg2); | |
2343 if(isArray(result)) { | |
2344 // The method has returned an array of keys so use this array | |
2345 // to build up the resulting object in the form we want it in. | |
2346 result = result.reduce(function(o, key, i) { | |
2347 o[key] = obj[key]; | |
2348 return o; | |
2349 }, {}); | |
2350 } | |
2351 return result; | |
2352 }; | |
2353 }); | |
2354 buildObjectInstanceMethods(names, Hash); | |
2355 } | |
2356 | |
2357 function exportSortAlgorithm() { | |
2358 array[AlphanumericSort] = collateStrings; | |
2359 } | |
2360 | |
2361 extend(object, false, true, { | |
2362 | |
2363 'map': function(obj, map) { | |
2364 var result = {}, key, value; | |
2365 for(key in obj) { | |
2366 if(!hasOwnProperty(obj, key)) continue; | |
2367 value = obj[key]; | |
2368 result[key] = transformArgument(value, map, obj, [key, value, obj]); | |
2369 } | |
2370 return result; | |
2371 }, | |
2372 | |
2373 'reduce': function(obj) { | |
2374 var values = keysWithObjectCoercion(obj).map(function(key) { | |
2375 return obj[key]; | |
2376 }); | |
2377 return values.reduce.apply(values, multiArgs(arguments, null, 1)); | |
2378 }, | |
2379 | |
2380 'each': function(obj, fn) { | |
2381 checkCallback(fn); | |
2382 iterateOverObject(obj, fn); | |
2383 return obj; | |
2384 }, | |
2385 | |
2386 /*** | |
2387 * @method size(<obj>) | |
2388 * @returns Number | |
2389 * @short Returns the number of properties in <obj>. | |
2390 * @extra %size% is available as an instance method on extended objects. | |
2391 * @example | |
2392 * | |
2393 * Object.size({ foo: 'bar' }) -> 1 | |
2394 * | |
2395 ***/ | |
2396 'size': function (obj) { | |
2397 return keysWithObjectCoercion(obj).length; | |
2398 } | |
2399 | |
2400 }); | |
2401 | |
2402 var EnumerableFindingMethods = 'any,all,none,count,find,findAll,isEmpty'.split
(','); | |
2403 var EnumerableMappingMethods = 'sum,average,min,max,least,most'.split(','); | |
2404 var EnumerableOtherMethods = 'map,reduce,size'.split(','); | |
2405 var EnumerableMethods = EnumerableFindingMethods.concat(EnumerableMappi
ngMethods).concat(EnumerableOtherMethods); | |
2406 | |
2407 buildEnhancements(); | |
2408 buildAlphanumericSort(); | |
2409 buildEnumerableMethods(EnumerableFindingMethods); | |
2410 buildEnumerableMethods(EnumerableMappingMethods, true); | |
2411 buildObjectInstanceMethods(EnumerableOtherMethods, Hash); | |
2412 exportSortAlgorithm(); | |
2413 | |
2414 | |
2415 /*** | |
2416 * @package Date | |
2417 * @dependency core | |
2418 * @description Date parsing and formatting, relative formats like "1 minute a
go", Number methods like "daysAgo", localization support with default English lo
cale definition. | |
2419 * | |
2420 ***/ | |
2421 | |
2422 var English; | |
2423 var CurrentLocalization; | |
2424 | |
2425 var TimeFormat = ['ampm','hour','minute','second','ampm','utc','offset_sign','
offset_hours','offset_minutes','ampm'] | |
2426 var DecimalReg = '(?:[,.]\\d+)?'; | |
2427 var HoursReg = '\\d{1,2}' + DecimalReg; | |
2428 var SixtyReg = '[0-5]\\d' + DecimalReg; | |
2429 var RequiredTime = '({t})?\\s*('+HoursReg+')(?:{h}('+SixtyReg+')?{m}(?::?('+Si
xtyReg+'){s})?\\s*(?:({t})|(Z)|(?:([+-])(\\d{2,2})(?::?(\\d{2,2}))?)?)?|\\s*({t}
))'; | |
2430 | |
2431 var KanjiDigits = '〇一二三四五六七八九十百千万'; | |
2432 var AsianDigitMap = {}; | |
2433 var AsianDigitReg; | |
2434 | |
2435 var DateArgumentUnits; | |
2436 var DateUnitsReversed; | |
2437 var CoreDateFormats = []; | |
2438 var CompiledOutputFormats = {}; | |
2439 | |
2440 var DateFormatTokens = { | |
2441 | |
2442 'yyyy': function(d) { | |
2443 return callDateGet(d, 'FullYear'); | |
2444 }, | |
2445 | |
2446 'yy': function(d) { | |
2447 return callDateGet(d, 'FullYear') % 100; | |
2448 }, | |
2449 | |
2450 'ord': function(d) { | |
2451 var date = callDateGet(d, 'Date'); | |
2452 return date + getOrdinalizedSuffix(date); | |
2453 }, | |
2454 | |
2455 'tz': function(d) { | |
2456 return d.getUTCOffset(); | |
2457 }, | |
2458 | |
2459 'isotz': function(d) { | |
2460 return d.getUTCOffset(true); | |
2461 }, | |
2462 | |
2463 'Z': function(d) { | |
2464 return d.getUTCOffset(); | |
2465 }, | |
2466 | |
2467 'ZZ': function(d) { | |
2468 return d.getUTCOffset().replace(/(\d{2})$/, ':$1'); | |
2469 } | |
2470 | |
2471 }; | |
2472 | |
2473 var DateUnits = [ | |
2474 { | |
2475 name: 'year', | |
2476 method: 'FullYear', | |
2477 ambiguous: true, | |
2478 multiplier: function(d) { | |
2479 var adjust = d ? (d.isLeapYear() ? 1 : 0) : 0.25; | |
2480 return (365 + adjust) * 24 * 60 * 60 * 1000; | |
2481 } | |
2482 }, | |
2483 { | |
2484 name: 'month', | |
2485 error: 0.919, // Feb 1-28 over 1 month | |
2486 method: 'Month', | |
2487 ambiguous: true, | |
2488 multiplier: function(d, ms) { | |
2489 var days = 30.4375, inMonth; | |
2490 if(d) { | |
2491 inMonth = d.daysInMonth(); | |
2492 if(ms <= inMonth.days()) { | |
2493 days = inMonth; | |
2494 } | |
2495 } | |
2496 return days * 24 * 60 * 60 * 1000; | |
2497 } | |
2498 }, | |
2499 { | |
2500 name: 'week', | |
2501 method: 'ISOWeek', | |
2502 multiplier: function() { | |
2503 return 7 * 24 * 60 * 60 * 1000; | |
2504 } | |
2505 }, | |
2506 { | |
2507 name: 'day', | |
2508 error: 0.958, // DST traversal over 1 day | |
2509 method: 'Date', | |
2510 ambiguous: true, | |
2511 multiplier: function() { | |
2512 return 24 * 60 * 60 * 1000; | |
2513 } | |
2514 }, | |
2515 { | |
2516 name: 'hour', | |
2517 method: 'Hours', | |
2518 multiplier: function() { | |
2519 return 60 * 60 * 1000; | |
2520 } | |
2521 }, | |
2522 { | |
2523 name: 'minute', | |
2524 method: 'Minutes', | |
2525 multiplier: function() { | |
2526 return 60 * 1000; | |
2527 } | |
2528 }, | |
2529 { | |
2530 name: 'second', | |
2531 method: 'Seconds', | |
2532 multiplier: function() { | |
2533 return 1000; | |
2534 } | |
2535 }, | |
2536 { | |
2537 name: 'millisecond', | |
2538 method: 'Milliseconds', | |
2539 multiplier: function() { | |
2540 return 1; | |
2541 } | |
2542 } | |
2543 ]; | |
2544 | |
2545 | |
2546 | |
2547 | |
2548 // Date Localization | |
2549 | |
2550 var Localizations = {}; | |
2551 | |
2552 // Localization object | |
2553 | |
2554 function Localization(l) { | |
2555 simpleMerge(this, l); | |
2556 this.compiledFormats = CoreDateFormats.concat(); | |
2557 } | |
2558 | |
2559 Localization.prototype = { | |
2560 | |
2561 getMonth: function(n) { | |
2562 if(isNumber(n)) { | |
2563 return n - 1; | |
2564 } else { | |
2565 return this['months'].indexOf(n) % 12; | |
2566 } | |
2567 }, | |
2568 | |
2569 getWeekday: function(n) { | |
2570 return this['weekdays'].indexOf(n) % 7; | |
2571 }, | |
2572 | |
2573 getNumber: function(n) { | |
2574 var i; | |
2575 if(isNumber(n)) { | |
2576 return n; | |
2577 } else if(n && (i = this['numbers'].indexOf(n)) !== -1) { | |
2578 return (i + 1) % 10; | |
2579 } else { | |
2580 return 1; | |
2581 } | |
2582 }, | |
2583 | |
2584 getNumericDate: function(n) { | |
2585 var self = this; | |
2586 return n.replace(regexp(this['num'], 'g'), function(d) { | |
2587 var num = self.getNumber(d); | |
2588 return num || ''; | |
2589 }); | |
2590 }, | |
2591 | |
2592 getUnitIndex: function(n) { | |
2593 return this['units'].indexOf(n) % 8; | |
2594 }, | |
2595 | |
2596 getRelativeFormat: function(adu) { | |
2597 return this.convertAdjustedToFormat(adu, adu[2] > 0 ? 'future' : 'past'); | |
2598 }, | |
2599 | |
2600 getDuration: function(ms) { | |
2601 return this.convertAdjustedToFormat(getAdjustedUnit(ms), 'duration'); | |
2602 }, | |
2603 | |
2604 hasVariant: function(code) { | |
2605 code = code || this.code; | |
2606 return code === 'en' || code === 'en-US' ? true : this['variant']; | |
2607 }, | |
2608 | |
2609 matchAM: function(str) { | |
2610 return str === this['ampm'][0]; | |
2611 }, | |
2612 | |
2613 matchPM: function(str) { | |
2614 return str && str === this['ampm'][1]; | |
2615 }, | |
2616 | |
2617 convertAdjustedToFormat: function(adu, mode) { | |
2618 var sign, unit, mult, | |
2619 num = adu[0], | |
2620 u = adu[1], | |
2621 ms = adu[2], | |
2622 format = this[mode] || this['relative']; | |
2623 if(isFunction(format)) { | |
2624 return format.call(this, num, u, ms, mode); | |
2625 } | |
2626 mult = this['plural'] && num > 1 ? 1 : 0; | |
2627 unit = this['units'][mult * 8 + u] || this['units'][u]; | |
2628 if(this['capitalizeUnit']) unit = simpleCapitalize(unit); | |
2629 sign = this['modifiers'].filter(function(m) { return m.name == 'sign' && m
.value == (ms > 0 ? 1 : -1); })[0]; | |
2630 return format.replace(/\{(.*?)\}/g, function(full, match) { | |
2631 switch(match) { | |
2632 case 'num': return num; | |
2633 case 'unit': return unit; | |
2634 case 'sign': return sign.src; | |
2635 } | |
2636 }); | |
2637 }, | |
2638 | |
2639 getFormats: function() { | |
2640 return this.cachedFormat ? [this.cachedFormat].concat(this.compiledFormats
) : this.compiledFormats; | |
2641 }, | |
2642 | |
2643 addFormat: function(src, allowsTime, match, variant, iso) { | |
2644 var to = match || [], loc = this, time, timeMarkers, lastIsNumeral; | |
2645 | |
2646 src = src.replace(/\s+/g, '[,. ]*'); | |
2647 src = src.replace(/\{([^,]+?)\}/g, function(all, k) { | |
2648 var value, arr, result, | |
2649 opt = k.match(/\?$/), | |
2650 nc = k.match(/^(\d+)\??$/), | |
2651 slice = k.match(/(\d)(?:-(\d))?/), | |
2652 key = k.replace(/[^a-z]+$/, ''); | |
2653 if(nc) { | |
2654 value = loc['tokens'][nc[1]]; | |
2655 } else if(loc[key]) { | |
2656 value = loc[key]; | |
2657 } else if(loc[key + 's']) { | |
2658 value = loc[key + 's']; | |
2659 if(slice) { | |
2660 // Can't use filter here as Prototype hijacks the method and doesn't | |
2661 // pass an index, so use a simple loop instead! | |
2662 arr = []; | |
2663 value.forEach(function(m, i) { | |
2664 var mod = i % (loc['units'] ? 8 : value.length); | |
2665 if(mod >= slice[1] && mod <= (slice[2] || slice[1])) { | |
2666 arr.push(m); | |
2667 } | |
2668 }); | |
2669 value = arr; | |
2670 } | |
2671 value = arrayToAlternates(value); | |
2672 } | |
2673 if(nc) { | |
2674 result = '(?:' + value + ')'; | |
2675 } else { | |
2676 if(!match) { | |
2677 to.push(key); | |
2678 } | |
2679 result = '(' + value + ')'; | |
2680 } | |
2681 if(opt) { | |
2682 result += '?'; | |
2683 } | |
2684 return result; | |
2685 }); | |
2686 if(allowsTime) { | |
2687 time = prepareTime(RequiredTime, loc, iso); | |
2688 timeMarkers = ['t','[\\s\\u3000]'].concat(loc['timeMarker']); | |
2689 lastIsNumeral = src.match(/\\d\{\d,\d\}\)+\??$/); | |
2690 addDateInputFormat(loc, '(?:' + time + ')[,\\s\\u3000]+?' + src, TimeFor
mat.concat(to), variant); | |
2691 addDateInputFormat(loc, src + '(?:[,\\s]*(?:' + timeMarkers.join('|') +
(lastIsNumeral ? '+' : '*') +')' + time + ')?', to.concat(TimeFormat), variant); | |
2692 } else { | |
2693 addDateInputFormat(loc, src, to, variant); | |
2694 } | |
2695 } | |
2696 | |
2697 }; | |
2698 | |
2699 | |
2700 // Localization helpers | |
2701 | |
2702 function getLocalization(localeCode, fallback) { | |
2703 var loc; | |
2704 if(!isString(localeCode)) localeCode = ''; | |
2705 loc = Localizations[localeCode] || Localizations[localeCode.slice(0,2)]; | |
2706 if(fallback === false && !loc) { | |
2707 throw new TypeError('Invalid locale.'); | |
2708 } | |
2709 return loc || CurrentLocalization; | |
2710 } | |
2711 | |
2712 function setLocalization(localeCode, set) { | |
2713 var loc, canAbbreviate; | |
2714 | |
2715 function initializeField(name) { | |
2716 var val = loc[name]; | |
2717 if(isString(val)) { | |
2718 loc[name] = val.split(','); | |
2719 } else if(!val) { | |
2720 loc[name] = []; | |
2721 } | |
2722 } | |
2723 | |
2724 function eachAlternate(str, fn) { | |
2725 str = str.split('+').map(function(split) { | |
2726 return split.replace(/(.+):(.+)$/, function(full, base, suffixes) { | |
2727 return suffixes.split('|').map(function(suffix) { | |
2728 return base + suffix; | |
2729 }).join('|'); | |
2730 }); | |
2731 }).join('|'); | |
2732 return str.split('|').forEach(fn); | |
2733 } | |
2734 | |
2735 function setArray(name, abbreviate, multiple) { | |
2736 var arr = []; | |
2737 loc[name].forEach(function(full, i) { | |
2738 if(abbreviate) { | |
2739 full += '+' + full.slice(0,3); | |
2740 } | |
2741 eachAlternate(full, function(day, j) { | |
2742 arr[j * multiple + i] = day.toLowerCase(); | |
2743 }); | |
2744 }); | |
2745 loc[name] = arr; | |
2746 } | |
2747 | |
2748 function getDigit(start, stop, allowNumbers) { | |
2749 var str = '\\d{' + start + ',' + stop + '}'; | |
2750 if(allowNumbers) str += '|(?:' + arrayToAlternates(loc['numbers']) + ')+'; | |
2751 return str; | |
2752 } | |
2753 | |
2754 function getNum() { | |
2755 var arr = ['-?\\d+'].concat(loc['articles']); | |
2756 if(loc['numbers']) arr = arr.concat(loc['numbers']); | |
2757 return arrayToAlternates(arr); | |
2758 } | |
2759 | |
2760 function setDefault(name, value) { | |
2761 loc[name] = loc[name] || value; | |
2762 } | |
2763 | |
2764 function setModifiers() { | |
2765 var arr = []; | |
2766 loc.modifiersByName = {}; | |
2767 loc['modifiers'].push({ 'name': 'day', 'src': 'yesterday', 'value': -1 }); | |
2768 loc['modifiers'].push({ 'name': 'day', 'src': 'today', 'value': 0 }); | |
2769 loc['modifiers'].push({ 'name': 'day', 'src': 'tomorrow', 'value': 1 }); | |
2770 loc['modifiers'].forEach(function(modifier) { | |
2771 var name = modifier.name; | |
2772 eachAlternate(modifier.src, function(t) { | |
2773 var locEntry = loc[name]; | |
2774 loc.modifiersByName[t] = modifier; | |
2775 arr.push({ name: name, src: t, value: modifier.value }); | |
2776 loc[name] = locEntry ? locEntry + '|' + t : t; | |
2777 }); | |
2778 }); | |
2779 loc['day'] += '|' + arrayToAlternates(loc['weekdays']); | |
2780 loc['modifiers'] = arr; | |
2781 } | |
2782 | |
2783 // Initialize the locale | |
2784 loc = new Localization(set); | |
2785 initializeField('modifiers'); | |
2786 'months,weekdays,units,numbers,articles,tokens,timeMarker,ampm,timeSuffixes,
dateParse,timeParse'.split(',').forEach(initializeField); | |
2787 | |
2788 canAbbreviate = !loc['monthSuffix']; | |
2789 | |
2790 setArray('months', canAbbreviate, 12); | |
2791 setArray('weekdays', canAbbreviate, 7); | |
2792 setArray('units', false, 8); | |
2793 setArray('numbers', false, 10); | |
2794 | |
2795 setDefault('code', localeCode); | |
2796 setDefault('date', getDigit(1,2, loc['digitDate'])); | |
2797 setDefault('year', "'\\d{2}|" + getDigit(4,4)); | |
2798 setDefault('num', getNum()); | |
2799 | |
2800 setModifiers(); | |
2801 | |
2802 if(loc['monthSuffix']) { | |
2803 loc['month'] = getDigit(1,2); | |
2804 loc['months'] = '1,2,3,4,5,6,7,8,9,10,11,12'.split(',').map(function(n) {
return n + loc['monthSuffix']; }); | |
2805 } | |
2806 loc['full_month'] = getDigit(1,2) + '|' + arrayToAlternates(loc['months']); | |
2807 | |
2808 // The order of these formats is very important. Order is reversed so format
s that come | |
2809 // later will take precedence over formats that come before. This generally
means that | |
2810 // more specific formats should come later, however, the {year} format shoul
d come before | |
2811 // {day}, as 2011 needs to be parsed as a year (2011) and not date (20) + ho
urs (11) | |
2812 | |
2813 // If the locale has time suffixes then add a time only format for that loca
le | |
2814 // that is separate from the core English-based one. | |
2815 if(loc['timeSuffixes'].length > 0) { | |
2816 loc.addFormat(prepareTime(RequiredTime, loc), false, TimeFormat) | |
2817 } | |
2818 | |
2819 loc.addFormat('{day}', true); | |
2820 loc.addFormat('{month}' + (loc['monthSuffix'] || '')); | |
2821 loc.addFormat('{year}' + (loc['yearSuffix'] || '')); | |
2822 | |
2823 loc['timeParse'].forEach(function(src) { | |
2824 loc.addFormat(src, true); | |
2825 }); | |
2826 | |
2827 loc['dateParse'].forEach(function(src) { | |
2828 loc.addFormat(src); | |
2829 }); | |
2830 | |
2831 return Localizations[localeCode] = loc; | |
2832 } | |
2833 | |
2834 | |
2835 // General helpers | |
2836 | |
2837 function addDateInputFormat(locale, format, match, variant) { | |
2838 locale.compiledFormats.unshift({ | |
2839 variant: variant, | |
2840 locale: locale, | |
2841 reg: regexp('^' + format + '$', 'i'), | |
2842 to: match | |
2843 }); | |
2844 } | |
2845 | |
2846 function simpleCapitalize(str) { | |
2847 return str.slice(0,1).toUpperCase() + str.slice(1); | |
2848 } | |
2849 | |
2850 function arrayToAlternates(arr) { | |
2851 return arr.filter(function(el) { | |
2852 return !!el; | |
2853 }).join('|'); | |
2854 } | |
2855 | |
2856 function getNewDate() { | |
2857 var fn = date.SugarNewDate; | |
2858 return fn ? fn() : new date; | |
2859 } | |
2860 | |
2861 // Date argument helpers | |
2862 | |
2863 function collectDateArguments(args, allowDuration) { | |
2864 var obj; | |
2865 if(isObjectType(args[0])) { | |
2866 return args; | |
2867 } else if (isNumber(args[0]) && !isNumber(args[1])) { | |
2868 return [args[0]]; | |
2869 } else if (isString(args[0]) && allowDuration) { | |
2870 return [getDateParamsFromString(args[0]), args[1]]; | |
2871 } | |
2872 obj = {}; | |
2873 DateArgumentUnits.forEach(function(u,i) { | |
2874 obj[u.name] = args[i]; | |
2875 }); | |
2876 return [obj]; | |
2877 } | |
2878 | |
2879 function getDateParamsFromString(str, num) { | |
2880 var match, params = {}; | |
2881 match = str.match(/^(\d+)?\s?(\w+?)s?$/i); | |
2882 if(match) { | |
2883 if(isUndefined(num)) { | |
2884 num = parseInt(match[1]) || 1; | |
2885 } | |
2886 params[match[2].toLowerCase()] = num; | |
2887 } | |
2888 return params; | |
2889 } | |
2890 | |
2891 // Date iteration helpers | |
2892 | |
2893 function iterateOverDateUnits(fn, from, to) { | |
2894 var i, unit; | |
2895 if(isUndefined(to)) to = DateUnitsReversed.length; | |
2896 for(i = from || 0; i < to; i++) { | |
2897 unit = DateUnitsReversed[i]; | |
2898 if(fn(unit.name, unit, i) === false) { | |
2899 break; | |
2900 } | |
2901 } | |
2902 } | |
2903 | |
2904 // Date parsing helpers | |
2905 | |
2906 function getFormatMatch(match, arr) { | |
2907 var obj = {}, value, num; | |
2908 arr.forEach(function(key, i) { | |
2909 value = match[i + 1]; | |
2910 if(isUndefined(value) || value === '') return; | |
2911 if(key === 'year') { | |
2912 obj.yearAsString = value.replace(/'/, ''); | |
2913 } | |
2914 num = parseFloat(value.replace(/'/, '').replace(/,/, '.')); | |
2915 obj[key] = !isNaN(num) ? num : value.toLowerCase(); | |
2916 }); | |
2917 return obj; | |
2918 } | |
2919 | |
2920 function cleanDateInput(str) { | |
2921 str = str.trim().replace(/^just (?=now)|\.+$/i, ''); | |
2922 return convertAsianDigits(str); | |
2923 } | |
2924 | |
2925 function convertAsianDigits(str) { | |
2926 return str.replace(AsianDigitReg, function(full, disallowed, match) { | |
2927 var sum = 0, place = 1, lastWasHolder, lastHolder; | |
2928 if(disallowed) return full; | |
2929 match.split('').reverse().forEach(function(letter) { | |
2930 var value = AsianDigitMap[letter], holder = value > 9; | |
2931 if(holder) { | |
2932 if(lastWasHolder) sum += place; | |
2933 place *= value / (lastHolder || 1); | |
2934 lastHolder = value; | |
2935 } else { | |
2936 if(lastWasHolder === false) { | |
2937 place *= 10; | |
2938 } | |
2939 sum += place * value; | |
2940 } | |
2941 lastWasHolder = holder; | |
2942 }); | |
2943 if(lastWasHolder) sum += place; | |
2944 return sum; | |
2945 }); | |
2946 } | |
2947 | |
2948 function getExtendedDate(f, localeCode, prefer, forceUTC) { | |
2949 var d, relative, baseLocalization, afterCallbacks, loc, set, unit, unitIndex
, weekday, num, tmp; | |
2950 | |
2951 d = getNewDate(); | |
2952 afterCallbacks = []; | |
2953 | |
2954 function afterDateSet(fn) { | |
2955 afterCallbacks.push(fn); | |
2956 } | |
2957 | |
2958 function fireCallbacks() { | |
2959 afterCallbacks.forEach(function(fn) { | |
2960 fn.call(); | |
2961 }); | |
2962 } | |
2963 | |
2964 function setWeekdayOfMonth() { | |
2965 var w = d.getWeekday(); | |
2966 d.setWeekday((7 * (set['num'] - 1)) + (w > weekday ? weekday + 7 : weekday
)); | |
2967 } | |
2968 | |
2969 function setUnitEdge() { | |
2970 var modifier = loc.modifiersByName[set['edge']]; | |
2971 iterateOverDateUnits(function(name) { | |
2972 if(isDefined(set[name])) { | |
2973 unit = name; | |
2974 return false; | |
2975 } | |
2976 }, 4); | |
2977 if(unit === 'year') set.specificity = 'month'; | |
2978 else if(unit === 'month' || unit === 'week') set.specificity = 'day'; | |
2979 d[(modifier.value < 0 ? 'endOf' : 'beginningOf') + simpleCapitalize(unit)]
(); | |
2980 // This value of -2 is arbitrary but it's a nice clean way to hook into th
is system. | |
2981 if(modifier.value === -2) d.reset(); | |
2982 } | |
2983 | |
2984 function separateAbsoluteUnits() { | |
2985 var params; | |
2986 iterateOverDateUnits(function(name, u, i) { | |
2987 if(name === 'day') name = 'date'; | |
2988 if(isDefined(set[name])) { | |
2989 // If there is a time unit set that is more specific than | |
2990 // the matched unit we have a string like "5:30am in 2 minutes", | |
2991 // which is meaningless, so invalidate the date... | |
2992 if(i >= unitIndex) { | |
2993 invalidateDate(d); | |
2994 return false; | |
2995 } | |
2996 // ...otherwise set the params to set the absolute date | |
2997 // as a callback after the relative date has been set. | |
2998 params = params || {}; | |
2999 params[name] = set[name]; | |
3000 delete set[name]; | |
3001 } | |
3002 }); | |
3003 if(params) { | |
3004 afterDateSet(function() { | |
3005 d.set(params, true); | |
3006 }); | |
3007 } | |
3008 } | |
3009 | |
3010 d.utc(forceUTC); | |
3011 | |
3012 if(isDate(f)) { | |
3013 // If the source here is already a date object, then the operation | |
3014 // is the same as cloning the date, which preserves the UTC flag. | |
3015 d.utc(f.isUTC()).setTime(f.getTime()); | |
3016 } else if(isNumber(f)) { | |
3017 d.setTime(f); | |
3018 } else if(isObjectType(f)) { | |
3019 d.set(f, true); | |
3020 set = f; | |
3021 } else if(isString(f)) { | |
3022 | |
3023 // The act of getting the localization will pre-initialize | |
3024 // if it is missing and add the required formats. | |
3025 baseLocalization = getLocalization(localeCode); | |
3026 | |
3027 // Clean the input and convert Kanji based numerals if they exist. | |
3028 f = cleanDateInput(f); | |
3029 | |
3030 if(baseLocalization) { | |
3031 iterateOverObject(baseLocalization.getFormats(), function(i, dif) { | |
3032 var match = f.match(dif.reg); | |
3033 if(match) { | |
3034 | |
3035 loc = dif.locale; | |
3036 set = getFormatMatch(match, dif.to, loc); | |
3037 loc.cachedFormat = dif; | |
3038 | |
3039 | |
3040 if(set['utc']) { | |
3041 d.utc(); | |
3042 } | |
3043 | |
3044 if(set.timestamp) { | |
3045 set = set.timestamp; | |
3046 return false; | |
3047 } | |
3048 | |
3049 // If there's a variant (crazy Endian American format), swap the mon
th and day. | |
3050 if(dif.variant && !isString(set['month']) && (isString(set['date'])
|| baseLocalization.hasVariant(localeCode))) { | |
3051 tmp = set['month']; | |
3052 set['month'] = set['date']; | |
3053 set['date'] = tmp; | |
3054 } | |
3055 | |
3056 // If the year is 2 digits then get the implied century. | |
3057 if(set['year'] && set.yearAsString.length === 2) { | |
3058 set['year'] = getYearFromAbbreviation(set['year']); | |
3059 } | |
3060 | |
3061 // Set the month which may be localized. | |
3062 if(set['month']) { | |
3063 set['month'] = loc.getMonth(set['month']); | |
3064 if(set['shift'] && !set['unit']) set['unit'] = loc['units'][7]; | |
3065 } | |
3066 | |
3067 // If there is both a weekday and a date, the date takes precedence. | |
3068 if(set['weekday'] && set['date']) { | |
3069 delete set['weekday']; | |
3070 // Otherwise set a localized weekday. | |
3071 } else if(set['weekday']) { | |
3072 set['weekday'] = loc.getWeekday(set['weekday']); | |
3073 if(set['shift'] && !set['unit']) set['unit'] = loc['units'][5]; | |
3074 } | |
3075 | |
3076 // Relative day localizations such as "today" and "tomorrow". | |
3077 if(set['day'] && (tmp = loc.modifiersByName[set['day']])) { | |
3078 set['day'] = tmp.value; | |
3079 d.reset(); | |
3080 relative = true; | |
3081 // If the day is a weekday, then set that instead. | |
3082 } else if(set['day'] && (weekday = loc.getWeekday(set['day'])) > -1)
{ | |
3083 delete set['day']; | |
3084 if(set['num'] && set['month']) { | |
3085 // If we have "the 2nd tuesday of June", set the day to the begi
nning of the month, then | |
3086 // set the weekday after all other properties have been set. The
weekday needs to be set | |
3087 // after the actual set because it requires overriding the "pref
er" argument which | |
3088 // could unintentionally send the year into the future, past, et
c. | |
3089 afterDateSet(setWeekdayOfMonth); | |
3090 set['day'] = 1; | |
3091 } else { | |
3092 set['weekday'] = weekday; | |
3093 } | |
3094 } | |
3095 | |
3096 if(set['date'] && !isNumber(set['date'])) { | |
3097 set['date'] = loc.getNumericDate(set['date']); | |
3098 } | |
3099 | |
3100 // If the time is 1pm-11pm advance the time by 12 hours. | |
3101 if(loc.matchPM(set['ampm']) && set['hour'] < 12) { | |
3102 set['hour'] += 12; | |
3103 } else if(loc.matchAM(set['ampm']) && set['hour'] === 12) { | |
3104 set['hour'] = 0; | |
3105 } | |
3106 | |
3107 // Adjust for timezone offset | |
3108 if('offset_hours' in set || 'offset_minutes' in set) { | |
3109 d.utc(); | |
3110 set['offset_minutes'] = set['offset_minutes'] || 0; | |
3111 set['offset_minutes'] += set['offset_hours'] * 60; | |
3112 if(set['offset_sign'] === '-') { | |
3113 set['offset_minutes'] *= -1; | |
3114 } | |
3115 set['minute'] -= set['offset_minutes']; | |
3116 } | |
3117 | |
3118 // Date has a unit like "days", "months", etc. are all relative to t
he current date. | |
3119 if(set['unit']) { | |
3120 relative = true; | |
3121 num = loc.getNumber(set['num']); | |
3122 unitIndex = loc.getUnitIndex(set['unit']); | |
3123 unit = English['units'][unitIndex]; | |
3124 | |
3125 // Formats like "the 15th of last month" or "6:30pm of next week" | |
3126 // contain absolute units in addition to relative ones, so separat
e | |
3127 // them here, remove them from the params, and set up a callback t
o | |
3128 // set them after the relative ones have been set. | |
3129 separateAbsoluteUnits(); | |
3130 | |
3131 // Shift and unit, ie "next month", "last week", etc. | |
3132 if(set['shift']) { | |
3133 num *= (tmp = loc.modifiersByName[set['shift']]) ? tmp.value : 0
; | |
3134 } | |
3135 | |
3136 // Unit and sign, ie "months ago", "weeks from now", etc. | |
3137 if(set['sign'] && (tmp = loc.modifiersByName[set['sign']])) { | |
3138 num *= tmp.value; | |
3139 } | |
3140 | |
3141 // Units can be with non-relative dates, set here. ie "the day aft
er monday" | |
3142 if(isDefined(set['weekday'])) { | |
3143 d.set({'weekday': set['weekday'] }, true); | |
3144 delete set['weekday']; | |
3145 } | |
3146 | |
3147 // Finally shift the unit. | |
3148 set[unit] = (set[unit] || 0) + num; | |
3149 } | |
3150 | |
3151 // If there is an "edge" it needs to be set after the | |
3152 // other fields are set. ie "the end of February" | |
3153 if(set['edge']) { | |
3154 afterDateSet(setUnitEdge); | |
3155 } | |
3156 | |
3157 if(set['year_sign'] === '-') { | |
3158 set['year'] *= -1; | |
3159 } | |
3160 | |
3161 iterateOverDateUnits(function(name, unit, i) { | |
3162 var value = set[name], fraction = value % 1; | |
3163 if(fraction) { | |
3164 set[DateUnitsReversed[i - 1].name] = round(fraction * (name ===
'second' ? 1000 : 60)); | |
3165 set[name] = floor(value); | |
3166 } | |
3167 }, 1, 4); | |
3168 return false; | |
3169 } | |
3170 }); | |
3171 } | |
3172 if(!set) { | |
3173 // The Date constructor does something tricky like checking the number | |
3174 // of arguments so simply passing in undefined won't work. | |
3175 if(f !== 'now') { | |
3176 d = new date(f); | |
3177 } | |
3178 if(forceUTC) { | |
3179 // Falling back to system date here which cannot be parsed as UTC, | |
3180 // so if we're forcing UTC then simply add the offset. | |
3181 d.addMinutes(-d.getTimezoneOffset()); | |
3182 } | |
3183 } else if(relative) { | |
3184 d.advance(set); | |
3185 } else { | |
3186 if(d._utc) { | |
3187 // UTC times can traverse into other days or even months, | |
3188 // so preemtively reset the time here to prevent this. | |
3189 d.reset(); | |
3190 } | |
3191 updateDate(d, set, true, false, prefer); | |
3192 } | |
3193 fireCallbacks(); | |
3194 // A date created by parsing a string presumes that the format *itself* is
UTC, but | |
3195 // not that the date, once created, should be manipulated as such. In othe
r words, | |
3196 // if you are creating a date object from a server time "2012-11-15T12:00:
00Z", | |
3197 // in the majority of cases you are using it to create a date that will, a
fter creation, | |
3198 // be manipulated as local, so reset the utc flag here. | |
3199 d.utc(false); | |
3200 } | |
3201 return { | |
3202 date: d, | |
3203 set: set | |
3204 } | |
3205 } | |
3206 | |
3207 // If the year is two digits, add the most appropriate century prefix. | |
3208 function getYearFromAbbreviation(year) { | |
3209 return round(callDateGet(getNewDate(), 'FullYear') / 100) * 100 - round(year
/ 100) * 100 + year; | |
3210 } | |
3211 | |
3212 function getShortHour(d) { | |
3213 var hours = callDateGet(d, 'Hours'); | |
3214 return hours === 0 ? 12 : hours - (floor(hours / 13) * 12); | |
3215 } | |
3216 | |
3217 // weeksSince won't work here as the result needs to be floored, not rounded. | |
3218 function getWeekNumber(date) { | |
3219 date = date.clone(); | |
3220 var dow = callDateGet(date, 'Day') || 7; | |
3221 date.addDays(4 - dow).reset(); | |
3222 return 1 + floor(date.daysSince(date.clone().beginningOfYear()) / 7); | |
3223 } | |
3224 | |
3225 function getAdjustedUnit(ms) { | |
3226 var next, ams = abs(ms), value = ams, unitIndex = 0; | |
3227 iterateOverDateUnits(function(name, unit, i) { | |
3228 next = floor(withPrecision(ams / unit.multiplier(), 1)); | |
3229 if(next >= 1) { | |
3230 value = next; | |
3231 unitIndex = i; | |
3232 } | |
3233 }, 1); | |
3234 return [value, unitIndex, ms]; | |
3235 } | |
3236 | |
3237 function getRelativeWithMonthFallback(date) { | |
3238 var adu = getAdjustedUnit(date.millisecondsFromNow()); | |
3239 if(allowMonthFallback(date, adu)) { | |
3240 // If the adjusted unit is in months, then better to use | |
3241 // the "monthsfromNow" which applies a special error margin | |
3242 // for edge cases such as Jan-09 - Mar-09 being less than | |
3243 // 2 months apart (when using a strict numeric definition). | |
3244 // The third "ms" element in the array will handle the sign | |
3245 // (past or future), so simply take the absolute value here. | |
3246 adu[0] = abs(date.monthsFromNow()); | |
3247 adu[1] = 6; | |
3248 } | |
3249 return adu; | |
3250 } | |
3251 | |
3252 function allowMonthFallback(date, adu) { | |
3253 // Allow falling back to monthsFromNow if the unit is in months... | |
3254 return adu[1] === 6 || | |
3255 // ...or if it's === 4 weeks and there are more days than in the given month | |
3256 (adu[1] === 5 && adu[0] === 4 && date.daysFromNow() >= getNewDate().daysInMo
nth()); | |
3257 } | |
3258 | |
3259 | |
3260 // Date format token helpers | |
3261 | |
3262 function createMeridianTokens(slice, caps) { | |
3263 var fn = function(d, localeCode) { | |
3264 var hours = callDateGet(d, 'Hours'); | |
3265 return getLocalization(localeCode)['ampm'][floor(hours / 12)] || ''; | |
3266 } | |
3267 createFormatToken('t', fn, 1); | |
3268 createFormatToken('tt', fn); | |
3269 createFormatToken('T', fn, 1, 1); | |
3270 createFormatToken('TT', fn, null, 2); | |
3271 } | |
3272 | |
3273 function createWeekdayTokens(slice, caps) { | |
3274 var fn = function(d, localeCode) { | |
3275 var dow = callDateGet(d, 'Day'); | |
3276 return getLocalization(localeCode)['weekdays'][dow]; | |
3277 } | |
3278 createFormatToken('dow', fn, 3); | |
3279 createFormatToken('Dow', fn, 3, 1); | |
3280 createFormatToken('weekday', fn); | |
3281 createFormatToken('Weekday', fn, null, 1); | |
3282 } | |
3283 | |
3284 function createMonthTokens(slice, caps) { | |
3285 createMonthToken('mon', 0, 3); | |
3286 createMonthToken('month', 0); | |
3287 | |
3288 // For inflected month forms, namely Russian. | |
3289 createMonthToken('month2', 1); | |
3290 createMonthToken('month3', 2); | |
3291 } | |
3292 | |
3293 function createMonthToken(token, multiplier, slice) { | |
3294 var fn = function(d, localeCode) { | |
3295 var month = callDateGet(d, 'Month'); | |
3296 return getLocalization(localeCode)['months'][month + (multiplier * 12)]; | |
3297 }; | |
3298 createFormatToken(token, fn, slice); | |
3299 createFormatToken(simpleCapitalize(token), fn, slice, 1); | |
3300 } | |
3301 | |
3302 function createFormatToken(t, fn, slice, caps) { | |
3303 DateFormatTokens[t] = function(d, localeCode) { | |
3304 var str = fn(d, localeCode); | |
3305 if(slice) str = str.slice(0, slice); | |
3306 if(caps) str = str.slice(0, caps).toUpperCase() + str.slice(caps); | |
3307 return str; | |
3308 } | |
3309 } | |
3310 | |
3311 function createPaddedToken(t, fn, ms) { | |
3312 DateFormatTokens[t] = fn; | |
3313 DateFormatTokens[t + t] = function (d, localeCode) { | |
3314 return padNumber(fn(d, localeCode), 2); | |
3315 }; | |
3316 if(ms) { | |
3317 DateFormatTokens[t + t + t] = function (d, localeCode) { | |
3318 return padNumber(fn(d, localeCode), 3); | |
3319 }; | |
3320 DateFormatTokens[t + t + t + t] = function (d, localeCode) { | |
3321 return padNumber(fn(d, localeCode), 4); | |
3322 }; | |
3323 } | |
3324 } | |
3325 | |
3326 | |
3327 // Date formatting helpers | |
3328 | |
3329 function buildCompiledOutputFormat(format) { | |
3330 var match = format.match(/(\{\w+\})|[^{}]+/g); | |
3331 CompiledOutputFormats[format] = match.map(function(p) { | |
3332 p.replace(/\{(\w+)\}/, function(full, token) { | |
3333 p = DateFormatTokens[token] || token; | |
3334 return token; | |
3335 }); | |
3336 return p; | |
3337 }); | |
3338 } | |
3339 | |
3340 function executeCompiledOutputFormat(date, format, localeCode) { | |
3341 var compiledFormat, length, i, t, result = ''; | |
3342 compiledFormat = CompiledOutputFormats[format]; | |
3343 for(i = 0, length = compiledFormat.length; i < length; i++) { | |
3344 t = compiledFormat[i]; | |
3345 result += isFunction(t) ? t(date, localeCode) : t; | |
3346 } | |
3347 return result; | |
3348 } | |
3349 | |
3350 function formatDate(date, format, relative, localeCode) { | |
3351 var adu; | |
3352 if(!date.isValid()) { | |
3353 return 'Invalid Date'; | |
3354 } else if(Date[format]) { | |
3355 format = Date[format]; | |
3356 } else if(isFunction(format)) { | |
3357 adu = getRelativeWithMonthFallback(date); | |
3358 format = format.apply(date, adu.concat(getLocalization(localeCode))); | |
3359 } | |
3360 if(!format && relative) { | |
3361 adu = adu || getRelativeWithMonthFallback(date); | |
3362 // Adjust up if time is in ms, as this doesn't | |
3363 // look very good for a standard relative date. | |
3364 if(adu[1] === 0) { | |
3365 adu[1] = 1; | |
3366 adu[0] = 1; | |
3367 } | |
3368 return getLocalization(localeCode).getRelativeFormat(adu); | |
3369 } | |
3370 format = format || 'long'; | |
3371 if(format === 'short' || format === 'long' || format === 'full') { | |
3372 format = getLocalization(localeCode)[format]; | |
3373 } | |
3374 | |
3375 if(!CompiledOutputFormats[format]) { | |
3376 buildCompiledOutputFormat(format); | |
3377 } | |
3378 | |
3379 return executeCompiledOutputFormat(date, format, localeCode); | |
3380 } | |
3381 | |
3382 // Date comparison helpers | |
3383 | |
3384 function compareDate(d, find, localeCode, buffer, forceUTC) { | |
3385 var p, t, min, max, override, capitalized, accuracy = 0, loBuffer = 0, hiBuf
fer = 0; | |
3386 p = getExtendedDate(find, localeCode, null, forceUTC); | |
3387 if(buffer > 0) { | |
3388 loBuffer = hiBuffer = buffer; | |
3389 override = true; | |
3390 } | |
3391 if(!p.date.isValid()) return false; | |
3392 if(p.set && p.set.specificity) { | |
3393 DateUnits.forEach(function(u, i) { | |
3394 if(u.name === p.set.specificity) { | |
3395 accuracy = u.multiplier(p.date, d - p.date) - 1; | |
3396 } | |
3397 }); | |
3398 capitalized = simpleCapitalize(p.set.specificity); | |
3399 if(p.set['edge'] || p.set['shift']) { | |
3400 p.date['beginningOf' + capitalized](); | |
3401 } | |
3402 if(p.set.specificity === 'month') { | |
3403 max = p.date.clone()['endOf' + capitalized]().getTime(); | |
3404 } | |
3405 if(!override && p.set['sign'] && p.set.specificity != 'millisecond') { | |
3406 // If the time is relative, there can occasionally be an disparity betwe
en the relative date | |
3407 // and "now", which it is being compared to, so set an extra buffer to a
ccount for this. | |
3408 loBuffer = 50; | |
3409 hiBuffer = -50; | |
3410 } | |
3411 } | |
3412 t = d.getTime(); | |
3413 min = p.date.getTime(); | |
3414 max = max || (min + accuracy); | |
3415 max = compensateForTimezoneTraversal(d, min, max); | |
3416 return t >= (min - loBuffer) && t <= (max + hiBuffer); | |
3417 } | |
3418 | |
3419 function compensateForTimezoneTraversal(d, min, max) { | |
3420 var dMin, dMax, minOffset, maxOffset; | |
3421 dMin = new date(min); | |
3422 dMax = new date(max).utc(d.isUTC()); | |
3423 if(callDateGet(dMax, 'Hours') !== 23) { | |
3424 minOffset = dMin.getTimezoneOffset(); | |
3425 maxOffset = dMax.getTimezoneOffset(); | |
3426 if(minOffset !== maxOffset) { | |
3427 max += (maxOffset - minOffset).minutes(); | |
3428 } | |
3429 } | |
3430 return max; | |
3431 } | |
3432 | |
3433 function updateDate(d, params, reset, advance, prefer) { | |
3434 var weekday, specificityIndex; | |
3435 | |
3436 function getParam(key) { | |
3437 return isDefined(params[key]) ? params[key] : params[key + 's']; | |
3438 } | |
3439 | |
3440 function paramExists(key) { | |
3441 return isDefined(getParam(key)); | |
3442 } | |
3443 | |
3444 function uniqueParamExists(key, isDay) { | |
3445 return paramExists(key) || (isDay && paramExists('weekday')); | |
3446 } | |
3447 | |
3448 function canDisambiguate() { | |
3449 switch(prefer) { | |
3450 case -1: return d > getNewDate(); | |
3451 case 1: return d < getNewDate(); | |
3452 } | |
3453 } | |
3454 | |
3455 if(isNumber(params) && advance) { | |
3456 // If param is a number and we're advancing, the number is presumed to be
milliseconds. | |
3457 params = { 'milliseconds': params }; | |
3458 } else if(isNumber(params)) { | |
3459 // Otherwise just set the timestamp and return. | |
3460 d.setTime(params); | |
3461 return d; | |
3462 } | |
3463 | |
3464 // "date" can also be passed for the day | |
3465 if(isDefined(params['date'])) { | |
3466 params['day'] = params['date']; | |
3467 } | |
3468 | |
3469 // Reset any unit lower than the least specific unit set. Do not do this for
weeks | |
3470 // or for years. This needs to be performed before the acutal setting of the
date | |
3471 // because the order needs to be reversed in order to get the lowest specifi
city, | |
3472 // also because higher order units can be overwritten by lower order units,
such | |
3473 // as setting hour: 3, minute: 345, etc. | |
3474 iterateOverDateUnits(function(name, unit, i) { | |
3475 var isDay = name === 'day'; | |
3476 if(uniqueParamExists(name, isDay)) { | |
3477 params.specificity = name; | |
3478 specificityIndex = +i; | |
3479 return false; | |
3480 } else if(reset && name !== 'week' && (!isDay || !paramExists('week'))) { | |
3481 // Days are relative to months, not weeks, so don't reset if a week exis
ts. | |
3482 callDateSet(d, unit.method, (isDay ? 1 : 0)); | |
3483 } | |
3484 }); | |
3485 | |
3486 // Now actually set or advance the date in order, higher units first. | |
3487 DateUnits.forEach(function(u, i) { | |
3488 var name = u.name, method = u.method, higherUnit = DateUnits[i - 1], value
; | |
3489 value = getParam(name) | |
3490 if(isUndefined(value)) return; | |
3491 if(advance) { | |
3492 if(name === 'week') { | |
3493 value = (params['day'] || 0) + (value * 7); | |
3494 method = 'Date'; | |
3495 } | |
3496 value = (value * advance) + callDateGet(d, method); | |
3497 } else if(name === 'month' && paramExists('day')) { | |
3498 // When setting the month, there is a chance that we will traverse into
a new month. | |
3499 // This happens in DST shifts, for example June 1st DST jumping to Janua
ry 1st | |
3500 // (non-DST) will have a shift of -1:00 which will traverse into the pre
vious year. | |
3501 // Prevent this by proactively setting the day when we know it will be s
et again anyway. | |
3502 // It can also happen when there are not enough days in the target month
. This second | |
3503 // situation is identical to checkMonthTraversal below, however when we
are advancing | |
3504 // we want to reset the date to "the last date in the target month". In
the case of | |
3505 // DST shifts, however, we want to avoid the "edges" of months as that i
s where this | |
3506 // unintended traversal can happen. This is the reason for the different
handling of | |
3507 // two similar but slightly different situations. | |
3508 // | |
3509 // TL;DR This method avoids the edges of a month IF not advancing and th
e date is going | |
3510 // to be set anyway, while checkMonthTraversal resets the date to the la
st day if advancing. | |
3511 // | |
3512 callDateSet(d, 'Date', 15); | |
3513 } | |
3514 callDateSet(d, method, value); | |
3515 if(advance && name === 'month') { | |
3516 checkMonthTraversal(d, value); | |
3517 } | |
3518 }); | |
3519 | |
3520 | |
3521 // If a weekday is included in the params, set it ahead of time and set the
params | |
3522 // to reflect the updated date so that resetting works properly. | |
3523 if(!advance && !paramExists('day') && paramExists('weekday')) { | |
3524 var weekday = getParam('weekday'), isAhead, futurePreferred; | |
3525 d.setWeekday(weekday); | |
3526 } | |
3527 | |
3528 // If past or future is preferred, then the process of "disambiguation" will
ensure that an | |
3529 // ambiguous time/date ("4pm", "thursday", "June", etc.) will be in the past
or future. | |
3530 if(canDisambiguate()) { | |
3531 iterateOverDateUnits(function(name, unit) { | |
3532 var ambiguous = unit.ambiguous || (name === 'week' && paramExists('weekd
ay')); | |
3533 if(ambiguous && !uniqueParamExists(name, name === 'day')) { | |
3534 d[unit.addMethod](prefer); | |
3535 return false; | |
3536 } | |
3537 }, specificityIndex + 1); | |
3538 } | |
3539 return d; | |
3540 } | |
3541 | |
3542 // The ISO format allows times strung together without a demarcating ":", so m
ake sure | |
3543 // that these markers are now optional. | |
3544 function prepareTime(format, loc, iso) { | |
3545 var timeSuffixMapping = {'h':0,'m':1,'s':2}, add; | |
3546 loc = loc || English; | |
3547 return format.replace(/{([a-z])}/g, function(full, token) { | |
3548 var separators = [], | |
3549 isHours = token === 'h', | |
3550 tokenIsRequired = isHours && !iso; | |
3551 if(token === 't') { | |
3552 return loc['ampm'].join('|'); | |
3553 } else { | |
3554 if(isHours) { | |
3555 separators.push(':'); | |
3556 } | |
3557 if(add = loc['timeSuffixes'][timeSuffixMapping[token]]) { | |
3558 separators.push(add + '\\s*'); | |
3559 } | |
3560 return separators.length === 0 ? '' : '(?:' + separators.join('|') + ')'
+ (tokenIsRequired ? '' : '?'); | |
3561 } | |
3562 }); | |
3563 } | |
3564 | |
3565 | |
3566 // If the month is being set, then we don't want to accidentally | |
3567 // traverse into a new month just because the target month doesn't have enough | |
3568 // days. In other words, "5 months ago" from July 30th is still February, even | |
3569 // though there is no February 30th, so it will of necessity be February 28th | |
3570 // (or 29th in the case of a leap year). | |
3571 | |
3572 function checkMonthTraversal(date, targetMonth) { | |
3573 if(targetMonth < 0) { | |
3574 targetMonth = targetMonth % 12 + 12; | |
3575 } | |
3576 if(targetMonth % 12 != callDateGet(date, 'Month')) { | |
3577 callDateSet(date, 'Date', 0); | |
3578 } | |
3579 } | |
3580 | |
3581 function createDate(args, prefer, forceUTC) { | |
3582 var f, localeCode; | |
3583 if(isNumber(args[1])) { | |
3584 // If the second argument is a number, then we have an enumerated construc
tor type as in "new Date(2003, 2, 12);" | |
3585 f = collectDateArguments(args)[0]; | |
3586 } else { | |
3587 f = args[0]; | |
3588 localeCode = args[1]; | |
3589 } | |
3590 return getExtendedDate(f, localeCode, prefer, forceUTC).date; | |
3591 } | |
3592 | |
3593 function invalidateDate(d) { | |
3594 d.setTime(NaN); | |
3595 } | |
3596 | |
3597 function buildDateUnits() { | |
3598 DateUnitsReversed = DateUnits.concat().reverse(); | |
3599 DateArgumentUnits = DateUnits.concat(); | |
3600 DateArgumentUnits.splice(2,1); | |
3601 } | |
3602 | |
3603 | |
3604 /*** | |
3605 * @method [units]Since([d], [locale] = currentLocale) | |
3606 * @returns Number | |
3607 * @short Returns the time since [d] in the appropriate unit. | |
3608 * @extra [d] will accept a date object, timestamp, or text format. If not spe
cified, [d] is assumed to be now. [locale] can be passed to specify the locale t
hat the date is in. %[unit]Ago% is provided as an alias to make this more readab
le when [d] is assumed to be the current date. For more see @date_format. | |
3609 * | |
3610 * @set | |
3611 * millisecondsSince | |
3612 * secondsSince | |
3613 * minutesSince | |
3614 * hoursSince | |
3615 * daysSince | |
3616 * weeksSince | |
3617 * monthsSince | |
3618 * yearsSince | |
3619 * | |
3620 * @example | |
3621 * | |
3622 * Date.create().millisecondsSince('1 hour ago') -> 3,600,000 | |
3623 * Date.create().daysSince('1 week ago') -> 7 | |
3624 * Date.create().yearsSince('15 years ago') -> 15 | |
3625 * Date.create('15 years ago').yearsAgo() -> 15 | |
3626 * | |
3627 *** | |
3628 * @method [units]Ago() | |
3629 * @returns Number | |
3630 * @short Returns the time ago in the appropriate unit. | |
3631 * | |
3632 * @set | |
3633 * millisecondsAgo | |
3634 * secondsAgo | |
3635 * minutesAgo | |
3636 * hoursAgo | |
3637 * daysAgo | |
3638 * weeksAgo | |
3639 * monthsAgo | |
3640 * yearsAgo | |
3641 * | |
3642 * @example | |
3643 * | |
3644 * Date.create('last year').millisecondsAgo() -> 3,600,000 | |
3645 * Date.create('last year').daysAgo() -> 7 | |
3646 * Date.create('last year').yearsAgo() -> 15 | |
3647 * | |
3648 *** | |
3649 * @method [units]Until([d], [locale] = currentLocale) | |
3650 * @returns Number | |
3651 * @short Returns the time until [d] in the appropriate unit. | |
3652 * @extra [d] will accept a date object, timestamp, or text format. If not spe
cified, [d] is assumed to be now. [locale] can be passed to specify the locale t
hat the date is in. %[unit]FromNow% is provided as an alias to make this more re
adable when [d] is assumed to be the current date. For more see @date_format. | |
3653 * | |
3654 * @set | |
3655 * millisecondsUntil | |
3656 * secondsUntil | |
3657 * minutesUntil | |
3658 * hoursUntil | |
3659 * daysUntil | |
3660 * weeksUntil | |
3661 * monthsUntil | |
3662 * yearsUntil | |
3663 * | |
3664 * @example | |
3665 * | |
3666 * Date.create().millisecondsUntil('1 hour from now') -> 3,600,000 | |
3667 * Date.create().daysUntil('1 week from now') -> 7 | |
3668 * Date.create().yearsUntil('15 years from now') -> 15 | |
3669 * Date.create('15 years from now').yearsFromNow() -> 15 | |
3670 * | |
3671 *** | |
3672 * @method [units]FromNow() | |
3673 * @returns Number | |
3674 * @short Returns the time from now in the appropriate unit. | |
3675 * | |
3676 * @set | |
3677 * millisecondsFromNow | |
3678 * secondsFromNow | |
3679 * minutesFromNow | |
3680 * hoursFromNow | |
3681 * daysFromNow | |
3682 * weeksFromNow | |
3683 * monthsFromNow | |
3684 * yearsFromNow | |
3685 * | |
3686 * @example | |
3687 * | |
3688 * Date.create('next year').millisecondsFromNow() -> 3,600,000 | |
3689 * Date.create('next year').daysFromNow() -> 7 | |
3690 * Date.create('next year').yearsFromNow() -> 15 | |
3691 * | |
3692 *** | |
3693 * @method add[Units](<num>, [reset] = false) | |
3694 * @returns Date | |
3695 * @short Adds <num> of the unit to the date. If [reset] is true, all lower un
its will be reset. | |
3696 * @extra Note that "months" is ambiguous as a unit of time. If the target dat
e falls on a day that does not exist (ie. August 31 -> February 31), the date wi
ll be shifted to the last day of the month. Don't use %addMonths% if you need pr
ecision. | |
3697 * | |
3698 * @set | |
3699 * addMilliseconds | |
3700 * addSeconds | |
3701 * addMinutes | |
3702 * addHours | |
3703 * addDays | |
3704 * addWeeks | |
3705 * addMonths | |
3706 * addYears | |
3707 * | |
3708 * @example | |
3709 * | |
3710 * Date.create().addMilliseconds(5) -> current time + 5 milliseconds | |
3711 * Date.create().addDays(5) -> current time + 5 days | |
3712 * Date.create().addYears(5) -> current time + 5 years | |
3713 * | |
3714 *** | |
3715 * @method isLast[Unit]() | |
3716 * @returns Boolean | |
3717 * @short Returns true if the date is last week/month/year. | |
3718 * | |
3719 * @set | |
3720 * isLastWeek | |
3721 * isLastMonth | |
3722 * isLastYear | |
3723 * | |
3724 * @example | |
3725 * | |
3726 * Date.create('yesterday').isLastWeek() -> true or false? | |
3727 * Date.create('yesterday').isLastMonth() -> probably not... | |
3728 * Date.create('yesterday').isLastYear() -> even less likely... | |
3729 * | |
3730 *** | |
3731 * @method isThis[Unit]() | |
3732 * @returns Boolean | |
3733 * @short Returns true if the date is this week/month/year. | |
3734 * | |
3735 * @set | |
3736 * isThisWeek | |
3737 * isThisMonth | |
3738 * isThisYear | |
3739 * | |
3740 * @example | |
3741 * | |
3742 * Date.create('tomorrow').isThisWeek() -> true or false? | |
3743 * Date.create('tomorrow').isThisMonth() -> probably... | |
3744 * Date.create('tomorrow').isThisYear() -> signs point to yes... | |
3745 * | |
3746 *** | |
3747 * @method isNext[Unit]() | |
3748 * @returns Boolean | |
3749 * @short Returns true if the date is next week/month/year. | |
3750 * | |
3751 * @set | |
3752 * isNextWeek | |
3753 * isNextMonth | |
3754 * isNextYear | |
3755 * | |
3756 * @example | |
3757 * | |
3758 * Date.create('tomorrow').isNextWeek() -> true or false? | |
3759 * Date.create('tomorrow').isNextMonth() -> probably not... | |
3760 * Date.create('tomorrow').isNextYear() -> even less likely... | |
3761 * | |
3762 *** | |
3763 * @method beginningOf[Unit]() | |
3764 * @returns Date | |
3765 * @short Sets the date to the beginning of the appropriate unit. | |
3766 * | |
3767 * @set | |
3768 * beginningOfDay | |
3769 * beginningOfWeek | |
3770 * beginningOfMonth | |
3771 * beginningOfYear | |
3772 * | |
3773 * @example | |
3774 * | |
3775 * Date.create().beginningOfDay() -> the beginning of today (resets the ti
me) | |
3776 * Date.create().beginningOfWeek() -> the beginning of the week | |
3777 * Date.create().beginningOfMonth() -> the beginning of the month | |
3778 * Date.create().beginningOfYear() -> the beginning of the year | |
3779 * | |
3780 *** | |
3781 * @method endOf[Unit]() | |
3782 * @returns Date | |
3783 * @short Sets the date to the end of the appropriate unit. | |
3784 * | |
3785 * @set | |
3786 * endOfDay | |
3787 * endOfWeek | |
3788 * endOfMonth | |
3789 * endOfYear | |
3790 * | |
3791 * @example | |
3792 * | |
3793 * Date.create().endOfDay() -> the end of today (sets the time to 23:59:59
.999) | |
3794 * Date.create().endOfWeek() -> the end of the week | |
3795 * Date.create().endOfMonth() -> the end of the month | |
3796 * Date.create().endOfYear() -> the end of the year | |
3797 * | |
3798 ***/ | |
3799 | |
3800 function buildDateMethods() { | |
3801 extendSimilar(date, true, true, DateUnits, function(methods, u, i) { | |
3802 var name = u.name, caps = simpleCapitalize(name), multiplier = u.multiplie
r(), since, until; | |
3803 u.addMethod = 'add' + caps + 's'; | |
3804 // "since/until now" only count "past" an integer, i.e. "2 days ago" is | |
3805 // anything between 2 - 2.999 days. The default margin of error is 0.999, | |
3806 // but "months" have an inherently larger margin, as the number of days | |
3807 // in a given month may be significantly less than the number of days in | |
3808 // the average month, so for example "30 days" before March 15 may in fact | |
3809 // be 1 month ago. Years also have a margin of error due to leap years, | |
3810 // but this is roughly 0.999 anyway (365 / 365.25). Other units do not | |
3811 // technically need the error margin applied to them but this accounts | |
3812 // for discrepancies like (15).hoursAgo() which technically creates the | |
3813 // current date first, then creates a date 15 hours before and compares | |
3814 // them, the discrepancy between the creation of the 2 dates means that | |
3815 // they may actually be 15.0001 hours apart. Milliseconds don't have | |
3816 // fractions, so they won't be subject to this error margin. | |
3817 function applyErrorMargin(ms) { | |
3818 var num = ms / multiplier, | |
3819 fraction = num % 1, | |
3820 error = u.error || 0.999; | |
3821 if(fraction && abs(fraction % 1) > error) { | |
3822 num = round(num); | |
3823 } | |
3824 return num < 0 ? ceil(num) : floor(num); | |
3825 } | |
3826 since = function(f, localeCode) { | |
3827 return applyErrorMargin(this.getTime() - date.create(f, localeCode).getT
ime()); | |
3828 }; | |
3829 until = function(f, localeCode) { | |
3830 return applyErrorMargin(date.create(f, localeCode).getTime() - this.getT
ime()); | |
3831 }; | |
3832 methods[name+'sAgo'] = until; | |
3833 methods[name+'sUntil'] = until; | |
3834 methods[name+'sSince'] = since; | |
3835 methods[name+'sFromNow'] = since; | |
3836 methods[u.addMethod] = function(num, reset) { | |
3837 var set = {}; | |
3838 set[name] = num; | |
3839 return this.advance(set, reset); | |
3840 }; | |
3841 buildNumberToDateAlias(u, multiplier); | |
3842 if(i < 3) { | |
3843 ['Last','This','Next'].forEach(function(shift) { | |
3844 methods['is' + shift + caps] = function() { | |
3845 return compareDate(this, shift + ' ' + name, 'en'); | |
3846 }; | |
3847 }); | |
3848 } | |
3849 if(i < 4) { | |
3850 methods['beginningOf' + caps] = function() { | |
3851 var set = {}; | |
3852 switch(name) { | |
3853 case 'year': set['year'] = callDateGet(this, 'FullYear'); break; | |
3854 case 'month': set['month'] = callDateGet(this, 'Month'); break; | |
3855 case 'day': set['day'] = callDateGet(this, 'Date'); break; | |
3856 case 'week': set['weekday'] = 0; break; | |
3857 } | |
3858 return this.set(set, true); | |
3859 }; | |
3860 methods['endOf' + caps] = function() { | |
3861 var set = { 'hours': 23, 'minutes': 59, 'seconds': 59, 'milliseconds':
999 }; | |
3862 switch(name) { | |
3863 case 'year': set['month'] = 11; set['day'] = 31; break; | |
3864 case 'month': set['day'] = this.daysInMonth(); break; | |
3865 case 'week': set['weekday'] = 6; break; | |
3866 } | |
3867 return this.set(set, true); | |
3868 }; | |
3869 } | |
3870 }); | |
3871 } | |
3872 | |
3873 function buildCoreInputFormats() { | |
3874 English.addFormat('([+-])?(\\d{4,4})[-.]?{full_month}[-.]?(\\d{1,2})?', true
, ['year_sign','year','month','date'], false, true); | |
3875 English.addFormat('(\\d{1,2})[-.\\/]{full_month}(?:[-.\\/](\\d{2,4}))?', tru
e, ['date','month','year'], true); | |
3876 English.addFormat('{full_month}[-.](\\d{4,4})', false, ['month','year']); | |
3877 English.addFormat('\\/Date\\((\\d+(?:[+-]\\d{4,4})?)\\)\\/', false, ['timest
amp']) | |
3878 English.addFormat(prepareTime(RequiredTime, English), false, TimeFormat) | |
3879 | |
3880 // When a new locale is initialized it will have the CoreDateFormats initial
ized by default. | |
3881 // From there, adding new formats will push them in front of the previous on
es, so the core | |
3882 // formats will be the last to be reached. However, the core formats themsel
ves have English | |
3883 // months in them, which means that English needs to first be initialized an
d creates a race | |
3884 // condition. I'm getting around this here by adding these generalized forma
ts in the order | |
3885 // specific -> general, which will mean they will be added to the English lo
calization in | |
3886 // general -> specific order, then chopping them off the front and reversing
to get the correct | |
3887 // order. Note that there are 7 formats as 2 have times which adds a front a
nd a back format. | |
3888 CoreDateFormats = English.compiledFormats.slice(0,7).reverse(); | |
3889 English.compiledFormats = English.compiledFormats.slice(7).concat(CoreDateFo
rmats); | |
3890 } | |
3891 | |
3892 function buildFormatTokens() { | |
3893 | |
3894 createPaddedToken('f', function(d) { | |
3895 return callDateGet(d, 'Milliseconds'); | |
3896 }, true); | |
3897 | |
3898 createPaddedToken('s', function(d) { | |
3899 return callDateGet(d, 'Seconds'); | |
3900 }); | |
3901 | |
3902 createPaddedToken('m', function(d) { | |
3903 return callDateGet(d, 'Minutes'); | |
3904 }); | |
3905 | |
3906 createPaddedToken('h', function(d) { | |
3907 return callDateGet(d, 'Hours') % 12 || 12; | |
3908 }); | |
3909 | |
3910 createPaddedToken('H', function(d) { | |
3911 return callDateGet(d, 'Hours'); | |
3912 }); | |
3913 | |
3914 createPaddedToken('d', function(d) { | |
3915 return callDateGet(d, 'Date'); | |
3916 }); | |
3917 | |
3918 createPaddedToken('M', function(d) { | |
3919 return callDateGet(d, 'Month') + 1; | |
3920 }); | |
3921 | |
3922 createMeridianTokens(); | |
3923 createWeekdayTokens(); | |
3924 createMonthTokens(); | |
3925 | |
3926 // Aliases | |
3927 DateFormatTokens['ms'] = DateFormatTokens['f']; | |
3928 DateFormatTokens['milliseconds'] = DateFormatTokens['f']; | |
3929 DateFormatTokens['seconds'] = DateFormatTokens['s']; | |
3930 DateFormatTokens['minutes'] = DateFormatTokens['m']; | |
3931 DateFormatTokens['hours'] = DateFormatTokens['h']; | |
3932 DateFormatTokens['24hr'] = DateFormatTokens['H']; | |
3933 DateFormatTokens['12hr'] = DateFormatTokens['h']; | |
3934 DateFormatTokens['date'] = DateFormatTokens['d']; | |
3935 DateFormatTokens['day'] = DateFormatTokens['d']; | |
3936 DateFormatTokens['year'] = DateFormatTokens['yyyy']; | |
3937 | |
3938 } | |
3939 | |
3940 function buildFormatShortcuts() { | |
3941 extendSimilar(date, true, true, 'short,long,full', function(methods, name) { | |
3942 methods[name] = function(localeCode) { | |
3943 return formatDate(this, name, false, localeCode); | |
3944 } | |
3945 }); | |
3946 } | |
3947 | |
3948 function buildAsianDigits() { | |
3949 KanjiDigits.split('').forEach(function(digit, value) { | |
3950 var holder; | |
3951 if(value > 9) { | |
3952 value = pow(10, value - 9); | |
3953 } | |
3954 AsianDigitMap[digit] = value; | |
3955 }); | |
3956 simpleMerge(AsianDigitMap, NumberNormalizeMap); | |
3957 // Kanji numerals may also be included in phrases which are text-based rathe
r | |
3958 // than actual numbers such as Chinese weekdays (上周三), and "the day before | |
3959 // yesterday" (一昨日) in Japanese, so don't match these. | |
3960 AsianDigitReg = regexp('([期週周])?([' + KanjiDigits + FullWidthDigits + ']+)(?
!昨)', 'g'); | |
3961 } | |
3962 | |
3963 /*** | |
3964 * @method is[Day]() | |
3965 * @returns Boolean | |
3966 * @short Returns true if the date falls on that day. | |
3967 * @extra Also available: %isYesterday%, %isToday%, %isTomorrow%, %isWeekday%,
and %isWeekend%. | |
3968 * | |
3969 * @set | |
3970 * isToday | |
3971 * isYesterday | |
3972 * isTomorrow | |
3973 * isWeekday | |
3974 * isWeekend | |
3975 * isSunday | |
3976 * isMonday | |
3977 * isTuesday | |
3978 * isWednesday | |
3979 * isThursday | |
3980 * isFriday | |
3981 * isSaturday | |
3982 * | |
3983 * @example | |
3984 * | |
3985 * Date.create('tomorrow').isToday() -> false | |
3986 * Date.create('thursday').isTomorrow() -> ? | |
3987 * Date.create('yesterday').isWednesday() -> ? | |
3988 * Date.create('today').isWeekend() -> ? | |
3989 * | |
3990 *** | |
3991 * @method isFuture() | |
3992 * @returns Boolean | |
3993 * @short Returns true if the date is in the future. | |
3994 * @example | |
3995 * | |
3996 * Date.create('next week').isFuture() -> true | |
3997 * Date.create('last week').isFuture() -> false | |
3998 * | |
3999 *** | |
4000 * @method isPast() | |
4001 * @returns Boolean | |
4002 * @short Returns true if the date is in the past. | |
4003 * @example | |
4004 * | |
4005 * Date.create('last week').isPast() -> true | |
4006 * Date.create('next week').isPast() -> false | |
4007 * | |
4008 ***/ | |
4009 function buildRelativeAliases() { | |
4010 var special = 'today,yesterday,tomorrow,weekday,weekend,future,past'.split(
','); | |
4011 var weekdays = English['weekdays'].slice(0,7); | |
4012 var months = English['months'].slice(0,12); | |
4013 extendSimilar(date, true, true, special.concat(weekdays).concat(months), fun
ction(methods, name) { | |
4014 methods['is'+ simpleCapitalize(name)] = function(utc) { | |
4015 return this.is(name, 0, utc); | |
4016 }; | |
4017 }); | |
4018 } | |
4019 | |
4020 function buildUTCAliases() { | |
4021 // Don't want to use extend here as it will override | |
4022 // the actual "utc" method on the prototype. | |
4023 if(date['utc']) return; | |
4024 date['utc'] = { | |
4025 | |
4026 'create': function() { | |
4027 return createDate(arguments, 0, true); | |
4028 }, | |
4029 | |
4030 'past': function() { | |
4031 return createDate(arguments, -1, true); | |
4032 }, | |
4033 | |
4034 'future': function() { | |
4035 return createDate(arguments, 1, true); | |
4036 } | |
4037 }; | |
4038 } | |
4039 | |
4040 function setDateProperties() { | |
4041 extend(date, false , true, { | |
4042 'RFC1123': '{Dow}, {dd} {Mon} {yyyy} {HH}:{mm}:{ss} {tz}', | |
4043 'RFC1036': '{Weekday}, {dd}-{Mon}-{yy} {HH}:{mm}:{ss} {tz}', | |
4044 'ISO8601_DATE': '{yyyy}-{MM}-{dd}', | |
4045 'ISO8601_DATETIME': '{yyyy}-{MM}-{dd}T{HH}:{mm}:{ss}.{fff}{isotz}' | |
4046 }); | |
4047 } | |
4048 | |
4049 | |
4050 extend(date, false, true, { | |
4051 | |
4052 /*** | |
4053 * @method Date.create(<d>, [locale] = currentLocale) | |
4054 * @returns Date | |
4055 * @short Alternate Date constructor which understands many different text f
ormats, a timestamp, or another date. | |
4056 * @extra If no argument is given, date is assumed to be now. %Date.create%
additionally can accept enumerated parameters as with the standard date construc
tor. [locale] can be passed to specify the locale that the date is in. When unsp
ecified, the current locale (default is English) is assumed. UTC-based dates can
be created through the %utc% object. For more see @date_format. | |
4057 * @set | |
4058 * Date.utc.create | |
4059 * | |
4060 * @example | |
4061 * | |
4062 * Date.create('July') -> July of this year | |
4063 * Date.create('1776') -> 1776 | |
4064 * Date.create('today') -> today | |
4065 * Date.create('wednesday') -> This wednesday | |
4066 * Date.create('next friday') -> Next friday | |
4067 * Date.create('July 4, 1776') -> July 4, 1776 | |
4068 * Date.create(-446806800000) -> November 5, 1955 | |
4069 * Date.create(1776, 6, 4) -> July 4, 1776 | |
4070 * Date.create('1776年07月04日', 'ja') -> July 4, 1776 | |
4071 * Date.utc.create('July 4, 1776', 'en') -> July 4, 1776 | |
4072 * | |
4073 ***/ | |
4074 'create': function() { | |
4075 return createDate(arguments); | |
4076 }, | |
4077 | |
4078 /*** | |
4079 * @method Date.past(<d>, [locale] = currentLocale) | |
4080 * @returns Date | |
4081 * @short Alternate form of %Date.create% with any ambiguity assumed to be t
he past. | |
4082 * @extra For example %"Sunday"% can be either "the Sunday coming up" or "th
e Sunday last" depending on context. Note that dates explicitly in the future ("
next Sunday") will remain in the future. This method simply provides a hint when
ambiguity exists. UTC-based dates can be created through the %utc% object. For
more, see @date_format. | |
4083 * @set | |
4084 * Date.utc.past | |
4085 * | |
4086 * @example | |
4087 * | |
4088 * Date.past('July') -> July of this year or last depending on th
e current month | |
4089 * Date.past('Wednesday') -> This wednesday or last depending on the c
urrent weekday | |
4090 * | |
4091 ***/ | |
4092 'past': function() { | |
4093 return createDate(arguments, -1); | |
4094 }, | |
4095 | |
4096 /*** | |
4097 * @method Date.future(<d>, [locale] = currentLocale) | |
4098 * @returns Date | |
4099 * @short Alternate form of %Date.create% with any ambiguity assumed to be t
he future. | |
4100 * @extra For example %"Sunday"% can be either "the Sunday coming up" or "th
e Sunday last" depending on context. Note that dates explicitly in the past ("la
st Sunday") will remain in the past. This method simply provides a hint when amb
iguity exists. UTC-based dates can be created through the %utc% object. For more
, see @date_format. | |
4101 * @set | |
4102 * Date.utc.future | |
4103 * | |
4104 * @example | |
4105 * | |
4106 * Date.future('July') -> July of this year or next depending on
the current month | |
4107 * Date.future('Wednesday') -> This wednesday or next depending on the
current weekday | |
4108 * | |
4109 ***/ | |
4110 'future': function() { | |
4111 return createDate(arguments, 1); | |
4112 }, | |
4113 | |
4114 /*** | |
4115 * @method Date.addLocale(<code>, <set>) | |
4116 * @returns Locale | |
4117 * @short Adds a locale <set> to the locales understood by Sugar. | |
4118 * @extra For more see @date_format. | |
4119 * | |
4120 ***/ | |
4121 'addLocale': function(localeCode, set) { | |
4122 return setLocalization(localeCode, set); | |
4123 }, | |
4124 | |
4125 /*** | |
4126 * @method Date.setLocale(<code>) | |
4127 * @returns Locale | |
4128 * @short Sets the current locale to be used with dates. | |
4129 * @extra Sugar has support for 13 locales that are available through the "D
ate Locales" package. In addition you can define a new locale with %Date.addLoca
le%. For more see @date_format. | |
4130 * | |
4131 ***/ | |
4132 'setLocale': function(localeCode, set) { | |
4133 var loc = getLocalization(localeCode, false); | |
4134 CurrentLocalization = loc; | |
4135 // The code is allowed to be more specific than the codes which are requir
ed: | |
4136 // i.e. zh-CN or en-US. Currently this only affects US date variants such
as 8/10/2000. | |
4137 if(localeCode && localeCode != loc['code']) { | |
4138 loc['code'] = localeCode; | |
4139 } | |
4140 return loc; | |
4141 }, | |
4142 | |
4143 /*** | |
4144 * @method Date.getLocale([code] = current) | |
4145 * @returns Locale | |
4146 * @short Gets the locale for the given code, or the current locale. | |
4147 * @extra The resulting locale object can be manipulated to provide more con
trol over date localizations. For more about locales, see @date_format. | |
4148 * | |
4149 ***/ | |
4150 'getLocale': function(localeCode) { | |
4151 return !localeCode ? CurrentLocalization : getLocalization(localeCode, fal
se); | |
4152 }, | |
4153 | |
4154 /** | |
4155 * @method Date.addFormat(<format>, <match>, [code] = null) | |
4156 * @returns Nothing | |
4157 * @short Manually adds a new date input format. | |
4158 * @extra This method allows fine grained control for alternate formats. <fo
rmat> is a string that can have regex tokens inside. <match> is an array of the
tokens that each regex capturing group will map to, for example %year%, %date%,
etc. For more, see @date_format. | |
4159 * | |
4160 **/ | |
4161 'addFormat': function(format, match, localeCode) { | |
4162 addDateInputFormat(getLocalization(localeCode), format, match); | |
4163 } | |
4164 | |
4165 }); | |
4166 | |
4167 extend(date, true, true, { | |
4168 | |
4169 /*** | |
4170 * @method set(<set>, [reset] = false) | |
4171 * @returns Date | |
4172 * @short Sets the date object. | |
4173 * @extra This method can accept multiple formats including a single number
as a timestamp, an object, or enumerated parameters (as with the Date constructo
r). If [reset] is %true%, any units more specific than those passed will be rese
t. | |
4174 * | |
4175 * @example | |
4176 * | |
4177 * new Date().set({ year: 2011, month: 11, day: 31 }) -> December 31, 2011 | |
4178 * new Date().set(2011, 11, 31) -> December 31, 2011 | |
4179 * new Date().set(86400000) -> 1 day after Jan 1
, 1970 | |
4180 * new Date().set({ year: 2004, month: 6 }, true) -> June 1, 2004, 00:
00:00.000 | |
4181 * | |
4182 ***/ | |
4183 'set': function() { | |
4184 var args = collectDateArguments(arguments); | |
4185 return updateDate(this, args[0], args[1]) | |
4186 }, | |
4187 | |
4188 /*** | |
4189 * @method setWeekday() | |
4190 * @returns Nothing | |
4191 * @short Sets the weekday of the date. | |
4192 * @extra In order to maintain a parallel with %getWeekday% (which itself is
an alias for Javascript native %getDay%), Sunday is considered day %0%. This co
ntrasts with ISO-8601 standard (used in %getISOWeek% and %setISOWeek%) which pla
ces Sunday at the end of the week (day 7). This effectively means that passing %
0% to this method while in the middle of a week will rewind the date, where pass
ing %7% will advance it. | |
4193 * | |
4194 * @example | |
4195 * | |
4196 * d = new Date(); d.setWeekday(1); d; -> Monday of this week | |
4197 * d = new Date(); d.setWeekday(6); d; -> Saturday of this week | |
4198 * | |
4199 ***/ | |
4200 'setWeekday': function(dow) { | |
4201 if(isUndefined(dow)) return; | |
4202 return callDateSet(this, 'Date', callDateGet(this, 'Date') + dow - callDat
eGet(this, 'Day')); | |
4203 }, | |
4204 | |
4205 /*** | |
4206 * @method setISOWeek() | |
4207 * @returns Nothing | |
4208 * @short Sets the week (of the year) as defined by the ISO-8601 standard. | |
4209 * @extra Note that this standard places Sunday at the end of the week (day
7). | |
4210 * | |
4211 * @example | |
4212 * | |
4213 * d = new Date(); d.setISOWeek(15); d; -> 15th week of the year | |
4214 * | |
4215 ***/ | |
4216 'setISOWeek': function(week) { | |
4217 var weekday = callDateGet(this, 'Day') || 7; | |
4218 if(isUndefined(week)) return; | |
4219 this.set({ 'month': 0, 'date': 4 }); | |
4220 this.set({ 'weekday': 1 }); | |
4221 if(week > 1) { | |
4222 this.addWeeks(week - 1); | |
4223 } | |
4224 if(weekday !== 1) { | |
4225 this.advance({ 'days': weekday - 1 }); | |
4226 } | |
4227 return this.getTime(); | |
4228 }, | |
4229 | |
4230 /*** | |
4231 * @method getISOWeek() | |
4232 * @returns Number | |
4233 * @short Gets the date's week (of the year) as defined by the ISO-8601 stan
dard. | |
4234 * @extra Note that this standard places Sunday at the end of the week (day
7). If %utc% is set on the date, the week will be according to UTC time. | |
4235 * | |
4236 * @example | |
4237 * | |
4238 * new Date().getISOWeek() -> today's week of the year | |
4239 * | |
4240 ***/ | |
4241 'getISOWeek': function() { | |
4242 return getWeekNumber(this); | |
4243 }, | |
4244 | |
4245 /*** | |
4246 * @method beginningOfISOWeek() | |
4247 * @returns Date | |
4248 * @short Set the date to the beginning of week as defined by this ISO-8601
standard. | |
4249 * @extra Note that this standard places Monday at the start of the week. | |
4250 * @example | |
4251 * | |
4252 * Date.create().beginningOfISOWeek() -> Monday | |
4253 * | |
4254 ***/ | |
4255 'beginningOfISOWeek': function() { | |
4256 var day = this.getDay(); | |
4257 if(day === 0) { | |
4258 day = -6; | |
4259 } else if(day !== 1) { | |
4260 day = 1; | |
4261 } | |
4262 this.setWeekday(day); | |
4263 return this.reset(); | |
4264 }, | |
4265 | |
4266 /*** | |
4267 * @method endOfISOWeek() | |
4268 * @returns Date | |
4269 * @short Set the date to the end of week as defined by this ISO-8601 standa
rd. | |
4270 * @extra Note that this standard places Sunday at the end of the week. | |
4271 * @example | |
4272 * | |
4273 * Date.create().endOfISOWeek() -> Sunday | |
4274 * | |
4275 ***/ | |
4276 'endOfISOWeek': function() { | |
4277 if(this.getDay() !== 0) { | |
4278 this.setWeekday(7); | |
4279 } | |
4280 return this.endOfDay() | |
4281 }, | |
4282 | |
4283 /*** | |
4284 * @method getUTCOffset([iso]) | |
4285 * @returns String | |
4286 * @short Returns a string representation of the offset from UTC time. If [i
so] is true the offset will be in ISO8601 format. | |
4287 * @example | |
4288 * | |
4289 * new Date().getUTCOffset() -> "+0900" | |
4290 * new Date().getUTCOffset(true) -> "+09:00" | |
4291 * | |
4292 ***/ | |
4293 'getUTCOffset': function(iso) { | |
4294 var offset = this._utc ? 0 : this.getTimezoneOffset(); | |
4295 var colon = iso === true ? ':' : ''; | |
4296 if(!offset && iso) return 'Z'; | |
4297 return padNumber(floor(-offset / 60), 2, true) + colon + padNumber(abs(off
set % 60), 2); | |
4298 }, | |
4299 | |
4300 /*** | |
4301 * @method utc([on] = true) | |
4302 * @returns Date | |
4303 * @short Sets the internal utc flag for the date. When on, UTC-based method
s will be called internally. | |
4304 * @extra For more see @date_format. | |
4305 * @example | |
4306 * | |
4307 * new Date().utc(true) | |
4308 * new Date().utc(false) | |
4309 * | |
4310 ***/ | |
4311 'utc': function(set) { | |
4312 defineProperty(this, '_utc', set === true || arguments.length === 0); | |
4313 return this; | |
4314 }, | |
4315 | |
4316 /*** | |
4317 * @method isUTC() | |
4318 * @returns Boolean | |
4319 * @short Returns true if the date has no timezone offset. | |
4320 * @extra This will also return true for utc-based dates (dates that have th
e %utc% method set true). Note that even if the utc flag is set, %getTimezoneOff
set% will always report the same thing as Javascript always reports that based o
n the environment's locale. | |
4321 * @example | |
4322 * | |
4323 * new Date().isUTC() -> true or false? | |
4324 * new Date().utc(true).isUTC() -> true | |
4325 * | |
4326 ***/ | |
4327 'isUTC': function() { | |
4328 return !!this._utc || this.getTimezoneOffset() === 0; | |
4329 }, | |
4330 | |
4331 /*** | |
4332 * @method advance(<set>, [reset] = false) | |
4333 * @returns Date | |
4334 * @short Sets the date forward. | |
4335 * @extra This method can accept multiple formats including an object, a str
ing in the format %3 days%, a single number as milliseconds, or enumerated param
eters (as with the Date constructor). If [reset] is %true%, any units more speci
fic than those passed will be reset. For more see @date_format. | |
4336 * @example | |
4337 * | |
4338 * new Date().advance({ year: 2 }) -> 2 years in the future | |
4339 * new Date().advance('2 days') -> 2 days in the future | |
4340 * new Date().advance(0, 2, 3) -> 2 months 3 days in the future | |
4341 * new Date().advance(86400000) -> 1 day in the future | |
4342 * | |
4343 ***/ | |
4344 'advance': function() { | |
4345 var args = collectDateArguments(arguments, true); | |
4346 return updateDate(this, args[0], args[1], 1); | |
4347 }, | |
4348 | |
4349 /*** | |
4350 * @method rewind(<set>, [reset] = false) | |
4351 * @returns Date | |
4352 * @short Sets the date back. | |
4353 * @extra This method can accept multiple formats including a single number
as a timestamp, an object, or enumerated parameters (as with the Date constructo
r). If [reset] is %true%, any units more specific than those passed will be rese
t. For more see @date_format. | |
4354 * @example | |
4355 * | |
4356 * new Date().rewind({ year: 2 }) -> 2 years in the past | |
4357 * new Date().rewind(0, 2, 3) -> 2 months 3 days in the past | |
4358 * new Date().rewind(86400000) -> 1 day in the past | |
4359 * | |
4360 ***/ | |
4361 'rewind': function() { | |
4362 var args = collectDateArguments(arguments, true); | |
4363 return updateDate(this, args[0], args[1], -1); | |
4364 }, | |
4365 | |
4366 /*** | |
4367 * @method isValid() | |
4368 * @returns Boolean | |
4369 * @short Returns true if the date is valid. | |
4370 * @example | |
4371 * | |
4372 * new Date().isValid() -> true | |
4373 * new Date('flexor').isValid() -> false | |
4374 * | |
4375 ***/ | |
4376 'isValid': function() { | |
4377 return !isNaN(this.getTime()); | |
4378 }, | |
4379 | |
4380 /*** | |
4381 * @method isAfter(<d>, [margin] = 0) | |
4382 * @returns Boolean | |
4383 * @short Returns true if the date is after the <d>. | |
4384 * @extra [margin] is to allow extra margin of error (in ms). <d> will accep
t a date object, timestamp, or text format. If not specified, <d> is assumed to
be now. See @date_format for more. | |
4385 * @example | |
4386 * | |
4387 * new Date().isAfter('tomorrow') -> false | |
4388 * new Date().isAfter('yesterday') -> true | |
4389 * | |
4390 ***/ | |
4391 'isAfter': function(d, margin, utc) { | |
4392 return this.getTime() > date.create(d).getTime() - (margin || 0); | |
4393 }, | |
4394 | |
4395 /*** | |
4396 * @method isBefore(<d>, [margin] = 0) | |
4397 * @returns Boolean | |
4398 * @short Returns true if the date is before <d>. | |
4399 * @extra [margin] is to allow extra margin of error (in ms). <d> will accep
t a date object, timestamp, or text format. If not specified, <d> is assumed to
be now. See @date_format for more. | |
4400 * @example | |
4401 * | |
4402 * new Date().isBefore('tomorrow') -> true | |
4403 * new Date().isBefore('yesterday') -> false | |
4404 * | |
4405 ***/ | |
4406 'isBefore': function(d, margin) { | |
4407 return this.getTime() < date.create(d).getTime() + (margin || 0); | |
4408 }, | |
4409 | |
4410 /*** | |
4411 * @method isBetween(<d1>, <d2>, [margin] = 0) | |
4412 * @returns Boolean | |
4413 * @short Returns true if the date falls between <d1> and <d2>. | |
4414 * @extra [margin] is to allow extra margin of error (in ms). <d1> and <d2>
will accept a date object, timestamp, or text format. If not specified, they are
assumed to be now. See @date_format for more. | |
4415 * @example | |
4416 * | |
4417 * new Date().isBetween('yesterday', 'tomorrow') -> true | |
4418 * new Date().isBetween('last year', '2 years ago') -> false | |
4419 * | |
4420 ***/ | |
4421 'isBetween': function(d1, d2, margin) { | |
4422 var t = this.getTime(); | |
4423 var t1 = date.create(d1).getTime(); | |
4424 var t2 = date.create(d2).getTime(); | |
4425 var lo = min(t1, t2); | |
4426 var hi = max(t1, t2); | |
4427 margin = margin || 0; | |
4428 return (lo - margin < t) && (hi + margin > t); | |
4429 }, | |
4430 | |
4431 /*** | |
4432 * @method isLeapYear() | |
4433 * @returns Boolean | |
4434 * @short Returns true if the date is a leap year. | |
4435 * @example | |
4436 * | |
4437 * Date.create('2000').isLeapYear() -> true | |
4438 * | |
4439 ***/ | |
4440 'isLeapYear': function() { | |
4441 var year = callDateGet(this, 'FullYear'); | |
4442 return (year % 4 === 0 && year % 100 !== 0) || (year % 400 === 0); | |
4443 }, | |
4444 | |
4445 /*** | |
4446 * @method daysInMonth() | |
4447 * @returns Number | |
4448 * @short Returns the number of days in the date's month. | |
4449 * @example | |
4450 * | |
4451 * Date.create('May').daysInMonth() -> 31 | |
4452 * Date.create('February, 2000').daysInMonth() -> 29 | |
4453 * | |
4454 ***/ | |
4455 'daysInMonth': function() { | |
4456 return 32 - callDateGet(new date(callDateGet(this, 'FullYear'), callDateGe
t(this, 'Month'), 32), 'Date'); | |
4457 }, | |
4458 | |
4459 /*** | |
4460 * @method format(<format>, [locale] = currentLocale) | |
4461 * @returns String | |
4462 * @short Formats and outputs the date. | |
4463 * @extra <format> can be a number of pre-determined formats or a string of
tokens. Locale-specific formats are %short%, %long%, and %full% which have their
own aliases and can be called with %date.short()%, etc. If <format> is not spec
ified the %long% format is assumed. [locale] specifies a locale code to use (if
not specified the current locale is used). See @date_format for more details. | |
4464 * | |
4465 * @set | |
4466 * short | |
4467 * long | |
4468 * full | |
4469 * | |
4470 * @example | |
4471 * | |
4472 * Date.create().format() -> ex. July 4,
2003 | |
4473 * Date.create().format('{Weekday} {d} {Month}, {yyyy}') -> ex. Monday
July 4, 2003 | |
4474 * Date.create().format('{hh}:{mm}') -> ex. 15:57 | |
4475 * Date.create().format('{12hr}:{mm}{tt}') -> ex. 3:57pm | |
4476 * Date.create().format(Date.ISO8601_DATETIME) -> ex. 2011-07
-05 12:24:55.528Z | |
4477 * Date.create('last week').format('short', 'ja') -> ex. 先週 | |
4478 * Date.create('yesterday').format(function(value,unit,ms,loc) { | |
4479 * // value = 1, unit = 3, ms = -86400000, loc = [current locale object] | |
4480 * }); -> ex. 1 day a
go | |
4481 * | |
4482 ***/ | |
4483 'format': function(f, localeCode) { | |
4484 return formatDate(this, f, false, localeCode); | |
4485 }, | |
4486 | |
4487 /*** | |
4488 * @method relative([fn], [locale] = currentLocale) | |
4489 * @returns String | |
4490 * @short Returns a relative date string offset to the current time. | |
4491 * @extra [fn] can be passed to provide for more granular control over the r
esulting string. [fn] is passed 4 arguments: the adjusted value, unit, offset in
milliseconds, and a localization object. As an alternate syntax, [locale] can a
lso be passed as the first (and only) parameter. For more, see @date_format. | |
4492 * @example | |
4493 * | |
4494 * Date.create('90 seconds ago').relative() -> 1 minute ago | |
4495 * Date.create('January').relative() -> ex. 5 months ago | |
4496 * Date.create('January').relative('ja') -> 3ヶ月前 | |
4497 * Date.create('120 minutes ago').relative(function(val,unit,ms,loc) { | |
4498 * // value = 2, unit = 3, ms = -7200, loc = [current locale object] | |
4499 * }); -> ex. 5 months ago | |
4500 * | |
4501 ***/ | |
4502 'relative': function(fn, localeCode) { | |
4503 if(isString(fn)) { | |
4504 localeCode = fn; | |
4505 fn = null; | |
4506 } | |
4507 return formatDate(this, fn, true, localeCode); | |
4508 }, | |
4509 | |
4510 /*** | |
4511 * @method is(<d>, [margin] = 0) | |
4512 * @returns Boolean | |
4513 * @short Returns true if the date is <d>. | |
4514 * @extra <d> will accept a date object, timestamp, or text format. %is% add
itionally understands more generalized expressions like month/weekday names, 'to
day', etc, and compares to the precision implied in <d>. [margin] allows an extr
a margin of error in milliseconds. For more, see @date_format. | |
4515 * @example | |
4516 * | |
4517 * Date.create().is('July') -> true or false? | |
4518 * Date.create().is('1776') -> false | |
4519 * Date.create().is('today') -> true | |
4520 * Date.create().is('weekday') -> true or false? | |
4521 * Date.create().is('July 4, 1776') -> false | |
4522 * Date.create().is(-6106093200000) -> false | |
4523 * Date.create().is(new Date(1776, 6, 4)) -> false | |
4524 * | |
4525 ***/ | |
4526 'is': function(d, margin, utc) { | |
4527 var tmp, comp; | |
4528 if(!this.isValid()) return; | |
4529 if(isString(d)) { | |
4530 d = d.trim().toLowerCase(); | |
4531 comp = this.clone().utc(utc); | |
4532 switch(true) { | |
4533 case d === 'future': return this.getTime() > getNewDate().getTime(); | |
4534 case d === 'past': return this.getTime() < getNewDate().getTime(); | |
4535 case d === 'weekday': return callDateGet(comp, 'Day') > 0 && callDateG
et(comp, 'Day') < 6; | |
4536 case d === 'weekend': return callDateGet(comp, 'Day') === 0 || callDat
eGet(comp, 'Day') === 6; | |
4537 case (tmp = English['weekdays'].indexOf(d) % 7) > -1: return callDateG
et(comp, 'Day') === tmp; | |
4538 case (tmp = English['months'].indexOf(d) % 12) > -1: return callDateG
et(comp, 'Month') === tmp; | |
4539 } | |
4540 } | |
4541 return compareDate(this, d, null, margin, utc); | |
4542 }, | |
4543 | |
4544 /*** | |
4545 * @method reset([unit] = 'hours') | |
4546 * @returns Date | |
4547 * @short Resets the unit passed and all smaller units. Default is "hours",
effectively resetting the time. | |
4548 * @example | |
4549 * | |
4550 * Date.create().reset('day') -> Beginning of today | |
4551 * Date.create().reset('month') -> 1st of the month | |
4552 * | |
4553 ***/ | |
4554 'reset': function(unit) { | |
4555 var params = {}, recognized; | |
4556 unit = unit || 'hours'; | |
4557 if(unit === 'date') unit = 'days'; | |
4558 recognized = DateUnits.some(function(u) { | |
4559 return unit === u.name || unit === u.name + 's'; | |
4560 }); | |
4561 params[unit] = unit.match(/^days?/) ? 1 : 0; | |
4562 return recognized ? this.set(params, true) : this; | |
4563 }, | |
4564 | |
4565 /*** | |
4566 * @method clone() | |
4567 * @returns Date | |
4568 * @short Clones the date. | |
4569 * @example | |
4570 * | |
4571 * Date.create().clone() -> Copy of now | |
4572 * | |
4573 ***/ | |
4574 'clone': function() { | |
4575 var d = new date(this.getTime()); | |
4576 d.utc(!!this._utc); | |
4577 return d; | |
4578 } | |
4579 | |
4580 }); | |
4581 | |
4582 | |
4583 // Instance aliases | |
4584 extend(date, true, true, { | |
4585 | |
4586 /*** | |
4587 * @method iso() | |
4588 * @alias toISOString | |
4589 * | |
4590 ***/ | |
4591 'iso': function() { | |
4592 return this.toISOString(); | |
4593 }, | |
4594 | |
4595 /*** | |
4596 * @method getWeekday() | |
4597 * @returns Number | |
4598 * @short Alias for %getDay%. | |
4599 * @set | |
4600 * getUTCWeekday | |
4601 * | |
4602 * @example | |
4603 * | |
4604 + Date.create().getWeekday(); -> (ex.) 3 | |
4605 + Date.create().getUTCWeekday(); -> (ex.) 3 | |
4606 * | |
4607 ***/ | |
4608 'getWeekday': date.prototype.getDay, | |
4609 'getUTCWeekday': date.prototype.getUTCDay | |
4610 | |
4611 }); | |
4612 | |
4613 | |
4614 | |
4615 /*** | |
4616 * Number module | |
4617 * | |
4618 ***/ | |
4619 | |
4620 /*** | |
4621 * @method [unit]() | |
4622 * @returns Number | |
4623 * @short Takes the number as a corresponding unit of time and converts to mil
liseconds. | |
4624 * @extra Method names can be singular or plural. Note that as "a month" is a
mbiguous as a unit of time, %months% will be equivalent to 30.4375 days, the ave
rage number in a month. Be careful using %months% if you need exact precision. | |
4625 * | |
4626 * @set | |
4627 * millisecond | |
4628 * milliseconds | |
4629 * second | |
4630 * seconds | |
4631 * minute | |
4632 * minutes | |
4633 * hour | |
4634 * hours | |
4635 * day | |
4636 * days | |
4637 * week | |
4638 * weeks | |
4639 * month | |
4640 * months | |
4641 * year | |
4642 * years | |
4643 * | |
4644 * @example | |
4645 * | |
4646 * (5).milliseconds() -> 5 | |
4647 * (10).hours() -> 36000000 | |
4648 * (1).day() -> 86400000 | |
4649 * | |
4650 *** | |
4651 * @method [unit]Before([d], [locale] = currentLocale) | |
4652 * @returns Date | |
4653 * @short Returns a date that is <n> units before [d], where <n> is the number
. | |
4654 * @extra [d] will accept a date object, timestamp, or text format. Note that
"months" is ambiguous as a unit of time. If the target date falls on a day that
does not exist (ie. August 31 -> February 31), the date will be shifted to the l
ast day of the month. Be careful using %monthsBefore% if you need exact precisio
n. See @date_format for more. | |
4655 * | |
4656 * @set | |
4657 * millisecondBefore | |
4658 * millisecondsBefore | |
4659 * secondBefore | |
4660 * secondsBefore | |
4661 * minuteBefore | |
4662 * minutesBefore | |
4663 * hourBefore | |
4664 * hoursBefore | |
4665 * dayBefore | |
4666 * daysBefore | |
4667 * weekBefore | |
4668 * weeksBefore | |
4669 * monthBefore | |
4670 * monthsBefore | |
4671 * yearBefore | |
4672 * yearsBefore | |
4673 * | |
4674 * @example | |
4675 * | |
4676 * (5).daysBefore('tuesday') -> 5 days before tuesday of this week | |
4677 * (1).yearBefore('January 23, 1997') -> January 23, 1996 | |
4678 * | |
4679 *** | |
4680 * @method [unit]Ago() | |
4681 * @returns Date | |
4682 * @short Returns a date that is <n> units ago. | |
4683 * @extra Note that "months" is ambiguous as a unit of time. If the target dat
e falls on a day that does not exist (ie. August 31 -> February 31), the date wi
ll be shifted to the last day of the month. Be careful using %monthsAgo% if you
need exact precision. | |
4684 * | |
4685 * @set | |
4686 * millisecondAgo | |
4687 * millisecondsAgo | |
4688 * secondAgo | |
4689 * secondsAgo | |
4690 * minuteAgo | |
4691 * minutesAgo | |
4692 * hourAgo | |
4693 * hoursAgo | |
4694 * dayAgo | |
4695 * daysAgo | |
4696 * weekAgo | |
4697 * weeksAgo | |
4698 * monthAgo | |
4699 * monthsAgo | |
4700 * yearAgo | |
4701 * yearsAgo | |
4702 * | |
4703 * @example | |
4704 * | |
4705 * (5).weeksAgo() -> 5 weeks ago | |
4706 * (1).yearAgo() -> January 23, 1996 | |
4707 * | |
4708 *** | |
4709 * @method [unit]After([d], [locale] = currentLocale) | |
4710 * @returns Date | |
4711 * @short Returns a date <n> units after [d], where <n> is the number. | |
4712 * @extra [d] will accept a date object, timestamp, or text format. Note that
"months" is ambiguous as a unit of time. If the target date falls on a day that
does not exist (ie. August 31 -> February 31), the date will be shifted to the l
ast day of the month. Be careful using %monthsAfter% if you need exact precision
. See @date_format for more. | |
4713 * | |
4714 * @set | |
4715 * millisecondAfter | |
4716 * millisecondsAfter | |
4717 * secondAfter | |
4718 * secondsAfter | |
4719 * minuteAfter | |
4720 * minutesAfter | |
4721 * hourAfter | |
4722 * hoursAfter | |
4723 * dayAfter | |
4724 * daysAfter | |
4725 * weekAfter | |
4726 * weeksAfter | |
4727 * monthAfter | |
4728 * monthsAfter | |
4729 * yearAfter | |
4730 * yearsAfter | |
4731 * | |
4732 * @example | |
4733 * | |
4734 * (5).daysAfter('tuesday') -> 5 days after tuesday of this week | |
4735 * (1).yearAfter('January 23, 1997') -> January 23, 1998 | |
4736 * | |
4737 *** | |
4738 * @method [unit]FromNow() | |
4739 * @returns Date | |
4740 * @short Returns a date <n> units from now. | |
4741 * @extra Note that "months" is ambiguous as a unit of time. If the target dat
e falls on a day that does not exist (ie. August 31 -> February 31), the date wi
ll be shifted to the last day of the month. Be careful using %monthsFromNow% if
you need exact precision. | |
4742 * | |
4743 * @set | |
4744 * millisecondFromNow | |
4745 * millisecondsFromNow | |
4746 * secondFromNow | |
4747 * secondsFromNow | |
4748 * minuteFromNow | |
4749 * minutesFromNow | |
4750 * hourFromNow | |
4751 * hoursFromNow | |
4752 * dayFromNow | |
4753 * daysFromNow | |
4754 * weekFromNow | |
4755 * weeksFromNow | |
4756 * monthFromNow | |
4757 * monthsFromNow | |
4758 * yearFromNow | |
4759 * yearsFromNow | |
4760 * | |
4761 * @example | |
4762 * | |
4763 * (5).weeksFromNow() -> 5 weeks ago | |
4764 * (1).yearFromNow() -> January 23, 1998 | |
4765 * | |
4766 ***/ | |
4767 function buildNumberToDateAlias(u, multiplier) { | |
4768 var name = u.name, methods = {}; | |
4769 function base() { return round(this * multiplier); } | |
4770 function after() { return createDate(arguments)[u.addMethod](this); } | |
4771 function before() { return createDate(arguments)[u.addMethod](-this); } | |
4772 methods[name] = base; | |
4773 methods[name + 's'] = base; | |
4774 methods[name + 'Before'] = before; | |
4775 methods[name + 'sBefore'] = before; | |
4776 methods[name + 'Ago'] = before; | |
4777 methods[name + 'sAgo'] = before; | |
4778 methods[name + 'After'] = after; | |
4779 methods[name + 'sAfter'] = after; | |
4780 methods[name + 'FromNow'] = after; | |
4781 methods[name + 'sFromNow'] = after; | |
4782 number.extend(methods); | |
4783 } | |
4784 | |
4785 extend(number, true, true, { | |
4786 | |
4787 /*** | |
4788 * @method duration([locale] = currentLocale) | |
4789 * @returns String | |
4790 * @short Takes the number as milliseconds and returns a unit-adjusted local
ized string. | |
4791 * @extra This method is the same as %Date#relative% without the localized e
quivalent of "from now" or "ago". [locale] can be passed as the first (and only)
parameter. Note that this method is only available when the dates package is in
cluded. | |
4792 * @example | |
4793 * | |
4794 * (500).duration() -> '500 milliseconds' | |
4795 * (1200).duration() -> '1 second' | |
4796 * (75).minutes().duration() -> '1 hour' | |
4797 * (75).minutes().duration('es') -> '1 hora' | |
4798 * | |
4799 ***/ | |
4800 'duration': function(localeCode) { | |
4801 return getLocalization(localeCode).getDuration(this); | |
4802 } | |
4803 | |
4804 }); | |
4805 | |
4806 | |
4807 English = CurrentLocalization = date.addLocale('en', { | |
4808 'plural': true, | |
4809 'timeMarker': 'at', | |
4810 'ampm': 'am,pm', | |
4811 'months': 'January,February,March,April,May,June,July,August,September,O
ctober,November,December', | |
4812 'weekdays': 'Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday', | |
4813 'units': 'millisecond:|s,second:|s,minute:|s,hour:|s,day:|s,week:|s,mon
th:|s,year:|s', | |
4814 'numbers': 'one,two,three,four,five,six,seven,eight,nine,ten', | |
4815 'articles': 'a,an,the', | |
4816 'tokens': 'the,st|nd|rd|th,of', | |
4817 'short': '{Month} {d}, {yyyy}', | |
4818 'long': '{Month} {d}, {yyyy} {h}:{mm}{tt}', | |
4819 'full': '{Weekday} {Month} {d}, {yyyy} {h}:{mm}:{ss}{tt}', | |
4820 'past': '{num} {unit} {sign}', | |
4821 'future': '{num} {unit} {sign}', | |
4822 'duration': '{num} {unit}', | |
4823 'modifiers': [ | |
4824 { 'name': 'sign', 'src': 'ago|before', 'value': -1 }, | |
4825 { 'name': 'sign', 'src': 'from now|after|from|in|later', 'value': 1 }, | |
4826 { 'name': 'edge', 'src': 'last day', 'value': -2 }, | |
4827 { 'name': 'edge', 'src': 'end', 'value': -1 }, | |
4828 { 'name': 'edge', 'src': 'first day|beginning', 'value': 1 }, | |
4829 { 'name': 'shift', 'src': 'last', 'value': -1 }, | |
4830 { 'name': 'shift', 'src': 'the|this', 'value': 0 }, | |
4831 { 'name': 'shift', 'src': 'next', 'value': 1 } | |
4832 ], | |
4833 'dateParse': [ | |
4834 '{month} {year}', | |
4835 '{shift} {unit=5-7}', | |
4836 '{0?} {date}{1}', | |
4837 '{0?} {edge} of {shift?} {unit=4-7?}{month?}{year?}' | |
4838 ], | |
4839 'timeParse': [ | |
4840 '{num} {unit} {sign}', | |
4841 '{sign} {num} {unit}', | |
4842 '{0} {num}{1} {day} of {month} {year?}', | |
4843 '{weekday?} {month} {date}{1?} {year?}', | |
4844 '{date} {month} {year}', | |
4845 '{date} {month}', | |
4846 '{shift} {weekday}', | |
4847 '{shift} week {weekday}', | |
4848 '{weekday} {2?} {shift} week', | |
4849 '{num} {unit=4-5} {sign} {day}', | |
4850 '{0?} {date}{1} of {month}', | |
4851 '{0?}{month?} {date?}{1?} of {shift} {unit=6-7}' | |
4852 ] | |
4853 }); | |
4854 | |
4855 buildDateUnits(); | |
4856 buildDateMethods(); | |
4857 buildCoreInputFormats(); | |
4858 buildFormatTokens(); | |
4859 buildFormatShortcuts(); | |
4860 buildAsianDigits(); | |
4861 buildRelativeAliases(); | |
4862 buildUTCAliases(); | |
4863 setDateProperties(); | |
4864 | |
4865 | |
4866 /*** | |
4867 * @package Range | |
4868 * @dependency core | |
4869 * @description Ranges allow creating spans of numbers, strings, or dates. The
y can enumerate over specific points within that range, and be manipulated and c
ompared. | |
4870 * | |
4871 ***/ | |
4872 | |
4873 function Range(start, end) { | |
4874 this.start = cloneRangeMember(start); | |
4875 this.end = cloneRangeMember(end); | |
4876 }; | |
4877 | |
4878 function getRangeMemberNumericValue(m) { | |
4879 return isString(m) ? m.charCodeAt(0) : m; | |
4880 } | |
4881 | |
4882 function getRangeMemberPrimitiveValue(m) { | |
4883 if(m == null) return m; | |
4884 return isDate(m) ? m.getTime() : m.valueOf(); | |
4885 } | |
4886 | |
4887 function cloneRangeMember(m) { | |
4888 if(isDate(m)) { | |
4889 return new date(m.getTime()); | |
4890 } else { | |
4891 return getRangeMemberPrimitiveValue(m); | |
4892 } | |
4893 } | |
4894 | |
4895 function isValidRangeMember(m) { | |
4896 var val = getRangeMemberPrimitiveValue(m); | |
4897 return !!val || val === 0; | |
4898 } | |
4899 | |
4900 function getDuration(amt) { | |
4901 var match, val, unit; | |
4902 if(isNumber(amt)) { | |
4903 return amt; | |
4904 } | |
4905 match = amt.toLowerCase().match(/^(\d+)?\s?(\w+?)s?$/i); | |
4906 val = parseInt(match[1]) || 1; | |
4907 unit = match[2].slice(0,1).toUpperCase() + match[2].slice(1); | |
4908 if(unit.match(/hour|minute|second/i)) { | |
4909 unit += 's'; | |
4910 } else if(unit === 'Year') { | |
4911 unit = 'FullYear'; | |
4912 } else if(unit === 'Day') { | |
4913 unit = 'Date'; | |
4914 } | |
4915 return [val, unit]; | |
4916 } | |
4917 | |
4918 function incrementDate(current, amount) { | |
4919 var num, unit, val, d; | |
4920 if(isNumber(amount)) { | |
4921 return new date(current.getTime() + amount); | |
4922 } | |
4923 num = amount[0]; | |
4924 unit = amount[1]; | |
4925 val = callDateGet(current, unit); | |
4926 d = new date(current.getTime()); | |
4927 callDateSet(d, unit, val + num); | |
4928 return d; | |
4929 } | |
4930 | |
4931 function incrementString(current, amount) { | |
4932 return string.fromCharCode(current.charCodeAt(0) + amount); | |
4933 } | |
4934 | |
4935 function incrementNumber(current, amount) { | |
4936 return current + amount; | |
4937 } | |
4938 | |
4939 /*** | |
4940 * @method toString() | |
4941 * @returns String | |
4942 * @short Returns a string representation of the range. | |
4943 * @example | |
4944 * | |
4945 * Number.range(1, 5).toString() -> 1..5 | |
4946 * Date.range(new Date(2003, 0), new Date(2005, 0)).toString() -> January 1,
2003..January 1, 2005 | |
4947 * | |
4948 ***/ | |
4949 | |
4950 // Note: 'toString' doesn't appear in a for..in loop in IE even though | |
4951 // hasOwnProperty reports true, so extend() can't be used here. | |
4952 // Also tried simply setting the prototype = {} up front for all | |
4953 // methods but GCC very oddly started dropping properties in the | |
4954 // object randomly (maybe because of the global scope?) hence | |
4955 // the need for the split logic here. | |
4956 Range.prototype.toString = function() { | |
4957 return this.isValid() ? this.start + ".." + this.end : 'Invalid Range'; | |
4958 }; | |
4959 | |
4960 extend(Range, true, true, { | |
4961 | |
4962 /*** | |
4963 * @method isValid() | |
4964 * @returns Boolean | |
4965 * @short Returns true if the range is valid, false otherwise. | |
4966 * @example | |
4967 * | |
4968 * Date.range(new Date(2003, 0), new Date(2005, 0)).isValid() -> true | |
4969 * Number.range(NaN, NaN).isValid() -> false | |
4970 * | |
4971 ***/ | |
4972 'isValid': function() { | |
4973 return isValidRangeMember(this.start) && isValidRangeMember(this.end) && t
ypeof this.start === typeof this.end; | |
4974 }, | |
4975 | |
4976 /*** | |
4977 * @method span() | |
4978 * @returns Number | |
4979 * @short Returns the span of the range. If the range is a date range, the v
alue is in milliseconds. | |
4980 * @extra The span includes both the start and the end. | |
4981 * @example | |
4982 * | |
4983 * Number.range(5, 10).span() -> 6 | |
4984 * Date.range(new Date(2003, 0), new Date(2005, 0)).span() -> 94694400000 | |
4985 * | |
4986 ***/ | |
4987 'span': function() { | |
4988 return this.isValid() ? abs( | |
4989 getRangeMemberNumericValue(this.end) - getRangeMemberNumericValue(this.s
tart) | |
4990 ) + 1 : NaN; | |
4991 }, | |
4992 | |
4993 /*** | |
4994 * @method contains(<obj>) | |
4995 * @returns Boolean | |
4996 * @short Returns true if <obj> is contained inside the range. <obj> may be
a value or another range. | |
4997 * @example | |
4998 * | |
4999 * Number.range(5, 10).contains(7)
-> true | |
5000 * Date.range(new Date(2003, 0), new Date(2005, 0)).contains(new Date(2004
, 0)) -> true | |
5001 * | |
5002 ***/ | |
5003 'contains': function(obj) { | |
5004 var self = this, arr; | |
5005 if(obj == null) return false; | |
5006 if(obj.start && obj.end) { | |
5007 return obj.start >= this.start && obj.start <= this.end && | |
5008 obj.end >= this.start && obj.end <= this.end; | |
5009 } else { | |
5010 return obj >= this.start && obj <= this.end; | |
5011 } | |
5012 }, | |
5013 | |
5014 /*** | |
5015 * @method every(<amount>, [fn]) | |
5016 * @returns Array | |
5017 * @short Iterates through the range for every <amount>, calling [fn] if it
is passed. Returns an array of each increment visited. | |
5018 * @extra In the case of date ranges, <amount> can also be a string, in whic
h case it will increment a number of units. Note that %(2).months()% first reso
lves to a number, which will be interpreted as milliseconds and is an approximat
ion, so stepping through the actual months by passing %"2 months"% is usually pr
eferable. | |
5019 * @example | |
5020 * | |
5021 * Number.range(2, 8).every(2) -> [2
,4,6,8] | |
5022 * Date.range(new Date(2003, 1), new Date(2003,3)).every("2 months") -> [.
..] | |
5023 * | |
5024 ***/ | |
5025 'every': function(amount, fn) { | |
5026 var increment, | |
5027 start = this.start, | |
5028 end = this.end, | |
5029 inverse = end < start, | |
5030 current = start, | |
5031 index = 0, | |
5032 result = []; | |
5033 | |
5034 if(isFunction(amount)) { | |
5035 fn = amount; | |
5036 amount = null; | |
5037 } | |
5038 amount = amount || 1; | |
5039 if(isNumber(start)) { | |
5040 increment = incrementNumber; | |
5041 } else if(isString(start)) { | |
5042 increment = incrementString; | |
5043 } else if(isDate(start)) { | |
5044 amount = getDuration(amount); | |
5045 increment = incrementDate; | |
5046 } | |
5047 // Avoiding infinite loops | |
5048 if(inverse && amount > 0) { | |
5049 amount *= -1; | |
5050 } | |
5051 while(inverse ? current >= end : current <= end) { | |
5052 result.push(current); | |
5053 if(fn) { | |
5054 fn(current, index); | |
5055 } | |
5056 current = increment(current, amount); | |
5057 index++; | |
5058 } | |
5059 return result; | |
5060 }, | |
5061 | |
5062 /*** | |
5063 * @method union(<range>) | |
5064 * @returns Range | |
5065 * @short Returns a new range with the earliest starting point as its start,
and the latest ending point as its end. If the two ranges do not intersect this
will effectively remove the "gap" between them. | |
5066 * @example | |
5067 * | |
5068 * Number.range(1, 3).union(Number.range(2, 5)) -> 1..5 | |
5069 * Date.range(new Date(2003, 1), new Date(2005, 1)).union(Date.range(new D
ate(2004, 1), new Date(2006, 1))) -> Jan 1, 2003..Jan 1, 2006 | |
5070 * | |
5071 ***/ | |
5072 'union': function(range) { | |
5073 return new Range( | |
5074 this.start < range.start ? this.start : range.start, | |
5075 this.end > range.end ? this.end : range.end | |
5076 ); | |
5077 }, | |
5078 | |
5079 /*** | |
5080 * @method intersect(<range>) | |
5081 * @returns Range | |
5082 * @short Returns a new range with the latest starting point as its start, a
nd the earliest ending point as its end. If the two ranges do not intersect this
will effectively produce an invalid range. | |
5083 * @example | |
5084 * | |
5085 * Number.range(1, 5).intersect(Number.range(4, 8)) -> 4..5 | |
5086 * Date.range(new Date(2003, 1), new Date(2005, 1)).intersect(Date.range(n
ew Date(2004, 1), new Date(2006, 1))) -> Jan 1, 2004..Jan 1, 2005 | |
5087 * | |
5088 ***/ | |
5089 'intersect': function(range) { | |
5090 if(range.start > this.end || range.end < this.start) { | |
5091 return new Range(NaN, NaN); | |
5092 } | |
5093 return new Range( | |
5094 this.start > range.start ? this.start : range.start, | |
5095 this.end < range.end ? this.end : range.end | |
5096 ); | |
5097 }, | |
5098 | |
5099 /*** | |
5100 * @method clone() | |
5101 * @returns Range | |
5102 * @short Clones the range. | |
5103 * @extra Members of the range will also be cloned. | |
5104 * @example | |
5105 * | |
5106 * Number.range(1, 5).clone() -> Returns a copy of the range. | |
5107 * | |
5108 ***/ | |
5109 'clone': function(range) { | |
5110 return new Range(this.start, this.end); | |
5111 }, | |
5112 | |
5113 /*** | |
5114 * @method clamp(<obj>) | |
5115 * @returns Mixed | |
5116 * @short Clamps <obj> to be within the range if it falls outside. | |
5117 * @example | |
5118 * | |
5119 * Number.range(1, 5).clamp(8) -> 5 | |
5120 * Date.range(new Date(2010, 0), new Date(2012, 0)).clamp(new Date(2013, 0
)) -> 2012-01 | |
5121 * | |
5122 ***/ | |
5123 'clamp': function(obj) { | |
5124 var clamped, | |
5125 start = this.start, | |
5126 end = this.end, | |
5127 min = end < start ? end : start, | |
5128 max = start > end ? start : end; | |
5129 if(obj < min) { | |
5130 clamped = min; | |
5131 } else if(obj > max) { | |
5132 clamped = max; | |
5133 } else { | |
5134 clamped = obj; | |
5135 } | |
5136 return cloneRangeMember(clamped); | |
5137 } | |
5138 | |
5139 }); | |
5140 | |
5141 | |
5142 /*** | |
5143 * Number module | |
5144 *** | |
5145 * @method Number.range([start], [end]) | |
5146 * @returns Range | |
5147 * @short Creates a new range between [start] and [end]. See @ranges for more. | |
5148 * @example | |
5149 * | |
5150 * Number.range(5, 10) | |
5151 * | |
5152 *** | |
5153 * String module | |
5154 *** | |
5155 * @method String.range([start], [end]) | |
5156 * @returns Range | |
5157 * @short Creates a new range between [start] and [end]. See @ranges for more. | |
5158 * @example | |
5159 * | |
5160 * String.range('a', 'z') | |
5161 * | |
5162 *** | |
5163 * Date module | |
5164 *** | |
5165 * @method Date.range([start], [end]) | |
5166 * @returns Range | |
5167 * @short Creates a new range between [start] and [end]. | |
5168 * @extra If either [start] or [end] are null, they will default to the curren
t date. See @ranges for more. | |
5169 * @example | |
5170 * | |
5171 * Date.range('today', 'tomorrow') | |
5172 * | |
5173 ***/ | |
5174 [number, string, date].forEach(function(klass) { | |
5175 extend(klass, false, true, { | |
5176 | |
5177 'range': function(start, end) { | |
5178 if(klass.create) { | |
5179 start = klass.create(start); | |
5180 end = klass.create(end); | |
5181 } | |
5182 return new Range(start, end); | |
5183 } | |
5184 | |
5185 }); | |
5186 | |
5187 }); | |
5188 | |
5189 /*** | |
5190 * Number module | |
5191 * | |
5192 ***/ | |
5193 | |
5194 extend(number, true, true, { | |
5195 | |
5196 /*** | |
5197 * @method upto(<num>, [fn], [step] = 1) | |
5198 * @returns Array | |
5199 * @short Returns an array containing numbers from the number up to <num>. | |
5200 * @extra Optionally calls [fn] callback for each number in that array. [ste
p] allows multiples greater than 1. | |
5201 * @example | |
5202 * | |
5203 * (2).upto(6) -> [2, 3, 4, 5, 6] | |
5204 * (2).upto(6, function(n) { | |
5205 * // This function is called 5 times receiving n as the value. | |
5206 * }); | |
5207 * (2).upto(8, null, 2) -> [2, 4, 6, 8] | |
5208 * | |
5209 ***/ | |
5210 'upto': function(num, fn, step) { | |
5211 return number.range(this, num).every(step, fn); | |
5212 }, | |
5213 | |
5214 /*** | |
5215 * @method clamp([start] = Infinity, [end] = Infinity) | |
5216 * @returns Number | |
5217 * @short Constrains the number so that it is between [start] and [end]. | |
5218 * @extra This will build a range object that has an equivalent %clamp% meth
od. | |
5219 * @example | |
5220 * | |
5221 * (3).clamp(50, 100) -> 50 | |
5222 * (85).clamp(50, 100) -> 85 | |
5223 * | |
5224 ***/ | |
5225 'clamp': function(start, end) { | |
5226 return new Range(start, end).clamp(this); | |
5227 }, | |
5228 | |
5229 /*** | |
5230 * @method cap([max] = Infinity) | |
5231 * @returns Number | |
5232 * @short Constrains the number so that it is no greater than [max]. | |
5233 * @extra This will build a range object that has an equivalent %cap% method
. | |
5234 * @example | |
5235 * | |
5236 * (100).cap(80) -> 80 | |
5237 * | |
5238 ***/ | |
5239 'cap': function(max) { | |
5240 return this.clamp(Undefined, max); | |
5241 } | |
5242 | |
5243 }); | |
5244 | |
5245 extend(number, true, true, { | |
5246 | |
5247 /*** | |
5248 * @method downto(<num>, [fn], [step] = 1) | |
5249 * @returns Array | |
5250 * @short Returns an array containing numbers from the number down to <num>. | |
5251 * @extra Optionally calls [fn] callback for each number in that array. [ste
p] allows multiples greater than 1. | |
5252 * @example | |
5253 * | |
5254 * (8).downto(3) -> [8, 7, 6, 5, 4, 3] | |
5255 * (8).downto(3, function(n) { | |
5256 * // This function is called 6 times receiving n as the value. | |
5257 * }); | |
5258 * (8).downto(2, null, 2) -> [8, 6, 4, 2] | |
5259 * | |
5260 ***/ | |
5261 'downto': number.prototype.upto | |
5262 | |
5263 }); | |
5264 | |
5265 | |
5266 /*** | |
5267 * Array module | |
5268 * | |
5269 ***/ | |
5270 | |
5271 extend(array, false, function(a) { return a instanceof Range; }, { | |
5272 | |
5273 'create': function(range) { | |
5274 return range.every(); | |
5275 } | |
5276 | |
5277 }); | |
5278 | |
5279 | |
5280 /*** | |
5281 * @package Function | |
5282 * @dependency core | |
5283 * @description Lazy, throttled, and memoized functions, delayed functions and
handling of timers, argument currying. | |
5284 * | |
5285 ***/ | |
5286 | |
5287 function setDelay(fn, ms, after, scope, args) { | |
5288 // Delay of infinity is never called of course... | |
5289 if(ms === Infinity) return; | |
5290 if(!fn.timers) fn.timers = []; | |
5291 if(!isNumber(ms)) ms = 1; | |
5292 // This is a workaround for <= IE8, which apparently has the | |
5293 // ability to call timeouts in the queue on the same tick (ms?) | |
5294 // even if functionally they have already been cleared. | |
5295 fn._canceled = false; | |
5296 fn.timers.push(setTimeout(function(){ | |
5297 if(!fn._canceled) { | |
5298 after.apply(scope, args || []); | |
5299 } | |
5300 }, ms)); | |
5301 } | |
5302 | |
5303 extend(Function, true, true, { | |
5304 | |
5305 /*** | |
5306 * @method lazy([ms] = 1, [immediate] = false, [limit] = Infinity) | |
5307 * @returns Function | |
5308 * @short Creates a lazy function that, when called repeatedly, will queue e
xecution and wait [ms] milliseconds to execute. | |
5309 * @extra If [immediate] is %true%, first execution will happen immediately,
then lock. If [limit] is a fininte number, calls past [limit] will be ignored w
hile execution is locked. Compare this to %throttle%, which will execute only on
ce per [ms] milliseconds. Note that [ms] can also be a fraction. Calling %cancel
% on a lazy function will clear the entire queue. For more see @functions. | |
5310 * @example | |
5311 * | |
5312 * (function() { | |
5313 * // Executes immediately. | |
5314 * }).lazy()(); | |
5315 * (3).times(function() { | |
5316 * // Executes 3 times, with each execution 20ms later than the last. | |
5317 * }.lazy(20)); | |
5318 * (100).times(function() { | |
5319 * // Executes 50 times, with each execution 20ms later than the last. | |
5320 * }.lazy(20, false, 50)); | |
5321 * | |
5322 ***/ | |
5323 'lazy': function(ms, immediate, limit) { | |
5324 var fn = this, queue = [], locked = false, execute, rounded, perExecution,
result; | |
5325 ms = ms || 1; | |
5326 limit = limit || Infinity; | |
5327 rounded = ceil(ms); | |
5328 perExecution = round(rounded / ms) || 1; | |
5329 execute = function() { | |
5330 var queueLength = queue.length, maxPerRound; | |
5331 if(queueLength == 0) return; | |
5332 // Allow fractions of a millisecond by calling | |
5333 // multiple times per actual timeout execution | |
5334 maxPerRound = max(queueLength - perExecution, 0); | |
5335 while(queueLength > maxPerRound) { | |
5336 // Getting uber-meta here... | |
5337 result = Function.prototype.apply.apply(fn, queue.shift()); | |
5338 queueLength--; | |
5339 } | |
5340 setDelay(lazy, rounded, function() { | |
5341 locked = false; | |
5342 execute(); | |
5343 }); | |
5344 } | |
5345 function lazy() { | |
5346 // If the execution has locked and it's immediate, then | |
5347 // allow 1 less in the queue as 1 call has already taken place. | |
5348 if(queue.length < limit - (locked && immediate ? 1 : 0)) { | |
5349 queue.push([this, arguments]); | |
5350 } | |
5351 if(!locked) { | |
5352 locked = true; | |
5353 if(immediate) { | |
5354 execute(); | |
5355 } else { | |
5356 setDelay(lazy, rounded, execute); | |
5357 } | |
5358 } | |
5359 // Return the memoized result | |
5360 return result; | |
5361 } | |
5362 return lazy; | |
5363 }, | |
5364 | |
5365 /*** | |
5366 * @method throttle([ms] = 1) | |
5367 * @returns Function | |
5368 * @short Creates a "throttled" version of the function that will only be ex
ecuted once per <ms> milliseconds. | |
5369 * @extra This is functionally equivalent to calling %lazy% with a [limit] o
f %1% and [immediate] as %true%. %throttle% is appropriate when you want to make
sure a function is only executed at most once for a given duration. For more se
e @functions. | |
5370 * @example | |
5371 * | |
5372 * (3).times(function() { | |
5373 * // called only once. will wait 50ms until it responds again | |
5374 * }.throttle(50)); | |
5375 * | |
5376 ***/ | |
5377 'throttle': function(ms) { | |
5378 return this.lazy(ms, true, 1); | |
5379 }, | |
5380 | |
5381 /*** | |
5382 * @method debounce([ms] = 1) | |
5383 * @returns Function | |
5384 * @short Creates a "debounced" function that postpones its execution until
after <ms> milliseconds have passed. | |
5385 * @extra This method is useful to execute a function after things have "set
tled down". A good example of this is when a user tabs quickly through form fiel
ds, execution of a heavy operation should happen after a few milliseconds when t
hey have "settled" on a field. For more see @functions. | |
5386 * @example | |
5387 * | |
5388 * var fn = (function(arg1) { | |
5389 * // called once 50ms later | |
5390 * }).debounce(50); fn() fn() fn(); | |
5391 * | |
5392 ***/ | |
5393 'debounce': function(ms) { | |
5394 var fn = this; | |
5395 function debounced() { | |
5396 debounced.cancel(); | |
5397 setDelay(debounced, ms, fn, this, arguments); | |
5398 }; | |
5399 return debounced; | |
5400 }, | |
5401 | |
5402 /*** | |
5403 * @method delay([ms] = 1, [arg1], ...) | |
5404 * @returns Function | |
5405 * @short Executes the function after <ms> milliseconds. | |
5406 * @extra Returns a reference to itself. %delay% is also a way to execute no
n-blocking operations that will wait until the CPU is free. Delayed functions ca
n be canceled using the %cancel% method. Can also curry arguments passed in afte
r <ms>. | |
5407 * @example | |
5408 * | |
5409 * (function(arg1) { | |
5410 * // called 1s later | |
5411 * }).delay(1000, 'arg1'); | |
5412 * | |
5413 ***/ | |
5414 'delay': function(ms) { | |
5415 var fn = this; | |
5416 var args = multiArgs(arguments, null, 1); | |
5417 setDelay(fn, ms, fn, fn, args); | |
5418 return fn; | |
5419 }, | |
5420 | |
5421 /*** | |
5422 * @method every([ms] = 1, [arg1], ...) | |
5423 * @returns Function | |
5424 * @short Executes the function every <ms> milliseconds. | |
5425 * @extra Returns a reference to itself. Repeating functions with %every% ca
n be canceled using the %cancel% method. Can also curry arguments passed in afte
r <ms>. | |
5426 * @example | |
5427 * | |
5428 * (function(arg1) { | |
5429 * // called every 1s | |
5430 * }).every(1000, 'arg1'); | |
5431 * | |
5432 ***/ | |
5433 'every': function(ms) { | |
5434 var fn = this, args = arguments; | |
5435 args = args.length > 1 ? multiArgs(args, null, 1) : []; | |
5436 function execute () { | |
5437 fn.apply(fn, args); | |
5438 setDelay(fn, ms, execute); | |
5439 } | |
5440 setDelay(fn, ms, execute); | |
5441 return fn; | |
5442 }, | |
5443 | |
5444 /*** | |
5445 * @method cancel() | |
5446 * @returns Function | |
5447 * @short Cancels a delayed function scheduled to be run. | |
5448 * @extra %delay%, %lazy%, %throttle%, and %debounce% can all set delays. | |
5449 * @example | |
5450 * | |
5451 * (function() { | |
5452 * alert('hay'); // Never called | |
5453 * }).delay(500).cancel(); | |
5454 * | |
5455 ***/ | |
5456 'cancel': function() { | |
5457 var timers = this.timers, timer; | |
5458 if(isArray(timers)) { | |
5459 while(timer = timers.shift()) { | |
5460 clearTimeout(timer); | |
5461 } | |
5462 } | |
5463 this._canceled = true; | |
5464 return this; | |
5465 }, | |
5466 | |
5467 /*** | |
5468 * @method after([num] = 1) | |
5469 * @returns Function | |
5470 * @short Creates a function that will execute after [num] calls. | |
5471 * @extra %after% is useful for running a final callback after a series of a
synchronous operations, when the order in which the operations will complete is
unknown. | |
5472 * @example | |
5473 * | |
5474 * var fn = (function() { | |
5475 * // Will be executed once only | |
5476 * }).after(3); fn(); fn(); fn(); | |
5477 * | |
5478 ***/ | |
5479 'after': function(num) { | |
5480 var fn = this, counter = 0, storedArguments = []; | |
5481 if(!isNumber(num)) { | |
5482 num = 1; | |
5483 } else if(num === 0) { | |
5484 fn.call(); | |
5485 return fn; | |
5486 } | |
5487 return function() { | |
5488 var ret; | |
5489 storedArguments.push(multiArgs(arguments)); | |
5490 counter++; | |
5491 if(counter == num) { | |
5492 ret = fn.call(this, storedArguments); | |
5493 counter = 0; | |
5494 storedArguments = []; | |
5495 return ret; | |
5496 } | |
5497 } | |
5498 }, | |
5499 | |
5500 /*** | |
5501 * @method once() | |
5502 * @returns Function | |
5503 * @short Creates a function that will execute only once and store the resul
t. | |
5504 * @extra %once% is useful for creating functions that will cache the result
of an expensive operation and use it on subsequent calls. Also it can be useful
for creating initialization functions that only need to be run once. | |
5505 * @example | |
5506 * | |
5507 * var fn = (function() { | |
5508 * // Will be executed once only | |
5509 * }).once(); fn(); fn(); fn(); | |
5510 * | |
5511 ***/ | |
5512 'once': function() { | |
5513 return this.throttle(Infinity, true); | |
5514 }, | |
5515 | |
5516 /*** | |
5517 * @method fill(<arg1>, <arg2>, ...) | |
5518 * @returns Function | |
5519 * @short Returns a new version of the function which when called will have
some of its arguments pre-emptively filled in, also known as "currying". | |
5520 * @extra Arguments passed to a "filled" function are generally appended to
the curried arguments. However, if %undefined% is passed as any of the arguments
to %fill%, it will be replaced, when the "filled" function is executed. This al
lows currying of arguments even when they occur toward the end of an argument li
st (the example demonstrates this much more clearly). | |
5521 * @example | |
5522 * | |
5523 * var delayOneSecond = setTimeout.fill(undefined, 1000); | |
5524 * delayOneSecond(function() { | |
5525 * // Will be executed 1s later | |
5526 * }); | |
5527 * | |
5528 ***/ | |
5529 'fill': function() { | |
5530 var fn = this, curried = multiArgs(arguments); | |
5531 return function() { | |
5532 var args = multiArgs(arguments); | |
5533 curried.forEach(function(arg, index) { | |
5534 if(arg != null || index >= args.length) args.splice(index, 0, arg); | |
5535 }); | |
5536 return fn.apply(this, args); | |
5537 } | |
5538 } | |
5539 | |
5540 | |
5541 }); | |
5542 | |
5543 | |
5544 /*** | |
5545 * @package Number | |
5546 * @dependency core | |
5547 * @description Number formatting, rounding (with precision), and ranges. Alia
ses to Math methods. | |
5548 * | |
5549 ***/ | |
5550 | |
5551 | |
5552 function abbreviateNumber(num, roundTo, str, mid, limit, bytes) { | |
5553 var fixed = num.toFixed(20), | |
5554 decimalPlace = fixed.search(/\./), | |
5555 numeralPlace = fixed.search(/[1-9]/), | |
5556 significant = decimalPlace - numeralPlace, | |
5557 unit, i, divisor; | |
5558 if(significant > 0) { | |
5559 significant -= 1; | |
5560 } | |
5561 i = max(min(floor(significant / 3), limit === false ? str.length : limit), -
mid); | |
5562 unit = str.charAt(i + mid - 1); | |
5563 if(significant < -9) { | |
5564 i = -3; | |
5565 roundTo = abs(significant) - 9; | |
5566 unit = str.slice(0,1); | |
5567 } | |
5568 divisor = bytes ? pow(2, 10 * i) : pow(10, i * 3); | |
5569 return withPrecision(num / divisor, roundTo || 0).format() + unit.trim(); | |
5570 } | |
5571 | |
5572 | |
5573 extend(number, false, true, { | |
5574 | |
5575 /*** | |
5576 * @method Number.random([n1], [n2]) | |
5577 * @returns Number | |
5578 * @short Returns a random integer between [n1] and [n2]. | |
5579 * @extra If only 1 number is passed, the other will be 0. If none are passe
d, the number will be either 0 or 1. | |
5580 * @example | |
5581 * | |
5582 * Number.random(50, 100) -> ex. 85 | |
5583 * Number.random(50) -> ex. 27 | |
5584 * Number.random() -> ex. 0 | |
5585 * | |
5586 ***/ | |
5587 'random': function(n1, n2) { | |
5588 var minNum, maxNum; | |
5589 if(arguments.length == 1) n2 = n1, n1 = 0; | |
5590 minNum = min(n1 || 0, isUndefined(n2) ? 1 : n2); | |
5591 maxNum = max(n1 || 0, isUndefined(n2) ? 1 : n2) + 1; | |
5592 return floor((math.random() * (maxNum - minNum)) + minNum); | |
5593 } | |
5594 | |
5595 }); | |
5596 | |
5597 extend(number, true, true, { | |
5598 | |
5599 /*** | |
5600 * @method log(<base> = Math.E) | |
5601 * @returns Number | |
5602 * @short Returns the logarithm of the number with base <base>, or natural l
ogarithm of the number if <base> is undefined. | |
5603 * @example | |
5604 * | |
5605 * (64).log(2) -> 6 | |
5606 * (9).log(3) -> 2 | |
5607 * (5).log() -> 1.6094379124341003 | |
5608 * | |
5609 ***/ | |
5610 | |
5611 'log': function(base) { | |
5612 return math.log(this) / (base ? math.log(base) : 1); | |
5613 }, | |
5614 | |
5615 /*** | |
5616 * @method abbr([precision] = 0) | |
5617 * @returns String | |
5618 * @short Returns an abbreviated form of the number. | |
5619 * @extra [precision] will round to the given precision. | |
5620 * @example | |
5621 * | |
5622 * (1000).abbr() -> "1k" | |
5623 * (1000000).abbr() -> "1m" | |
5624 * (1280).abbr(1) -> "1.3k" | |
5625 * | |
5626 ***/ | |
5627 'abbr': function(precision) { | |
5628 return abbreviateNumber(this, precision, 'kmbt', 0, 4); | |
5629 }, | |
5630 | |
5631 /*** | |
5632 * @method metric([precision] = 0, [limit] = 1) | |
5633 * @returns String | |
5634 * @short Returns the number as a string in metric notation. | |
5635 * @extra [precision] will round to the given precision. Both very large num
bers and very small numbers are supported. [limit] is the upper limit for the un
its. The default is %1%, which is "kilo". If [limit] is %false%, the upper limit
will be "exa". The lower limit is "nano", and cannot be changed. | |
5636 * @example | |
5637 * | |
5638 * (1000).metric() -> "1k" | |
5639 * (1000000).metric() -> "1,000k" | |
5640 * (1000000).metric(0, false) -> "1M" | |
5641 * (1249).metric(2) + 'g' -> "1.25kg" | |
5642 * (0.025).metric() + 'm' -> "25mm" | |
5643 * | |
5644 ***/ | |
5645 'metric': function(precision, limit) { | |
5646 return abbreviateNumber(this, precision, 'nμm kMGTPE', 4, isUndefined(limi
t) ? 1 : limit); | |
5647 }, | |
5648 | |
5649 /*** | |
5650 * @method bytes([precision] = 0, [limit] = 4) | |
5651 * @returns String | |
5652 * @short Returns an abbreviated form of the number, considered to be "Bytes
". | |
5653 * @extra [precision] will round to the given precision. [limit] is the uppe
r limit for the units. The default is %4%, which is "terabytes" (TB). If [limit]
is %false%, the upper limit will be "exa". | |
5654 * @example | |
5655 * | |
5656 * (1000).bytes() -> "1kB" | |
5657 * (1000).bytes(2) -> "0.98kB" | |
5658 * ((10).pow(20)).bytes() -> "90,949,470TB" | |
5659 * ((10).pow(20)).bytes(0, false) -> "87EB" | |
5660 * | |
5661 ***/ | |
5662 'bytes': function(precision, limit) { | |
5663 return abbreviateNumber(this, precision, 'kMGTPE', 0, isUndefined(limit) ?
4 : limit, true) + 'B'; | |
5664 }, | |
5665 | |
5666 /*** | |
5667 * @method isInteger() | |
5668 * @returns Boolean | |
5669 * @short Returns true if the number has no trailing decimal. | |
5670 * @example | |
5671 * | |
5672 * (420).isInteger() -> true | |
5673 * (4.5).isInteger() -> false | |
5674 * | |
5675 ***/ | |
5676 'isInteger': function() { | |
5677 return this % 1 == 0; | |
5678 }, | |
5679 | |
5680 /*** | |
5681 * @method isOdd() | |
5682 * @returns Boolean | |
5683 * @short Returns true if the number is odd. | |
5684 * @example | |
5685 * | |
5686 * (3).isOdd() -> true | |
5687 * (18).isOdd() -> false | |
5688 * | |
5689 ***/ | |
5690 'isOdd': function() { | |
5691 return !isNaN(this) && !this.isMultipleOf(2); | |
5692 }, | |
5693 | |
5694 /*** | |
5695 * @method isEven() | |
5696 * @returns Boolean | |
5697 * @short Returns true if the number is even. | |
5698 * @example | |
5699 * | |
5700 * (6).isEven() -> true | |
5701 * (17).isEven() -> false | |
5702 * | |
5703 ***/ | |
5704 'isEven': function() { | |
5705 return this.isMultipleOf(2); | |
5706 }, | |
5707 | |
5708 /*** | |
5709 * @method isMultipleOf(<num>) | |
5710 * @returns Boolean | |
5711 * @short Returns true if the number is a multiple of <num>. | |
5712 * @example | |
5713 * | |
5714 * (6).isMultipleOf(2) -> true | |
5715 * (17).isMultipleOf(2) -> false | |
5716 * (32).isMultipleOf(4) -> true | |
5717 * (34).isMultipleOf(4) -> false | |
5718 * | |
5719 ***/ | |
5720 'isMultipleOf': function(num) { | |
5721 return this % num === 0; | |
5722 }, | |
5723 | |
5724 | |
5725 /*** | |
5726 * @method format([place] = 0, [thousands] = ',', [decimal] = '.') | |
5727 * @returns String | |
5728 * @short Formats the number to a readable string. | |
5729 * @extra If [place] is %undefined%, will automatically determine the place.
[thousands] is the character used for the thousands separator. [decimal] is the
character used for the decimal point. | |
5730 * @example | |
5731 * | |
5732 * (56782).format() -> '56,782' | |
5733 * (56782).format(2) -> '56,782.00' | |
5734 * (4388.43).format(2, ' ') -> '4 388.43' | |
5735 * (4388.43).format(2, '.', ',') -> '4.388,43' | |
5736 * | |
5737 ***/ | |
5738 'format': function(place, thousands, decimal) { | |
5739 var i, str, split, integer, fraction, result = ''; | |
5740 if(isUndefined(thousands)) { | |
5741 thousands = ','; | |
5742 } | |
5743 if(isUndefined(decimal)) { | |
5744 decimal = '.'; | |
5745 } | |
5746 str = (isNumber(place) ? withPrecision(this, place || 0).toFixed(max(
place, 0)) : this.toString()).replace(/^-/, ''); | |
5747 split = str.split('.'); | |
5748 integer = split[0]; | |
5749 fraction = split[1]; | |
5750 for(i = integer.length; i > 0; i -= 3) { | |
5751 if(i < integer.length) { | |
5752 result = thousands + result; | |
5753 } | |
5754 result = integer.slice(max(0, i - 3), i) + result; | |
5755 } | |
5756 if(fraction) { | |
5757 result += decimal + repeatString('0', (place || 0) - fraction.length) +
fraction; | |
5758 } | |
5759 return (this < 0 ? '-' : '') + result; | |
5760 }, | |
5761 | |
5762 /*** | |
5763 * @method hex([pad] = 1) | |
5764 * @returns String | |
5765 * @short Converts the number to hexidecimal. | |
5766 * @extra [pad] will pad the resulting string to that many places. | |
5767 * @example | |
5768 * | |
5769 * (255).hex() -> 'ff'; | |
5770 * (255).hex(4) -> '00ff'; | |
5771 * (23654).hex() -> '5c66'; | |
5772 * | |
5773 ***/ | |
5774 'hex': function(pad) { | |
5775 return this.pad(pad || 1, false, 16); | |
5776 }, | |
5777 | |
5778 /*** | |
5779 * @method times(<fn>) | |
5780 * @returns Number | |
5781 * @short Calls <fn> a number of times equivalent to the number. | |
5782 * @example | |
5783 * | |
5784 * (8).times(function(i) { | |
5785 * // This function is called 8 times. | |
5786 * }); | |
5787 * | |
5788 ***/ | |
5789 'times': function(fn) { | |
5790 if(fn) { | |
5791 for(var i = 0; i < this; i++) { | |
5792 fn.call(this, i); | |
5793 } | |
5794 } | |
5795 return this.toNumber(); | |
5796 }, | |
5797 | |
5798 /*** | |
5799 * @method chr() | |
5800 * @returns String | |
5801 * @short Returns a string at the code point of the number. | |
5802 * @example | |
5803 * | |
5804 * (65).chr() -> "A" | |
5805 * (75).chr() -> "K" | |
5806 * | |
5807 ***/ | |
5808 'chr': function() { | |
5809 return string.fromCharCode(this); | |
5810 }, | |
5811 | |
5812 /*** | |
5813 * @method pad(<place> = 0, [sign] = false, [base] = 10) | |
5814 * @returns String | |
5815 * @short Pads a number with "0" to <place>. | |
5816 * @extra [sign] allows you to force the sign as well (+05, etc). [base] can
change the base for numeral conversion. | |
5817 * @example | |
5818 * | |
5819 * (5).pad(2) -> '05' | |
5820 * (-5).pad(4) -> '-0005' | |
5821 * (82).pad(3, true) -> '+082' | |
5822 * | |
5823 ***/ | |
5824 'pad': function(place, sign, base) { | |
5825 return padNumber(this, place, sign, base); | |
5826 }, | |
5827 | |
5828 /*** | |
5829 * @method ordinalize() | |
5830 * @returns String | |
5831 * @short Returns an ordinalized (English) string, i.e. "1st", "2nd", etc. | |
5832 * @example | |
5833 * | |
5834 * (1).ordinalize() -> '1st'; | |
5835 * (2).ordinalize() -> '2nd'; | |
5836 * (8).ordinalize() -> '8th'; | |
5837 * | |
5838 ***/ | |
5839 'ordinalize': function() { | |
5840 var suffix, num = abs(this), last = parseInt(num.toString().slice(-2)); | |
5841 return this + getOrdinalizedSuffix(last); | |
5842 }, | |
5843 | |
5844 /*** | |
5845 * @method toNumber() | |
5846 * @returns Number | |
5847 * @short Returns a number. This is mostly for compatibility reasons. | |
5848 * @example | |
5849 * | |
5850 * (420).toNumber() -> 420 | |
5851 * | |
5852 ***/ | |
5853 'toNumber': function() { | |
5854 return parseFloat(this, 10); | |
5855 } | |
5856 | |
5857 }); | |
5858 | |
5859 /*** | |
5860 * @method round(<precision> = 0) | |
5861 * @returns Number | |
5862 * @short Shortcut for %Math.round% that also allows a <precision>. | |
5863 * | |
5864 * @example | |
5865 * | |
5866 * (3.241).round() -> 3 | |
5867 * (-3.841).round() -> -4 | |
5868 * (3.241).round(2) -> 3.24 | |
5869 * (3748).round(-2) -> 3800 | |
5870 * | |
5871 *** | |
5872 * @method ceil(<precision> = 0) | |
5873 * @returns Number | |
5874 * @short Shortcut for %Math.ceil% that also allows a <precision>. | |
5875 * | |
5876 * @example | |
5877 * | |
5878 * (3.241).ceil() -> 4 | |
5879 * (-3.241).ceil() -> -3 | |
5880 * (3.241).ceil(2) -> 3.25 | |
5881 * (3748).ceil(-2) -> 3800 | |
5882 * | |
5883 *** | |
5884 * @method floor(<precision> = 0) | |
5885 * @returns Number | |
5886 * @short Shortcut for %Math.floor% that also allows a <precision>. | |
5887 * | |
5888 * @example | |
5889 * | |
5890 * (3.241).floor() -> 3 | |
5891 * (-3.841).floor() -> -4 | |
5892 * (3.241).floor(2) -> 3.24 | |
5893 * (3748).floor(-2) -> 3700 | |
5894 * | |
5895 *** | |
5896 * @method [math]() | |
5897 * @returns Number | |
5898 * @short Math related functions are mapped as shortcuts to numbers and are id
entical. Note that %Number#log% provides some special defaults. | |
5899 * | |
5900 * @set | |
5901 * abs | |
5902 * sin | |
5903 * asin | |
5904 * cos | |
5905 * acos | |
5906 * tan | |
5907 * atan | |
5908 * sqrt | |
5909 * exp | |
5910 * pow | |
5911 * | |
5912 * @example | |
5913 * | |
5914 * (3).pow(3) -> 27 | |
5915 * (-3).abs() -> 3 | |
5916 * (1024).sqrt() -> 32 | |
5917 * | |
5918 ***/ | |
5919 | |
5920 function buildNumber() { | |
5921 function createRoundingFunction(fn) { | |
5922 return function (precision) { | |
5923 return precision ? withPrecision(this, precision, fn) : fn(this); | |
5924 } | |
5925 } | |
5926 extend(number, true, true, { | |
5927 'ceil': createRoundingFunction(ceil), | |
5928 'round': createRoundingFunction(round), | |
5929 'floor': createRoundingFunction(floor) | |
5930 }); | |
5931 extendSimilar(number, true, true, 'abs,pow,sin,asin,cos,acos,tan,atan,exp,po
w,sqrt', function(methods, name) { | |
5932 methods[name] = function(a, b) { | |
5933 return math[name](this, a, b); | |
5934 } | |
5935 }); | |
5936 } | |
5937 | |
5938 buildNumber(); | |
5939 | |
5940 | |
5941 /*** | |
5942 * @package Object | |
5943 * @dependency core | |
5944 * @description Object manipulation, type checking (isNumber, isString, ...),
extended objects with hash-like methods available as instance methods. | |
5945 * | |
5946 * Much thanks to kangax for his informative aricle about how problems with in
stanceof and constructor | |
5947 * http://perfectionkills.com/instanceof-considered-harmful-or-how-to-write-a-
robust-isarray/ | |
5948 * | |
5949 ***/ | |
5950 | |
5951 var ObjectTypeMethods = 'isObject,isNaN'.split(','); | |
5952 var ObjectHashMethods = 'keys,values,select,reject,each,merge,clone,equal,watc
h,tap,has,toQueryString'.split(','); | |
5953 | |
5954 function setParamsObject(obj, param, value, castBoolean) { | |
5955 var reg = /^(.+?)(\[.*\])$/, paramIsArray, match, allKeys, key; | |
5956 if(match = param.match(reg)) { | |
5957 key = match[1]; | |
5958 allKeys = match[2].replace(/^\[|\]$/g, '').split(']['); | |
5959 allKeys.forEach(function(k) { | |
5960 paramIsArray = !k || k.match(/^\d+$/); | |
5961 if(!key && isArray(obj)) key = obj.length; | |
5962 if(!hasOwnProperty(obj, key)) { | |
5963 obj[key] = paramIsArray ? [] : {}; | |
5964 } | |
5965 obj = obj[key]; | |
5966 key = k; | |
5967 }); | |
5968 if(!key && paramIsArray) key = obj.length.toString(); | |
5969 setParamsObject(obj, key, value, castBoolean); | |
5970 } else if(castBoolean && value === 'true') { | |
5971 obj[param] = true; | |
5972 } else if(castBoolean && value === 'false') { | |
5973 obj[param] = false; | |
5974 } else { | |
5975 obj[param] = value; | |
5976 } | |
5977 } | |
5978 | |
5979 function objectToQueryString(base, obj) { | |
5980 var tmp; | |
5981 // If a custom toString exists bail here and use that instead | |
5982 if(isArray(obj) || (isObjectType(obj) && obj.toString === internalToString))
{ | |
5983 tmp = []; | |
5984 iterateOverObject(obj, function(key, value) { | |
5985 if(base) { | |
5986 key = base + '[' + key + ']'; | |
5987 } | |
5988 tmp.push(objectToQueryString(key, value)); | |
5989 }); | |
5990 return tmp.join('&'); | |
5991 } else { | |
5992 if(!base) return ''; | |
5993 return sanitizeURIComponent(base) + '=' + (isDate(obj) ? obj.getTime() : s
anitizeURIComponent(obj)); | |
5994 } | |
5995 } | |
5996 | |
5997 function sanitizeURIComponent(obj) { | |
5998 // undefined, null, and NaN are represented as a blank string, | |
5999 // while false and 0 are stringified. "+" is allowed in query string | |
6000 return !obj && obj !== false && obj !== 0 ? '' : encodeURIComponent(obj).rep
lace(/%20/g, '+'); | |
6001 } | |
6002 | |
6003 function matchInObject(match, key, value) { | |
6004 if(isRegExp(match)) { | |
6005 return match.test(key); | |
6006 } else if(isObjectType(match)) { | |
6007 return match[key] === value; | |
6008 } else { | |
6009 return key === string(match); | |
6010 } | |
6011 } | |
6012 | |
6013 function selectFromObject(obj, args, select) { | |
6014 var match, result = obj instanceof Hash ? new Hash : {}; | |
6015 iterateOverObject(obj, function(key, value) { | |
6016 match = false; | |
6017 flattenedArgs(args, function(arg) { | |
6018 if(matchInObject(arg, key, value)) { | |
6019 match = true; | |
6020 } | |
6021 }, 1); | |
6022 if(match === select) { | |
6023 result[key] = value; | |
6024 } | |
6025 }); | |
6026 return result; | |
6027 } | |
6028 | |
6029 | |
6030 /*** | |
6031 * @method Object.is[Type](<obj>) | |
6032 * @returns Boolean | |
6033 * @short Returns true if <obj> is an object of that type. | |
6034 * @extra %isObject% will return false on anything that is not an object liter
al, including instances of inherited classes. Note also that %isNaN% will ONLY r
eturn true if the object IS %NaN%. It does not mean the same as browser native %
isNaN%, which returns true for anything that is "not a number". | |
6035 * | |
6036 * @set | |
6037 * isArray | |
6038 * isObject | |
6039 * isBoolean | |
6040 * isDate | |
6041 * isFunction | |
6042 * isNaN | |
6043 * isNumber | |
6044 * isString | |
6045 * isRegExp | |
6046 * | |
6047 * @example | |
6048 * | |
6049 * Object.isArray([1,2,3]) -> true | |
6050 * Object.isDate(3) -> false | |
6051 * Object.isRegExp(/wasabi/) -> true | |
6052 * Object.isObject({ broken:'wear' }) -> true | |
6053 * | |
6054 ***/ | |
6055 function buildTypeMethods() { | |
6056 extendSimilar(object, false, true, ClassNames, function(methods, name) { | |
6057 var method = 'is' + name; | |
6058 ObjectTypeMethods.push(method); | |
6059 methods[method] = typeChecks[name]; | |
6060 }); | |
6061 } | |
6062 | |
6063 function buildObjectExtend() { | |
6064 extend(object, false, function(){ return arguments.length === 0; }, { | |
6065 'extend': function() { | |
6066 var methods = ObjectTypeMethods.concat(ObjectHashMethods) | |
6067 if(typeof EnumerableMethods !== 'undefined') { | |
6068 methods = methods.concat(EnumerableMethods); | |
6069 } | |
6070 buildObjectInstanceMethods(methods, object); | |
6071 } | |
6072 }); | |
6073 } | |
6074 | |
6075 extend(object, false, true, { | |
6076 /*** | |
6077 * @method watch(<obj>, <prop>, <fn>) | |
6078 * @returns Nothing | |
6079 * @short Watches a property of <obj> and runs <fn> when it changes. | |
6080 * @extra <fn> is passed three arguments: the property <prop>, the old val
ue, and the new value. The return value of [fn] will be set as the new value. Th
is method is useful for things such as validating or cleaning the value when it
is set. Warning: this method WILL NOT work in browsers that don't support %Objec
t.defineProperty% (IE 8 and below). This is the only method in Sugar that is not
fully compatible with all browsers. %watch% is available as an instance method
on extended objects. | |
6081 * @example | |
6082 * | |
6083 * Object.watch({ foo: 'bar' }, 'foo', function(prop, oldVal, newVal) { | |
6084 * // Will be run when the property 'foo' is set on the object. | |
6085 * }); | |
6086 * Object.extended().watch({ foo: 'bar' }, 'foo', function(prop, oldVal,
newVal) { | |
6087 * // Will be run when the property 'foo' is set on the object. | |
6088 * }); | |
6089 * | |
6090 ***/ | |
6091 'watch': function(obj, prop, fn) { | |
6092 if(!definePropertySupport) return; | |
6093 var value = obj[prop]; | |
6094 object.defineProperty(obj, prop, { | |
6095 'enumerable' : true, | |
6096 'configurable': true, | |
6097 'get': function() { | |
6098 return value; | |
6099 }, | |
6100 'set': function(to) { | |
6101 value = fn.call(obj, prop, value, to); | |
6102 } | |
6103 }); | |
6104 } | |
6105 }); | |
6106 | |
6107 extend(object, false, function() { return arguments.length > 1; }, { | |
6108 | |
6109 /*** | |
6110 * @method keys(<obj>, [fn]) | |
6111 * @returns Array | |
6112 * @short Returns an array containing the keys in <obj>. Optionally calls [f
n] for each key. | |
6113 * @extra This method is provided for browsers that don't support it nativel
y, and additionally is enhanced to accept the callback [fn]. Returned keys are i
n no particular order. %keys% is available as an instance method on extended obj
ects. | |
6114 * @example | |
6115 * | |
6116 * Object.keys({ broken: 'wear' }) -> ['broken'] | |
6117 * Object.keys({ broken: 'wear' }, function(key, value) { | |
6118 * // Called once for each key. | |
6119 * }); | |
6120 * Object.extended({ broken: 'wear' }).keys() -> ['broken'] | |
6121 * | |
6122 ***/ | |
6123 'keys': function(obj, fn) { | |
6124 var keys = object.keys(obj); | |
6125 keys.forEach(function(key) { | |
6126 fn.call(obj, key, obj[key]); | |
6127 }); | |
6128 return keys; | |
6129 } | |
6130 | |
6131 }); | |
6132 | |
6133 extend(object, false, true, { | |
6134 | |
6135 'isObject': function(obj) { | |
6136 return isPlainObject(obj); | |
6137 }, | |
6138 | |
6139 'isNaN': function(obj) { | |
6140 // This is only true of NaN | |
6141 return isNumber(obj) && obj.valueOf() !== obj.valueOf(); | |
6142 }, | |
6143 | |
6144 /*** | |
6145 * @method equal(<a>, <b>) | |
6146 * @returns Boolean | |
6147 * @short Returns true if <a> and <b> are equal. | |
6148 * @extra %equal% in Sugar is "egal", meaning the values are equal if they a
re "not observably distinguishable". Note that on extended objects the name is %
equals% for readability. | |
6149 * @example | |
6150 * | |
6151 * Object.equal({a:2}, {a:2}) -> true | |
6152 * Object.equal({a:2}, {a:3}) -> false | |
6153 * Object.extended({a:2}).equals({a:3}) -> false | |
6154 * | |
6155 ***/ | |
6156 'equal': function(a, b) { | |
6157 return isEqual(a, b); | |
6158 }, | |
6159 | |
6160 /*** | |
6161 * @method Object.extended(<obj> = {}) | |
6162 * @returns Extended object | |
6163 * @short Creates a new object, equivalent to %new Object()% or %{}%, but wi
th extended methods. | |
6164 * @extra See extended objects for more. | |
6165 * @example | |
6166 * | |
6167 * Object.extended() | |
6168 * Object.extended({ happy:true, pappy:false }).keys() -> ['happy','pappy'
] | |
6169 * Object.extended({ happy:true, pappy:false }).values() -> [true, false] | |
6170 * | |
6171 ***/ | |
6172 'extended': function(obj) { | |
6173 return new Hash(obj); | |
6174 }, | |
6175 | |
6176 /*** | |
6177 * @method merge(<target>, <source>, [deep] = false, [resolve] = true) | |
6178 * @returns Merged object | |
6179 * @short Merges all the properties of <source> into <target>. | |
6180 * @extra Merges are shallow unless [deep] is %true%. Properties of <source>
will win in the case of conflicts, unless [resolve] is %false%. [resolve] can a
lso be a function that resolves the conflict. In this case it will be passed 3 a
rguments, %key%, %targetVal%, and %sourceVal%, with the context set to <source>.
This will allow you to solve conflict any way you want, ie. adding two numbers
together, etc. %merge% is available as an instance method on extended objects. | |
6181 * @example | |
6182 * | |
6183 * Object.merge({a:1},{b:2}) -> { a:1, b:2 } | |
6184 * Object.merge({a:1},{a:2}, false, false) -> { a:1 } | |
6185 + Object.merge({a:1},{a:2}, false, function(key, a, b) { | |
6186 * return a + b; | |
6187 * }); -> { a:3 } | |
6188 * Object.extended({a:1}).merge({b:2}) -> { a:1, b:2 } | |
6189 * | |
6190 ***/ | |
6191 'merge': function(target, source, deep, resolve) { | |
6192 var key, sourceIsObject, targetIsObject, sourceVal, targetVal, conflict, r
esult; | |
6193 // Strings cannot be reliably merged thanks to | |
6194 // their properties not being enumerable in < IE8. | |
6195 if(target && typeof source !== 'string') { | |
6196 for(key in source) { | |
6197 if(!hasOwnProperty(source, key) || !target) continue; | |
6198 sourceVal = source[key]; | |
6199 targetVal = target[key]; | |
6200 conflict = isDefined(targetVal); | |
6201 sourceIsObject = isObjectType(sourceVal); | |
6202 targetIsObject = isObjectType(targetVal); | |
6203 result = conflict && resolve === false ? targetVal : sourceVal
; | |
6204 | |
6205 if(conflict) { | |
6206 if(isFunction(resolve)) { | |
6207 // Use the result of the callback as the result. | |
6208 result = resolve.call(source, key, targetVal, sourceVal) | |
6209 } | |
6210 } | |
6211 | |
6212 // Going deep | |
6213 if(deep && (sourceIsObject || targetIsObject)) { | |
6214 if(isDate(sourceVal)) { | |
6215 result = new date(sourceVal.getTime()); | |
6216 } else if(isRegExp(sourceVal)) { | |
6217 result = new regexp(sourceVal.source, getRegExpFlags(sourceVal)); | |
6218 } else { | |
6219 if(!targetIsObject) target[key] = array.isArray(sourceVal) ? [] :
{}; | |
6220 object.merge(target[key], sourceVal, deep, resolve); | |
6221 continue; | |
6222 } | |
6223 } | |
6224 target[key] = result; | |
6225 } | |
6226 } | |
6227 return target; | |
6228 }, | |
6229 | |
6230 /*** | |
6231 * @method values(<obj>, [fn]) | |
6232 * @returns Array | |
6233 * @short Returns an array containing the values in <obj>. Optionally calls
[fn] for each value. | |
6234 * @extra Returned values are in no particular order. %values% is available
as an instance method on extended objects. | |
6235 * @example | |
6236 * | |
6237 * Object.values({ broken: 'wear' }) -> ['wear'] | |
6238 * Object.values({ broken: 'wear' }, function(value) { | |
6239 * // Called once for each value. | |
6240 * }); | |
6241 * Object.extended({ broken: 'wear' }).values() -> ['wear'] | |
6242 * | |
6243 ***/ | |
6244 'values': function(obj, fn) { | |
6245 var values = []; | |
6246 iterateOverObject(obj, function(k,v) { | |
6247 values.push(v); | |
6248 if(fn) fn.call(obj,v); | |
6249 }); | |
6250 return values; | |
6251 }, | |
6252 | |
6253 /*** | |
6254 * @method clone(<obj> = {}, [deep] = false) | |
6255 * @returns Cloned object | |
6256 * @short Creates a clone (copy) of <obj>. | |
6257 * @extra Default is a shallow clone, unless [deep] is true. %clone% is avai
lable as an instance method on extended objects. | |
6258 * @example | |
6259 * | |
6260 * Object.clone({foo:'bar'}) -> { foo: 'bar' } | |
6261 * Object.clone() -> {} | |
6262 * Object.extended({foo:'bar'}).clone() -> { foo: 'bar' } | |
6263 * | |
6264 ***/ | |
6265 'clone': function(obj, deep) { | |
6266 var target, klass; | |
6267 if(!isObjectType(obj)) { | |
6268 return obj; | |
6269 } | |
6270 klass = className(obj); | |
6271 if(isDate(obj, klass) && obj.clone) { | |
6272 // Preserve internal UTC flag when applicable. | |
6273 return obj.clone(); | |
6274 } else if(isDate(obj, klass) || isRegExp(obj, klass)) { | |
6275 return new obj.constructor(obj); | |
6276 } else if(obj instanceof Hash) { | |
6277 target = new Hash; | |
6278 } else if(isArray(obj, klass)) { | |
6279 target = []; | |
6280 } else if(isPlainObject(obj, klass)) { | |
6281 target = {}; | |
6282 } else { | |
6283 throw new TypeError('Clone must be a basic data type.'); | |
6284 } | |
6285 return object.merge(target, obj, deep); | |
6286 }, | |
6287 | |
6288 /*** | |
6289 * @method Object.fromQueryString(<str>, [booleans] = false) | |
6290 * @returns Object | |
6291 * @short Converts the query string of a URL into an object. | |
6292 * @extra If [booleans] is true, then %"true"% and %"false"% will be cast in
to booleans. All other values, including numbers will remain their string values
. | |
6293 * @example | |
6294 * | |
6295 * Object.fromQueryString('foo=bar&broken=wear') -> { foo: 'bar', broken:
'wear' } | |
6296 * Object.fromQueryString('foo[]=1&foo[]=2') -> { foo: ['1','2'] } | |
6297 * Object.fromQueryString('foo=true', true) -> { foo: true } | |
6298 * | |
6299 ***/ | |
6300 'fromQueryString': function(str, castBoolean) { | |
6301 var result = object.extended(), split; | |
6302 str = str && str.toString ? str.toString() : ''; | |
6303 str.replace(/^.*?\?/, '').split('&').forEach(function(p) { | |
6304 var split = p.split('='); | |
6305 if(split.length !== 2) return; | |
6306 setParamsObject(result, split[0], decodeURIComponent(split[1]), castBool
ean); | |
6307 }); | |
6308 return result; | |
6309 }, | |
6310 | |
6311 /*** | |
6312 * @method Object.toQueryString(<obj>, [namespace] = null) | |
6313 * @returns Object | |
6314 * @short Converts the object into a query string. | |
6315 * @extra Accepts deep nested objects and arrays. If [namespace] is passed,
it will be prefixed to all param names. | |
6316 * @example | |
6317 * | |
6318 * Object.toQueryString({foo:'bar'}) -> 'foo=bar' | |
6319 * Object.toQueryString({foo:['a','b','c']}) -> 'foo[0]=a&foo[1]=b&foo[2]
=c' | |
6320 * Object.toQueryString({name:'Bob'}, 'user') -> 'user[name]=Bob' | |
6321 * | |
6322 ***/ | |
6323 'toQueryString': function(obj, namespace) { | |
6324 return objectToQueryString(namespace, obj); | |
6325 }, | |
6326 | |
6327 /*** | |
6328 * @method tap(<obj>, <fn>) | |
6329 * @returns Object | |
6330 * @short Runs <fn> and returns <obj>. | |
6331 * @extra A string can also be used as a shortcut to a method. This method
is used to run an intermediary function in the middle of method chaining. As a s
tandalone method on the Object class it doesn't have too much use. The power of
%tap% comes when using extended objects or modifying the Object prototype with O
bject.extend(). | |
6332 * @example | |
6333 * | |
6334 * Object.extend(); | |
6335 * [2,4,6].map(Math.exp).tap(function(arr) { | |
6336 * arr.pop() | |
6337 * }); | |
6338 * [2,4,6].map(Math.exp).tap('pop').map(Math.round); -> [7,55] | |
6339 * | |
6340 ***/ | |
6341 'tap': function(obj, arg) { | |
6342 var fn = arg; | |
6343 if(!isFunction(arg)) { | |
6344 fn = function() { | |
6345 if(arg) obj[arg](); | |
6346 } | |
6347 } | |
6348 fn.call(obj, obj); | |
6349 return obj; | |
6350 }, | |
6351 | |
6352 /*** | |
6353 * @method has(<obj>, <key>) | |
6354 * @returns Boolean | |
6355 * @short Checks if <obj> has <key> using hasOwnProperty from Object.prototy
pe. | |
6356 * @extra This method is considered safer than %Object#hasOwnProperty% when
using objects as hashes. See http://www.devthought.com/2012/01/18/an-object-is-n
ot-a-hash/ for more. | |
6357 * @example | |
6358 * | |
6359 * Object.has({ foo: 'bar' }, 'foo') -> true | |
6360 * Object.has({ foo: 'bar' }, 'baz') -> false | |
6361 * Object.has({ hasOwnProperty: true }, 'foo') -> false | |
6362 * | |
6363 ***/ | |
6364 'has': function (obj, key) { | |
6365 return hasOwnProperty(obj, key); | |
6366 }, | |
6367 | |
6368 /*** | |
6369 * @method select(<obj>, <find>, ...) | |
6370 * @returns Object | |
6371 * @short Builds a new object containing the values specified in <find>. | |
6372 * @extra When <find> is a string, that single key will be selected. It can
also be a regex, selecting any key that matches, or an object which will match i
f the key also exists in that object, effectively doing an "intersect" operation
on that object. Multiple selections may also be passed as an array or directly
as enumerated arguments. %select% is available as an instance method on extended
objects. | |
6373 * @example | |
6374 * | |
6375 * Object.select({a:1,b:2}, 'a') -> {a:1} | |
6376 * Object.select({a:1,b:2}, /[a-z]/) -> {a:1,ba:2} | |
6377 * Object.select({a:1,b:2}, {a:1}) -> {a:1} | |
6378 * Object.select({a:1,b:2}, 'a', 'b') -> {a:1,b:2} | |
6379 * Object.select({a:1,b:2}, ['a', 'b']) -> {a:1,b:2} | |
6380 * | |
6381 ***/ | |
6382 'select': function (obj) { | |
6383 return selectFromObject(obj, arguments, true); | |
6384 }, | |
6385 | |
6386 /*** | |
6387 * @method reject(<obj>, <find>, ...) | |
6388 * @returns Object | |
6389 * @short Builds a new object containing all values except those specified i
n <find>. | |
6390 * @extra When <find> is a string, that single key will be rejected. It can
also be a regex, rejecting any key that matches, or an object which will match i
f the key also exists in that object, effectively "subtracting" that object. Mul
tiple selections may also be passed as an array or directly as enumerated argume
nts. %reject% is available as an instance method on extended objects. | |
6391 * @example | |
6392 * | |
6393 * Object.reject({a:1,b:2}, 'a') -> {b:2} | |
6394 * Object.reject({a:1,b:2}, /[a-z]/) -> {} | |
6395 * Object.reject({a:1,b:2}, {a:1}) -> {b:2} | |
6396 * Object.reject({a:1,b:2}, 'a', 'b') -> {} | |
6397 * Object.reject({a:1,b:2}, ['a', 'b']) -> {} | |
6398 * | |
6399 ***/ | |
6400 'reject': function (obj) { | |
6401 return selectFromObject(obj, arguments, false); | |
6402 } | |
6403 | |
6404 }); | |
6405 | |
6406 | |
6407 buildTypeMethods(); | |
6408 buildObjectExtend(); | |
6409 buildObjectInstanceMethods(ObjectHashMethods, Hash); | |
6410 | |
6411 /*** | |
6412 * @package RegExp | |
6413 * @dependency core | |
6414 * @description Escaping regexes and manipulating their flags. | |
6415 * | |
6416 * Note here that methods on the RegExp class like .exec and .test will fail i
n the current version of SpiderMonkey being | |
6417 * used by CouchDB when using shorthand regex notation like /foo/. This is the
reason for the intermixed use of shorthand | |
6418 * and compiled regexes here. If you're using JS in CouchDB, it is safer to AL
WAYS compile your regexes from a string. | |
6419 * | |
6420 ***/ | |
6421 | |
6422 extend(regexp, false, true, { | |
6423 | |
6424 /*** | |
6425 * @method RegExp.escape(<str> = '') | |
6426 * @returns String | |
6427 * @short Escapes all RegExp tokens in a string. | |
6428 * @example | |
6429 * | |
6430 * RegExp.escape('really?') -> 'really\?' | |
6431 * RegExp.escape('yes.') -> 'yes\.' | |
6432 * RegExp.escape('(not really)') -> '\(not really\)' | |
6433 * | |
6434 ***/ | |
6435 'escape': function(str) { | |
6436 return escapeRegExp(str); | |
6437 } | |
6438 | |
6439 }); | |
6440 | |
6441 extend(regexp, true, true, { | |
6442 | |
6443 /*** | |
6444 * @method getFlags() | |
6445 * @returns String | |
6446 * @short Returns the flags of the regex as a string. | |
6447 * @example | |
6448 * | |
6449 * /texty/gim.getFlags('testy') -> 'gim' | |
6450 * | |
6451 ***/ | |
6452 'getFlags': function() { | |
6453 return getRegExpFlags(this); | |
6454 }, | |
6455 | |
6456 /*** | |
6457 * @method setFlags(<flags>) | |
6458 * @returns RegExp | |
6459 * @short Sets the flags on a regex and retuns a copy. | |
6460 * @example | |
6461 * | |
6462 * /texty/.setFlags('gim') -> now has global, ignoreCase, and multiline set | |
6463 * | |
6464 ***/ | |
6465 'setFlags': function(flags) { | |
6466 return regexp(this.source, flags); | |
6467 }, | |
6468 | |
6469 /*** | |
6470 * @method addFlag(<flag>) | |
6471 * @returns RegExp | |
6472 * @short Adds <flag> to the regex. | |
6473 * @example | |
6474 * | |
6475 * /texty/.addFlag('g') -> now has global flag set | |
6476 * | |
6477 ***/ | |
6478 'addFlag': function(flag) { | |
6479 return this.setFlags(getRegExpFlags(this, flag)); | |
6480 }, | |
6481 | |
6482 /*** | |
6483 * @method removeFlag(<flag>) | |
6484 * @returns RegExp | |
6485 * @short Removes <flag> from the regex. | |
6486 * @example | |
6487 * | |
6488 * /texty/g.removeFlag('g') -> now has global flag removed | |
6489 * | |
6490 ***/ | |
6491 'removeFlag': function(flag) { | |
6492 return this.setFlags(getRegExpFlags(this).replace(flag, '')); | |
6493 } | |
6494 | |
6495 }); | |
6496 | |
6497 | |
6498 | |
6499 /*** | |
6500 * @package String | |
6501 * @dependency core | |
6502 * @description String manupulation, escaping, encoding, truncation, and:conve
rsion. | |
6503 * | |
6504 ***/ | |
6505 | |
6506 function getAcronym(word) { | |
6507 var inflector = string.Inflector; | |
6508 var word = inflector && inflector.acronyms[word]; | |
6509 if(isString(word)) { | |
6510 return word; | |
6511 } | |
6512 } | |
6513 | |
6514 function checkRepeatRange(num) { | |
6515 num = +num; | |
6516 if(num < 0 || num === Infinity) { | |
6517 throw new RangeError('Invalid number'); | |
6518 } | |
6519 return num; | |
6520 } | |
6521 | |
6522 function padString(num, padding) { | |
6523 return repeatString(isDefined(padding) ? padding : ' ', num); | |
6524 } | |
6525 | |
6526 function truncateString(str, length, from, ellipsis, split) { | |
6527 var str1, str2, len1, len2; | |
6528 if(str.length <= length) { | |
6529 return str.toString(); | |
6530 } | |
6531 ellipsis = isUndefined(ellipsis) ? '...' : ellipsis; | |
6532 switch(from) { | |
6533 case 'left': | |
6534 str2 = split ? truncateOnWord(str, length, true) : str.slice(str.length
- length); | |
6535 return ellipsis + str2; | |
6536 case 'middle': | |
6537 len1 = ceil(length / 2); | |
6538 len2 = floor(length / 2); | |
6539 str1 = split ? truncateOnWord(str, len1) : str.slice(0, len1); | |
6540 str2 = split ? truncateOnWord(str, len2, true) : str.slice(str.length -
len2); | |
6541 return str1 + ellipsis + str2; | |
6542 default: | |
6543 str1 = split ? truncateOnWord(str, length) : str.slice(0, length); | |
6544 return str1 + ellipsis; | |
6545 } | |
6546 } | |
6547 | |
6548 function truncateOnWord(str, limit, fromLeft) { | |
6549 if(fromLeft) { | |
6550 return truncateOnWord(str.reverse(), limit).reverse(); | |
6551 } | |
6552 var reg = regexp('(?=[' + getTrimmableCharacters() + '])'); | |
6553 var words = str.split(reg); | |
6554 var count = 0; | |
6555 return words.filter(function(word) { | |
6556 count += word.length; | |
6557 return count <= limit; | |
6558 }).join(''); | |
6559 } | |
6560 | |
6561 function numberOrIndex(str, n, from) { | |
6562 if(isString(n)) { | |
6563 n = str.indexOf(n); | |
6564 if(n === -1) { | |
6565 n = from ? str.length : 0; | |
6566 } | |
6567 } | |
6568 return n; | |
6569 } | |
6570 | |
6571 var btoa, atob; | |
6572 | |
6573 function buildBase64(key) { | |
6574 if(globalContext.btoa) { | |
6575 btoa = globalContext.btoa; | |
6576 atob = globalContext.atob; | |
6577 return; | |
6578 } | |
6579 var base64reg = /[^A-Za-z0-9\+\/\=]/g; | |
6580 btoa = function(str) { | |
6581 var output = ''; | |
6582 var chr1, chr2, chr3; | |
6583 var enc1, enc2, enc3, enc4; | |
6584 var i = 0; | |
6585 do { | |
6586 chr1 = str.charCodeAt(i++); | |
6587 chr2 = str.charCodeAt(i++); | |
6588 chr3 = str.charCodeAt(i++); | |
6589 enc1 = chr1 >> 2; | |
6590 enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); | |
6591 enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); | |
6592 enc4 = chr3 & 63; | |
6593 if (isNaN(chr2)) { | |
6594 enc3 = enc4 = 64; | |
6595 } else if (isNaN(chr3)) { | |
6596 enc4 = 64; | |
6597 } | |
6598 output = output + key.charAt(enc1) + key.charAt(enc2) + key.charAt(enc3)
+ key.charAt(enc4); | |
6599 chr1 = chr2 = chr3 = ''; | |
6600 enc1 = enc2 = enc3 = enc4 = ''; | |
6601 } while (i < str.length); | |
6602 return output; | |
6603 } | |
6604 atob = function(input) { | |
6605 var output = ''; | |
6606 var chr1, chr2, chr3; | |
6607 var enc1, enc2, enc3, enc4; | |
6608 var i = 0; | |
6609 if(input.match(base64reg)) { | |
6610 throw new Error('String contains invalid base64 characters'); | |
6611 } | |
6612 input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ''); | |
6613 do { | |
6614 enc1 = key.indexOf(input.charAt(i++)); | |
6615 enc2 = key.indexOf(input.charAt(i++)); | |
6616 enc3 = key.indexOf(input.charAt(i++)); | |
6617 enc4 = key.indexOf(input.charAt(i++)); | |
6618 chr1 = (enc1 << 2) | (enc2 >> 4); | |
6619 chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); | |
6620 chr3 = ((enc3 & 3) << 6) | enc4; | |
6621 output = output + chr(chr1); | |
6622 if (enc3 != 64) { | |
6623 output = output + chr(chr2); | |
6624 } | |
6625 if (enc4 != 64) { | |
6626 output = output + chr(chr3); | |
6627 } | |
6628 chr1 = chr2 = chr3 = ''; | |
6629 enc1 = enc2 = enc3 = enc4 = ''; | |
6630 } while (i < input.length); | |
6631 return output; | |
6632 } | |
6633 } | |
6634 | |
6635 extend(string, true, false, { | |
6636 /*** | |
6637 * @method repeat([num] = 0) | |
6638 * @returns String | |
6639 * @short Returns the string repeated [num] times. | |
6640 * @example | |
6641 * | |
6642 * 'jumpy'.repeat(2) -> 'jumpyjumpy' | |
6643 * 'a'.repeat(5) -> 'aaaaa' | |
6644 * 'a'.repeat(0) -> '' | |
6645 * | |
6646 ***/ | |
6647 'repeat': function(num) { | |
6648 num = checkRepeatRange(num); | |
6649 return repeatString(this, num); | |
6650 } | |
6651 | |
6652 }); | |
6653 | |
6654 extend(string, true, function(reg) { return isRegExp(reg) || arguments.length
> 2; }, { | |
6655 | |
6656 /*** | |
6657 * @method startsWith(<find>, [pos] = 0, [case] = true) | |
6658 * @returns Boolean | |
6659 * @short Returns true if the string starts with <find>. | |
6660 * @extra <find> may be either a string or regex. Search begins at [pos], wh
ich defaults to the entire string. Case sensitive if [case] is true. | |
6661 * @example | |
6662 * | |
6663 * 'hello'.startsWith('hell') -> true | |
6664 * 'hello'.startsWith(/[a-h]/) -> true | |
6665 * 'hello'.startsWith('HELL') -> false | |
6666 * 'hello'.startsWith('ell', 1) -> true | |
6667 * 'hello'.startsWith('HELL', 0, false) -> true | |
6668 * | |
6669 ***/ | |
6670 'startsWith': function(reg) { | |
6671 var args = arguments, pos = args[1], c = args[2], str = this, source; | |
6672 if(pos) str = str.slice(pos); | |
6673 if(isUndefined(c)) c = true; | |
6674 source = isRegExp(reg) ? reg.source.replace('^', '') : escapeRegExp(reg); | |
6675 return regexp('^' + source, c ? '' : 'i').test(str); | |
6676 }, | |
6677 | |
6678 /*** | |
6679 * @method endsWith(<find>, [pos] = length, [case] = true) | |
6680 * @returns Boolean | |
6681 * @short Returns true if the string ends with <find>. | |
6682 * @extra <find> may be either a string or regex. Search ends at [pos], whic
h defaults to the entire string. Case sensitive if [case] is true. | |
6683 * @example | |
6684 * | |
6685 * 'jumpy'.endsWith('py') -> true | |
6686 * 'jumpy'.endsWith(/[q-z]/) -> true | |
6687 * 'jumpy'.endsWith('MPY') -> false | |
6688 * 'jumpy'.endsWith('mp', 4) -> false | |
6689 * 'jumpy'.endsWith('MPY', 5, false) -> true | |
6690 * | |
6691 ***/ | |
6692 'endsWith': function(reg) { | |
6693 var args = arguments, pos = args[1], c = args[2], str = this, source; | |
6694 if(isDefined(pos)) str = str.slice(0, pos); | |
6695 if(isUndefined(c)) c = true; | |
6696 source = isRegExp(reg) ? reg.source.replace('$', '') : escapeRegExp(reg); | |
6697 return regexp(source + '$', c ? '' : 'i').test(str); | |
6698 } | |
6699 | |
6700 }); | |
6701 | |
6702 extend(string, true, true, { | |
6703 | |
6704 /*** | |
6705 * @method escapeRegExp() | |
6706 * @returns String | |
6707 * @short Escapes all RegExp tokens in the string. | |
6708 * @example | |
6709 * | |
6710 * 'really?'.escapeRegExp() -> 'really\?' | |
6711 * 'yes.'.escapeRegExp() -> 'yes\.' | |
6712 * '(not really)'.escapeRegExp() -> '\(not really\)' | |
6713 * | |
6714 ***/ | |
6715 'escapeRegExp': function() { | |
6716 return escapeRegExp(this); | |
6717 }, | |
6718 | |
6719 /*** | |
6720 * @method escapeURL([param] = false) | |
6721 * @returns String | |
6722 * @short Escapes characters in a string to make a valid URL. | |
6723 * @extra If [param] is true, it will also escape valid URL characters for
use as a URL parameter. | |
6724 * @example | |
6725 * | |
6726 * 'http://foo.com/"bar"'.escapeURL() -> 'http://foo.com/%22bar%22' | |
6727 * 'http://foo.com/"bar"'.escapeURL(true) -> 'http%3A%2F%2Ffoo.com%2F%22b
ar%22' | |
6728 * | |
6729 ***/ | |
6730 'escapeURL': function(param) { | |
6731 return param ? encodeURIComponent(this) : encodeURI(this); | |
6732 }, | |
6733 | |
6734 /*** | |
6735 * @method unescapeURL([partial] = false) | |
6736 * @returns String | |
6737 * @short Restores escaped characters in a URL escaped string. | |
6738 * @extra If [partial] is true, it will only unescape non-valid URL charact
ers. [partial] is included here for completeness, but should very rarely be need
ed. | |
6739 * @example | |
6740 * | |
6741 * 'http%3A%2F%2Ffoo.com%2Fthe%20bar'.unescapeURL() -> 'http://foo.co
m/the bar' | |
6742 * 'http%3A%2F%2Ffoo.com%2Fthe%20bar'.unescapeURL(true) -> 'http%3A%2F%2F
foo.com%2Fthe bar' | |
6743 * | |
6744 ***/ | |
6745 'unescapeURL': function(param) { | |
6746 return param ? decodeURI(this) : decodeURIComponent(this); | |
6747 }, | |
6748 | |
6749 /*** | |
6750 * @method escapeHTML() | |
6751 * @returns String | |
6752 * @short Converts HTML characters to their entity equivalents. | |
6753 * @example | |
6754 * | |
6755 * '<p>some text</p>'.escapeHTML() -> '<p>some text</p>' | |
6756 * 'one & two'.escapeHTML() -> 'one & two' | |
6757 * | |
6758 ***/ | |
6759 'escapeHTML': function() { | |
6760 return this.replace(/&/g, '&' ) | |
6761 .replace(/</g, '<' ) | |
6762 .replace(/>/g, '>' ) | |
6763 .replace(/"/g, '"') | |
6764 .replace(/'/g, ''') | |
6765 .replace(/\//g, '/'); | |
6766 }, | |
6767 | |
6768 /*** | |
6769 * @method unescapeHTML([partial] = false) | |
6770 * @returns String | |
6771 * @short Restores escaped HTML characters. | |
6772 * @example | |
6773 * | |
6774 * '<p>some text</p>'.unescapeHTML() -> '<p>some text</p>' | |
6775 * 'one & two'.unescapeHTML() -> 'one & two' | |
6776 * | |
6777 ***/ | |
6778 'unescapeHTML': function() { | |
6779 return this.replace(/</g, '<') | |
6780 .replace(/>/g, '>') | |
6781 .replace(/"/g, '"') | |
6782 .replace(/'/g, "'") | |
6783 .replace(///g, '/') | |
6784 .replace(/&/g, '&'); | |
6785 }, | |
6786 | |
6787 /*** | |
6788 * @method encodeBase64() | |
6789 * @returns String | |
6790 * @short Encodes the string into base64 encoding. | |
6791 * @extra This method wraps the browser native %btoa% when available, and u
ses a custom implementation when not available. It can also handle Unicode strin
g encodings. | |
6792 * @example | |
6793 * | |
6794 * 'gonna get encoded!'.encodeBase64() -> 'Z29ubmEgZ2V0IGVuY29kZWQh' | |
6795 * 'http://twitter.com/'.encodeBase64() -> 'aHR0cDovL3R3aXR0ZXIuY29tLw==' | |
6796 * | |
6797 ***/ | |
6798 'encodeBase64': function() { | |
6799 return btoa(unescape(encodeURIComponent(this))); | |
6800 }, | |
6801 | |
6802 /*** | |
6803 * @method decodeBase64() | |
6804 * @returns String | |
6805 * @short Decodes the string from base64 encoding. | |
6806 * @extra This method wraps the browser native %atob% when available, and u
ses a custom implementation when not available. It can also handle Unicode strin
g encodings. | |
6807 * @example | |
6808 * | |
6809 * 'aHR0cDovL3R3aXR0ZXIuY29tLw=='.decodeBase64() -> 'http://twitter.com/' | |
6810 * 'anVzdCBnb3QgZGVjb2RlZA=='.decodeBase64() -> 'just got decoded!' | |
6811 * | |
6812 ***/ | |
6813 'decodeBase64': function() { | |
6814 return decodeURIComponent(escape(atob(this))); | |
6815 }, | |
6816 | |
6817 /*** | |
6818 * @method each([search] = single character, [fn]) | |
6819 * @returns Array | |
6820 * @short Runs callback [fn] against each occurence of [search]. | |
6821 * @extra Returns an array of matches. [search] may be either a string or re
gex, and defaults to every character in the string. | |
6822 * @example | |
6823 * | |
6824 * 'jumpy'.each() -> ['j','u','m','p','y'] | |
6825 * 'jumpy'.each(/[r-z]/) -> ['u','y'] | |
6826 * 'jumpy'.each(/[r-z]/, function(m) { | |
6827 * // Called twice: "u", "y" | |
6828 * }); | |
6829 * | |
6830 ***/ | |
6831 'each': function(search, fn) { | |
6832 var match, i, len; | |
6833 if(isFunction(search)) { | |
6834 fn = search; | |
6835 search = /[\s\S]/g; | |
6836 } else if(!search) { | |
6837 search = /[\s\S]/g | |
6838 } else if(isString(search)) { | |
6839 search = regexp(escapeRegExp(search), 'gi'); | |
6840 } else if(isRegExp(search)) { | |
6841 search = regexp(search.source, getRegExpFlags(search, 'g')); | |
6842 } | |
6843 match = this.match(search) || []; | |
6844 if(fn) { | |
6845 for(i = 0, len = match.length; i < len; i++) { | |
6846 match[i] = fn.call(this, match[i], i, match) || match[i]; | |
6847 } | |
6848 } | |
6849 return match; | |
6850 }, | |
6851 | |
6852 /*** | |
6853 * @method shift(<n>) | |
6854 * @returns Array | |
6855 * @short Shifts each character in the string <n> places in the character ma
p. | |
6856 * @example | |
6857 * | |
6858 * 'a'.shift(1) -> 'b' | |
6859 * 'ク'.shift(1) -> 'グ' | |
6860 * | |
6861 ***/ | |
6862 'shift': function(n) { | |
6863 var result = ''; | |
6864 n = n || 0; | |
6865 this.codes(function(c) { | |
6866 result += chr(c + n); | |
6867 }); | |
6868 return result; | |
6869 }, | |
6870 | |
6871 /*** | |
6872 * @method codes([fn]) | |
6873 * @returns Array | |
6874 * @short Runs callback [fn] against each character code in the string. Retu
rns an array of character codes. | |
6875 * @example | |
6876 * | |
6877 * 'jumpy'.codes() -> [106,117,109,112,121] | |
6878 * 'jumpy'.codes(function(c) { | |
6879 * // Called 5 times: 106, 117, 109, 112, 121 | |
6880 * }); | |
6881 * | |
6882 ***/ | |
6883 'codes': function(fn) { | |
6884 var codes = [], i, len; | |
6885 for(i = 0, len = this.length; i < len; i++) { | |
6886 var code = this.charCodeAt(i); | |
6887 codes.push(code); | |
6888 if(fn) fn.call(this, code, i); | |
6889 } | |
6890 return codes; | |
6891 }, | |
6892 | |
6893 /*** | |
6894 * @method chars([fn]) | |
6895 * @returns Array | |
6896 * @short Runs callback [fn] against each character in the string. Returns a
n array of characters. | |
6897 * @example | |
6898 * | |
6899 * 'jumpy'.chars() -> ['j','u','m','p','y'] | |
6900 * 'jumpy'.chars(function(c) { | |
6901 * // Called 5 times: "j","u","m","p","y" | |
6902 * }); | |
6903 * | |
6904 ***/ | |
6905 'chars': function(fn) { | |
6906 return this.each(fn); | |
6907 }, | |
6908 | |
6909 /*** | |
6910 * @method words([fn]) | |
6911 * @returns Array | |
6912 * @short Runs callback [fn] against each word in the string. Returns an arr
ay of words. | |
6913 * @extra A "word" here is defined as any sequence of non-whitespace charact
ers. | |
6914 * @example | |
6915 * | |
6916 * 'broken wear'.words() -> ['broken','wear'] | |
6917 * 'broken wear'.words(function(w) { | |
6918 * // Called twice: "broken", "wear" | |
6919 * }); | |
6920 * | |
6921 ***/ | |
6922 'words': function(fn) { | |
6923 return this.trim().each(/\S+/g, fn); | |
6924 }, | |
6925 | |
6926 /*** | |
6927 * @method lines([fn]) | |
6928 * @returns Array | |
6929 * @short Runs callback [fn] against each line in the string. Returns an arr
ay of lines. | |
6930 * @example | |
6931 * | |
6932 * 'broken wear\nand\njumpy jump'.lines() -> ['broken wear','and','jumpy j
ump'] | |
6933 * 'broken wear\nand\njumpy jump'.lines(function(l) { | |
6934 * // Called three times: "broken wear", "and", "jumpy jump" | |
6935 * }); | |
6936 * | |
6937 ***/ | |
6938 'lines': function(fn) { | |
6939 return this.trim().each(/^.*$/gm, fn); | |
6940 }, | |
6941 | |
6942 /*** | |
6943 * @method paragraphs([fn]) | |
6944 * @returns Array | |
6945 * @short Runs callback [fn] against each paragraph in the string. Returns a
n array of paragraphs. | |
6946 * @extra A paragraph here is defined as a block of text bounded by two or m
ore line breaks. | |
6947 * @example | |
6948 * | |
6949 * 'Once upon a time.\n\nIn the land of oz...'.paragraphs() -> ['Once upon
a time.','In the land of oz...'] | |
6950 * 'Once upon a time.\n\nIn the land of oz...'.paragraphs(function(p) { | |
6951 * // Called twice: "Once upon a time.", "In teh land of oz..." | |
6952 * }); | |
6953 * | |
6954 ***/ | |
6955 'paragraphs': function(fn) { | |
6956 var paragraphs = this.trim().split(/[\r\n]{2,}/); | |
6957 paragraphs = paragraphs.map(function(p) { | |
6958 if(fn) var s = fn.call(p); | |
6959 return s ? s : p; | |
6960 }); | |
6961 return paragraphs; | |
6962 }, | |
6963 | |
6964 /*** | |
6965 * @method isBlank() | |
6966 * @returns Boolean | |
6967 * @short Returns true if the string has a length of 0 or contains only whit
espace. | |
6968 * @example | |
6969 * | |
6970 * ''.isBlank() -> true | |
6971 * ' '.isBlank() -> true | |
6972 * 'noway'.isBlank() -> false | |
6973 * | |
6974 ***/ | |
6975 'isBlank': function() { | |
6976 return this.trim().length === 0; | |
6977 }, | |
6978 | |
6979 /*** | |
6980 * @method has(<find>) | |
6981 * @returns Boolean | |
6982 * @short Returns true if the string matches <find>. | |
6983 * @extra <find> may be a string or regex. | |
6984 * @example | |
6985 * | |
6986 * 'jumpy'.has('py') -> true | |
6987 * 'broken'.has(/[a-n]/) -> true | |
6988 * 'broken'.has(/[s-z]/) -> false | |
6989 * | |
6990 ***/ | |
6991 'has': function(find) { | |
6992 return this.search(isRegExp(find) ? find : escapeRegExp(find)) !== -1; | |
6993 }, | |
6994 | |
6995 | |
6996 /*** | |
6997 * @method add(<str>, [index] = length) | |
6998 * @returns String | |
6999 * @short Adds <str> at [index]. Negative values are also allowed. | |
7000 * @extra %insert% is provided as an alias, and is generally more readable w
hen using an index. | |
7001 * @example | |
7002 * | |
7003 * 'schfifty'.add(' five') -> schfifty five | |
7004 * 'dopamine'.insert('e', 3) -> dopeamine | |
7005 * 'spelling eror'.insert('r', -3) -> spelling error | |
7006 * | |
7007 ***/ | |
7008 'add': function(str, index) { | |
7009 index = isUndefined(index) ? this.length : index; | |
7010 return this.slice(0, index) + str + this.slice(index); | |
7011 }, | |
7012 | |
7013 /*** | |
7014 * @method remove(<f>) | |
7015 * @returns String | |
7016 * @short Removes any part of the string that matches <f>. | |
7017 * @extra <f> can be a string or a regex. | |
7018 * @example | |
7019 * | |
7020 * 'schfifty five'.remove('f') -> 'schity ive' | |
7021 * 'schfifty five'.remove(/[a-f]/g) -> 'shity iv' | |
7022 * | |
7023 ***/ | |
7024 'remove': function(f) { | |
7025 return this.replace(f, ''); | |
7026 }, | |
7027 | |
7028 /*** | |
7029 * @method reverse() | |
7030 * @returns String | |
7031 * @short Reverses the string. | |
7032 * @example | |
7033 * | |
7034 * 'jumpy'.reverse() -> 'ypmuj' | |
7035 * 'lucky charms'.reverse() -> 'smrahc ykcul' | |
7036 * | |
7037 ***/ | |
7038 'reverse': function() { | |
7039 return this.split('').reverse().join(''); | |
7040 }, | |
7041 | |
7042 /*** | |
7043 * @method compact() | |
7044 * @returns String | |
7045 * @short Compacts all white space in the string to a single space and trims
the ends. | |
7046 * @example | |
7047 * | |
7048 * 'too \n much \n space'.compact() -> 'too much space' | |
7049 * 'enough \n '.compact() -> 'enought' | |
7050 * | |
7051 ***/ | |
7052 'compact': function() { | |
7053 return this.trim().replace(/([\r\n\s ])+/g, function(match, whitespace){ | |
7054 return whitespace === ' ' ? whitespace : ' '; | |
7055 }); | |
7056 }, | |
7057 | |
7058 /*** | |
7059 * @method at(<index>, [loop] = true) | |
7060 * @returns String or Array | |
7061 * @short Gets the character(s) at a given index. | |
7062 * @extra When [loop] is true, overshooting the end of the string (or the be
ginning) will begin counting from the other end. As an alternate syntax, passing
multiple indexes will get the characters at those indexes. | |
7063 * @example | |
7064 * | |
7065 * 'jumpy'.at(0) -> 'j' | |
7066 * 'jumpy'.at(2) -> 'm' | |
7067 * 'jumpy'.at(5) -> 'j' | |
7068 * 'jumpy'.at(5, false) -> '' | |
7069 * 'jumpy'.at(-1) -> 'y' | |
7070 * 'lucky charms'.at(2,4,6,8) -> ['u','k','y',c'] | |
7071 * | |
7072 ***/ | |
7073 'at': function() { | |
7074 return getEntriesForIndexes(this, arguments, true); | |
7075 }, | |
7076 | |
7077 /*** | |
7078 * @method from([index] = 0) | |
7079 * @returns String | |
7080 * @short Returns a section of the string starting from [index]. | |
7081 * @example | |
7082 * | |
7083 * 'lucky charms'.from() -> 'lucky charms' | |
7084 * 'lucky charms'.from(7) -> 'harms' | |
7085 * | |
7086 ***/ | |
7087 'from': function(from) { | |
7088 return this.slice(numberOrIndex(this, from, true)); | |
7089 }, | |
7090 | |
7091 /*** | |
7092 * @method to([index] = end) | |
7093 * @returns String | |
7094 * @short Returns a section of the string ending at [index]. | |
7095 * @example | |
7096 * | |
7097 * 'lucky charms'.to() -> 'lucky charms' | |
7098 * 'lucky charms'.to(7) -> 'lucky ch' | |
7099 * | |
7100 ***/ | |
7101 'to': function(to) { | |
7102 if(isUndefined(to)) to = this.length; | |
7103 return this.slice(0, numberOrIndex(this, to)); | |
7104 }, | |
7105 | |
7106 /*** | |
7107 * @method dasherize() | |
7108 * @returns String | |
7109 * @short Converts underscores and camel casing to hypens. | |
7110 * @example | |
7111 * | |
7112 * 'a_farewell_to_arms'.dasherize() -> 'a-farewell-to-arms' | |
7113 * 'capsLock'.dasherize() -> 'caps-lock' | |
7114 * | |
7115 ***/ | |
7116 'dasherize': function() { | |
7117 return this.underscore().replace(/_/g, '-'); | |
7118 }, | |
7119 | |
7120 /*** | |
7121 * @method underscore() | |
7122 * @returns String | |
7123 * @short Converts hyphens and camel casing to underscores. | |
7124 * @example | |
7125 * | |
7126 * 'a-farewell-to-arms'.underscore() -> 'a_farewell_to_arms' | |
7127 * 'capsLock'.underscore() -> 'caps_lock' | |
7128 * | |
7129 ***/ | |
7130 'underscore': function() { | |
7131 return this | |
7132 .replace(/[-\s]+/g, '_') | |
7133 .replace(string.Inflector && string.Inflector.acronymRegExp, function(ac
ronym, index) { | |
7134 return (index > 0 ? '_' : '') + acronym.toLowerCase(); | |
7135 }) | |
7136 .replace(/([A-Z\d]+)([A-Z][a-z])/g,'$1_$2') | |
7137 .replace(/([a-z\d])([A-Z])/g,'$1_$2') | |
7138 .toLowerCase(); | |
7139 }, | |
7140 | |
7141 /*** | |
7142 * @method camelize([first] = true) | |
7143 * @returns String | |
7144 * @short Converts underscores and hyphens to camel case. If [first] is true
the first letter will also be capitalized. | |
7145 * @extra If the Inflections package is included acryonyms can also be defin
ed that will be used when camelizing. | |
7146 * @example | |
7147 * | |
7148 * 'caps_lock'.camelize() -> 'CapsLock' | |
7149 * 'moz-border-radius'.camelize() -> 'MozBorderRadius' | |
7150 * 'moz-border-radius'.camelize(false) -> 'mozBorderRadius' | |
7151 * | |
7152 ***/ | |
7153 'camelize': function(first) { | |
7154 return this.underscore().replace(/(^|_)([^_]+)/g, function(match, pre, wor
d, index) { | |
7155 var acronym = getAcronym(word), capitalize = first !== false || index >
0; | |
7156 if(acronym) return capitalize ? acronym : acronym.toLowerCase(); | |
7157 return capitalize ? word.capitalize() : word; | |
7158 }); | |
7159 }, | |
7160 | |
7161 /*** | |
7162 * @method spacify() | |
7163 * @returns String | |
7164 * @short Converts camel case, underscores, and hyphens to a properly spaced
string. | |
7165 * @example | |
7166 * | |
7167 * 'camelCase'.spacify() -> 'camel case' | |
7168 * 'an-ugly-string'.spacify() -> 'an ugly string' | |
7169 * 'oh-no_youDid-not'.spacify().capitalize(true) -> 'something else' | |
7170 * | |
7171 ***/ | |
7172 'spacify': function() { | |
7173 return this.underscore().replace(/_/g, ' '); | |
7174 }, | |
7175 | |
7176 /*** | |
7177 * @method stripTags([tag1], [tag2], ...) | |
7178 * @returns String | |
7179 * @short Strips all HTML tags from the string. | |
7180 * @extra Tags to strip may be enumerated in the parameters, otherwise will
strip all. | |
7181 * @example | |
7182 * | |
7183 * '<p>just <b>some</b> text</p>'.stripTags() -> 'just some text' | |
7184 * '<p>just <b>some</b> text</p>'.stripTags('p') -> 'just <b>some</b> text
' | |
7185 * | |
7186 ***/ | |
7187 'stripTags': function() { | |
7188 var str = this, args = arguments.length > 0 ? arguments : ['']; | |
7189 flattenedArgs(args, function(tag) { | |
7190 str = str.replace(regexp('<\/?' + escapeRegExp(tag) + '[^<>]*>', 'gi'),
''); | |
7191 }); | |
7192 return str; | |
7193 }, | |
7194 | |
7195 /*** | |
7196 * @method removeTags([tag1], [tag2], ...) | |
7197 * @returns String | |
7198 * @short Removes all HTML tags and their contents from the string. | |
7199 * @extra Tags to remove may be enumerated in the parameters, otherwise will
remove all. | |
7200 * @example | |
7201 * | |
7202 * '<p>just <b>some</b> text</p>'.removeTags() -> '' | |
7203 * '<p>just <b>some</b> text</p>'.removeTags('b') -> '<p>just text</p>' | |
7204 * | |
7205 ***/ | |
7206 'removeTags': function() { | |
7207 var str = this, args = arguments.length > 0 ? arguments : ['\\S+']; | |
7208 flattenedArgs(args, function(t) { | |
7209 var reg = regexp('<(' + t + ')[^<>]*(?:\\/>|>.*?<\\/\\1>)', 'gi'); | |
7210 str = str.replace(reg, ''); | |
7211 }); | |
7212 return str; | |
7213 }, | |
7214 | |
7215 /*** | |
7216 * @method truncate(<length>, [from] = 'right', [ellipsis] = '...') | |
7217 * @returns String | |
7218 * @short Truncates a string. | |
7219 * @extra [from] can be %'right'%, %'left'%, or %'middle'%. If the string is
shorter than <length>, [ellipsis] will not be added. | |
7220 * @example | |
7221 * | |
7222 * 'sittin on the dock of the bay'.truncate(18) -> 'just sittin
on the do...' | |
7223 * 'sittin on the dock of the bay'.truncate(18, 'left') -> '...the dock
of the bay' | |
7224 * 'sittin on the dock of the bay'.truncate(18, 'middle') -> 'just sitt...
of the bay' | |
7225 * | |
7226 ***/ | |
7227 'truncate': function(length, from, ellipsis) { | |
7228 return truncateString(this, length, from, ellipsis); | |
7229 }, | |
7230 | |
7231 /*** | |
7232 * @method truncateOnWord(<length>, [from] = 'right', [ellipsis] = '...') | |
7233 * @returns String | |
7234 * @short Truncates a string without splitting up words. | |
7235 * @extra [from] can be %'right'%, %'left'%, or %'middle'%. If the string is
shorter than <length>, [ellipsis] will not be added. | |
7236 * @example | |
7237 * | |
7238 * 'here we go'.truncateOnWord(5) -> 'here...' | |
7239 * 'here we go'.truncateOnWord(5, 'left') -> '...we go' | |
7240 * | |
7241 ***/ | |
7242 'truncateOnWord': function(length, from, ellipsis) { | |
7243 return truncateString(this, length, from, ellipsis, true); | |
7244 }, | |
7245 | |
7246 /*** | |
7247 * @method pad[Side](<num> = null, [padding] = ' ') | |
7248 * @returns String | |
7249 * @short Pads the string out with [padding] to be exactly <num> characters. | |
7250 * | |
7251 * @set | |
7252 * pad | |
7253 * padLeft | |
7254 * padRight | |
7255 * | |
7256 * @example | |
7257 * | |
7258 * 'wasabi'.pad(8) -> ' wasabi ' | |
7259 * 'wasabi'.padLeft(8) -> ' wasabi' | |
7260 * 'wasabi'.padRight(8) -> 'wasabi ' | |
7261 * 'wasabi'.padRight(8, '-') -> 'wasabi--' | |
7262 * | |
7263 ***/ | |
7264 'pad': function(num, padding) { | |
7265 var half, front, back; | |
7266 num = checkRepeatRange(num); | |
7267 half = max(0, num - this.length) / 2; | |
7268 front = floor(half); | |
7269 back = ceil(half); | |
7270 return padString(front, padding) + this + padString(back, padding); | |
7271 }, | |
7272 | |
7273 'padLeft': function(num, padding) { | |
7274 num = checkRepeatRange(num); | |
7275 return padString(max(0, num - this.length), padding) + this; | |
7276 }, | |
7277 | |
7278 'padRight': function(num, padding) { | |
7279 num = checkRepeatRange(num); | |
7280 return this + padString(max(0, num - this.length), padding); | |
7281 }, | |
7282 | |
7283 /*** | |
7284 * @method first([n] = 1) | |
7285 * @returns String | |
7286 * @short Returns the first [n] characters of the string. | |
7287 * @example | |
7288 * | |
7289 * 'lucky charms'.first() -> 'l' | |
7290 * 'lucky charms'.first(3) -> 'luc' | |
7291 * | |
7292 ***/ | |
7293 'first': function(num) { | |
7294 if(isUndefined(num)) num = 1; | |
7295 return this.substr(0, num); | |
7296 }, | |
7297 | |
7298 /*** | |
7299 * @method last([n] = 1) | |
7300 * @returns String | |
7301 * @short Returns the last [n] characters of the string. | |
7302 * @example | |
7303 * | |
7304 * 'lucky charms'.last() -> 's' | |
7305 * 'lucky charms'.last(3) -> 'rms' | |
7306 * | |
7307 ***/ | |
7308 'last': function(num) { | |
7309 if(isUndefined(num)) num = 1; | |
7310 var start = this.length - num < 0 ? 0 : this.length - num; | |
7311 return this.substr(start); | |
7312 }, | |
7313 | |
7314 /*** | |
7315 * @method toNumber([base] = 10) | |
7316 * @returns Number | |
7317 * @short Converts the string into a number. | |
7318 * @extra Any value with a "." fill be converted to a floating point value,
otherwise an integer. | |
7319 * @example | |
7320 * | |
7321 * '153'.toNumber() -> 153 | |
7322 * '12,000'.toNumber() -> 12000 | |
7323 * '10px'.toNumber() -> 10 | |
7324 * 'ff'.toNumber(16) -> 255 | |
7325 * | |
7326 ***/ | |
7327 'toNumber': function(base) { | |
7328 return stringToNumber(this, base); | |
7329 }, | |
7330 | |
7331 /*** | |
7332 * @method capitalize([all] = false) | |
7333 * @returns String | |
7334 * @short Capitalizes the first character in the string and downcases all ot
her letters. | |
7335 * @extra If [all] is true, all words in the string will be capitalized. | |
7336 * @example | |
7337 * | |
7338 * 'hello'.capitalize() -> 'Hello' | |
7339 * 'hello kitty'.capitalize() -> 'Hello kitty' | |
7340 * 'hello kitty'.capitalize(true) -> 'Hello Kitty' | |
7341 * | |
7342 * | |
7343 ***/ | |
7344 'capitalize': function(all) { | |
7345 var lastResponded; | |
7346 return this.toLowerCase().replace(all ? /[^']/g : /^\S/, function(lower) { | |
7347 var upper = lower.toUpperCase(), result; | |
7348 result = lastResponded ? lower : upper; | |
7349 lastResponded = upper !== lower; | |
7350 return result; | |
7351 }); | |
7352 }, | |
7353 | |
7354 /*** | |
7355 * @method assign(<obj1>, <obj2>, ...) | |
7356 * @returns String | |
7357 * @short Assigns variables to tokens in a string, demarcated with `{}`. | |
7358 * @extra If an object is passed, it's properties can be assigned using the
object's keys (i.e. {name}). If a non-object (string, number, etc.) is passed it
can be accessed by the argument number beginning with {1} (as with regex tokens
). Multiple objects can be passed and will be merged together (original objects
are unaffected). | |
7359 * @example | |
7360 * | |
7361 * 'Welcome, Mr. {name}.'.assign({ name: 'Franklin' }) -> 'Welcome, Mr.
Franklin.' | |
7362 * 'You are {1} years old today.'.assign(14) -> 'You are 14 ye
ars old today.' | |
7363 * '{n} and {r}'.assign({ n: 'Cheech' }, { r: 'Chong' }) -> 'Cheech and Ch
ong' | |
7364 * | |
7365 ***/ | |
7366 'assign': function() { | |
7367 var assign = {}; | |
7368 flattenedArgs(arguments, function(a, i) { | |
7369 if(isObjectType(a)) { | |
7370 simpleMerge(assign, a); | |
7371 } else { | |
7372 assign[i + 1] = a; | |
7373 } | |
7374 }); | |
7375 return this.replace(/\{([^{]+?)\}/g, function(m, key) { | |
7376 return hasOwnProperty(assign, key) ? assign[key] : m; | |
7377 }); | |
7378 } | |
7379 | |
7380 }); | |
7381 | |
7382 | |
7383 // Aliases | |
7384 | |
7385 extend(string, true, true, { | |
7386 | |
7387 /*** | |
7388 * @method insert() | |
7389 * @alias add | |
7390 * | |
7391 ***/ | |
7392 'insert': string.prototype.add | |
7393 }); | |
7394 | |
7395 buildBase64('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=
'); | |
7396 | |
7397 | |
7398 /*** | |
7399 * | |
7400 * @package Inflections | |
7401 * @dependency string | |
7402 * @description Pluralization similar to ActiveSupport including uncountable w
ords and acronyms. Humanized and URL-friendly strings. | |
7403 * | |
7404 ***/ | |
7405 | |
7406 /*** | |
7407 * String module | |
7408 * | |
7409 ***/ | |
7410 | |
7411 | |
7412 var plurals = [], | |
7413 singulars = [], | |
7414 uncountables = [], | |
7415 humans = [], | |
7416 acronyms = {}, | |
7417 Downcased, | |
7418 Inflector; | |
7419 | |
7420 function removeFromArray(arr, find) { | |
7421 var index = arr.indexOf(find); | |
7422 if(index > -1) { | |
7423 arr.splice(index, 1); | |
7424 } | |
7425 } | |
7426 | |
7427 function removeFromUncountablesAndAddTo(arr, rule, replacement) { | |
7428 if(isString(rule)) { | |
7429 removeFromArray(uncountables, rule); | |
7430 } | |
7431 removeFromArray(uncountables, replacement); | |
7432 arr.unshift({ rule: rule, replacement: replacement }) | |
7433 } | |
7434 | |
7435 function paramMatchesType(param, type) { | |
7436 return param == type || param == 'all' || !param; | |
7437 } | |
7438 | |
7439 function isUncountable(word) { | |
7440 return uncountables.some(function(uncountable) { | |
7441 return new regexp('\\b' + uncountable + '$', 'i').test(word); | |
7442 }); | |
7443 } | |
7444 | |
7445 function inflect(word, pluralize) { | |
7446 word = isString(word) ? word.toString() : ''; | |
7447 if(word.isBlank() || isUncountable(word)) { | |
7448 return word; | |
7449 } else { | |
7450 return runReplacements(word, pluralize ? plurals : singulars); | |
7451 } | |
7452 } | |
7453 | |
7454 function runReplacements(word, table) { | |
7455 iterateOverObject(table, function(i, inflection) { | |
7456 if(word.match(inflection.rule)) { | |
7457 word = word.replace(inflection.rule, inflection.replacement); | |
7458 return false; | |
7459 } | |
7460 }); | |
7461 return word; | |
7462 } | |
7463 | |
7464 function capitalize(word) { | |
7465 return word.replace(/^\W*[a-z]/, function(w){ | |
7466 return w.toUpperCase(); | |
7467 }); | |
7468 } | |
7469 | |
7470 Inflector = { | |
7471 | |
7472 /* | |
7473 * Specifies a new acronym. An acronym must be specified as it will appear i
n a camelized string. An underscore | |
7474 * string that contains the acronym will retain the acronym when passed to %
camelize%, %humanize%, or %titleize%. | |
7475 * A camelized string that contains the acronym will maintain the acronym wh
en titleized or humanized, and will | |
7476 * convert the acronym into a non-delimited single lowercase word when passe
d to String#underscore. | |
7477 * | |
7478 * Examples: | |
7479 * String.Inflector.acronym('HTML') | |
7480 * 'html'.titleize() -> 'HTML' | |
7481 * 'html'.camelize() -> 'HTML' | |
7482 * 'MyHTML'.underscore() -> 'my_html' | |
7483 * | |
7484 * The acronym, however, must occur as a delimited unit and not be part of a
nother word for conversions to recognize it: | |
7485 * | |
7486 * String.Inflector.acronym('HTTP') | |
7487 * 'my_http_delimited'.camelize() -> 'MyHTTPDelimited' | |
7488 * 'https'.camelize() -> 'Https', not 'HTTPs' | |
7489 * 'HTTPS'.underscore() -> 'http_s', not 'https' | |
7490 * | |
7491 * String.Inflector.acronym('HTTPS') | |
7492 * 'https'.camelize() -> 'HTTPS' | |
7493 * 'HTTPS'.underscore() -> 'https' | |
7494 * | |
7495 * Note: Acronyms that are passed to %pluralize% will no longer be recognize
d, since the acronym will not occur as | |
7496 * a delimited unit in the pluralized result. To work around this, you must
specify the pluralized form as an | |
7497 * acronym as well: | |
7498 * | |
7499 * String.Inflector.acronym('API') | |
7500 * 'api'.pluralize().camelize() -> 'Apis' | |
7501 * | |
7502 * String.Inflector.acronym('APIs') | |
7503 * 'api'.pluralize().camelize() -> 'APIs' | |
7504 * | |
7505 * %acronym% may be used to specify any word that contains an acronym or oth
erwise needs to maintain a non-standard | |
7506 * capitalization. The only restriction is that the word must begin with a c
apital letter. | |
7507 * | |
7508 * Examples: | |
7509 * String.Inflector.acronym('RESTful') | |
7510 * 'RESTful'.underscore() -> 'restful' | |
7511 * 'RESTfulController'.underscore() -> 'restful_controller' | |
7512 * 'RESTfulController'.titleize() -> 'RESTful Controller' | |
7513 * 'restful'.camelize() -> 'RESTful' | |
7514 * 'restful_controller'.camelize() -> 'RESTfulController' | |
7515 * | |
7516 * String.Inflector.acronym('McDonald') | |
7517 * 'McDonald'.underscore() -> 'mcdonald' | |
7518 * 'mcdonald'.camelize() -> 'McDonald' | |
7519 */ | |
7520 'acronym': function(word) { | |
7521 acronyms[word.toLowerCase()] = word; | |
7522 var all = object.keys(acronyms).map(function(key) { | |
7523 return acronyms[key]; | |
7524 }); | |
7525 Inflector.acronymRegExp = regexp(all.join('|'), 'g'); | |
7526 }, | |
7527 | |
7528 /* | |
7529 * Specifies a new pluralization rule and its replacement. The rule can eith
er be a string or a regular expression. | |
7530 * The replacement should always be a string that may include references to
the matched data from the rule. | |
7531 */ | |
7532 'plural': function(rule, replacement) { | |
7533 removeFromUncountablesAndAddTo(plurals, rule, replacement); | |
7534 }, | |
7535 | |
7536 /* | |
7537 * Specifies a new singularization rule and its replacement. The rule can ei
ther be a string or a regular expression. | |
7538 * The replacement should always be a string that may include references to
the matched data from the rule. | |
7539 */ | |
7540 'singular': function(rule, replacement) { | |
7541 removeFromUncountablesAndAddTo(singulars, rule, replacement); | |
7542 }, | |
7543 | |
7544 /* | |
7545 * Specifies a new irregular that applies to both pluralization and singular
ization at the same time. This can only be used | |
7546 * for strings, not regular expressions. You simply pass the irregular in si
ngular and plural form. | |
7547 * | |
7548 * Examples: | |
7549 * String.Inflector.irregular('octopus', 'octopi') | |
7550 * String.Inflector.irregular('person', 'people') | |
7551 */ | |
7552 'irregular': function(singular, plural) { | |
7553 var singularFirst = singular.first(), | |
7554 singularRest = singular.from(1), | |
7555 pluralFirst = plural.first(), | |
7556 pluralRest = plural.from(1), | |
7557 pluralFirstUpper = pluralFirst.toUpperCase(), | |
7558 pluralFirstLower = pluralFirst.toLowerCase(), | |
7559 singularFirstUpper = singularFirst.toUpperCase(), | |
7560 singularFirstLower = singularFirst.toLowerCase(); | |
7561 removeFromArray(uncountables, singular); | |
7562 removeFromArray(uncountables, plural); | |
7563 if(singularFirstUpper == pluralFirstUpper) { | |
7564 Inflector.plural(new regexp('({1}){2}$'.assign(singularFirst, singularRe
st), 'i'), '$1' + pluralRest); | |
7565 Inflector.plural(new regexp('({1}){2}$'.assign(pluralFirst, pluralRest),
'i'), '$1' + pluralRest); | |
7566 Inflector.singular(new regexp('({1}){2}$'.assign(pluralFirst, pluralRest
), 'i'), '$1' + singularRest); | |
7567 } else { | |
7568 Inflector.plural(new regexp('{1}{2}$'.assign(singularFirstUpper, singula
rRest)), pluralFirstUpper + pluralRest); | |
7569 Inflector.plural(new regexp('{1}{2}$'.assign(singularFirstLower, singula
rRest)), pluralFirstLower + pluralRest); | |
7570 Inflector.plural(new regexp('{1}{2}$'.assign(pluralFirstUpper, pluralRes
t)), pluralFirstUpper + pluralRest); | |
7571 Inflector.plural(new regexp('{1}{2}$'.assign(pluralFirstLower, pluralRes
t)), pluralFirstLower + pluralRest); | |
7572 Inflector.singular(new regexp('{1}{2}$'.assign(pluralFirstUpper, pluralR
est)), singularFirstUpper + singularRest); | |
7573 Inflector.singular(new regexp('{1}{2}$'.assign(pluralFirstLower, pluralR
est)), singularFirstLower + singularRest); | |
7574 } | |
7575 }, | |
7576 | |
7577 /* | |
7578 * Add uncountable words that shouldn't be attempted inflected. | |
7579 * | |
7580 * Examples: | |
7581 * String.Inflector.uncountable('money') | |
7582 * String.Inflector.uncountable('money', 'information') | |
7583 * String.Inflector.uncountable(['money', 'information', 'rice']) | |
7584 */ | |
7585 'uncountable': function(first) { | |
7586 var add = array.isArray(first) ? first : multiArgs(arguments); | |
7587 uncountables = uncountables.concat(add); | |
7588 }, | |
7589 | |
7590 /* | |
7591 * Specifies a humanized form of a string by a regular expression rule or by
a string mapping. | |
7592 * When using a regular expression based replacement, the normal humanize fo
rmatting is called after the replacement. | |
7593 * When a string is used, the human form should be specified as desired (exa
mple: 'The name', not 'the_name') | |
7594 * | |
7595 * Examples: | |
7596 * String.Inflector.human(/_cnt$/i, '_count') | |
7597 * String.Inflector.human('legacy_col_person_name', 'Name') | |
7598 */ | |
7599 'human': function(rule, replacement) { | |
7600 humans.unshift({ rule: rule, replacement: replacement }) | |
7601 }, | |
7602 | |
7603 | |
7604 /* | |
7605 * Clears the loaded inflections within a given scope (default is 'all'). | |
7606 * Options are: 'all', 'plurals', 'singulars', 'uncountables', 'humans'. | |
7607 * | |
7608 * Examples: | |
7609 * String.Inflector.clear('all') | |
7610 * String.Inflector.clear('plurals') | |
7611 */ | |
7612 'clear': function(type) { | |
7613 if(paramMatchesType(type, 'singulars')) singulars = []; | |
7614 if(paramMatchesType(type, 'plurals')) plurals = []; | |
7615 if(paramMatchesType(type, 'uncountables')) uncountables = []; | |
7616 if(paramMatchesType(type, 'humans')) humans = []; | |
7617 if(paramMatchesType(type, 'acronyms')) acronyms = {}; | |
7618 } | |
7619 | |
7620 }; | |
7621 | |
7622 Downcased = [ | |
7623 'and', 'or', 'nor', 'a', 'an', 'the', 'so', 'but', 'to', 'of', 'at', | |
7624 'by', 'from', 'into', 'on', 'onto', 'off', 'out', 'in', 'over', | |
7625 'with', 'for' | |
7626 ]; | |
7627 | |
7628 Inflector.plural(/$/, 's'); | |
7629 Inflector.plural(/s$/gi, 's'); | |
7630 Inflector.plural(/(ax|test)is$/gi, '$1es'); | |
7631 Inflector.plural(/(octop|vir|fung|foc|radi|alumn)(i|us)$/gi, '$1i'); | |
7632 Inflector.plural(/(census|alias|status)$/gi, '$1es'); | |
7633 Inflector.plural(/(bu)s$/gi, '$1ses'); | |
7634 Inflector.plural(/(buffal|tomat)o$/gi, '$1oes'); | |
7635 Inflector.plural(/([ti])um$/gi, '$1a'); | |
7636 Inflector.plural(/([ti])a$/gi, '$1a'); | |
7637 Inflector.plural(/sis$/gi, 'ses'); | |
7638 Inflector.plural(/f+e?$/gi, 'ves'); | |
7639 Inflector.plural(/(cuff|roof)$/gi, '$1s'); | |
7640 Inflector.plural(/([ht]ive)$/gi, '$1s'); | |
7641 Inflector.plural(/([^aeiouy]o)$/gi, '$1es'); | |
7642 Inflector.plural(/([^aeiouy]|qu)y$/gi, '$1ies'); | |
7643 Inflector.plural(/(x|ch|ss|sh)$/gi, '$1es'); | |
7644 Inflector.plural(/(matr|vert|ind)(?:ix|ex)$/gi, '$1ices'); | |
7645 Inflector.plural(/([ml])ouse$/gi, '$1ice'); | |
7646 Inflector.plural(/([ml])ice$/gi, '$1ice'); | |
7647 Inflector.plural(/^(ox)$/gi, '$1en'); | |
7648 Inflector.plural(/^(oxen)$/gi, '$1'); | |
7649 Inflector.plural(/(quiz)$/gi, '$1zes'); | |
7650 Inflector.plural(/(phot|cant|hom|zer|pian|portic|pr|quart|kimon)o$/gi, '$1os')
; | |
7651 Inflector.plural(/(craft)$/gi, '$1'); | |
7652 Inflector.plural(/([ft])[eo]{2}(th?)$/gi, '$1ee$2'); | |
7653 | |
7654 Inflector.singular(/s$/gi, ''); | |
7655 Inflector.singular(/([pst][aiu]s)$/gi, '$1'); | |
7656 Inflector.singular(/([aeiouy])ss$/gi, '$1ss'); | |
7657 Inflector.singular(/(n)ews$/gi, '$1ews'); | |
7658 Inflector.singular(/([ti])a$/gi, '$1um'); | |
7659 Inflector.singular(/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)s
es$/gi, '$1$2sis'); | |
7660 Inflector.singular(/(^analy)ses$/gi, '$1sis'); | |
7661 Inflector.singular(/(i)(f|ves)$/i, '$1fe'); | |
7662 Inflector.singular(/([aeolr]f?)(f|ves)$/i, '$1f'); | |
7663 Inflector.singular(/([ht]ive)s$/gi, '$1'); | |
7664 Inflector.singular(/([^aeiouy]|qu)ies$/gi, '$1y'); | |
7665 Inflector.singular(/(s)eries$/gi, '$1eries'); | |
7666 Inflector.singular(/(m)ovies$/gi, '$1ovie'); | |
7667 Inflector.singular(/(x|ch|ss|sh)es$/gi, '$1'); | |
7668 Inflector.singular(/([ml])(ous|ic)e$/gi, '$1ouse'); | |
7669 Inflector.singular(/(bus)(es)?$/gi, '$1'); | |
7670 Inflector.singular(/(o)es$/gi, '$1'); | |
7671 Inflector.singular(/(shoe)s?$/gi, '$1'); | |
7672 Inflector.singular(/(cris|ax|test)[ie]s$/gi, '$1is'); | |
7673 Inflector.singular(/(octop|vir|fung|foc|radi|alumn)(i|us)$/gi, '$1us'); | |
7674 Inflector.singular(/(census|alias|status)(es)?$/gi, '$1'); | |
7675 Inflector.singular(/^(ox)(en)?/gi, '$1'); | |
7676 Inflector.singular(/(vert|ind)(ex|ices)$/gi, '$1ex'); | |
7677 Inflector.singular(/(matr)(ix|ices)$/gi, '$1ix'); | |
7678 Inflector.singular(/(quiz)(zes)?$/gi, '$1'); | |
7679 Inflector.singular(/(database)s?$/gi, '$1'); | |
7680 Inflector.singular(/ee(th?)$/gi, 'oo$1'); | |
7681 | |
7682 Inflector.irregular('person', 'people'); | |
7683 Inflector.irregular('man', 'men'); | |
7684 Inflector.irregular('child', 'children'); | |
7685 Inflector.irregular('sex', 'sexes'); | |
7686 Inflector.irregular('move', 'moves'); | |
7687 Inflector.irregular('save', 'saves'); | |
7688 Inflector.irregular('cow', 'kine'); | |
7689 Inflector.irregular('goose', 'geese'); | |
7690 Inflector.irregular('zombie', 'zombies'); | |
7691 | |
7692 Inflector.uncountable('equipment,information,rice,money,species,series,fish,sh
eep,jeans'.split(',')); | |
7693 | |
7694 | |
7695 extend(string, true, true, { | |
7696 | |
7697 /*** | |
7698 * @method pluralize() | |
7699 * @returns String | |
7700 * @short Returns the plural form of the word in the string. | |
7701 * @example | |
7702 * | |
7703 * 'post'.pluralize() -> 'posts' | |
7704 * 'octopus'.pluralize() -> 'octopi' | |
7705 * 'sheep'.pluralize() -> 'sheep' | |
7706 * 'words'.pluralize() -> 'words' | |
7707 * 'CamelOctopus'.pluralize() -> 'CamelOctopi' | |
7708 * | |
7709 ***/ | |
7710 'pluralize': function() { | |
7711 return inflect(this, true); | |
7712 }, | |
7713 | |
7714 /*** | |
7715 * @method singularize() | |
7716 * @returns String | |
7717 * @short The reverse of String#pluralize. Returns the singular form of a wo
rd in a string. | |
7718 * @example | |
7719 * | |
7720 * 'posts'.singularize() -> 'post' | |
7721 * 'octopi'.singularize() -> 'octopus' | |
7722 * 'sheep'.singularize() -> 'sheep' | |
7723 * 'word'.singularize() -> 'word' | |
7724 * 'CamelOctopi'.singularize() -> 'CamelOctopus' | |
7725 * | |
7726 ***/ | |
7727 'singularize': function() { | |
7728 return inflect(this, false); | |
7729 }, | |
7730 | |
7731 /*** | |
7732 * @method humanize() | |
7733 * @returns String | |
7734 * @short Creates a human readable string. | |
7735 * @extra Capitalizes the first word and turns underscores into spaces and s
trips a trailing '_id', if any. Like String#titleize, this is meant for creating
pretty output. | |
7736 * @example | |
7737 * | |
7738 * 'employee_salary'.humanize() -> 'Employee salary' | |
7739 * 'author_id'.humanize() -> 'Author' | |
7740 * | |
7741 ***/ | |
7742 'humanize': function() { | |
7743 var str = runReplacements(this, humans), acronym; | |
7744 str = str.replace(/_id$/g, ''); | |
7745 str = str.replace(/(_)?([a-z\d]*)/gi, function(match, _, word){ | |
7746 acronym = hasOwnProperty(acronyms, word) ? acronyms[word] : null; | |
7747 return (_ ? ' ' : '') + (acronym || word.toLowerCase()); | |
7748 }); | |
7749 return capitalize(str); | |
7750 }, | |
7751 | |
7752 /*** | |
7753 * @method titleize() | |
7754 * @returns String | |
7755 * @short Creates a title version of the string. | |
7756 * @extra Capitalizes all the words and replaces some characters in the stri
ng to create a nicer looking title. String#titleize is meant for creating pretty
output. | |
7757 * @example | |
7758 * | |
7759 * 'man from the boondocks'.titleize() -> 'Man from the Boondocks' | |
7760 * 'x-men: the last stand'.titleize() -> 'X Men: The Last Stand' | |
7761 * 'TheManWithoutAPast'.titleize() -> 'The Man Without a Past' | |
7762 * 'raiders_of_the_lost_ark'.titleize() -> 'Raiders of the Lost Ark' | |
7763 * | |
7764 ***/ | |
7765 'titleize': function() { | |
7766 var fullStopPunctuation = /[.:;!]$/, hasPunctuation, lastHadPunctuation, i
sFirstOrLast; | |
7767 return this.spacify().humanize().words(function(word, index, words) { | |
7768 hasPunctuation = fullStopPunctuation.test(word); | |
7769 isFirstOrLast = index == 0 || index == words.length - 1 || hasPunctuatio
n || lastHadPunctuation; | |
7770 lastHadPunctuation = hasPunctuation; | |
7771 if(isFirstOrLast || Downcased.indexOf(word) === -1) { | |
7772 return capitalize(word); | |
7773 } else { | |
7774 return word; | |
7775 } | |
7776 }).join(' '); | |
7777 }, | |
7778 | |
7779 /*** | |
7780 * @method parameterize() | |
7781 * @returns String | |
7782 * @short Replaces special characters in a string so that it may be used as
part of a pretty URL. | |
7783 * @example | |
7784 * | |
7785 * 'hell, no!'.parameterize() -> 'hell-no' | |
7786 * | |
7787 ***/ | |
7788 'parameterize': function(separator) { | |
7789 var str = this; | |
7790 if(separator === undefined) separator = '-'; | |
7791 if(str.normalize) { | |
7792 str = str.normalize(); | |
7793 } | |
7794 str = str.replace(/[^a-z0-9\-_]+/gi, separator) | |
7795 if(separator) { | |
7796 str = str.replace(new regexp('^{sep}+|{sep}+$|({sep}){sep}+'.assign({ 's
ep': escapeRegExp(separator) }), 'g'), '$1'); | |
7797 } | |
7798 return encodeURI(str.toLowerCase()); | |
7799 } | |
7800 | |
7801 }); | |
7802 | |
7803 string.Inflector = Inflector; | |
7804 string.Inflector.acronyms = acronyms; | |
7805 | |
7806 | |
7807 /*** | |
7808 * | |
7809 * @package Language | |
7810 * @dependency string | |
7811 * @description Detecting language by character block. Full-width <-> half-wid
th character conversion. Hiragana and Katakana conversions. | |
7812 * | |
7813 ***/ | |
7814 | |
7815 /*** | |
7816 * String module | |
7817 * | |
7818 ***/ | |
7819 | |
7820 | |
7821 /*** | |
7822 * @method has[Script]() | |
7823 * @returns Boolean | |
7824 * @short Returns true if the string contains any characters in that script. | |
7825 * | |
7826 * @set | |
7827 * hasArabic | |
7828 * hasCyrillic | |
7829 * hasGreek | |
7830 * hasHangul | |
7831 * hasHan | |
7832 * hasKanji | |
7833 * hasHebrew | |
7834 * hasHiragana | |
7835 * hasKana | |
7836 * hasKatakana | |
7837 * hasLatin | |
7838 * hasThai | |
7839 * hasDevanagari | |
7840 * | |
7841 * @example | |
7842 * | |
7843 * 'أتكلم'.hasArabic() -> true | |
7844 * 'визит'.hasCyrillic() -> true | |
7845 * '잘 먹겠습니다!'.hasHangul() -> true | |
7846 * 'ミックスです'.hasKatakana() -> true | |
7847 * "l'année".hasLatin() -> true | |
7848 * | |
7849 *** | |
7850 * @method is[Script]() | |
7851 * @returns Boolean | |
7852 * @short Returns true if the string contains only characters in that script.
Whitespace is ignored. | |
7853 * | |
7854 * @set | |
7855 * isArabic | |
7856 * isCyrillic | |
7857 * isGreek | |
7858 * isHangul | |
7859 * isHan | |
7860 * isKanji | |
7861 * isHebrew | |
7862 * isHiragana | |
7863 * isKana | |
7864 * isKatakana | |
7865 * isKatakana | |
7866 * isThai | |
7867 * isDevanagari | |
7868 * | |
7869 * @example | |
7870 * | |
7871 * 'أتكلم'.isArabic() -> true | |
7872 * 'визит'.isCyrillic() -> true | |
7873 * '잘 먹겠습니다!'.isHangul() -> true | |
7874 * 'ミックスです'.isKatakana() -> false | |
7875 * "l'année".isLatin() -> true | |
7876 * | |
7877 ***/ | |
7878 var unicodeScripts = [ | |
7879 { names: ['Arabic'], source: '\u0600-\u06FF' }, | |
7880 { names: ['Cyrillic'], source: '\u0400-\u04FF' }, | |
7881 { names: ['Devanagari'], source: '\u0900-\u097F' }, | |
7882 { names: ['Greek'], source: '\u0370-\u03FF' }, | |
7883 { names: ['Hangul'], source: '\uAC00-\uD7AF\u1100-\u11FF' }, | |
7884 { names: ['Han','Kanji'], source: '\u4E00-\u9FFF\uF900-\uFAFF' }, | |
7885 { names: ['Hebrew'], source: '\u0590-\u05FF' }, | |
7886 { names: ['Hiragana'], source: '\u3040-\u309F\u30FB-\u30FC' }, | |
7887 { names: ['Kana'], source: '\u3040-\u30FF\uFF61-\uFF9F' }, | |
7888 { names: ['Katakana'], source: '\u30A0-\u30FF\uFF61-\uFF9F' }, | |
7889 { names: ['Latin'], source: '\u0001-\u007F\u0080-\u00FF\u0100-\u017F\u
0180-\u024F' }, | |
7890 { names: ['Thai'], source: '\u0E00-\u0E7F' } | |
7891 ]; | |
7892 | |
7893 function buildUnicodeScripts() { | |
7894 unicodeScripts.forEach(function(s) { | |
7895 var is = regexp('^['+s.source+'\\s]+$'); | |
7896 var has = regexp('['+s.source+']'); | |
7897 s.names.forEach(function(name) { | |
7898 defineProperty(string.prototype, 'is' + name, function() { return is.tes
t(this.trim()); }); | |
7899 defineProperty(string.prototype, 'has' + name, function() { return has.t
est(this); }); | |
7900 }); | |
7901 }); | |
7902 } | |
7903 | |
7904 // Support for converting character widths and katakana to hiragana. | |
7905 | |
7906 var HALF_WIDTH_TO_FULL_WIDTH_TRAVERSAL = 65248; | |
7907 | |
7908 var widthConversionRanges = [ | |
7909 { type: 'a', start: 65, end: 90 }, | |
7910 { type: 'a', start: 97, end: 122 }, | |
7911 { type: 'n', start: 48, end: 57 }, | |
7912 { type: 'p', start: 33, end: 47 }, | |
7913 { type: 'p', start: 58, end: 64 }, | |
7914 { type: 'p', start: 91, end: 96 }, | |
7915 { type: 'p', start: 123, end: 126 } | |
7916 ]; | |
7917 | |
7918 var WidthConversionTable; | |
7919 var allHankaku = /[\u0020-\u00A5]|[\uFF61-\uFF9F][゙゚]?/g; | |
7920 var allZenkaku = /[\u3000-\u301C]|[\u301A-\u30FC]|[\uFF01-\uFF60]|[\uFFE0-\u
FFE6]/g; | |
7921 var hankakuPunctuation = '。、「」¥¢£'; | |
7922 var zenkakuPunctuation = '。、「」¥¢£'; | |
7923 var voicedKatakana = /[カキクケコサシスセソタチツテトハヒフヘホ]/; | |
7924 var semiVoicedKatakana = /[ハヒフヘホヲ]/; | |
7925 var hankakuKatakana = 'アイウエオァィゥェォカキクケコサシスセソタチツッテトナニヌネノハヒフヘホマミムメモヤャユュヨョラリルレ
ロワヲンー・'; | |
7926 var zenkakuKatakana = 'アイウエオァィゥェォカキクケコサシスセソタチツッテトナニヌネノハヒフヘホマミムメモヤャユュヨョラリルレ
ロワヲンー・'; | |
7927 | |
7928 function convertCharacterWidth(str, args, reg, type) { | |
7929 if(!WidthConversionTable) { | |
7930 buildWidthConversionTables(); | |
7931 } | |
7932 var mode = multiArgs(args).join(''), table = WidthConversionTable[type]; | |
7933 mode = mode.replace(/all/, '').replace(/(\w)lphabet|umbers?|atakana|paces?|u
nctuation/g, '$1'); | |
7934 return str.replace(reg, function(c) { | |
7935 if(table[c] && (!mode || mode.has(table[c].type))) { | |
7936 return table[c].to; | |
7937 } else { | |
7938 return c; | |
7939 } | |
7940 }); | |
7941 } | |
7942 | |
7943 function buildWidthConversionTables() { | |
7944 var hankaku; | |
7945 WidthConversionTable = { | |
7946 'zenkaku': {}, | |
7947 'hankaku': {} | |
7948 }; | |
7949 widthConversionRanges.forEach(function(r) { | |
7950 simpleRepeat(r.end - r.start + 1, function(n) { | |
7951 n += r.start; | |
7952 setWidthConversion(r.type, chr(n), chr(n + HALF_WIDTH_TO_FULL_WIDTH_TRAV
ERSAL)); | |
7953 }); | |
7954 }); | |
7955 zenkakuKatakana.each(function(c, i) { | |
7956 hankaku = hankakuKatakana.charAt(i); | |
7957 setWidthConversion('k', hankaku, c); | |
7958 if(c.match(voicedKatakana)) { | |
7959 setWidthConversion('k', hankaku + '゙', c.shift(1)); | |
7960 } | |
7961 if(c.match(semiVoicedKatakana)) { | |
7962 setWidthConversion('k', hankaku + '゚', c.shift(2)); | |
7963 } | |
7964 }); | |
7965 zenkakuPunctuation.each(function(c, i) { | |
7966 setWidthConversion('p', hankakuPunctuation.charAt(i), c); | |
7967 }); | |
7968 setWidthConversion('k', 'ヴ', 'ヴ'); | |
7969 setWidthConversion('k', 'ヺ', 'ヺ'); | |
7970 setWidthConversion('s', ' ', ' '); | |
7971 } | |
7972 | |
7973 function setWidthConversion(type, half, full) { | |
7974 WidthConversionTable['zenkaku'][half] = { type: type, to: full }; | |
7975 WidthConversionTable['hankaku'][full] = { type: type, to: half }; | |
7976 } | |
7977 | |
7978 | |
7979 extend(string, true, true, { | |
7980 | |
7981 /*** | |
7982 * @method hankaku([mode] = 'all') | |
7983 * @returns String | |
7984 * @short Converts full-width characters (zenkaku) to half-width (hankaku). | |
7985 * @extra [mode] accepts any combination of "a" (alphabet), "n" (numbers), "
k" (katakana), "s" (spaces), "p" (punctuation), or "all". | |
7986 * @example | |
7987 * | |
7988 * 'タロウ YAMADAです!'.hankaku() -> 'タロウ YAMADAです!' | |
7989 * 'タロウ YAMADAです!'.hankaku('a') -> 'タロウ YAMADAです!' | |
7990 * 'タロウ YAMADAです!'.hankaku('alphabet') -> 'タロウ YAMADAです!' | |
7991 * 'タロウです! 25歳です!'.hankaku('katakana', 'numbers') -> 'タロウです! 25歳です!' | |
7992 * 'タロウです! 25歳です!'.hankaku('k', 'n') -> 'タロウです! 25歳です!' | |
7993 * 'タロウです! 25歳です!'.hankaku('kn') -> 'タロウです! 25歳です!' | |
7994 * 'タロウです! 25歳です!'.hankaku('sp') -> 'タロウです! 25歳です!' | |
7995 * | |
7996 ***/ | |
7997 'hankaku': function() { | |
7998 return convertCharacterWidth(this, arguments, allZenkaku, 'hankaku'); | |
7999 }, | |
8000 | |
8001 /*** | |
8002 * @method zenkaku([mode] = 'all') | |
8003 * @returns String | |
8004 * @short Converts half-width characters (hankaku) to full-width (zenkaku). | |
8005 * @extra [mode] accepts any combination of "a" (alphabet), "n" (numbers), "
k" (katakana), "s" (spaces), "p" (punctuation), or "all". | |
8006 * @example | |
8007 * | |
8008 * 'タロウ YAMADAです!'.zenkaku() -> 'タロウ YAMADAです!' | |
8009 * 'タロウ YAMADAです!'.zenkaku('a') -> 'タロウ YAMADAです!' | |
8010 * 'タロウ YAMADAです!'.zenkaku('alphabet') -> 'タロウ YAMADAです!' | |
8011 * 'タロウです! 25歳です!'.zenkaku('katakana', 'numbers') -> 'タロウです! 25歳です!' | |
8012 * 'タロウです! 25歳です!'.zenkaku('k', 'n') -> 'タロウです! 25歳です!' | |
8013 * 'タロウです! 25歳です!'.zenkaku('kn') -> 'タロウです! 25歳です!' | |
8014 * 'タロウです! 25歳です!'.zenkaku('sp') -> 'タロウです! 25歳です!' | |
8015 * | |
8016 ***/ | |
8017 'zenkaku': function() { | |
8018 return convertCharacterWidth(this, arguments, allHankaku, 'zenkaku'); | |
8019 }, | |
8020 | |
8021 /*** | |
8022 * @method hiragana([all] = true) | |
8023 * @returns String | |
8024 * @short Converts katakana into hiragana. | |
8025 * @extra If [all] is false, only full-width katakana will be converted. | |
8026 * @example | |
8027 * | |
8028 * 'カタカナ'.hiragana() -> 'かたかな' | |
8029 * 'コンニチハ'.hiragana() -> 'こんにちは' | |
8030 * 'カタカナ'.hiragana() -> 'かたかな' | |
8031 * 'カタカナ'.hiragana(false) -> 'カタカナ' | |
8032 * | |
8033 ***/ | |
8034 'hiragana': function(all) { | |
8035 var str = this; | |
8036 if(all !== false) { | |
8037 str = str.zenkaku('k'); | |
8038 } | |
8039 return str.replace(/[\u30A1-\u30F6]/g, function(c) { | |
8040 return c.shift(-96); | |
8041 }); | |
8042 }, | |
8043 | |
8044 /*** | |
8045 * @method katakana() | |
8046 * @returns String | |
8047 * @short Converts hiragana into katakana. | |
8048 * @example | |
8049 * | |
8050 * 'かたかな'.katakana() -> 'カタカナ' | |
8051 * 'こんにちは'.katakana() -> 'コンニチハ' | |
8052 * | |
8053 ***/ | |
8054 'katakana': function() { | |
8055 return this.replace(/[\u3041-\u3096]/g, function(c) { | |
8056 return c.shift(96); | |
8057 }); | |
8058 } | |
8059 | |
8060 | |
8061 }); | |
8062 | |
8063 buildUnicodeScripts(); | |
8064 | |
8065 /* | |
8066 * | |
8067 * Date.addLocale(<code>) adds this locale to Sugar. | |
8068 * To set the locale globally, simply call: | |
8069 * | |
8070 * Date.setLocale('da'); | |
8071 * | |
8072 * var locale = Date.getLocale(<code>) will return this object, which | |
8073 * can be tweaked to change the behavior of parsing/formatting in the locales. | |
8074 * | |
8075 * locale.addFormat adds a date format (see this file for examples). | |
8076 * Special tokens in the date format will be parsed out into regex tokens: | |
8077 * | |
8078 * {0} is a reference to an entry in locale.tokens. Output: (?:the)? | |
8079 * {unit} is a reference to all units. Output: (day|week|month|...) | |
8080 * {unit3} is a reference to a specific unit. Output: (hour) | |
8081 * {unit3-5} is a reference to a subset of the units array. Output: (hour|day|we
ek) | |
8082 * {unit?} "?" makes that token optional. Output: (day|week|month)? | |
8083 * | |
8084 * {day} Any reference to tokens in the modifiers array will include all with th
e same name. Output: (yesterday|today|tomorrow) | |
8085 * | |
8086 * All spaces are optional and will be converted to "\s*" | |
8087 * | |
8088 * Locale arrays months, weekdays, units, numbers, as well as the "src" field fo
r | |
8089 * all entries in the modifiers array follow a special format indicated by a col
on: | |
8090 * | |
8091 * minute:|s = minute|minutes | |
8092 * thicke:n|r = thicken|thicker | |
8093 * | |
8094 * Additionally in the months, weekdays, units, and numbers array these will be
added at indexes that are multiples | |
8095 * of the relevant number for retrieval. For example having "sunday:|s" in the u
nits array will result in: | |
8096 * | |
8097 * units: ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'sa
turday', 'sundays'] | |
8098 * | |
8099 * When matched, the index will be found using: | |
8100 * | |
8101 * units.indexOf(match) % 7; | |
8102 * | |
8103 * Resulting in the correct index with any number of alternates for that entry. | |
8104 * | |
8105 */ | |
8106 | |
8107 Date.addLocale('da', { | |
8108 'plural': true, | |
8109 'months': 'januar,februar,marts,april,maj,juni,juli,august,september,oktober,n
ovember,december', | |
8110 'weekdays': 'søndag|sondag,mandag,tirsdag,onsdag,torsdag,fredag,lørdag|lordag'
, | |
8111 'units': 'millisekund:|er,sekund:|er,minut:|ter,tim:e|er,dag:|e,ug:e|er|en,mån
ed:|er|en+maaned:|er|en,år:||et+aar:||et', | |
8112 'numbers': 'en|et,to,tre,fire,fem,seks,syv,otte,ni,ti', | |
8113 'tokens': 'den,for', | |
8114 'articles': 'den', | |
8115 'short':'d. {d}. {month} {yyyy}', | |
8116 'long': 'den {d}. {month} {yyyy} {H}:{mm}', | |
8117 'full': '{Weekday} den {d}. {month} {yyyy} {H}:{mm}:{ss}', | |
8118 'past': '{num} {unit} {sign}', | |
8119 'future': '{sign} {num} {unit}', | |
8120 'duration': '{num} {unit}', | |
8121 'ampm': 'am,pm', | |
8122 'modifiers': [ | |
8123 { 'name': 'day', 'src': 'forgårs|i forgårs|forgaars|i forgaars', 'value': -2
}, | |
8124 { 'name': 'day', 'src': 'i går|igår|i gaar|igaar', 'value': -1 }, | |
8125 { 'name': 'day', 'src': 'i dag|idag', 'value': 0 }, | |
8126 { 'name': 'day', 'src': 'i morgen|imorgen', 'value': 1 }, | |
8127 { 'name': 'day', 'src': 'over morgon|overmorgen|i over morgen|i overmorgen|i
overmorgen', 'value': 2 }, | |
8128 { 'name': 'sign', 'src': 'siden', 'value': -1 }, | |
8129 { 'name': 'sign', 'src': 'om', 'value': 1 }, | |
8130 { 'name': 'shift', 'src': 'i sidste|sidste', 'value': -1 }, | |
8131 { 'name': 'shift', 'src': 'denne', 'value': 0 }, | |
8132 { 'name': 'shift', 'src': 'næste|naeste', 'value': 1 } | |
8133 ], | |
8134 'dateParse': [ | |
8135 '{num} {unit} {sign}', | |
8136 '{sign} {num} {unit}', | |
8137 '{1?} {num} {unit} {sign}', | |
8138 '{shift} {unit=5-7}' | |
8139 ], | |
8140 'timeParse': [ | |
8141 '{0?} {weekday?} {date?} {month} {year}', | |
8142 '{date} {month}', | |
8143 '{shift} {weekday}' | |
8144 ] | |
8145 }); | |
8146 | |
8147 /* | |
8148 * | |
8149 * Date.addLocale(<code>) adds this locale to Sugar. | |
8150 * To set the locale globally, simply call: | |
8151 * | |
8152 * Date.setLocale('de'); | |
8153 * | |
8154 * var locale = Date.getLocale(<code>) will return this object, which | |
8155 * can be tweaked to change the behavior of parsing/formatting in the locales. | |
8156 * | |
8157 * locale.addFormat adds a date format (see this file for examples). | |
8158 * Special tokens in the date format will be parsed out into regex tokens: | |
8159 * | |
8160 * {0} is a reference to an entry in locale.tokens. Output: (?:the)? | |
8161 * {unit} is a reference to all units. Output: (day|week|month|...) | |
8162 * {unit3} is a reference to a specific unit. Output: (hour) | |
8163 * {unit3-5} is a reference to a subset of the units array. Output: (hour|day|we
ek) | |
8164 * {unit?} "?" makes that token optional. Output: (day|week|month)? | |
8165 * | |
8166 * {day} Any reference to tokens in the modifiers array will include all with th
e same name. Output: (yesterday|today|tomorrow) | |
8167 * | |
8168 * All spaces are optional and will be converted to "\s*" | |
8169 * | |
8170 * Locale arrays months, weekdays, units, numbers, as well as the "src" field fo
r | |
8171 * all entries in the modifiers array follow a special format indicated by a col
on: | |
8172 * | |
8173 * minute:|s = minute|minutes | |
8174 * thicke:n|r = thicken|thicker | |
8175 * | |
8176 * Additionally in the months, weekdays, units, and numbers array these will be
added at indexes that are multiples | |
8177 * of the relevant number for retrieval. For example having "sunday:|s" in the u
nits array will result in: | |
8178 * | |
8179 * units: ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'sa
turday', 'sundays'] | |
8180 * | |
8181 * When matched, the index will be found using: | |
8182 * | |
8183 * units.indexOf(match) % 7; | |
8184 * | |
8185 * Resulting in the correct index with any number of alternates for that entry. | |
8186 * | |
8187 */ | |
8188 | |
8189 Date.addLocale('de', { | |
8190 'plural': true, | |
8191 'capitalizeUnit': true, | |
8192 'months': 'Januar,Februar,März|Marz,April,Mai,Juni,Juli,August,September,Oktob
er,November,Dezember', | |
8193 'weekdays': 'Sonntag,Montag,Dienstag,Mittwoch,Donnerstag,Freitag,Samstag', | |
8194 'units': 'Millisekunde:|n,Sekunde:|n,Minute:|n,Stunde:|n,Tag:|en,Woche:|n,Mona
t:|en,Jahr:|en', | |
8195 'numbers': 'ein:|e|er|en|em,zwei,drei,vier,fuenf,sechs,sieben,acht,neun,zehn', | |
8196 'tokens': 'der', | |
8197 'short':'{d}. {Month} {yyyy}', | |
8198 'long': '{d}. {Month} {yyyy} {H}:{mm}', | |
8199 'full': '{Weekday} {d}. {Month} {yyyy} {H}:{mm}:{ss}', | |
8200 'past': '{sign} {num} {unit}', | |
8201 'future': '{sign} {num} {unit}', | |
8202 'duration': '{num} {unit}', | |
8203 'timeMarker': 'um', | |
8204 'ampm': 'am,pm', | |
8205 'modifiers': [ | |
8206 { 'name': 'day', 'src': 'vorgestern', 'value': -2 }, | |
8207 { 'name': 'day', 'src': 'gestern', 'value': -1 }, | |
8208 { 'name': 'day', 'src': 'heute', 'value': 0 }, | |
8209 { 'name': 'day', 'src': 'morgen', 'value': 1 }, | |
8210 { 'name': 'day', 'src': 'übermorgen|ubermorgen|uebermorgen', 'value': 2 }, | |
8211 { 'name': 'sign', 'src': 'vor:|her', 'value': -1 }, | |
8212 { 'name': 'sign', 'src': 'in', 'value': 1 }, | |
8213 { 'name': 'shift', 'src': 'letzte:|r|n|s', 'value': -1 }, | |
8214 { 'name': 'shift', 'src': 'nächste:|r|n|s+nachste:|r|n|s+naechste:|r|n|s+kom
mende:n|r', 'value': 1 } | |
8215 ], | |
8216 'dateParse': [ | |
8217 '{sign} {num} {unit}', | |
8218 '{num} {unit} {sign}', | |
8219 '{shift} {unit=5-7}' | |
8220 ], | |
8221 'timeParse': [ | |
8222 '{weekday?} {date?} {month} {year?}', | |
8223 '{shift} {weekday}' | |
8224 ] | |
8225 }); | |
8226 | |
8227 /* | |
8228 * | |
8229 * Date.addLocale(<code>) adds this locale to Sugar. | |
8230 * To set the locale globally, simply call: | |
8231 * | |
8232 * Date.setLocale('es'); | |
8233 * | |
8234 * var locale = Date.getLocale(<code>) will return this object, which | |
8235 * can be tweaked to change the behavior of parsing/formatting in the locales. | |
8236 * | |
8237 * locale.addFormat adds a date format (see this file for examples). | |
8238 * Special tokens in the date format will be parsed out into regex tokens: | |
8239 * | |
8240 * {0} is a reference to an entry in locale.tokens. Output: (?:the)? | |
8241 * {unit} is a reference to all units. Output: (day|week|month|...) | |
8242 * {unit3} is a reference to a specific unit. Output: (hour) | |
8243 * {unit3-5} is a reference to a subset of the units array. Output: (hour|day|we
ek) | |
8244 * {unit?} "?" makes that token optional. Output: (day|week|month)? | |
8245 * | |
8246 * {day} Any reference to tokens in the modifiers array will include all with th
e same name. Output: (yesterday|today|tomorrow) | |
8247 * | |
8248 * All spaces are optional and will be converted to "\s*" | |
8249 * | |
8250 * Locale arrays months, weekdays, units, numbers, as well as the "src" field fo
r | |
8251 * all entries in the modifiers array follow a special format indicated by a col
on: | |
8252 * | |
8253 * minute:|s = minute|minutes | |
8254 * thicke:n|r = thicken|thicker | |
8255 * | |
8256 * Additionally in the months, weekdays, units, and numbers array these will be
added at indexes that are multiples | |
8257 * of the relevant number for retrieval. For example having "sunday:|s" in the u
nits array will result in: | |
8258 * | |
8259 * units: ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'sa
turday', 'sundays'] | |
8260 * | |
8261 * When matched, the index will be found using: | |
8262 * | |
8263 * units.indexOf(match) % 7; | |
8264 * | |
8265 * Resulting in the correct index with any number of alternates for that entry. | |
8266 * | |
8267 */ | |
8268 | |
8269 Date.addLocale('es', { | |
8270 'plural': true, | |
8271 'months': 'enero,febrero,marzo,abril,mayo,junio,julio,agosto,septiembre,octubr
e,noviembre,diciembre', | |
8272 'weekdays': 'domingo,lunes,martes,miércoles|miercoles,jueves,viernes,sábado|sa
bado', | |
8273 'units': 'milisegundo:|s,segundo:|s,minuto:|s,hora:|s,día|días|dia|dias,semana
:|s,mes:|es,año|años|ano|anos', | |
8274 'numbers': 'uno,dos,tres,cuatro,cinco,seis,siete,ocho,nueve,diez', | |
8275 'tokens': 'el,la,de', | |
8276 'short':'{d} {month} {yyyy}', | |
8277 'long': '{d} {month} {yyyy} {H}:{mm}', | |
8278 'full': '{Weekday} {d} {month} {yyyy} {H}:{mm}:{ss}', | |
8279 'past': '{sign} {num} {unit}', | |
8280 'future': '{sign} {num} {unit}', | |
8281 'duration': '{num} {unit}', | |
8282 'timeMarker': 'a las', | |
8283 'ampm': 'am,pm', | |
8284 'modifiers': [ | |
8285 { 'name': 'day', 'src': 'anteayer', 'value': -2 }, | |
8286 { 'name': 'day', 'src': 'ayer', 'value': -1 }, | |
8287 { 'name': 'day', 'src': 'hoy', 'value': 0 }, | |
8288 { 'name': 'day', 'src': 'mañana|manana', 'value': 1 }, | |
8289 { 'name': 'sign', 'src': 'hace', 'value': -1 }, | |
8290 { 'name': 'sign', 'src': 'dentro de', 'value': 1 }, | |
8291 { 'name': 'shift', 'src': 'pasad:o|a', 'value': -1 }, | |
8292 { 'name': 'shift', 'src': 'próximo|próxima|proximo|proxima', 'value': 1 } | |
8293 ], | |
8294 'dateParse': [ | |
8295 '{sign} {num} {unit}', | |
8296 '{num} {unit} {sign}', | |
8297 '{0?}{1?} {unit=5-7} {shift}', | |
8298 '{0?}{1?} {shift} {unit=5-7}' | |
8299 ], | |
8300 'timeParse': [ | |
8301 '{shift} {weekday}', | |
8302 '{weekday} {shift}', | |
8303 '{date?} {2?} {month} {2?} {year?}' | |
8304 ] | |
8305 }); | |
8306 Date.addLocale('fi', { | |
8307 'plural': true, | |
8308 'timeMarker': 'kello', | |
8309 'ampm': ',', | |
8310 'months': 'tammikuu,helmikuu,maaliskuu,huhtikuu,toukokuu,kesäkuu,heinäku
u,elokuu,syyskuu,lokakuu,marraskuu,joulukuu', | |
8311 'weekdays': 'sunnuntai,maanantai,tiistai,keskiviikko,torstai,perjantai,lau
antai', | |
8312 'units': 'millisekun:ti|tia|teja|tina|nin,sekun:ti|tia|teja|tina|nin,mi
nuut:ti|tia|teja|tina|in,tun:ti|tia|teja|tina|nin,päiv:ä|ää|iä|änä|än,viik:ko|ko
a|koja|on|kona,kuukau:si|sia|tta|den|tena,vuo:si|sia|tta|den|tena', | |
8313 'numbers': 'yksi|ensimmäinen,kaksi|toinen,kolm:e|as,neljä:s,vii:si|des,ku
u:si|des,seitsemä:n|s,kahdeksa:n|s,yhdeksä:n|s,kymmene:n|s', | |
8314 'articles': '', | |
8315 'optionals': '', | |
8316 'short': '{d}. {month}ta {yyyy}', | |
8317 'long': '{d}. {month}ta {yyyy} kello {H}.{mm}', | |
8318 'full': '{Weekday}na {d}. {month}ta {yyyy} kello {H}.{mm}', | |
8319 'relative': function(num, unit, ms, format) { | |
8320 var units = this['units']; | |
8321 function numberWithUnit(mult) { | |
8322 return (num === 1 ? '' : num + ' ') + units[(8 * mult) + unit]; | |
8323 } | |
8324 switch(format) { | |
8325 case 'duration': return numberWithUnit(0); | |
8326 case 'past': return numberWithUnit(num > 1 ? 1 : 0) + ' sitten'; | |
8327 case 'future': return numberWithUnit(4) + ' päästä'; | |
8328 } | |
8329 }, | |
8330 'modifiers': [ | |
8331 { 'name': 'day', 'src': 'toissa päivänä|toissa päiväistä', 'value': -2
}, | |
8332 { 'name': 'day', 'src': 'eilen|eilistä', 'value': -1 }, | |
8333 { 'name': 'day', 'src': 'tänään', 'value': 0 }, | |
8334 { 'name': 'day', 'src': 'huomenna|huomista', 'value': 1 }, | |
8335 { 'name': 'day', 'src': 'ylihuomenna|ylihuomista', 'value': 2 }, | |
8336 { 'name': 'sign', 'src': 'sitten|aiemmin', 'value': -1 }, | |
8337 { 'name': 'sign', 'src': 'päästä|kuluttua|myöhemmin', 'value': 1 }, | |
8338 { 'name': 'edge', 'src': 'viimeinen|viimeisenä', 'value': -2 }, | |
8339 { 'name': 'edge', 'src': 'lopussa', 'value': -1 }, | |
8340 { 'name': 'edge', 'src': 'ensimmäinen|ensimmäisenä', 'value': 1 }, | |
8341 { 'name': 'shift', 'src': 'edellinen|edellisenä|edeltävä|edeltävänä|viim
e|toissa', 'value': -1 }, | |
8342 { 'name': 'shift', 'src': 'tänä|tämän', 'value': 0 }, | |
8343 { 'name': 'shift', 'src': 'seuraava|seuraavana|tuleva|tulevana|ensi', 'v
alue': 1 } | |
8344 ], | |
8345 'dateParse': [ | |
8346 '{num} {unit} {sign}', | |
8347 '{sign} {num} {unit}', | |
8348 '{num} {unit=4-5} {sign} {day}', | |
8349 '{month} {year}', | |
8350 '{shift} {unit=5-7}' | |
8351 ], | |
8352 'timeParse': [ | |
8353 '{0} {num}{1} {day} of {month} {year?}', | |
8354 '{weekday?} {month} {date}{1} {year?}', | |
8355 '{date} {month} {year}', | |
8356 '{shift} {weekday}', | |
8357 '{shift} week {weekday}', | |
8358 '{weekday} {2} {shift} week', | |
8359 '{0} {date}{1} of {month}', | |
8360 '{0}{month?} {date?}{1} of {shift} {unit=6-7}' | |
8361 ] | |
8362 }); | |
8363 /* | |
8364 * | |
8365 * Date.addLocale(<code>) adds this locale to Sugar. | |
8366 * To set the locale globally, simply call: | |
8367 * | |
8368 * Date.setLocale('fr'); | |
8369 * | |
8370 * var locale = Date.getLocale(<code>) will return this object, which | |
8371 * can be tweaked to change the behavior of parsing/formatting in the locales. | |
8372 * | |
8373 * locale.addFormat adds a date format (see this file for examples). | |
8374 * Special tokens in the date format will be parsed out into regex tokens: | |
8375 * | |
8376 * {0} is a reference to an entry in locale.tokens. Output: (?:the)? | |
8377 * {unit} is a reference to all units. Output: (day|week|month|...) | |
8378 * {unit3} is a reference to a specific unit. Output: (hour) | |
8379 * {unit3-5} is a reference to a subset of the units array. Output: (hour|day|we
ek) | |
8380 * {unit?} "?" makes that token optional. Output: (day|week|month)? | |
8381 * | |
8382 * {day} Any reference to tokens in the modifiers array will include all with th
e same name. Output: (yesterday|today|tomorrow) | |
8383 * | |
8384 * All spaces are optional and will be converted to "\s*" | |
8385 * | |
8386 * Locale arrays months, weekdays, units, numbers, as well as the "src" field fo
r | |
8387 * all entries in the modifiers array follow a special format indicated by a col
on: | |
8388 * | |
8389 * minute:|s = minute|minutes | |
8390 * thicke:n|r = thicken|thicker | |
8391 * | |
8392 * Additionally in the months, weekdays, units, and numbers array these will be
added at indexes that are multiples | |
8393 * of the relevant number for retrieval. For example having "sunday:|s" in the u
nits array will result in: | |
8394 * | |
8395 * units: ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'sa
turday', 'sundays'] | |
8396 * | |
8397 * When matched, the index will be found using: | |
8398 * | |
8399 * units.indexOf(match) % 7; | |
8400 * | |
8401 * Resulting in the correct index with any number of alternates for that entry. | |
8402 * | |
8403 */ | |
8404 | |
8405 Date.addLocale('fr', { | |
8406 'plural': true, | |
8407 'months': 'janvier,février|fevrier,mars,avril,mai,juin,juillet,août,septembre,
octobre,novembre,décembre|decembre', | |
8408 'weekdays': 'dimanche,lundi,mardi,mercredi,jeudi,vendredi,samedi', | |
8409 'units': 'milliseconde:|s,seconde:|s,minute:|s,heure:|s,jour:|s,semaine:|s,moi
s,an:|s|née|nee', | |
8410 'numbers': 'un:|e,deux,trois,quatre,cinq,six,sept,huit,neuf,dix', | |
8411 'tokens': "l'|la|le", | |
8412 'short':'{d} {month} {yyyy}', | |
8413 'long': '{d} {month} {yyyy} {H}:{mm}', | |
8414 'full': '{Weekday} {d} {month} {yyyy} {H}:{mm}:{ss}', | |
8415 'past': '{sign} {num} {unit}', | |
8416 'future': '{sign} {num} {unit}', | |
8417 'duration': '{num} {unit}', | |
8418 'timeMarker': 'à', | |
8419 'ampm': 'am,pm', | |
8420 'modifiers': [ | |
8421 { 'name': 'day', 'src': 'hier', 'value': -1 }, | |
8422 { 'name': 'day', 'src': "aujourd'hui", 'value': 0 }, | |
8423 { 'name': 'day', 'src': 'demain', 'value': 1 }, | |
8424 { 'name': 'sign', 'src': 'il y a', 'value': -1 }, | |
8425 { 'name': 'sign', 'src': "dans|d'ici", 'value': 1 }, | |
8426 { 'name': 'shift', 'src': 'derni:èr|er|ère|ere', 'value': -1 }, | |
8427 { 'name': 'shift', 'src': 'prochain:|e', 'value': 1 } | |
8428 ], | |
8429 'dateParse': [ | |
8430 '{sign} {num} {unit}', | |
8431 '{sign} {num} {unit}', | |
8432 '{0?} {unit=5-7} {shift}' | |
8433 ], | |
8434 'timeParse': [ | |
8435 '{weekday?} {0?} {date?} {month} {year?}', | |
8436 '{0?} {weekday} {shift}' | |
8437 ] | |
8438 }); | |
8439 | |
8440 /* | |
8441 * | |
8442 * Date.addLocale(<code>) adds this locale to Sugar. | |
8443 * To set the locale globally, simply call: | |
8444 * | |
8445 * Date.setLocale('it'); | |
8446 * | |
8447 * var locale = Date.getLocale(<code>) will return this object, which | |
8448 * can be tweaked to change the behavior of parsing/formatting in the locales. | |
8449 * | |
8450 * locale.addFormat adds a date format (see this file for examples). | |
8451 * Special tokens in the date format will be parsed out into regex tokens: | |
8452 * | |
8453 * {0} is a reference to an entry in locale.tokens. Output: (?:the)? | |
8454 * {unit} is a reference to all units. Output: (day|week|month|...) | |
8455 * {unit3} is a reference to a specific unit. Output: (hour) | |
8456 * {unit3-5} is a reference to a subset of the units array. Output: (hour|day|we
ek) | |
8457 * {unit?} "?" makes that token optional. Output: (day|week|month)? | |
8458 * | |
8459 * {day} Any reference to tokens in the modifiers array will include all with th
e same name. Output: (yesterday|today|tomorrow) | |
8460 * | |
8461 * All spaces are optional and will be converted to "\s*" | |
8462 * | |
8463 * Locale arrays months, weekdays, units, numbers, as well as the "src" field fo
r | |
8464 * all entries in the modifiers array follow a special format indicated by a col
on: | |
8465 * | |
8466 * minute:|s = minute|minutes | |
8467 * thicke:n|r = thicken|thicker | |
8468 * | |
8469 * Additionally in the months, weekdays, units, and numbers array these will be
added at indexes that are multiples | |
8470 * of the relevant number for retrieval. For example having "sunday:|s" in the u
nits array will result in: | |
8471 * | |
8472 * units: ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'sa
turday', 'sundays'] | |
8473 * | |
8474 * When matched, the index will be found using: | |
8475 * | |
8476 * units.indexOf(match) % 7; | |
8477 * | |
8478 * Resulting in the correct index with any number of alternates for that entry. | |
8479 * | |
8480 */ | |
8481 | |
8482 Date.addLocale('it', { | |
8483 'plural': true, | |
8484 'months': 'Gennaio,Febbraio,Marzo,Aprile,Maggio,Giugno,Luglio,Agosto,Settembre
,Ottobre,Novembre,Dicembre', | |
8485 'weekdays': 'Domenica,Luned:ì|i,Marted:ì|i,Mercoled:ì|i,Gioved:ì|i,Venerd:ì|i,
Sabato', | |
8486 'units': 'millisecond:o|i,second:o|i,minut:o|i,or:a|e,giorn:o|i,settiman:a|e,m
es:e|i,ann:o|i', | |
8487 'numbers': "un:|a|o|',due,tre,quattro,cinque,sei,sette,otto,nove,dieci", | |
8488 'tokens': "l'|la|il", | |
8489 'short':'{d} {Month} {yyyy}', | |
8490 'long': '{d} {Month} {yyyy} {H}:{mm}', | |
8491 'full': '{Weekday} {d} {Month} {yyyy} {H}:{mm}:{ss}', | |
8492 'past': '{num} {unit} {sign}', | |
8493 'future': '{num} {unit} {sign}', | |
8494 'duration': '{num} {unit}', | |
8495 'timeMarker': 'alle', | |
8496 'ampm': 'am,pm', | |
8497 'modifiers': [ | |
8498 { 'name': 'day', 'src': 'ieri', 'value': -1 }, | |
8499 { 'name': 'day', 'src': 'oggi', 'value': 0 }, | |
8500 { 'name': 'day', 'src': 'domani', 'value': 1 }, | |
8501 { 'name': 'day', 'src': 'dopodomani', 'value': 2 }, | |
8502 { 'name': 'sign', 'src': 'fa', 'value': -1 }, | |
8503 { 'name': 'sign', 'src': 'da adesso', 'value': 1 }, | |
8504 { 'name': 'shift', 'src': 'scors:o|a', 'value': -1 }, | |
8505 { 'name': 'shift', 'src': 'prossim:o|a', 'value': 1 } | |
8506 ], | |
8507 'dateParse': [ | |
8508 '{num} {unit} {sign}', | |
8509 '{0?} {unit=5-7} {shift}', | |
8510 '{0?} {shift} {unit=5-7}' | |
8511 ], | |
8512 'timeParse': [ | |
8513 '{weekday?} {date?} {month} {year?}', | |
8514 '{shift} {weekday}' | |
8515 ] | |
8516 }); | |
8517 | |
8518 /* | |
8519 * | |
8520 * Date.addLocale(<code>) adds this locale to Sugar. | |
8521 * To set the locale globally, simply call: | |
8522 * | |
8523 * Date.setLocale('ja'); | |
8524 * | |
8525 * var locale = Date.getLocale(<code>) will return this object, which | |
8526 * can be tweaked to change the behavior of parsing/formatting in the locales. | |
8527 * | |
8528 * locale.addFormat adds a date format (see this file for examples). | |
8529 * Special tokens in the date format will be parsed out into regex tokens: | |
8530 * | |
8531 * {0} is a reference to an entry in locale.tokens. Output: (?:the)? | |
8532 * {unit} is a reference to all units. Output: (day|week|month|...) | |
8533 * {unit3} is a reference to a specific unit. Output: (hour) | |
8534 * {unit3-5} is a reference to a subset of the units array. Output: (hour|day|we
ek) | |
8535 * {unit?} "?" makes that token optional. Output: (day|week|month)? | |
8536 * | |
8537 * {day} Any reference to tokens in the modifiers array will include all with th
e same name. Output: (yesterday|today|tomorrow) | |
8538 * | |
8539 * All spaces are optional and will be converted to "\s*" | |
8540 * | |
8541 * Locale arrays months, weekdays, units, numbers, as well as the "src" field fo
r | |
8542 * all entries in the modifiers array follow a special format indicated by a col
on: | |
8543 * | |
8544 * minute:|s = minute|minutes | |
8545 * thicke:n|r = thicken|thicker | |
8546 * | |
8547 * Additionally in the months, weekdays, units, and numbers array these will be
added at indexes that are multiples | |
8548 * of the relevant number for retrieval. For example having "sunday:|s" in the u
nits array will result in: | |
8549 * | |
8550 * units: ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'sa
turday', 'sundays'] | |
8551 * | |
8552 * When matched, the index will be found using: | |
8553 * | |
8554 * units.indexOf(match) % 7; | |
8555 * | |
8556 * Resulting in the correct index with any number of alternates for that entry. | |
8557 * | |
8558 */ | |
8559 | |
8560 Date.addLocale('ja', { | |
8561 'monthSuffix': '月', | |
8562 'weekdays': '日曜日,月曜日,火曜日,水曜日,木曜日,金曜日,土曜日', | |
8563 'units': 'ミリ秒,秒,分,時間,日,週間|週,ヶ月|ヵ月|月,年', | |
8564 'short': '{yyyy}年{M}月{d}日', | |
8565 'long': '{yyyy}年{M}月{d}日 {H}時{mm}分', | |
8566 'full': '{yyyy}年{M}月{d}日 {Weekday} {H}時{mm}分{ss}秒', | |
8567 'past': '{num}{unit}{sign}', | |
8568 'future': '{num}{unit}{sign}', | |
8569 'duration': '{num}{unit}', | |
8570 'timeSuffixes': '時,分,秒', | |
8571 'ampm': '午前,午後', | |
8572 'modifiers': [ | |
8573 { 'name': 'day', 'src': '一昨日', 'value': -2 }, | |
8574 { 'name': 'day', 'src': '昨日', 'value': -1 }, | |
8575 { 'name': 'day', 'src': '今日', 'value': 0 }, | |
8576 { 'name': 'day', 'src': '明日', 'value': 1 }, | |
8577 { 'name': 'day', 'src': '明後日', 'value': 2 }, | |
8578 { 'name': 'sign', 'src': '前', 'value': -1 }, | |
8579 { 'name': 'sign', 'src': '後', 'value': 1 }, | |
8580 { 'name': 'shift', 'src': '去|先', 'value': -1 }, | |
8581 { 'name': 'shift', 'src': '来', 'value': 1 } | |
8582 ], | |
8583 'dateParse': [ | |
8584 '{num}{unit}{sign}' | |
8585 ], | |
8586 'timeParse': [ | |
8587 '{shift}{unit=5-7}{weekday?}', | |
8588 '{year}年{month?}月?{date?}日?', | |
8589 '{month}月{date?}日?', | |
8590 '{date}日' | |
8591 ] | |
8592 }); | |
8593 | |
8594 /* | |
8595 * | |
8596 * Date.addLocale(<code>) adds this locale to Sugar. | |
8597 * To set the locale globally, simply call: | |
8598 * | |
8599 * Date.setLocale('ko'); | |
8600 * | |
8601 * var locale = Date.getLocale(<code>) will return this object, which | |
8602 * can be tweaked to change the behavior of parsing/formatting in the locales. | |
8603 * | |
8604 * locale.addFormat adds a date format (see this file for examples). | |
8605 * Special tokens in the date format will be parsed out into regex tokens: | |
8606 * | |
8607 * {0} is a reference to an entry in locale.tokens. Output: (?:the)? | |
8608 * {unit} is a reference to all units. Output: (day|week|month|...) | |
8609 * {unit3} is a reference to a specific unit. Output: (hour) | |
8610 * {unit3-5} is a reference to a subset of the units array. Output: (hour|day|we
ek) | |
8611 * {unit?} "?" makes that token optional. Output: (day|week|month)? | |
8612 * | |
8613 * {day} Any reference to tokens in the modifiers array will include all with th
e same name. Output: (yesterday|today|tomorrow) | |
8614 * | |
8615 * All spaces are optional and will be converted to "\s*" | |
8616 * | |
8617 * Locale arrays months, weekdays, units, numbers, as well as the "src" field fo
r | |
8618 * all entries in the modifiers array follow a special format indicated by a col
on: | |
8619 * | |
8620 * minute:|s = minute|minutes | |
8621 * thicke:n|r = thicken|thicker | |
8622 * | |
8623 * Additionally in the months, weekdays, units, and numbers array these will be
added at indexes that are multiples | |
8624 * of the relevant number for retrieval. For example having "sunday:|s" in the u
nits array will result in: | |
8625 * | |
8626 * units: ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'sa
turday', 'sundays'] | |
8627 * | |
8628 * When matched, the index will be found using: | |
8629 * | |
8630 * units.indexOf(match) % 7; | |
8631 * | |
8632 * Resulting in the correct index with any number of alternates for that entry. | |
8633 * | |
8634 */ | |
8635 | |
8636 Date.addLocale('ko', { | |
8637 'digitDate': true, | |
8638 'monthSuffix': '월', | |
8639 'weekdays': '일요일,월요일,화요일,수요일,목요일,금요일,토요일', | |
8640 'units': '밀리초,초,분,시간,일,주,개월|달,년', | |
8641 'numbers': '일|한,이,삼,사,오,육,칠,팔,구,십', | |
8642 'short': '{yyyy}년{M}월{d}일', | |
8643 'long': '{yyyy}년{M}월{d}일 {H}시{mm}분', | |
8644 'full': '{yyyy}년{M}월{d}일 {Weekday} {H}시{mm}분{ss}초', | |
8645 'past': '{num}{unit} {sign}', | |
8646 'future': '{num}{unit} {sign}', | |
8647 'duration': '{num}{unit}', | |
8648 'timeSuffixes': '시,분,초', | |
8649 'ampm': '오전,오후', | |
8650 'modifiers': [ | |
8651 { 'name': 'day', 'src': '그저께', 'value': -2 }, | |
8652 { 'name': 'day', 'src': '어제', 'value': -1 }, | |
8653 { 'name': 'day', 'src': '오늘', 'value': 0 }, | |
8654 { 'name': 'day', 'src': '내일', 'value': 1 }, | |
8655 { 'name': 'day', 'src': '모레', 'value': 2 }, | |
8656 { 'name': 'sign', 'src': '전', 'value': -1 }, | |
8657 { 'name': 'sign', 'src': '후', 'value': 1 }, | |
8658 { 'name': 'shift', 'src': '지난|작', 'value': -1 }, | |
8659 { 'name': 'shift', 'src': '이번', 'value': 0 }, | |
8660 { 'name': 'shift', 'src': '다음|내', 'value': 1 } | |
8661 ], | |
8662 'dateParse': [ | |
8663 '{num}{unit} {sign}', | |
8664 '{shift?} {unit=5-7}' | |
8665 ], | |
8666 'timeParse': [ | |
8667 '{shift} {unit=5?} {weekday}', | |
8668 '{year}년{month?}월?{date?}일?', | |
8669 '{month}월{date?}일?', | |
8670 '{date}일' | |
8671 ] | |
8672 }); | |
8673 | |
8674 /* | |
8675 * | |
8676 * Date.addLocale(<code>) adds this locale to Sugar. | |
8677 * To set the locale globally, simply call: | |
8678 * | |
8679 * Date.setLocale('nl'); | |
8680 * | |
8681 * var locale = Date.getLocale(<code>) will return this object, which | |
8682 * can be tweaked to change the behavior of parsing/formatting in the locales. | |
8683 * | |
8684 * locale.addFormat adds a date format (see this file for examples). | |
8685 * Special tokens in the date format will be parsed out into regex tokens: | |
8686 * | |
8687 * {0} is a reference to an entry in locale.tokens. Output: (?:the)? | |
8688 * {unit} is a reference to all units. Output: (day|week|month|...) | |
8689 * {unit3} is a reference to a specific unit. Output: (hour) | |
8690 * {unit3-5} is a reference to a subset of the units array. Output: (hour|day|we
ek) | |
8691 * {unit?} "?" makes that token optional. Output: (day|week|month)? | |
8692 * | |
8693 * {day} Any reference to tokens in the modifiers array will include all with th
e same name. Output: (yesterday|today|tomorrow) | |
8694 * | |
8695 * All spaces are optional and will be converted to "\s*" | |
8696 * | |
8697 * Locale arrays months, weekdays, units, numbers, as well as the "src" field fo
r | |
8698 * all entries in the modifiers array follow a special format indicated by a col
on: | |
8699 * | |
8700 * minute:|s = minute|minutes | |
8701 * thicke:n|r = thicken|thicker | |
8702 * | |
8703 * Additionally in the months, weekdays, units, and numbers array these will be
added at indexes that are multiples | |
8704 * of the relevant number for retrieval. For example having "sunday:|s" in the u
nits array will result in: | |
8705 * | |
8706 * units: ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'sa
turday', 'sundays'] | |
8707 * | |
8708 * When matched, the index will be found using: | |
8709 * | |
8710 * units.indexOf(match) % 7; | |
8711 * | |
8712 * Resulting in the correct index with any number of alternates for that entry. | |
8713 * | |
8714 */ | |
8715 | |
8716 Date.addLocale('nl', { | |
8717 'plural': true, | |
8718 'months': 'januari,februari,maart,april,mei,juni,juli,augustus,september,oktob
er,november,december', | |
8719 'weekdays': 'zondag|zo,maandag|ma,dinsdag|di,woensdag|woe|wo,donderdag|do,vrij
dag|vrij|vr,zaterdag|za', | |
8720 'units': 'milliseconde:|n,seconde:|n,minu:ut|ten,uur,dag:|en,we:ek|ken,maand:|
en,jaar', | |
8721 'numbers': 'een,twee,drie,vier,vijf,zes,zeven,acht,negen', | |
8722 'tokens': '', | |
8723 'short':'{d} {Month} {yyyy}', | |
8724 'long': '{d} {Month} {yyyy} {H}:{mm}', | |
8725 'full': '{Weekday} {d} {Month} {yyyy} {H}:{mm}:{ss}', | |
8726 'past': '{num} {unit} {sign}', | |
8727 'future': '{num} {unit} {sign}', | |
8728 'duration': '{num} {unit}', | |
8729 'timeMarker': "'s|om", | |
8730 'modifiers': [ | |
8731 { 'name': 'day', 'src': 'gisteren', 'value': -1 }, | |
8732 { 'name': 'day', 'src': 'vandaag', 'value': 0 }, | |
8733 { 'name': 'day', 'src': 'morgen', 'value': 1 }, | |
8734 { 'name': 'day', 'src': 'overmorgen', 'value': 2 }, | |
8735 { 'name': 'sign', 'src': 'geleden', 'value': -1 }, | |
8736 { 'name': 'sign', 'src': 'vanaf nu', 'value': 1 }, | |
8737 { 'name': 'shift', 'src': 'laatste|vorige|afgelopen', 'value': -1 }, | |
8738 { 'name': 'shift', 'src': 'volgend:|e', 'value': 1 } | |
8739 ], | |
8740 'dateParse': [ | |
8741 '{num} {unit} {sign}', | |
8742 '{0?} {unit=5-7} {shift}', | |
8743 '{0?} {shift} {unit=5-7}' | |
8744 ], | |
8745 'timeParse': [ | |
8746 '{weekday?} {date?} {month} {year?}', | |
8747 '{shift} {weekday}' | |
8748 ] | |
8749 }); | |
8750 /* | |
8751 * | |
8752 * Date.addLocale(<code>) adds this locale to Sugar. | |
8753 * To set the locale globally, simply call: | |
8754 * | |
8755 * Date.setLocale('pl'); | |
8756 * | |
8757 * var locale = Date.getLocale(<code>) will return this object, which | |
8758 * can be tweaked to change the behavior of parsing/formatting in the locales. | |
8759 * | |
8760 * locale.addFormat adds a date format (see this file for examples). | |
8761 * Special tokens in the date format will be parsed out into regex tokens: | |
8762 * | |
8763 * {0} is a reference to an entry in locale.optionals. Output: (?:the)? | |
8764 * {unit} is a reference to all units. Output: (day|week|month|...) | |
8765 * {unit3} is a reference to a specific unit. Output: (hour) | |
8766 * {unit3-5} is a reference to a subset of the units array. Output: (hour|day|we
ek) | |
8767 * {unit?} "?" makes that token optional. Output: (day|week|month)? | |
8768 * | |
8769 * {day} Any reference to tokens in the modifiers array will include all with th
e same name. Output: (yesterday|today|tomorrow) | |
8770 * | |
8771 * All spaces are optional and will be converted to "\s*" | |
8772 * | |
8773 * Locale arrays months, weekdays, units, numbers, as well as the "src" field fo
r | |
8774 * all entries in the modifiers array follow a special format indicated by a col
on: | |
8775 * | |
8776 * minute:|s = minute|minutes | |
8777 * thicke:n|r = thicken|thicker | |
8778 * | |
8779 * Additionally in the months, weekdays, units, and numbers array these will be
added at indexes that are multiples | |
8780 * of the relevant number for retrieval. For example having "sunday:|s" in the u
nits array will result in: | |
8781 * | |
8782 * units: ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'sa
turday', 'sundays'] | |
8783 * | |
8784 * When matched, the index will be found using: | |
8785 * | |
8786 * units.indexOf(match) % 7; | |
8787 * | |
8788 * Resulting in the correct index with any number of alternates for that entry. | |
8789 * | |
8790 */ | |
8791 | |
8792 Date.addLocale('pl', { | |
8793 'plural': true, | |
8794 'months': 'Styczeń|Stycznia,Luty|Lutego,Marzec|Marca,Kwiecień|Kwietnia,Maj|
Maja,Czerwiec|Czerwca,Lipiec|Lipca,Sierpień|Sierpnia,Wrzesień|Września,Październ
ik|Października,Listopad|Listopada,Grudzień|Grudnia', | |
8795 'weekdays': 'Niedziela|Niedzielę,Poniedziałek,Wtorek,Środ:a|ę,Czwartek,Piątek
,Sobota|Sobotę', | |
8796 'units': 'milisekund:a|y|,sekund:a|y|,minut:a|y|,godzin:a|y|,dzień|dni,tyd
zień|tygodnie|tygodni,miesiące|miesiące|miesięcy,rok|lata|lat', | |
8797 'numbers': 'jeden|jedną,dwa|dwie,trzy,cztery,pięć,sześć,siedem,osiem,dziewię
ć,dziesięć', | |
8798 'optionals': 'w|we,roku', | |
8799 'short': '{d} {Month} {yyyy}', | |
8800 'long': '{d} {Month} {yyyy} {H}:{mm}', | |
8801 'full' : '{Weekday}, {d} {Month} {yyyy} {H}:{mm}:{ss}', | |
8802 'past': '{num} {unit} {sign}', | |
8803 'future': '{sign} {num} {unit}', | |
8804 'duration': '{num} {unit}', | |
8805 'timeMarker':'o', | |
8806 'ampm': 'am,pm', | |
8807 'modifiers': [ | |
8808 { 'name': 'day', 'src': 'przedwczoraj', 'value': -2 }, | |
8809 { 'name': 'day', 'src': 'wczoraj', 'value': -1 }, | |
8810 { 'name': 'day', 'src': 'dzisiaj|dziś', 'value': 0 }, | |
8811 { 'name': 'day', 'src': 'jutro', 'value': 1 }, | |
8812 { 'name': 'day', 'src': 'pojutrze', 'value': 2 }, | |
8813 { 'name': 'sign', 'src': 'temu|przed', 'value': -1 }, | |
8814 { 'name': 'sign', 'src': 'za', 'value': 1 }, | |
8815 { 'name': 'shift', 'src': 'zeszły|zeszła|ostatni|ostatnia', 'value': -1 }, | |
8816 { 'name': 'shift', 'src': 'następny|następna|następnego|przyszły|przyszła|pr
zyszłego', 'value': 1 } | |
8817 ], | |
8818 'dateParse': [ | |
8819 '{num} {unit} {sign}', | |
8820 '{sign} {num} {unit}', | |
8821 '{month} {year}', | |
8822 '{shift} {unit=5-7}', | |
8823 '{0} {shift?} {weekday}' | |
8824 ], | |
8825 'timeParse': [ | |
8826 '{date} {month} {year?} {1}', | |
8827 '{0} {shift?} {weekday}' | |
8828 ] | |
8829 }); | |
8830 | |
8831 /* | |
8832 * | |
8833 * Date.addLocale(<code>) adds this locale to Sugar. | |
8834 * To set the locale globally, simply call: | |
8835 * | |
8836 * Date.setLocale('pt'); | |
8837 * | |
8838 * var locale = Date.getLocale(<code>) will return this object, which | |
8839 * can be tweaked to change the behavior of parsing/formatting in the locales. | |
8840 * | |
8841 * locale.addFormat adds a date format (see this file for examples). | |
8842 * Special tokens in the date format will be parsed out into regex tokens: | |
8843 * | |
8844 * {0} is a reference to an entry in locale.tokens. Output: (?:the)? | |
8845 * {unit} is a reference to all units. Output: (day|week|month|...) | |
8846 * {unit3} is a reference to a specific unit. Output: (hour) | |
8847 * {unit3-5} is a reference to a subset of the units array. Output: (hour|day|we
ek) | |
8848 * {unit?} "?" makes that token optional. Output: (day|week|month)? | |
8849 * | |
8850 * {day} Any reference to tokens in the modifiers array will include all with th
e same name. Output: (yesterday|today|tomorrow) | |
8851 * | |
8852 * All spaces are optional and will be converted to "\s*" | |
8853 * | |
8854 * Locale arrays months, weekdays, units, numbers, as well as the "src" field fo
r | |
8855 * all entries in the modifiers array follow a special format indicated by a col
on: | |
8856 * | |
8857 * minute:|s = minute|minutes | |
8858 * thicke:n|r = thicken|thicker | |
8859 * | |
8860 * Additionally in the months, weekdays, units, and numbers array these will be
added at indexes that are multiples | |
8861 * of the relevant number for retrieval. For example having "sunday:|s" in the u
nits array will result in: | |
8862 * | |
8863 * units: ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'sa
turday', 'sundays'] | |
8864 * | |
8865 * When matched, the index will be found using: | |
8866 * | |
8867 * units.indexOf(match) % 7; | |
8868 * | |
8869 * Resulting in the correct index with any number of alternates for that entry. | |
8870 * | |
8871 */ | |
8872 | |
8873 Date.addLocale('pt', { | |
8874 'plural': true, | |
8875 'months': 'janeiro,fevereiro,março,abril,maio,junho,julho,agosto,setembro,outu
bro,novembro,dezembro', | |
8876 'weekdays': 'domingo,segunda-feira,terça-feira,quarta-feira,quinta-feira,sexta
-feira,sábado|sabado', | |
8877 'units': 'milisegundo:|s,segundo:|s,minuto:|s,hora:|s,dia:|s,semana:|s,mês|mês
es|mes|meses,ano:|s', | |
8878 'numbers': 'um,dois,três|tres,quatro,cinco,seis,sete,oito,nove,dez,uma,duas', | |
8879 'tokens': 'a,de', | |
8880 'short':'{d} de {month} de {yyyy}', | |
8881 'long': '{d} de {month} de {yyyy} {H}:{mm}', | |
8882 'full': '{Weekday}, {d} de {month} de {yyyy} {H}:{mm}:{ss}', | |
8883 'past': '{num} {unit} {sign}', | |
8884 'future': '{sign} {num} {unit}', | |
8885 'duration': '{num} {unit}', | |
8886 'timeMarker': 'às', | |
8887 'ampm': 'am,pm', | |
8888 'modifiers': [ | |
8889 { 'name': 'day', 'src': 'anteontem', 'value': -2 }, | |
8890 { 'name': 'day', 'src': 'ontem', 'value': -1 }, | |
8891 { 'name': 'day', 'src': 'hoje', 'value': 0 }, | |
8892 { 'name': 'day', 'src': 'amanh:ã|a', 'value': 1 }, | |
8893 { 'name': 'sign', 'src': 'atrás|atras|há|ha', 'value': -1 }, | |
8894 { 'name': 'sign', 'src': 'daqui a', 'value': 1 }, | |
8895 { 'name': 'shift', 'src': 'passad:o|a', 'value': -1 }, | |
8896 { 'name': 'shift', 'src': 'próximo|próxima|proximo|proxima', 'value': 1 } | |
8897 ], | |
8898 'dateParse': [ | |
8899 '{num} {unit} {sign}', | |
8900 '{sign} {num} {unit}', | |
8901 '{0?} {unit=5-7} {shift}', | |
8902 '{0?} {shift} {unit=5-7}' | |
8903 ], | |
8904 'timeParse': [ | |
8905 '{date?} {1?} {month} {1?} {year?}', | |
8906 '{0?} {shift} {weekday}' | |
8907 ] | |
8908 }); | |
8909 | |
8910 /* | |
8911 * | |
8912 * Date.addLocale(<code>) adds this locale to Sugar. | |
8913 * To set the locale globally, simply call: | |
8914 * | |
8915 * Date.setLocale('ru'); | |
8916 * | |
8917 * var locale = Date.getLocale(<code>) will return this object, which | |
8918 * can be tweaked to change the behavior of parsing/formatting in the locales. | |
8919 * | |
8920 * locale.addFormat adds a date format (see this file for examples). | |
8921 * Special tokens in the date format will be parsed out into regex tokens: | |
8922 * | |
8923 * {0} is a reference to an entry in locale.tokens. Output: (?:the)? | |
8924 * {unit} is a reference to all units. Output: (day|week|month|...) | |
8925 * {unit3} is a reference to a specific unit. Output: (hour) | |
8926 * {unit3-5} is a reference to a subset of the units array. Output: (hour|day|we
ek) | |
8927 * {unit?} "?" makes that token optional. Output: (day|week|month)? | |
8928 * | |
8929 * {day} Any reference to tokens in the modifiers array will include all with th
e same name. Output: (yesterday|today|tomorrow) | |
8930 * | |
8931 * All spaces are optional and will be converted to "\s*" | |
8932 * | |
8933 * Locale arrays months, weekdays, units, numbers, as well as the "src" field fo
r | |
8934 * all entries in the modifiers array follow a special format indicated by a col
on: | |
8935 * | |
8936 * minute:|s = minute|minutes | |
8937 * thicke:n|r = thicken|thicker | |
8938 * | |
8939 * Additionally in the months, weekdays, units, and numbers array these will be
added at indexes that are multiples | |
8940 * of the relevant number for retrieval. For example having "sunday:|s" in the u
nits array will result in: | |
8941 * | |
8942 * units: ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'sa
turday', 'sundays'] | |
8943 * | |
8944 * When matched, the index will be found using: | |
8945 * | |
8946 * units.indexOf(match) % 7; | |
8947 * | |
8948 * Resulting in the correct index with any number of alternates for that entry. | |
8949 * | |
8950 */ | |
8951 | |
8952 Date.addLocale('ru', { | |
8953 'months': 'Январ:я|ь,Феврал:я|ь,Март:а|,Апрел:я|ь,Ма:я|й,Июн:я|ь,Июл:я|ь,Авгус
т:а|,Сентябр:я|ь,Октябр:я|ь,Ноябр:я|ь,Декабр:я|ь', | |
8954 'weekdays': 'Воскресенье,Понедельник,Вторник,Среда,Четверг,Пятница,Суббота', | |
8955 'units': 'миллисекунд:а|у|ы|,секунд:а|у|ы|,минут:а|у|ы|,час:||а|ов,день|день|д
ня|дней,недел:я|ю|и|ь|е,месяц:||а|ев|е,год|год|года|лет|году', | |
8956 'numbers': 'од:ин|ну,дв:а|е,три,четыре,пять,шесть,семь,восемь,девять,десять', | |
8957 'tokens': 'в|на,года', | |
8958 'short':'{d} {month} {yyyy} года', | |
8959 'long': '{d} {month} {yyyy} года {H}:{mm}', | |
8960 'full': '{Weekday} {d} {month} {yyyy} года {H}:{mm}:{ss}', | |
8961 'relative': function(num, unit, ms, format) { | |
8962 var numberWithUnit, last = num.toString().slice(-1), mult; | |
8963 switch(true) { | |
8964 case num >= 11 && num <= 15: mult = 3; break; | |
8965 case last == 1: mult = 1; break; | |
8966 case last >= 2 && last <= 4: mult = 2; break; | |
8967 default: mult = 3; | |
8968 } | |
8969 numberWithUnit = num + ' ' + this['units'][(mult * 8) + unit]; | |
8970 switch(format) { | |
8971 case 'duration': return numberWithUnit; | |
8972 case 'past': return numberWithUnit + ' назад'; | |
8973 case 'future': return 'через ' + numberWithUnit; | |
8974 } | |
8975 }, | |
8976 'timeMarker': 'в', | |
8977 'ampm': ' утра, вечера', | |
8978 'modifiers': [ | |
8979 { 'name': 'day', 'src': 'позавчера', 'value': -2 }, | |
8980 { 'name': 'day', 'src': 'вчера', 'value': -1 }, | |
8981 { 'name': 'day', 'src': 'сегодня', 'value': 0 }, | |
8982 { 'name': 'day', 'src': 'завтра', 'value': 1 }, | |
8983 { 'name': 'day', 'src': 'послезавтра', 'value': 2 }, | |
8984 { 'name': 'sign', 'src': 'назад', 'value': -1 }, | |
8985 { 'name': 'sign', 'src': 'через', 'value': 1 }, | |
8986 { 'name': 'shift', 'src': 'прошл:ый|ой|ом', 'value': -1 }, | |
8987 { 'name': 'shift', 'src': 'следующ:ий|ей|ем', 'value': 1 } | |
8988 ], | |
8989 'dateParse': [ | |
8990 '{num} {unit} {sign}', | |
8991 '{sign} {num} {unit}', | |
8992 '{month} {year}', | |
8993 '{0?} {shift} {unit=5-7}' | |
8994 ], | |
8995 'timeParse': [ | |
8996 '{date} {month} {year?} {1?}', | |
8997 '{0?} {shift} {weekday}' | |
8998 ] | |
8999 }); | |
9000 | |
9001 /* | |
9002 * | |
9003 * Date.addLocale(<code>) adds this locale to Sugar. | |
9004 * To set the locale globally, simply call: | |
9005 * | |
9006 * Date.setLocale('sv'); | |
9007 * | |
9008 * var locale = Date.getLocale(<code>) will return this object, which | |
9009 * can be tweaked to change the behavior of parsing/formatting in the locales. | |
9010 * | |
9011 * locale.addFormat adds a date format (see this file for examples). | |
9012 * Special tokens in the date format will be parsed out into regex tokens: | |
9013 * | |
9014 * {0} is a reference to an entry in locale.tokens. Output: (?:the)? | |
9015 * {unit} is a reference to all units. Output: (day|week|month|...) | |
9016 * {unit3} is a reference to a specific unit. Output: (hour) | |
9017 * {unit3-5} is a reference to a subset of the units array. Output: (hour|day|we
ek) | |
9018 * {unit?} "?" makes that token optional. Output: (day|week|month)? | |
9019 * | |
9020 * {day} Any reference to tokens in the modifiers array will include all with th
e same name. Output: (yesterday|today|tomorrow) | |
9021 * | |
9022 * All spaces are optional and will be converted to "\s*" | |
9023 * | |
9024 * Locale arrays months, weekdays, units, numbers, as well as the "src" field fo
r | |
9025 * all entries in the modifiers array follow a special format indicated by a col
on: | |
9026 * | |
9027 * minute:|s = minute|minutes | |
9028 * thicke:n|r = thicken|thicker | |
9029 * | |
9030 * Additionally in the months, weekdays, units, and numbers array these will be
added at indexes that are multiples | |
9031 * of the relevant number for retrieval. For example having "sunday:|s" in the u
nits array will result in: | |
9032 * | |
9033 * units: ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'sa
turday', 'sundays'] | |
9034 * | |
9035 * When matched, the index will be found using: | |
9036 * | |
9037 * units.indexOf(match) % 7; | |
9038 * | |
9039 * Resulting in the correct index with any number of alternates for that entry. | |
9040 * | |
9041 */ | |
9042 | |
9043 Date.addLocale('sv', { | |
9044 'plural': true, | |
9045 'months': 'januari,februari,mars,april,maj,juni,juli,augusti,september,oktober
,november,december', | |
9046 'weekdays': 'söndag|sondag,måndag:|en+mandag:|en,tisdag,onsdag,torsdag,fredag,
lördag|lordag', | |
9047 'units': 'millisekund:|er,sekund:|er,minut:|er,timm:e|ar,dag:|ar,veck:a|or|an,
månad:|er|en+manad:|er|en,år:||et+ar:||et', | |
9048 'numbers': 'en|ett,två|tva,tre,fyra,fem,sex,sju,åtta|atta,nio,tio', | |
9049 'tokens': 'den,för|for', | |
9050 'articles': 'den', | |
9051 'short':'den {d} {month} {yyyy}', | |
9052 'long': 'den {d} {month} {yyyy} {H}:{mm}', | |
9053 'full': '{Weekday} den {d} {month} {yyyy} {H}:{mm}:{ss}', | |
9054 'past': '{num} {unit} {sign}', | |
9055 'future': '{sign} {num} {unit}', | |
9056 'duration': '{num} {unit}', | |
9057 'ampm': 'am,pm', | |
9058 'modifiers': [ | |
9059 { 'name': 'day', 'src': 'förrgår|i förrgår|iförrgår|forrgar|i forrgar|iforrg
ar', 'value': -2 }, | |
9060 { 'name': 'day', 'src': 'går|i går|igår|gar|i gar|igar', 'value': -1 }, | |
9061 { 'name': 'day', 'src': 'dag|i dag|idag', 'value': 0 }, | |
9062 { 'name': 'day', 'src': 'morgon|i morgon|imorgon', 'value': 1 }, | |
9063 { 'name': 'day', 'src': 'över morgon|övermorgon|i över morgon|i övermorgon|i
övermorgon|over morgon|overmorgon|i over morgon|i overmorgon|iovermorgon', 'valu
e': 2 }, | |
9064 { 'name': 'sign', 'src': 'sedan|sen', 'value': -1 }, | |
9065 { 'name': 'sign', 'src': 'om', 'value': 1 }, | |
9066 { 'name': 'shift', 'src': 'i förra|förra|i forra|forra', 'value': -1 }, | |
9067 { 'name': 'shift', 'src': 'denna', 'value': 0 }, | |
9068 { 'name': 'shift', 'src': 'nästa|nasta', 'value': 1 } | |
9069 ], | |
9070 'dateParse': [ | |
9071 '{num} {unit} {sign}', | |
9072 '{sign} {num} {unit}', | |
9073 '{1?} {num} {unit} {sign}', | |
9074 '{shift} {unit=5-7}' | |
9075 ], | |
9076 'timeParse': [ | |
9077 '{0?} {weekday?} {date?} {month} {year}', | |
9078 '{date} {month}', | |
9079 '{shift} {weekday}' | |
9080 ] | |
9081 }); | |
9082 | |
9083 /* | |
9084 * | |
9085 * Date.addLocale(<code>) adds this locale to Sugar. | |
9086 * To set the locale globally, simply call: | |
9087 * | |
9088 * Date.setLocale('zh-CN'); | |
9089 * | |
9090 * var locale = Date.getLocale(<code>) will return this object, which | |
9091 * can be tweaked to change the behavior of parsing/formatting in the locales. | |
9092 * | |
9093 * locale.addFormat adds a date format (see this file for examples). | |
9094 * Special tokens in the date format will be parsed out into regex tokens: | |
9095 * | |
9096 * {0} is a reference to an entry in locale.tokens. Output: (?:the)? | |
9097 * {unit} is a reference to all units. Output: (day|week|month|...) | |
9098 * {unit3} is a reference to a specific unit. Output: (hour) | |
9099 * {unit3-5} is a reference to a subset of the units array. Output: (hour|day|we
ek) | |
9100 * {unit?} "?" makes that token optional. Output: (day|week|month)? | |
9101 * | |
9102 * {day} Any reference to tokens in the modifiers array will include all with th
e same name. Output: (yesterday|today|tomorrow) | |
9103 * | |
9104 * All spaces are optional and will be converted to "\s*" | |
9105 * | |
9106 * Locale arrays months, weekdays, units, numbers, as well as the "src" field fo
r | |
9107 * all entries in the modifiers array follow a special format indicated by a col
on: | |
9108 * | |
9109 * minute:|s = minute|minutes | |
9110 * thicke:n|r = thicken|thicker | |
9111 * | |
9112 * Additionally in the months, weekdays, units, and numbers array these will be
added at indexes that are multiples | |
9113 * of the relevant number for retrieval. For example having "sunday:|s" in the u
nits array will result in: | |
9114 * | |
9115 * units: ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'sa
turday', 'sundays'] | |
9116 * | |
9117 * When matched, the index will be found using: | |
9118 * | |
9119 * units.indexOf(match) % 7; | |
9120 * | |
9121 * Resulting in the correct index with any number of alternates for that entry. | |
9122 * | |
9123 */ | |
9124 | |
9125 Date.addLocale('zh-CN', { | |
9126 'variant': true, | |
9127 'monthSuffix': '月', | |
9128 'weekdays': '星期日|周日,星期一|周一,星期二|周二,星期三|周三,星期四|周四,星期五|周五,星期六|周六', | |
9129 'units': '毫秒,秒钟,分钟,小时,天,个星期|周,个月,年', | |
9130 'tokens': '日|号', | |
9131 'short':'{yyyy}年{M}月{d}日', | |
9132 'long': '{yyyy}年{M}月{d}日 {tt}{h}:{mm}', | |
9133 'full': '{yyyy}年{M}月{d}日 {weekday} {tt}{h}:{mm}:{ss}', | |
9134 'past': '{num}{unit}{sign}', | |
9135 'future': '{num}{unit}{sign}', | |
9136 'duration': '{num}{unit}', | |
9137 'timeSuffixes': '点|时,分钟?,秒', | |
9138 'ampm': '上午,下午', | |
9139 'modifiers': [ | |
9140 { 'name': 'day', 'src': '前天', 'value': -2 }, | |
9141 { 'name': 'day', 'src': '昨天', 'value': -1 }, | |
9142 { 'name': 'day', 'src': '今天', 'value': 0 }, | |
9143 { 'name': 'day', 'src': '明天', 'value': 1 }, | |
9144 { 'name': 'day', 'src': '后天', 'value': 2 }, | |
9145 { 'name': 'sign', 'src': '前', 'value': -1 }, | |
9146 { 'name': 'sign', 'src': '后', 'value': 1 }, | |
9147 { 'name': 'shift', 'src': '上|去', 'value': -1 }, | |
9148 { 'name': 'shift', 'src': '这', 'value': 0 }, | |
9149 { 'name': 'shift', 'src': '下|明', 'value': 1 } | |
9150 ], | |
9151 'dateParse': [ | |
9152 '{num}{unit}{sign}', | |
9153 '{shift}{unit=5-7}' | |
9154 ], | |
9155 'timeParse': [ | |
9156 '{shift}{weekday}', | |
9157 '{year}年{month?}月?{date?}{0?}', | |
9158 '{month}月{date?}{0?}', | |
9159 '{date}[日号]' | |
9160 ] | |
9161 }); | |
9162 | |
9163 /* | |
9164 * | |
9165 * Date.addLocale(<code>) adds this locale to Sugar. | |
9166 * To set the locale globally, simply call: | |
9167 * | |
9168 * Date.setLocale('zh-TW'); | |
9169 * | |
9170 * var locale = Date.getLocale(<code>) will return this object, which | |
9171 * can be tweaked to change the behavior of parsing/formatting in the locales. | |
9172 * | |
9173 * locale.addFormat adds a date format (see this file for examples). | |
9174 * Special tokens in the date format will be parsed out into regex tokens: | |
9175 * | |
9176 * {0} is a reference to an entry in locale.tokens. Output: (?:the)? | |
9177 * {unit} is a reference to all units. Output: (day|week|month|...) | |
9178 * {unit3} is a reference to a specific unit. Output: (hour) | |
9179 * {unit3-5} is a reference to a subset of the units array. Output: (hour|day|we
ek) | |
9180 * {unit?} "?" makes that token optional. Output: (day|week|month)? | |
9181 * | |
9182 * {day} Any reference to tokens in the modifiers array will include all with th
e same name. Output: (yesterday|today|tomorrow) | |
9183 * | |
9184 * All spaces are optional and will be converted to "\s*" | |
9185 * | |
9186 * Locale arrays months, weekdays, units, numbers, as well as the "src" field fo
r | |
9187 * all entries in the modifiers array follow a special format indicated by a col
on: | |
9188 * | |
9189 * minute:|s = minute|minutes | |
9190 * thicke:n|r = thicken|thicker | |
9191 * | |
9192 * Additionally in the months, weekdays, units, and numbers array these will be
added at indexes that are multiples | |
9193 * of the relevant number for retrieval. For example having "sunday:|s" in the u
nits array will result in: | |
9194 * | |
9195 * units: ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'sa
turday', 'sundays'] | |
9196 * | |
9197 * When matched, the index will be found using: | |
9198 * | |
9199 * units.indexOf(match) % 7; | |
9200 * | |
9201 * Resulting in the correct index with any number of alternates for that entry. | |
9202 * | |
9203 */ | |
9204 | |
9205 //'zh-TW': '1;月;年;;星期日|週日,星期一|週一,星期二|週二,星期三|週三,星期四|週四,星期五|週五,星期六|週六;毫秒,秒鐘,分鐘,小
時,天,個星期|週,個月,年;;;日|號;;上午,下午;點|時,分鐘?,秒;{num}{unit}{sign},{shift}{unit=5-7};{shift
}{weekday},{year}年{month?}月?{date?}{0},{month}月{date?}{0},{date}{0};{yyyy}年{M}月{
d}日 {Weekday};{tt}{h}:{mm}:{ss};前天,昨天,今天,明天,後天;,前,,後;,上|去,這,下|明', | |
9206 | |
9207 Date.addLocale('zh-TW', { | |
9208 'monthSuffix': '月', | |
9209 'weekdays': '星期日|週日,星期一|週一,星期二|週二,星期三|週三,星期四|週四,星期五|週五,星期六|週六', | |
9210 'units': '毫秒,秒鐘,分鐘,小時,天,個星期|週,個月,年', | |
9211 'tokens': '日|號', | |
9212 'short':'{yyyy}年{M}月{d}日', | |
9213 'long': '{yyyy}年{M}月{d}日 {tt}{h}:{mm}', | |
9214 'full': '{yyyy}年{M}月{d}日 {Weekday} {tt}{h}:{mm}:{ss}', | |
9215 'past': '{num}{unit}{sign}', | |
9216 'future': '{num}{unit}{sign}', | |
9217 'duration': '{num}{unit}', | |
9218 'timeSuffixes': '點|時,分鐘?,秒', | |
9219 'ampm': '上午,下午', | |
9220 'modifiers': [ | |
9221 { 'name': 'day', 'src': '前天', 'value': -2 }, | |
9222 { 'name': 'day', 'src': '昨天', 'value': -1 }, | |
9223 { 'name': 'day', 'src': '今天', 'value': 0 }, | |
9224 { 'name': 'day', 'src': '明天', 'value': 1 }, | |
9225 { 'name': 'day', 'src': '後天', 'value': 2 }, | |
9226 { 'name': 'sign', 'src': '前', 'value': -1 }, | |
9227 { 'name': 'sign', 'src': '後', 'value': 1 }, | |
9228 { 'name': 'shift', 'src': '上|去', 'value': -1 }, | |
9229 { 'name': 'shift', 'src': '這', 'value': 0 }, | |
9230 { 'name': 'shift', 'src': '下|明', 'value': 1 } | |
9231 ], | |
9232 'dateParse': [ | |
9233 '{num}{unit}{sign}', | |
9234 '{shift}{unit=5-7}' | |
9235 ], | |
9236 'timeParse': [ | |
9237 '{shift}{weekday}', | |
9238 '{year}年{month?}月?{date?}{0?}', | |
9239 '{month}月{date?}{0?}', | |
9240 '{date}[日號]' | |
9241 ] | |
9242 }); | |
9243 | |
9244 | |
9245 }).call(this); | |
OLD | NEW |