master
1
2 'use strict';
3
4 /***
5 * @package Date
6 * @dependency core
7 * @description Date parsing and formatting, relative formats like "1 minute ago", Number methods like "daysAgo", localization support with default English locale definition.
8 *
9 ***/
10
11 var English;
12 var CurrentLocalization;
13
14 var TimeFormat = ['ampm','hour','minute','second','ampm','utc','offset_sign','offset_hours','offset_minutes','ampm']
15 var DecimalReg = '(?:[,.]\\d+)?';
16 var HoursReg = '\\d{1,2}' + DecimalReg;
17 var SixtyReg = '[0-5]\\d' + DecimalReg;
18 var RequiredTime = '({t})?\\s*('+HoursReg+')(?:{h}('+SixtyReg+')?{m}(?::?('+SixtyReg+'){s})?\\s*(?:({t})|(Z)|(?:([+-])(\\d{2,2})(?::?(\\d{2,2}))?)?)?|\\s*({t}))';
19
20 var KanjiDigits = '〇一二三四五六七八九十百千万';
21 var AsianDigitMap = {};
22 var AsianDigitReg;
23
24 var DateArgumentUnits;
25 var DateUnitsReversed;
26 var CoreDateFormats = [];
27 var CompiledOutputFormats = {};
28
29 var DateFormatTokens = {
30
31 'yyyy': function(d) {
32 return callDateGet(d, 'FullYear');
33 },
34
35 'yy': function(d) {
36 return callDateGet(d, 'FullYear') % 100;
37 },
38
39 'ord': function(d) {
40 var date = callDateGet(d, 'Date');
41 return date + getOrdinalizedSuffix(date);
42 },
43
44 'tz': function(d) {
45 return d.getUTCOffset();
46 },
47
48 'isotz': function(d) {
49 return d.getUTCOffset(true);
50 },
51
52 'Z': function(d) {
53 return d.getUTCOffset();
54 },
55
56 'ZZ': function(d) {
57 return d.getUTCOffset().replace(/(\d{2})$/, ':$1');
58 }
59
60 };
61
62 var DateUnits = [
63 {
64 name: 'year',
65 method: 'FullYear',
66 ambiguous: true,
67 multiplier: function(d) {
68 var adjust = d ? (d.isLeapYear() ? 1 : 0) : 0.25;
69 return (365 + adjust) * 24 * 60 * 60 * 1000;
70 }
71 },
72 {
73 name: 'month',
74 error: 0.919, // Feb 1-28 over 1 month
75 method: 'Month',
76 ambiguous: true,
77 multiplier: function(d, ms) {
78 var days = 30.4375, inMonth;
79 if(d) {
80 inMonth = d.daysInMonth();
81 if(ms <= inMonth.days()) {
82 days = inMonth;
83 }
84 }
85 return days * 24 * 60 * 60 * 1000;
86 }
87 },
88 {
89 name: 'week',
90 method: 'ISOWeek',
91 multiplier: function() {
92 return 7 * 24 * 60 * 60 * 1000;
93 }
94 },
95 {
96 name: 'day',
97 error: 0.958, // DST traversal over 1 day
98 method: 'Date',
99 ambiguous: true,
100 multiplier: function() {
101 return 24 * 60 * 60 * 1000;
102 }
103 },
104 {
105 name: 'hour',
106 method: 'Hours',
107 multiplier: function() {
108 return 60 * 60 * 1000;
109 }
110 },
111 {
112 name: 'minute',
113 method: 'Minutes',
114 multiplier: function() {
115 return 60 * 1000;
116 }
117 },
118 {
119 name: 'second',
120 method: 'Seconds',
121 multiplier: function() {
122 return 1000;
123 }
124 },
125 {
126 name: 'millisecond',
127 method: 'Milliseconds',
128 multiplier: function() {
129 return 1;
130 }
131 }
132 ];
133
134
135
136
137 // Date Localization
138
139 var Localizations = {};
140
141 // Localization object
142
143 function Localization(l) {
144 simpleMerge(this, l);
145 this.compiledFormats = CoreDateFormats.concat();
146 }
147
148 Localization.prototype = {
149
150 getMonth: function(n) {
151 if(isNumber(n)) {
152 return n - 1;
153 } else {
154 return this['months'].indexOf(n) % 12;
155 }
156 },
157
158 getWeekday: function(n) {
159 return this['weekdays'].indexOf(n) % 7;
160 },
161
162 getNumber: function(n) {
163 var i;
164 if(isNumber(n)) {
165 return n;
166 } else if(n && (i = this['numbers'].indexOf(n)) !== -1) {
167 return (i + 1) % 10;
168 } else {
169 return 1;
170 }
171 },
172
173 getNumericDate: function(n) {
174 var self = this;
175 return n.replace(regexp(this['num'], 'g'), function(d) {
176 var num = self.getNumber(d);
177 return num || '';
178 });
179 },
180
181 getUnitIndex: function(n) {
182 return this['units'].indexOf(n) % 8;
183 },
184
185 getRelativeFormat: function(adu) {
186 return this.convertAdjustedToFormat(adu, adu[2] > 0 ? 'future' : 'past');
187 },
188
189 getDuration: function(ms) {
190 return this.convertAdjustedToFormat(getAdjustedUnit(ms), 'duration');
191 },
192
193 hasVariant: function(code) {
194 code = code || this.code;
195 return code === 'en' || code === 'en-US' ? true : this['variant'];
196 },
197
198 matchAM: function(str) {
199 return str === this['ampm'][0];
200 },
201
202 matchPM: function(str) {
203 return str && str === this['ampm'][1];
204 },
205
206 convertAdjustedToFormat: function(adu, mode) {
207 var sign, unit, mult,
208 num = adu[0],
209 u = adu[1],
210 ms = adu[2],
211 format = this[mode] || this['relative'];
212 if(isFunction(format)) {
213 return format.call(this, num, u, ms, mode);
214 }
215 mult = this['plural'] && num > 1 ? 1 : 0;
216 unit = this['units'][mult * 8 + u] || this['units'][u];
217 if(this['capitalizeUnit']) unit = simpleCapitalize(unit);
218 sign = this['modifiers'].filter(function(m) { return m.name == 'sign' && m.value == (ms > 0 ? 1 : -1); })[0];
219 return format.replace(/\{(.*?)\}/g, function(full, match) {
220 switch(match) {
221 case 'num': return num;
222 case 'unit': return unit;
223 case 'sign': return sign.src;
224 }
225 });
226 },
227
228 getFormats: function() {
229 return this.cachedFormat ? [this.cachedFormat].concat(this.compiledFormats) : this.compiledFormats;
230 },
231
232 addFormat: function(src, allowsTime, match, variant, iso) {
233 var to = match || [], loc = this, time, timeMarkers, lastIsNumeral;
234
235 src = src.replace(/\s+/g, '[,. ]*');
236 src = src.replace(/\{([^,]+?)\}/g, function(all, k) {
237 var value, arr, result,
238 opt = k.match(/\?$/),
239 nc = k.match(/^(\d+)\??$/),
240 slice = k.match(/(\d)(?:-(\d))?/),
241 key = k.replace(/[^a-z]+$/, '');
242 if(nc) {
243 value = loc['tokens'][nc[1]];
244 } else if(loc[key]) {
245 value = loc[key];
246 } else if(loc[key + 's']) {
247 value = loc[key + 's'];
248 if(slice) {
249 // Can't use filter here as Prototype hijacks the method and doesn't
250 // pass an index, so use a simple loop instead!
251 arr = [];
252 value.forEach(function(m, i) {
253 var mod = i % (loc['units'] ? 8 : value.length);
254 if(mod >= slice[1] && mod <= (slice[2] || slice[1])) {
255 arr.push(m);
256 }
257 });
258 value = arr;
259 }
260 value = arrayToAlternates(value);
261 }
262 if(nc) {
263 result = '(?:' + value + ')';
264 } else {
265 if(!match) {
266 to.push(key);
267 }
268 result = '(' + value + ')';
269 }
270 if(opt) {
271 result += '?';
272 }
273 return result;
274 });
275 if(allowsTime) {
276 time = prepareTime(RequiredTime, loc, iso);
277 timeMarkers = ['t','[\\s\\u3000]'].concat(loc['timeMarker']);
278 lastIsNumeral = src.match(/\\d\{\d,\d\}\)+\??$/);
279 addDateInputFormat(loc, '(?:' + time + ')[,\\s\\u3000]+?' + src, TimeFormat.concat(to), variant);
280 addDateInputFormat(loc, src + '(?:[,\\s]*(?:' + timeMarkers.join('|') + (lastIsNumeral ? '+' : '*') +')' + time + ')?', to.concat(TimeFormat), variant);
281 } else {
282 addDateInputFormat(loc, src, to, variant);
283 }
284 }
285
286 };
287
288
289 // Localization helpers
290
291 function getLocalization(localeCode, fallback) {
292 var loc;
293 if(!isString(localeCode)) localeCode = '';
294 loc = Localizations[localeCode] || Localizations[localeCode.slice(0,2)];
295 if(fallback === false && !loc) {
296 throw new TypeError('Invalid locale.');
297 }
298 return loc || CurrentLocalization;
299 }
300
301 function setLocalization(localeCode, set) {
302 var loc, canAbbreviate;
303
304 function initializeField(name) {
305 var val = loc[name];
306 if(isString(val)) {
307 loc[name] = val.split(',');
308 } else if(!val) {
309 loc[name] = [];
310 }
311 }
312
313 function eachAlternate(str, fn) {
314 str = str.split('+').map(function(split) {
315 return split.replace(/(.+):(.+)$/, function(full, base, suffixes) {
316 return suffixes.split('|').map(function(suffix) {
317 return base + suffix;
318 }).join('|');
319 });
320 }).join('|');
321 return str.split('|').forEach(fn);
322 }
323
324 function setArray(name, abbreviate, multiple) {
325 var arr = [];
326 loc[name].forEach(function(full, i) {
327 if(abbreviate) {
328 full += '+' + full.slice(0,3);
329 }
330 eachAlternate(full, function(day, j) {
331 arr[j * multiple + i] = day.toLowerCase();
332 });
333 });
334 loc[name] = arr;
335 }
336
337 function getDigit(start, stop, allowNumbers) {
338 var str = '\\d{' + start + ',' + stop + '}';
339 if(allowNumbers) str += '|(?:' + arrayToAlternates(loc['numbers']) + ')+';
340 return str;
341 }
342
343 function getNum() {
344 var arr = ['-?\\d+'].concat(loc['articles']);
345 if(loc['numbers']) arr = arr.concat(loc['numbers']);
346 return arrayToAlternates(arr);
347 }
348
349 function setDefault(name, value) {
350 loc[name] = loc[name] || value;
351 }
352
353 function setModifiers() {
354 var arr = [];
355 loc.modifiersByName = {};
356 loc['modifiers'].push({ 'name': 'day', 'src': 'yesterday', 'value': -1 });
357 loc['modifiers'].push({ 'name': 'day', 'src': 'today', 'value': 0 });
358 loc['modifiers'].push({ 'name': 'day', 'src': 'tomorrow', 'value': 1 });
359 loc['modifiers'].forEach(function(modifier) {
360 var name = modifier.name;
361 eachAlternate(modifier.src, function(t) {
362 var locEntry = loc[name];
363 loc.modifiersByName[t] = modifier;
364 arr.push({ name: name, src: t, value: modifier.value });
365 loc[name] = locEntry ? locEntry + '|' + t : t;
366 });
367 });
368 loc['day'] += '|' + arrayToAlternates(loc['weekdays']);
369 loc['modifiers'] = arr;
370 }
371
372 // Initialize the locale
373 loc = new Localization(set);
374 initializeField('modifiers');
375 'months,weekdays,units,numbers,articles,tokens,timeMarker,ampm,timeSuffixes,dateParse,timeParse'.split(',').forEach(initializeField);
376
377 canAbbreviate = !loc['monthSuffix'];
378
379 setArray('months', canAbbreviate, 12);
380 setArray('weekdays', canAbbreviate, 7);
381 setArray('units', false, 8);
382 setArray('numbers', false, 10);
383
384 setDefault('code', localeCode);
385 setDefault('date', getDigit(1,2, loc['digitDate']));
386 setDefault('year', "'\\d{2}|" + getDigit(4,4));
387 setDefault('num', getNum());
388
389 setModifiers();
390
391 if(loc['monthSuffix']) {
392 loc['month'] = getDigit(1,2);
393 loc['months'] = '1,2,3,4,5,6,7,8,9,10,11,12'.split(',').map(function(n) { return n + loc['monthSuffix']; });
394 }
395 loc['full_month'] = getDigit(1,2) + '|' + arrayToAlternates(loc['months']);
396
397 // The order of these formats is very important. Order is reversed so formats that come
398 // later will take precedence over formats that come before. This generally means that
399 // more specific formats should come later, however, the {year} format should come before
400 // {day}, as 2011 needs to be parsed as a year (2011) and not date (20) + hours (11)
401
402 // If the locale has time suffixes then add a time only format for that locale
403 // that is separate from the core English-based one.
404 if(loc['timeSuffixes'].length > 0) {
405 loc.addFormat(prepareTime(RequiredTime, loc), false, TimeFormat)
406 }
407
408 loc.addFormat('{day}', true);
409 loc.addFormat('{month}' + (loc['monthSuffix'] || ''));
410 loc.addFormat('{year}' + (loc['yearSuffix'] || ''));
411
412 loc['timeParse'].forEach(function(src) {
413 loc.addFormat(src, true);
414 });
415
416 loc['dateParse'].forEach(function(src) {
417 loc.addFormat(src);
418 });
419
420 return Localizations[localeCode] = loc;
421 }
422
423
424 // General helpers
425
426 function addDateInputFormat(locale, format, match, variant) {
427 locale.compiledFormats.unshift({
428 variant: variant,
429 locale: locale,
430 reg: regexp('^' + format + '$', 'i'),
431 to: match
432 });
433 }
434
435 function simpleCapitalize(str) {
436 return str.slice(0,1).toUpperCase() + str.slice(1);
437 }
438
439 function arrayToAlternates(arr) {
440 return arr.filter(function(el) {
441 return !!el;
442 }).join('|');
443 }
444
445 function getNewDate() {
446 var fn = date.SugarNewDate;
447 return fn ? fn() : new date;
448 }
449
450 // Date argument helpers
451
452 function collectDateArguments(args, allowDuration) {
453 var obj;
454 if(isObjectType(args[0])) {
455 return args;
456 } else if (isNumber(args[0]) && !isNumber(args[1])) {
457 return [args[0]];
458 } else if (isString(args[0]) && allowDuration) {
459 return [getDateParamsFromString(args[0]), args[1]];
460 }
461 obj = {};
462 DateArgumentUnits.forEach(function(u,i) {
463 obj[u.name] = args[i];
464 });
465 return [obj];
466 }
467
468 function getDateParamsFromString(str, num) {
469 var match, params = {};
470 match = str.match(/^(\d+)?\s?(\w+?)s?$/i);
471 if(match) {
472 if(isUndefined(num)) {
473 num = parseInt(match[1]) || 1;
474 }
475 params[match[2].toLowerCase()] = num;
476 }
477 return params;
478 }
479
480 // Date iteration helpers
481
482 function iterateOverDateUnits(fn, from, to) {
483 var i, unit;
484 if(isUndefined(to)) to = DateUnitsReversed.length;
485 for(i = from || 0; i < to; i++) {
486 unit = DateUnitsReversed[i];
487 if(fn(unit.name, unit, i) === false) {
488 break;
489 }
490 }
491 }
492
493 // Date parsing helpers
494
495 function getFormatMatch(match, arr) {
496 var obj = {}, value, num;
497 arr.forEach(function(key, i) {
498 value = match[i + 1];
499 if(isUndefined(value) || value === '') return;
500 if(key === 'year') {
501 obj.yearAsString = value.replace(/'/, '');
502 }
503 num = parseFloat(value.replace(/'/, '').replace(/,/, '.'));
504 obj[key] = !isNaN(num) ? num : value.toLowerCase();
505 });
506 return obj;
507 }
508
509 function cleanDateInput(str) {
510 str = str.trim().replace(/^just (?=now)|\.+$/i, '');
511 return convertAsianDigits(str);
512 }
513
514 function convertAsianDigits(str) {
515 return str.replace(AsianDigitReg, function(full, disallowed, match) {
516 var sum = 0, place = 1, lastWasHolder, lastHolder;
517 if(disallowed) return full;
518 match.split('').reverse().forEach(function(letter) {
519 var value = AsianDigitMap[letter], holder = value > 9;
520 if(holder) {
521 if(lastWasHolder) sum += place;
522 place *= value / (lastHolder || 1);
523 lastHolder = value;
524 } else {
525 if(lastWasHolder === false) {
526 place *= 10;
527 }
528 sum += place * value;
529 }
530 lastWasHolder = holder;
531 });
532 if(lastWasHolder) sum += place;
533 return sum;
534 });
535 }
536
537 function getExtendedDate(f, localeCode, prefer, forceUTC) {
538 var d, relative, baseLocalization, afterCallbacks, loc, set, unit, unitIndex, weekday, num, tmp;
539
540 d = getNewDate();
541 afterCallbacks = [];
542
543 function afterDateSet(fn) {
544 afterCallbacks.push(fn);
545 }
546
547 function fireCallbacks() {
548 afterCallbacks.forEach(function(fn) {
549 fn.call();
550 });
551 }
552
553 function setWeekdayOfMonth() {
554 var w = d.getWeekday();
555 d.setWeekday((7 * (set['num'] - 1)) + (w > weekday ? weekday + 7 : weekday));
556 }
557
558 function setUnitEdge() {
559 var modifier = loc.modifiersByName[set['edge']];
560 iterateOverDateUnits(function(name) {
561 if(isDefined(set[name])) {
562 unit = name;
563 return false;
564 }
565 }, 4);
566 if(unit === 'year') set.specificity = 'month';
567 else if(unit === 'month' || unit === 'week') set.specificity = 'day';
568 d[(modifier.value < 0 ? 'endOf' : 'beginningOf') + simpleCapitalize(unit)]();
569 // This value of -2 is arbitrary but it's a nice clean way to hook into this system.
570 if(modifier.value === -2) d.reset();
571 }
572
573 function separateAbsoluteUnits() {
574 var params;
575 iterateOverDateUnits(function(name, u, i) {
576 if(name === 'day') name = 'date';
577 if(isDefined(set[name])) {
578 // If there is a time unit set that is more specific than
579 // the matched unit we have a string like "5:30am in 2 minutes",
580 // which is meaningless, so invalidate the date...
581 if(i >= unitIndex) {
582 invalidateDate(d);
583 return false;
584 }
585 // ...otherwise set the params to set the absolute date
586 // as a callback after the relative date has been set.
587 params = params || {};
588 params[name] = set[name];
589 delete set[name];
590 }
591 });
592 if(params) {
593 afterDateSet(function() {
594 d.set(params, true);
595 });
596 }
597 }
598
599 d.utc(forceUTC);
600
601 if(isDate(f)) {
602 // If the source here is already a date object, then the operation
603 // is the same as cloning the date, which preserves the UTC flag.
604 d.utc(f.isUTC()).setTime(f.getTime());
605 } else if(isNumber(f)) {
606 d.setTime(f);
607 } else if(isObjectType(f)) {
608 d.set(f, true);
609 set = f;
610 } else if(isString(f)) {
611
612 // The act of getting the localization will pre-initialize
613 // if it is missing and add the required formats.
614 baseLocalization = getLocalization(localeCode);
615
616 // Clean the input and convert Kanji based numerals if they exist.
617 f = cleanDateInput(f);
618
619 if(baseLocalization) {
620 iterateOverObject(baseLocalization.getFormats(), function(i, dif) {
621 var match = f.match(dif.reg);
622 if(match) {
623
624 loc = dif.locale;
625 set = getFormatMatch(match, dif.to, loc);
626 loc.cachedFormat = dif;
627
628
629 if(set['utc']) {
630 d.utc();
631 }
632
633 if(set.timestamp) {
634 set = set.timestamp;
635 return false;
636 }
637
638 // If there's a variant (crazy Endian American format), swap the month and day.
639 if(dif.variant && !isString(set['month']) && (isString(set['date']) || baseLocalization.hasVariant(localeCode))) {
640 tmp = set['month'];
641 set['month'] = set['date'];
642 set['date'] = tmp;
643 }
644
645 // If the year is 2 digits then get the implied century.
646 if(set['year'] && set.yearAsString.length === 2) {
647 set['year'] = getYearFromAbbreviation(set['year']);
648 }
649
650 // Set the month which may be localized.
651 if(set['month']) {
652 set['month'] = loc.getMonth(set['month']);
653 if(set['shift'] && !set['unit']) set['unit'] = loc['units'][7];
654 }
655
656 // If there is both a weekday and a date, the date takes precedence.
657 if(set['weekday'] && set['date']) {
658 delete set['weekday'];
659 // Otherwise set a localized weekday.
660 } else if(set['weekday']) {
661 set['weekday'] = loc.getWeekday(set['weekday']);
662 if(set['shift'] && !set['unit']) set['unit'] = loc['units'][5];
663 }
664
665 // Relative day localizations such as "today" and "tomorrow".
666 if(set['day'] && (tmp = loc.modifiersByName[set['day']])) {
667 set['day'] = tmp.value;
668 d.reset();
669 relative = true;
670 // If the day is a weekday, then set that instead.
671 } else if(set['day'] && (weekday = loc.getWeekday(set['day'])) > -1) {
672 delete set['day'];
673 if(set['num'] && set['month']) {
674 // If we have "the 2nd tuesday of June", set the day to the beginning of the month, then
675 // set the weekday after all other properties have been set. The weekday needs to be set
676 // after the actual set because it requires overriding the "prefer" argument which
677 // could unintentionally send the year into the future, past, etc.
678 afterDateSet(setWeekdayOfMonth);
679 set['day'] = 1;
680 } else {
681 set['weekday'] = weekday;
682 }
683 }
684
685 if(set['date'] && !isNumber(set['date'])) {
686 set['date'] = loc.getNumericDate(set['date']);
687 }
688
689 // If the time is 1pm-11pm advance the time by 12 hours.
690 if(loc.matchPM(set['ampm']) && set['hour'] < 12) {
691 set['hour'] += 12;
692 } else if(loc.matchAM(set['ampm']) && set['hour'] === 12) {
693 set['hour'] = 0;
694 }
695
696 // Adjust for timezone offset
697 if('offset_hours' in set || 'offset_minutes' in set) {
698 d.utc();
699 set['offset_minutes'] = set['offset_minutes'] || 0;
700 set['offset_minutes'] += set['offset_hours'] * 60;
701 if(set['offset_sign'] === '-') {
702 set['offset_minutes'] *= -1;
703 }
704 set['minute'] -= set['offset_minutes'];
705 }
706
707 // Date has a unit like "days", "months", etc. are all relative to the current date.
708 if(set['unit']) {
709 relative = true;
710 num = loc.getNumber(set['num']);
711 unitIndex = loc.getUnitIndex(set['unit']);
712 unit = English['units'][unitIndex];
713
714 // Formats like "the 15th of last month" or "6:30pm of next week"
715 // contain absolute units in addition to relative ones, so separate
716 // them here, remove them from the params, and set up a callback to
717 // set them after the relative ones have been set.
718 separateAbsoluteUnits();
719
720 // Shift and unit, ie "next month", "last week", etc.
721 if(set['shift']) {
722 num *= (tmp = loc.modifiersByName[set['shift']]) ? tmp.value : 0;
723 }
724
725 // Unit and sign, ie "months ago", "weeks from now", etc.
726 if(set['sign'] && (tmp = loc.modifiersByName[set['sign']])) {
727 num *= tmp.value;
728 }
729
730 // Units can be with non-relative dates, set here. ie "the day after monday"
731 if(isDefined(set['weekday'])) {
732 d.set({'weekday': set['weekday'] }, true);
733 delete set['weekday'];
734 }
735
736 // Finally shift the unit.
737 set[unit] = (set[unit] || 0) + num;
738 }
739
740 // If there is an "edge" it needs to be set after the
741 // other fields are set. ie "the end of February"
742 if(set['edge']) {
743 afterDateSet(setUnitEdge);
744 }
745
746 if(set['year_sign'] === '-') {
747 set['year'] *= -1;
748 }
749
750 iterateOverDateUnits(function(name, unit, i) {
751 var value = set[name], fraction = value % 1;
752 if(fraction) {
753 set[DateUnitsReversed[i - 1].name] = round(fraction * (name === 'second' ? 1000 : 60));
754 set[name] = floor(value);
755 }
756 }, 1, 4);
757 return false;
758 }
759 });
760 }
761 if(!set) {
762 // The Date constructor does something tricky like checking the number
763 // of arguments so simply passing in undefined won't work.
764 if(f !== 'now') {
765 d = new date(f);
766 }
767 if(forceUTC) {
768 // Falling back to system date here which cannot be parsed as UTC,
769 // so if we're forcing UTC then simply add the offset.
770 d.addMinutes(-d.getTimezoneOffset());
771 }
772 } else if(relative) {
773 d.advance(set);
774 } else {
775 if(d._utc) {
776 // UTC times can traverse into other days or even months,
777 // so preemtively reset the time here to prevent this.
778 d.reset();
779 }
780 updateDate(d, set, true, false, prefer);
781 }
782 fireCallbacks();
783 // A date created by parsing a string presumes that the format *itself* is UTC, but
784 // not that the date, once created, should be manipulated as such. In other words,
785 // if you are creating a date object from a server time "2012-11-15T12:00:00Z",
786 // in the majority of cases you are using it to create a date that will, after creation,
787 // be manipulated as local, so reset the utc flag here.
788 d.utc(false);
789 }
790 return {
791 date: d,
792 set: set
793 }
794 }
795
796 // If the year is two digits, add the most appropriate century prefix.
797 function getYearFromAbbreviation(year) {
798 return round(callDateGet(getNewDate(), 'FullYear') / 100) * 100 - round(year / 100) * 100 + year;
799 }
800
801 function getShortHour(d) {
802 var hours = callDateGet(d, 'Hours');
803 return hours === 0 ? 12 : hours - (floor(hours / 13) * 12);
804 }
805
806 // weeksSince won't work here as the result needs to be floored, not rounded.
807 function getWeekNumber(date) {
808 date = date.clone();
809 var dow = callDateGet(date, 'Day') || 7;
810 date.addDays(4 - dow).reset();
811 return 1 + floor(date.daysSince(date.clone().beginningOfYear()) / 7);
812 }
813
814 function getAdjustedUnit(ms) {
815 var next, ams = abs(ms), value = ams, unitIndex = 0;
816 iterateOverDateUnits(function(name, unit, i) {
817 next = floor(withPrecision(ams / unit.multiplier(), 1));
818 if(next >= 1) {
819 value = next;
820 unitIndex = i;
821 }
822 }, 1);
823 return [value, unitIndex, ms];
824 }
825
826 function getRelativeWithMonthFallback(date) {
827 var adu = getAdjustedUnit(date.millisecondsFromNow());
828 if(allowMonthFallback(date, adu)) {
829 // If the adjusted unit is in months, then better to use
830 // the "monthsfromNow" which applies a special error margin
831 // for edge cases such as Jan-09 - Mar-09 being less than
832 // 2 months apart (when using a strict numeric definition).
833 // The third "ms" element in the array will handle the sign
834 // (past or future), so simply take the absolute value here.
835 adu[0] = abs(date.monthsFromNow());
836 adu[1] = 6;
837 }
838 return adu;
839 }
840
841 function allowMonthFallback(date, adu) {
842 // Allow falling back to monthsFromNow if the unit is in months...
843 return adu[1] === 6 ||
844 // ...or if it's === 4 weeks and there are more days than in the given month
845 (adu[1] === 5 && adu[0] === 4 && date.daysFromNow() >= getNewDate().daysInMonth());
846 }
847
848
849 // Date format token helpers
850
851 function createMeridianTokens(slice, caps) {
852 var fn = function(d, localeCode) {
853 var hours = callDateGet(d, 'Hours');
854 return getLocalization(localeCode)['ampm'][floor(hours / 12)] || '';
855 }
856 createFormatToken('t', fn, 1);
857 createFormatToken('tt', fn);
858 createFormatToken('T', fn, 1, 1);
859 createFormatToken('TT', fn, null, 2);
860 }
861
862 function createWeekdayTokens(slice, caps) {
863 var fn = function(d, localeCode) {
864 var dow = callDateGet(d, 'Day');
865 return getLocalization(localeCode)['weekdays'][dow];
866 }
867 createFormatToken('dow', fn, 3);
868 createFormatToken('Dow', fn, 3, 1);
869 createFormatToken('weekday', fn);
870 createFormatToken('Weekday', fn, null, 1);
871 }
872
873 function createMonthTokens(slice, caps) {
874 createMonthToken('mon', 0, 3);
875 createMonthToken('month', 0);
876
877 // For inflected month forms, namely Russian.
878 createMonthToken('month2', 1);
879 createMonthToken('month3', 2);
880 }
881
882 function createMonthToken(token, multiplier, slice) {
883 var fn = function(d, localeCode) {
884 var month = callDateGet(d, 'Month');
885 return getLocalization(localeCode)['months'][month + (multiplier * 12)];
886 };
887 createFormatToken(token, fn, slice);
888 createFormatToken(simpleCapitalize(token), fn, slice, 1);
889 }
890
891 function createFormatToken(t, fn, slice, caps) {
892 DateFormatTokens[t] = function(d, localeCode) {
893 var str = fn(d, localeCode);
894 if(slice) str = str.slice(0, slice);
895 if(caps) str = str.slice(0, caps).toUpperCase() + str.slice(caps);
896 return str;
897 }
898 }
899
900 function createPaddedToken(t, fn, ms) {
901 DateFormatTokens[t] = fn;
902 DateFormatTokens[t + t] = function (d, localeCode) {
903 return padNumber(fn(d, localeCode), 2);
904 };
905 if(ms) {
906 DateFormatTokens[t + t + t] = function (d, localeCode) {
907 return padNumber(fn(d, localeCode), 3);
908 };
909 DateFormatTokens[t + t + t + t] = function (d, localeCode) {
910 return padNumber(fn(d, localeCode), 4);
911 };
912 }
913 }
914
915
916 // Date formatting helpers
917
918 function buildCompiledOutputFormat(format) {
919 var match = format.match(/(\{\w+\})|[^{}]+/g);
920 CompiledOutputFormats[format] = match.map(function(p) {
921 p.replace(/\{(\w+)\}/, function(full, token) {
922 p = DateFormatTokens[token] || token;
923 return token;
924 });
925 return p;
926 });
927 }
928
929 function executeCompiledOutputFormat(date, format, localeCode) {
930 var compiledFormat, length, i, t, result = '';
931 compiledFormat = CompiledOutputFormats[format];
932 for(i = 0, length = compiledFormat.length; i < length; i++) {
933 t = compiledFormat[i];
934 result += isFunction(t) ? t(date, localeCode) : t;
935 }
936 return result;
937 }
938
939 function formatDate(date, format, relative, localeCode) {
940 var adu;
941 if(!date.isValid()) {
942 return 'Invalid Date';
943 } else if(Date[format]) {
944 format = Date[format];
945 } else if(isFunction(format)) {
946 adu = getRelativeWithMonthFallback(date);
947 format = format.apply(date, adu.concat(getLocalization(localeCode)));
948 }
949 if(!format && relative) {
950 adu = adu || getRelativeWithMonthFallback(date);
951 // Adjust up if time is in ms, as this doesn't
952 // look very good for a standard relative date.
953 if(adu[1] === 0) {
954 adu[1] = 1;
955 adu[0] = 1;
956 }
957 return getLocalization(localeCode).getRelativeFormat(adu);
958 }
959 format = format || 'long';
960 if(format === 'short' || format === 'long' || format === 'full') {
961 format = getLocalization(localeCode)[format];
962 }
963
964 if(!CompiledOutputFormats[format]) {
965 buildCompiledOutputFormat(format);
966 }
967
968 return executeCompiledOutputFormat(date, format, localeCode);
969 }
970
971 // Date comparison helpers
972
973 function compareDate(d, find, localeCode, buffer, forceUTC) {
974 var p, t, min, max, override, capitalized, accuracy = 0, loBuffer = 0, hiBuffer = 0;
975 p = getExtendedDate(find, localeCode, null, forceUTC);
976 if(buffer > 0) {
977 loBuffer = hiBuffer = buffer;
978 override = true;
979 }
980 if(!p.date.isValid()) return false;
981 if(p.set && p.set.specificity) {
982 DateUnits.forEach(function(u, i) {
983 if(u.name === p.set.specificity) {
984 accuracy = u.multiplier(p.date, d - p.date) - 1;
985 }
986 });
987 capitalized = simpleCapitalize(p.set.specificity);
988 if(p.set['edge'] || p.set['shift']) {
989 p.date['beginningOf' + capitalized]();
990 }
991 if(p.set.specificity === 'month') {
992 max = p.date.clone()['endOf' + capitalized]().getTime();
993 }
994 if(!override && p.set['sign'] && p.set.specificity != 'millisecond') {
995 // If the time is relative, there can occasionally be an disparity between the relative date
996 // and "now", which it is being compared to, so set an extra buffer to account for this.
997 loBuffer = 50;
998 hiBuffer = -50;
999 }
1000 }
1001 t = d.getTime();
1002 min = p.date.getTime();
1003 max = max || (min + accuracy);
1004 max = compensateForTimezoneTraversal(d, min, max);
1005 return t >= (min - loBuffer) && t <= (max + hiBuffer);
1006 }
1007
1008 function compensateForTimezoneTraversal(d, min, max) {
1009 var dMin, dMax, minOffset, maxOffset;
1010 dMin = new date(min);
1011 dMax = new date(max).utc(d.isUTC());
1012 if(callDateGet(dMax, 'Hours') !== 23) {
1013 minOffset = dMin.getTimezoneOffset();
1014 maxOffset = dMax.getTimezoneOffset();
1015 if(minOffset !== maxOffset) {
1016 max += (maxOffset - minOffset).minutes();
1017 }
1018 }
1019 return max;
1020 }
1021
1022 function updateDate(d, params, reset, advance, prefer) {
1023 var weekday, specificityIndex;
1024
1025 function getParam(key) {
1026 return isDefined(params[key]) ? params[key] : params[key + 's'];
1027 }
1028
1029 function paramExists(key) {
1030 return isDefined(getParam(key));
1031 }
1032
1033 function uniqueParamExists(key, isDay) {
1034 return paramExists(key) || (isDay && paramExists('weekday'));
1035 }
1036
1037 function canDisambiguate() {
1038 switch(prefer) {
1039 case -1: return d > getNewDate();
1040 case 1: return d < getNewDate();
1041 }
1042 }
1043
1044 if(isNumber(params) && advance) {
1045 // If param is a number and we're advancing, the number is presumed to be milliseconds.
1046 params = { 'milliseconds': params };
1047 } else if(isNumber(params)) {
1048 // Otherwise just set the timestamp and return.
1049 d.setTime(params);
1050 return d;
1051 }
1052
1053 // "date" can also be passed for the day
1054 if(isDefined(params['date'])) {
1055 params['day'] = params['date'];
1056 }
1057
1058 // Reset any unit lower than the least specific unit set. Do not do this for weeks
1059 // or for years. This needs to be performed before the acutal setting of the date
1060 // because the order needs to be reversed in order to get the lowest specificity,
1061 // also because higher order units can be overwritten by lower order units, such
1062 // as setting hour: 3, minute: 345, etc.
1063 iterateOverDateUnits(function(name, unit, i) {
1064 var isDay = name === 'day';
1065 if(uniqueParamExists(name, isDay)) {
1066 params.specificity = name;
1067 specificityIndex = +i;
1068 return false;
1069 } else if(reset && name !== 'week' && (!isDay || !paramExists('week'))) {
1070 // Days are relative to months, not weeks, so don't reset if a week exists.
1071 callDateSet(d, unit.method, (isDay ? 1 : 0));
1072 }
1073 });
1074
1075 // Now actually set or advance the date in order, higher units first.
1076 DateUnits.forEach(function(u, i) {
1077 var name = u.name, method = u.method, higherUnit = DateUnits[i - 1], value;
1078 value = getParam(name)
1079 if(isUndefined(value)) return;
1080 if(advance) {
1081 if(name === 'week') {
1082 value = (params['day'] || 0) + (value * 7);
1083 method = 'Date';
1084 }
1085 value = (value * advance) + callDateGet(d, method);
1086 } else if(name === 'month' && paramExists('day')) {
1087 // When setting the month, there is a chance that we will traverse into a new month.
1088 // This happens in DST shifts, for example June 1st DST jumping to January 1st
1089 // (non-DST) will have a shift of -1:00 which will traverse into the previous year.
1090 // Prevent this by proactively setting the day when we know it will be set again anyway.
1091 // It can also happen when there are not enough days in the target month. This second
1092 // situation is identical to checkMonthTraversal below, however when we are advancing
1093 // we want to reset the date to "the last date in the target month". In the case of
1094 // DST shifts, however, we want to avoid the "edges" of months as that is where this
1095 // unintended traversal can happen. This is the reason for the different handling of
1096 // two similar but slightly different situations.
1097 //
1098 // TL;DR This method avoids the edges of a month IF not advancing and the date is going
1099 // to be set anyway, while checkMonthTraversal resets the date to the last day if advancing.
1100 //
1101 callDateSet(d, 'Date', 15);
1102 }
1103 callDateSet(d, method, value);
1104 if(advance && name === 'month') {
1105 checkMonthTraversal(d, value);
1106 }
1107 });
1108
1109
1110 // If a weekday is included in the params, set it ahead of time and set the params
1111 // to reflect the updated date so that resetting works properly.
1112 if(!advance && !paramExists('day') && paramExists('weekday')) {
1113 var weekday = getParam('weekday'), isAhead, futurePreferred;
1114 d.setWeekday(weekday);
1115 }
1116
1117 // If past or future is preferred, then the process of "disambiguation" will ensure that an
1118 // ambiguous time/date ("4pm", "thursday", "June", etc.) will be in the past or future.
1119 if(canDisambiguate()) {
1120 iterateOverDateUnits(function(name, unit) {
1121 var ambiguous = unit.ambiguous || (name === 'week' && paramExists('weekday'));
1122 if(ambiguous && !uniqueParamExists(name, name === 'day')) {
1123 d[unit.addMethod](prefer);
1124 return false;
1125 }
1126 }, specificityIndex + 1);
1127 }
1128 return d;
1129 }
1130
1131 // The ISO format allows times strung together without a demarcating ":", so make sure
1132 // that these markers are now optional.
1133 function prepareTime(format, loc, iso) {
1134 var timeSuffixMapping = {'h':0,'m':1,'s':2}, add;
1135 loc = loc || English;
1136 return format.replace(/{([a-z])}/g, function(full, token) {
1137 var separators = [],
1138 isHours = token === 'h',
1139 tokenIsRequired = isHours && !iso;
1140 if(token === 't') {
1141 return loc['ampm'].join('|');
1142 } else {
1143 if(isHours) {
1144 separators.push(':');
1145 }
1146 if(add = loc['timeSuffixes'][timeSuffixMapping[token]]) {
1147 separators.push(add + '\\s*');
1148 }
1149 return separators.length === 0 ? '' : '(?:' + separators.join('|') + ')' + (tokenIsRequired ? '' : '?');
1150 }
1151 });
1152 }
1153
1154
1155 // If the month is being set, then we don't want to accidentally
1156 // traverse into a new month just because the target month doesn't have enough
1157 // days. In other words, "5 months ago" from July 30th is still February, even
1158 // though there is no February 30th, so it will of necessity be February 28th
1159 // (or 29th in the case of a leap year).
1160
1161 function checkMonthTraversal(date, targetMonth) {
1162 if(targetMonth < 0) {
1163 targetMonth = targetMonth % 12 + 12;
1164 }
1165 if(targetMonth % 12 != callDateGet(date, 'Month')) {
1166 callDateSet(date, 'Date', 0);
1167 }
1168 }
1169
1170 function createDate(args, prefer, forceUTC) {
1171 var f, localeCode;
1172 if(isNumber(args[1])) {
1173 // If the second argument is a number, then we have an enumerated constructor type as in "new Date(2003, 2, 12);"
1174 f = collectDateArguments(args)[0];
1175 } else {
1176 f = args[0];
1177 localeCode = args[1];
1178 }
1179 return getExtendedDate(f, localeCode, prefer, forceUTC).date;
1180 }
1181
1182 function invalidateDate(d) {
1183 d.setTime(NaN);
1184 }
1185
1186 function buildDateUnits() {
1187 DateUnitsReversed = DateUnits.concat().reverse();
1188 DateArgumentUnits = DateUnits.concat();
1189 DateArgumentUnits.splice(2,1);
1190 }
1191
1192
1193 /***
1194 * @method [units]Since([d], [locale] = currentLocale)
1195 * @returns Number
1196 * @short Returns the time since [d] in the appropriate unit.
1197 * @extra [d] will accept a date object, timestamp, or text format. If not specified, [d] is assumed to be now. [locale] can be passed to specify the locale that the date is in. %[unit]Ago% is provided as an alias to make this more readable when [d] is assumed to be the current date. For more see @date_format.
1198 *
1199 * @set
1200 * millisecondsSince
1201 * secondsSince
1202 * minutesSince
1203 * hoursSince
1204 * daysSince
1205 * weeksSince
1206 * monthsSince
1207 * yearsSince
1208 *
1209 * @example
1210 *
1211 * Date.create().millisecondsSince('1 hour ago') -> 3,600,000
1212 * Date.create().daysSince('1 week ago') -> 7
1213 * Date.create().yearsSince('15 years ago') -> 15
1214 * Date.create('15 years ago').yearsAgo() -> 15
1215 *
1216 ***
1217 * @method [units]Ago()
1218 * @returns Number
1219 * @short Returns the time ago in the appropriate unit.
1220 *
1221 * @set
1222 * millisecondsAgo
1223 * secondsAgo
1224 * minutesAgo
1225 * hoursAgo
1226 * daysAgo
1227 * weeksAgo
1228 * monthsAgo
1229 * yearsAgo
1230 *
1231 * @example
1232 *
1233 * Date.create('last year').millisecondsAgo() -> 3,600,000
1234 * Date.create('last year').daysAgo() -> 7
1235 * Date.create('last year').yearsAgo() -> 15
1236 *
1237 ***
1238 * @method [units]Until([d], [locale] = currentLocale)
1239 * @returns Number
1240 * @short Returns the time until [d] in the appropriate unit.
1241 * @extra [d] will accept a date object, timestamp, or text format. If not specified, [d] is assumed to be now. [locale] can be passed to specify the locale that the date is in. %[unit]FromNow% is provided as an alias to make this more readable when [d] is assumed to be the current date. For more see @date_format.
1242 *
1243 * @set
1244 * millisecondsUntil
1245 * secondsUntil
1246 * minutesUntil
1247 * hoursUntil
1248 * daysUntil
1249 * weeksUntil
1250 * monthsUntil
1251 * yearsUntil
1252 *
1253 * @example
1254 *
1255 * Date.create().millisecondsUntil('1 hour from now') -> 3,600,000
1256 * Date.create().daysUntil('1 week from now') -> 7
1257 * Date.create().yearsUntil('15 years from now') -> 15
1258 * Date.create('15 years from now').yearsFromNow() -> 15
1259 *
1260 ***
1261 * @method [units]FromNow()
1262 * @returns Number
1263 * @short Returns the time from now in the appropriate unit.
1264 *
1265 * @set
1266 * millisecondsFromNow
1267 * secondsFromNow
1268 * minutesFromNow
1269 * hoursFromNow
1270 * daysFromNow
1271 * weeksFromNow
1272 * monthsFromNow
1273 * yearsFromNow
1274 *
1275 * @example
1276 *
1277 * Date.create('next year').millisecondsFromNow() -> 3,600,000
1278 * Date.create('next year').daysFromNow() -> 7
1279 * Date.create('next year').yearsFromNow() -> 15
1280 *
1281 ***
1282 * @method add[Units](<num>, [reset] = false)
1283 * @returns Date
1284 * @short Adds <num> of the unit to the date. If [reset] is true, all lower units will be reset.
1285 * @extra 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 last day of the month. Don't use %addMonths% if you need precision.
1286 *
1287 * @set
1288 * addMilliseconds
1289 * addSeconds
1290 * addMinutes
1291 * addHours
1292 * addDays
1293 * addWeeks
1294 * addMonths
1295 * addYears
1296 *
1297 * @example
1298 *
1299 * Date.create().addMilliseconds(5) -> current time + 5 milliseconds
1300 * Date.create().addDays(5) -> current time + 5 days
1301 * Date.create().addYears(5) -> current time + 5 years
1302 *
1303 ***
1304 * @method isLast[Unit]()
1305 * @returns Boolean
1306 * @short Returns true if the date is last week/month/year.
1307 *
1308 * @set
1309 * isLastWeek
1310 * isLastMonth
1311 * isLastYear
1312 *
1313 * @example
1314 *
1315 * Date.create('yesterday').isLastWeek() -> true or false?
1316 * Date.create('yesterday').isLastMonth() -> probably not...
1317 * Date.create('yesterday').isLastYear() -> even less likely...
1318 *
1319 ***
1320 * @method isThis[Unit]()
1321 * @returns Boolean
1322 * @short Returns true if the date is this week/month/year.
1323 *
1324 * @set
1325 * isThisWeek
1326 * isThisMonth
1327 * isThisYear
1328 *
1329 * @example
1330 *
1331 * Date.create('tomorrow').isThisWeek() -> true or false?
1332 * Date.create('tomorrow').isThisMonth() -> probably...
1333 * Date.create('tomorrow').isThisYear() -> signs point to yes...
1334 *
1335 ***
1336 * @method isNext[Unit]()
1337 * @returns Boolean
1338 * @short Returns true if the date is next week/month/year.
1339 *
1340 * @set
1341 * isNextWeek
1342 * isNextMonth
1343 * isNextYear
1344 *
1345 * @example
1346 *
1347 * Date.create('tomorrow').isNextWeek() -> true or false?
1348 * Date.create('tomorrow').isNextMonth() -> probably not...
1349 * Date.create('tomorrow').isNextYear() -> even less likely...
1350 *
1351 ***
1352 * @method beginningOf[Unit]()
1353 * @returns Date
1354 * @short Sets the date to the beginning of the appropriate unit.
1355 *
1356 * @set
1357 * beginningOfDay
1358 * beginningOfWeek
1359 * beginningOfMonth
1360 * beginningOfYear
1361 *
1362 * @example
1363 *
1364 * Date.create().beginningOfDay() -> the beginning of today (resets the time)
1365 * Date.create().beginningOfWeek() -> the beginning of the week
1366 * Date.create().beginningOfMonth() -> the beginning of the month
1367 * Date.create().beginningOfYear() -> the beginning of the year
1368 *
1369 ***
1370 * @method endOf[Unit]()
1371 * @returns Date
1372 * @short Sets the date to the end of the appropriate unit.
1373 *
1374 * @set
1375 * endOfDay
1376 * endOfWeek
1377 * endOfMonth
1378 * endOfYear
1379 *
1380 * @example
1381 *
1382 * Date.create().endOfDay() -> the end of today (sets the time to 23:59:59.999)
1383 * Date.create().endOfWeek() -> the end of the week
1384 * Date.create().endOfMonth() -> the end of the month
1385 * Date.create().endOfYear() -> the end of the year
1386 *
1387 ***/
1388
1389 function buildDateMethods() {
1390 extendSimilar(date, true, true, DateUnits, function(methods, u, i) {
1391 var name = u.name, caps = simpleCapitalize(name), multiplier = u.multiplier(), since, until;
1392 u.addMethod = 'add' + caps + 's';
1393 // "since/until now" only count "past" an integer, i.e. "2 days ago" is
1394 // anything between 2 - 2.999 days. The default margin of error is 0.999,
1395 // but "months" have an inherently larger margin, as the number of days
1396 // in a given month may be significantly less than the number of days in
1397 // the average month, so for example "30 days" before March 15 may in fact
1398 // be 1 month ago. Years also have a margin of error due to leap years,
1399 // but this is roughly 0.999 anyway (365 / 365.25). Other units do not
1400 // technically need the error margin applied to them but this accounts
1401 // for discrepancies like (15).hoursAgo() which technically creates the
1402 // current date first, then creates a date 15 hours before and compares
1403 // them, the discrepancy between the creation of the 2 dates means that
1404 // they may actually be 15.0001 hours apart. Milliseconds don't have
1405 // fractions, so they won't be subject to this error margin.
1406 function applyErrorMargin(ms) {
1407 var num = ms / multiplier,
1408 fraction = num % 1,
1409 error = u.error || 0.999;
1410 if(fraction && abs(fraction % 1) > error) {
1411 num = round(num);
1412 }
1413 return num < 0 ? ceil(num) : floor(num);
1414 }
1415 since = function(f, localeCode) {
1416 return applyErrorMargin(this.getTime() - date.create(f, localeCode).getTime());
1417 };
1418 until = function(f, localeCode) {
1419 return applyErrorMargin(date.create(f, localeCode).getTime() - this.getTime());
1420 };
1421 methods[name+'sAgo'] = until;
1422 methods[name+'sUntil'] = until;
1423 methods[name+'sSince'] = since;
1424 methods[name+'sFromNow'] = since;
1425 methods[u.addMethod] = function(num, reset) {
1426 var set = {};
1427 set[name] = num;
1428 return this.advance(set, reset);
1429 };
1430 buildNumberToDateAlias(u, multiplier);
1431 if(i < 3) {
1432 ['Last','This','Next'].forEach(function(shift) {
1433 methods['is' + shift + caps] = function() {
1434 return compareDate(this, shift + ' ' + name, 'en');
1435 };
1436 });
1437 }
1438 if(i < 4) {
1439 methods['beginningOf' + caps] = function() {
1440 var set = {};
1441 switch(name) {
1442 case 'year': set['year'] = callDateGet(this, 'FullYear'); break;
1443 case 'month': set['month'] = callDateGet(this, 'Month'); break;
1444 case 'day': set['day'] = callDateGet(this, 'Date'); break;
1445 case 'week': set['weekday'] = 0; break;
1446 }
1447 return this.set(set, true);
1448 };
1449 methods['endOf' + caps] = function() {
1450 var set = { 'hours': 23, 'minutes': 59, 'seconds': 59, 'milliseconds': 999 };
1451 switch(name) {
1452 case 'year': set['month'] = 11; set['day'] = 31; break;
1453 case 'month': set['day'] = this.daysInMonth(); break;
1454 case 'week': set['weekday'] = 6; break;
1455 }
1456 return this.set(set, true);
1457 };
1458 }
1459 });
1460 }
1461
1462 function buildCoreInputFormats() {
1463 English.addFormat('([+-])?(\\d{4,4})[-.]?{full_month}[-.]?(\\d{1,2})?', true, ['year_sign','year','month','date'], false, true);
1464 English.addFormat('(\\d{1,2})[-.\\/]{full_month}(?:[-.\\/](\\d{2,4}))?', true, ['date','month','year'], true);
1465 English.addFormat('{full_month}[-.](\\d{4,4})', false, ['month','year']);
1466 English.addFormat('\\/Date\\((\\d+(?:[+-]\\d{4,4})?)\\)\\/', false, ['timestamp'])
1467 English.addFormat(prepareTime(RequiredTime, English), false, TimeFormat)
1468
1469 // When a new locale is initialized it will have the CoreDateFormats initialized by default.
1470 // From there, adding new formats will push them in front of the previous ones, so the core
1471 // formats will be the last to be reached. However, the core formats themselves have English
1472 // months in them, which means that English needs to first be initialized and creates a race
1473 // condition. I'm getting around this here by adding these generalized formats in the order
1474 // specific -> general, which will mean they will be added to the English localization in
1475 // general -> specific order, then chopping them off the front and reversing to get the correct
1476 // order. Note that there are 7 formats as 2 have times which adds a front and a back format.
1477 CoreDateFormats = English.compiledFormats.slice(0,7).reverse();
1478 English.compiledFormats = English.compiledFormats.slice(7).concat(CoreDateFormats);
1479 }
1480
1481 function buildFormatTokens() {
1482
1483 createPaddedToken('f', function(d) {
1484 return callDateGet(d, 'Milliseconds');
1485 }, true);
1486
1487 createPaddedToken('s', function(d) {
1488 return callDateGet(d, 'Seconds');
1489 });
1490
1491 createPaddedToken('m', function(d) {
1492 return callDateGet(d, 'Minutes');
1493 });
1494
1495 createPaddedToken('h', function(d) {
1496 return callDateGet(d, 'Hours') % 12 || 12;
1497 });
1498
1499 createPaddedToken('H', function(d) {
1500 return callDateGet(d, 'Hours');
1501 });
1502
1503 createPaddedToken('d', function(d) {
1504 return callDateGet(d, 'Date');
1505 });
1506
1507 createPaddedToken('M', function(d) {
1508 return callDateGet(d, 'Month') + 1;
1509 });
1510
1511 createMeridianTokens();
1512 createWeekdayTokens();
1513 createMonthTokens();
1514
1515 // Aliases
1516 DateFormatTokens['ms'] = DateFormatTokens['f'];
1517 DateFormatTokens['milliseconds'] = DateFormatTokens['f'];
1518 DateFormatTokens['seconds'] = DateFormatTokens['s'];
1519 DateFormatTokens['minutes'] = DateFormatTokens['m'];
1520 DateFormatTokens['hours'] = DateFormatTokens['h'];
1521 DateFormatTokens['24hr'] = DateFormatTokens['H'];
1522 DateFormatTokens['12hr'] = DateFormatTokens['h'];
1523 DateFormatTokens['date'] = DateFormatTokens['d'];
1524 DateFormatTokens['day'] = DateFormatTokens['d'];
1525 DateFormatTokens['year'] = DateFormatTokens['yyyy'];
1526
1527 }
1528
1529 function buildFormatShortcuts() {
1530 extendSimilar(date, true, true, 'short,long,full', function(methods, name) {
1531 methods[name] = function(localeCode) {
1532 return formatDate(this, name, false, localeCode);
1533 }
1534 });
1535 }
1536
1537 function buildAsianDigits() {
1538 KanjiDigits.split('').forEach(function(digit, value) {
1539 var holder;
1540 if(value > 9) {
1541 value = pow(10, value - 9);
1542 }
1543 AsianDigitMap[digit] = value;
1544 });
1545 simpleMerge(AsianDigitMap, NumberNormalizeMap);
1546 // Kanji numerals may also be included in phrases which are text-based rather
1547 // than actual numbers such as Chinese weekdays (上周三), and "the day before
1548 // yesterday" (一昨日) in Japanese, so don't match these.
1549 AsianDigitReg = regexp('([期週周])?([' + KanjiDigits + FullWidthDigits + ']+)(?!昨)', 'g');
1550 }
1551
1552 /***
1553 * @method is[Day]()
1554 * @returns Boolean
1555 * @short Returns true if the date falls on that day.
1556 * @extra Also available: %isYesterday%, %isToday%, %isTomorrow%, %isWeekday%, and %isWeekend%.
1557 *
1558 * @set
1559 * isToday
1560 * isYesterday
1561 * isTomorrow
1562 * isWeekday
1563 * isWeekend
1564 * isSunday
1565 * isMonday
1566 * isTuesday
1567 * isWednesday
1568 * isThursday
1569 * isFriday
1570 * isSaturday
1571 *
1572 * @example
1573 *
1574 * Date.create('tomorrow').isToday() -> false
1575 * Date.create('thursday').isTomorrow() -> ?
1576 * Date.create('yesterday').isWednesday() -> ?
1577 * Date.create('today').isWeekend() -> ?
1578 *
1579 ***
1580 * @method isFuture()
1581 * @returns Boolean
1582 * @short Returns true if the date is in the future.
1583 * @example
1584 *
1585 * Date.create('next week').isFuture() -> true
1586 * Date.create('last week').isFuture() -> false
1587 *
1588 ***
1589 * @method isPast()
1590 * @returns Boolean
1591 * @short Returns true if the date is in the past.
1592 * @example
1593 *
1594 * Date.create('last week').isPast() -> true
1595 * Date.create('next week').isPast() -> false
1596 *
1597 ***/
1598 function buildRelativeAliases() {
1599 var special = 'today,yesterday,tomorrow,weekday,weekend,future,past'.split(',');
1600 var weekdays = English['weekdays'].slice(0,7);
1601 var months = English['months'].slice(0,12);
1602 extendSimilar(date, true, true, special.concat(weekdays).concat(months), function(methods, name) {
1603 methods['is'+ simpleCapitalize(name)] = function(utc) {
1604 return this.is(name, 0, utc);
1605 };
1606 });
1607 }
1608
1609 function buildUTCAliases() {
1610 // Don't want to use extend here as it will override
1611 // the actual "utc" method on the prototype.
1612 if(date['utc']) return;
1613 date['utc'] = {
1614
1615 'create': function() {
1616 return createDate(arguments, 0, true);
1617 },
1618
1619 'past': function() {
1620 return createDate(arguments, -1, true);
1621 },
1622
1623 'future': function() {
1624 return createDate(arguments, 1, true);
1625 }
1626 };
1627 }
1628
1629 function setDateProperties() {
1630 extend(date, false , true, {
1631 'RFC1123': '{Dow}, {dd} {Mon} {yyyy} {HH}:{mm}:{ss} {tz}',
1632 'RFC1036': '{Weekday}, {dd}-{Mon}-{yy} {HH}:{mm}:{ss} {tz}',
1633 'ISO8601_DATE': '{yyyy}-{MM}-{dd}',
1634 'ISO8601_DATETIME': '{yyyy}-{MM}-{dd}T{HH}:{mm}:{ss}.{fff}{isotz}'
1635 });
1636 }
1637
1638
1639 extend(date, false, true, {
1640
1641 /***
1642 * @method Date.create(<d>, [locale] = currentLocale)
1643 * @returns Date
1644 * @short Alternate Date constructor which understands many different text formats, a timestamp, or another date.
1645 * @extra If no argument is given, date is assumed to be now. %Date.create% additionally can accept enumerated parameters as with the standard date constructor. [locale] can be passed to specify the locale that the date is in. When unspecified, the current locale (default is English) is assumed. UTC-based dates can be created through the %utc% object. For more see @date_format.
1646 * @set
1647 * Date.utc.create
1648 *
1649 * @example
1650 *
1651 * Date.create('July') -> July of this year
1652 * Date.create('1776') -> 1776
1653 * Date.create('today') -> today
1654 * Date.create('wednesday') -> This wednesday
1655 * Date.create('next friday') -> Next friday
1656 * Date.create('July 4, 1776') -> July 4, 1776
1657 * Date.create(-446806800000) -> November 5, 1955
1658 * Date.create(1776, 6, 4) -> July 4, 1776
1659 * Date.create('1776年07月04日', 'ja') -> July 4, 1776
1660 * Date.utc.create('July 4, 1776', 'en') -> July 4, 1776
1661 *
1662 ***/
1663 'create': function() {
1664 return createDate(arguments);
1665 },
1666
1667 /***
1668 * @method Date.past(<d>, [locale] = currentLocale)
1669 * @returns Date
1670 * @short Alternate form of %Date.create% with any ambiguity assumed to be the past.
1671 * @extra For example %"Sunday"% can be either "the Sunday coming up" or "the 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.
1672 * @set
1673 * Date.utc.past
1674 *
1675 * @example
1676 *
1677 * Date.past('July') -> July of this year or last depending on the current month
1678 * Date.past('Wednesday') -> This wednesday or last depending on the current weekday
1679 *
1680 ***/
1681 'past': function() {
1682 return createDate(arguments, -1);
1683 },
1684
1685 /***
1686 * @method Date.future(<d>, [locale] = currentLocale)
1687 * @returns Date
1688 * @short Alternate form of %Date.create% with any ambiguity assumed to be the future.
1689 * @extra For example %"Sunday"% can be either "the Sunday coming up" or "the Sunday last" depending on context. Note that dates explicitly in the past ("last Sunday") will remain in the past. This method simply provides a hint when ambiguity exists. UTC-based dates can be created through the %utc% object. For more, see @date_format.
1690 * @set
1691 * Date.utc.future
1692 *
1693 * @example
1694 *
1695 * Date.future('July') -> July of this year or next depending on the current month
1696 * Date.future('Wednesday') -> This wednesday or next depending on the current weekday
1697 *
1698 ***/
1699 'future': function() {
1700 return createDate(arguments, 1);
1701 },
1702
1703 /***
1704 * @method Date.addLocale(<code>, <set>)
1705 * @returns Locale
1706 * @short Adds a locale <set> to the locales understood by Sugar.
1707 * @extra For more see @date_format.
1708 *
1709 ***/
1710 'addLocale': function(localeCode, set) {
1711 return setLocalization(localeCode, set);
1712 },
1713
1714 /***
1715 * @method Date.setLocale(<code>)
1716 * @returns Locale
1717 * @short Sets the current locale to be used with dates.
1718 * @extra Sugar has support for 13 locales that are available through the "Date Locales" package. In addition you can define a new locale with %Date.addLocale%. For more see @date_format.
1719 *
1720 ***/
1721 'setLocale': function(localeCode, set) {
1722 var loc = getLocalization(localeCode, false);
1723 CurrentLocalization = loc;
1724 // The code is allowed to be more specific than the codes which are required:
1725 // i.e. zh-CN or en-US. Currently this only affects US date variants such as 8/10/2000.
1726 if(localeCode && localeCode != loc['code']) {
1727 loc['code'] = localeCode;
1728 }
1729 return loc;
1730 },
1731
1732 /***
1733 * @method Date.getLocale([code] = current)
1734 * @returns Locale
1735 * @short Gets the locale for the given code, or the current locale.
1736 * @extra The resulting locale object can be manipulated to provide more control over date localizations. For more about locales, see @date_format.
1737 *
1738 ***/
1739 'getLocale': function(localeCode) {
1740 return !localeCode ? CurrentLocalization : getLocalization(localeCode, false);
1741 },
1742
1743 /**
1744 * @method Date.addFormat(<format>, <match>, [code] = null)
1745 * @returns Nothing
1746 * @short Manually adds a new date input format.
1747 * @extra This method allows fine grained control for alternate formats. <format> 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.
1748 *
1749 **/
1750 'addFormat': function(format, match, localeCode) {
1751 addDateInputFormat(getLocalization(localeCode), format, match);
1752 }
1753
1754 });
1755
1756 extend(date, true, true, {
1757
1758 /***
1759 * @method set(<set>, [reset] = false)
1760 * @returns Date
1761 * @short Sets the date object.
1762 * @extra This method can accept multiple formats including a single number as a timestamp, an object, or enumerated parameters (as with the Date constructor). If [reset] is %true%, any units more specific than those passed will be reset.
1763 *
1764 * @example
1765 *
1766 * new Date().set({ year: 2011, month: 11, day: 31 }) -> December 31, 2011
1767 * new Date().set(2011, 11, 31) -> December 31, 2011
1768 * new Date().set(86400000) -> 1 day after Jan 1, 1970
1769 * new Date().set({ year: 2004, month: 6 }, true) -> June 1, 2004, 00:00:00.000
1770 *
1771 ***/
1772 'set': function() {
1773 var args = collectDateArguments(arguments);
1774 return updateDate(this, args[0], args[1])
1775 },
1776
1777 /***
1778 * @method setWeekday()
1779 * @returns Nothing
1780 * @short Sets the weekday of the date.
1781 * @extra In order to maintain a parallel with %getWeekday% (which itself is an alias for Javascript native %getDay%), Sunday is considered day %0%. This contrasts with ISO-8601 standard (used in %getISOWeek% and %setISOWeek%) which places 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 passing %7% will advance it.
1782 *
1783 * @example
1784 *
1785 * d = new Date(); d.setWeekday(1); d; -> Monday of this week
1786 * d = new Date(); d.setWeekday(6); d; -> Saturday of this week
1787 *
1788 ***/
1789 'setWeekday': function(dow) {
1790 if(isUndefined(dow)) return;
1791 return callDateSet(this, 'Date', callDateGet(this, 'Date') + dow - callDateGet(this, 'Day'));
1792 },
1793
1794 /***
1795 * @method setISOWeek()
1796 * @returns Nothing
1797 * @short Sets the week (of the year) as defined by the ISO-8601 standard.
1798 * @extra Note that this standard places Sunday at the end of the week (day 7).
1799 *
1800 * @example
1801 *
1802 * d = new Date(); d.setISOWeek(15); d; -> 15th week of the year
1803 *
1804 ***/
1805 'setISOWeek': function(week) {
1806 var weekday = callDateGet(this, 'Day') || 7;
1807 if(isUndefined(week)) return;
1808 this.set({ 'month': 0, 'date': 4 });
1809 this.set({ 'weekday': 1 });
1810 if(week > 1) {
1811 this.addWeeks(week - 1);
1812 }
1813 if(weekday !== 1) {
1814 this.advance({ 'days': weekday - 1 });
1815 }
1816 return this.getTime();
1817 },
1818
1819 /***
1820 * @method getISOWeek()
1821 * @returns Number
1822 * @short Gets the date's week (of the year) as defined by the ISO-8601 standard.
1823 * @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.
1824 *
1825 * @example
1826 *
1827 * new Date().getISOWeek() -> today's week of the year
1828 *
1829 ***/
1830 'getISOWeek': function() {
1831 return getWeekNumber(this);
1832 },
1833
1834 /***
1835 * @method beginningOfISOWeek()
1836 * @returns Date
1837 * @short Set the date to the beginning of week as defined by this ISO-8601 standard.
1838 * @extra Note that this standard places Monday at the start of the week.
1839 * @example
1840 *
1841 * Date.create().beginningOfISOWeek() -> Monday
1842 *
1843 ***/
1844 'beginningOfISOWeek': function() {
1845 var day = this.getDay();
1846 if(day === 0) {
1847 day = -6;
1848 } else if(day !== 1) {
1849 day = 1;
1850 }
1851 this.setWeekday(day);
1852 return this.reset();
1853 },
1854
1855 /***
1856 * @method endOfISOWeek()
1857 * @returns Date
1858 * @short Set the date to the end of week as defined by this ISO-8601 standard.
1859 * @extra Note that this standard places Sunday at the end of the week.
1860 * @example
1861 *
1862 * Date.create().endOfISOWeek() -> Sunday
1863 *
1864 ***/
1865 'endOfISOWeek': function() {
1866 if(this.getDay() !== 0) {
1867 this.setWeekday(7);
1868 }
1869 return this.endOfDay()
1870 },
1871
1872 /***
1873 * @method getUTCOffset([iso])
1874 * @returns String
1875 * @short Returns a string representation of the offset from UTC time. If [iso] is true the offset will be in ISO8601 format.
1876 * @example
1877 *
1878 * new Date().getUTCOffset() -> "+0900"
1879 * new Date().getUTCOffset(true) -> "+09:00"
1880 *
1881 ***/
1882 'getUTCOffset': function(iso) {
1883 var offset = this._utc ? 0 : this.getTimezoneOffset();
1884 var colon = iso === true ? ':' : '';
1885 if(!offset && iso) return 'Z';
1886 return padNumber(floor(-offset / 60), 2, true) + colon + padNumber(abs(offset % 60), 2);
1887 },
1888
1889 /***
1890 * @method utc([on] = true)
1891 * @returns Date
1892 * @short Sets the internal utc flag for the date. When on, UTC-based methods will be called internally.
1893 * @extra For more see @date_format.
1894 * @example
1895 *
1896 * new Date().utc(true)
1897 * new Date().utc(false)
1898 *
1899 ***/
1900 'utc': function(set) {
1901 defineProperty(this, '_utc', set === true || arguments.length === 0);
1902 return this;
1903 },
1904
1905 /***
1906 * @method isUTC()
1907 * @returns Boolean
1908 * @short Returns true if the date has no timezone offset.
1909 * @extra This will also return true for utc-based dates (dates that have the %utc% method set true). Note that even if the utc flag is set, %getTimezoneOffset% will always report the same thing as Javascript always reports that based on the environment's locale.
1910 * @example
1911 *
1912 * new Date().isUTC() -> true or false?
1913 * new Date().utc(true).isUTC() -> true
1914 *
1915 ***/
1916 'isUTC': function() {
1917 return !!this._utc || this.getTimezoneOffset() === 0;
1918 },
1919
1920 /***
1921 * @method advance(<set>, [reset] = false)
1922 * @returns Date
1923 * @short Sets the date forward.
1924 * @extra This method can accept multiple formats including an object, a string in the format %3 days%, a single number as milliseconds, or enumerated parameters (as with the Date constructor). If [reset] is %true%, any units more specific than those passed will be reset. For more see @date_format.
1925 * @example
1926 *
1927 * new Date().advance({ year: 2 }) -> 2 years in the future
1928 * new Date().advance('2 days') -> 2 days in the future
1929 * new Date().advance(0, 2, 3) -> 2 months 3 days in the future
1930 * new Date().advance(86400000) -> 1 day in the future
1931 *
1932 ***/
1933 'advance': function() {
1934 var args = collectDateArguments(arguments, true);
1935 return updateDate(this, args[0], args[1], 1);
1936 },
1937
1938 /***
1939 * @method rewind(<set>, [reset] = false)
1940 * @returns Date
1941 * @short Sets the date back.
1942 * @extra This method can accept multiple formats including a single number as a timestamp, an object, or enumerated parameters (as with the Date constructor). If [reset] is %true%, any units more specific than those passed will be reset. For more see @date_format.
1943 * @example
1944 *
1945 * new Date().rewind({ year: 2 }) -> 2 years in the past
1946 * new Date().rewind(0, 2, 3) -> 2 months 3 days in the past
1947 * new Date().rewind(86400000) -> 1 day in the past
1948 *
1949 ***/
1950 'rewind': function() {
1951 var args = collectDateArguments(arguments, true);
1952 return updateDate(this, args[0], args[1], -1);
1953 },
1954
1955 /***
1956 * @method isValid()
1957 * @returns Boolean
1958 * @short Returns true if the date is valid.
1959 * @example
1960 *
1961 * new Date().isValid() -> true
1962 * new Date('flexor').isValid() -> false
1963 *
1964 ***/
1965 'isValid': function() {
1966 return !isNaN(this.getTime());
1967 },
1968
1969 /***
1970 * @method isAfter(<d>, [margin] = 0)
1971 * @returns Boolean
1972 * @short Returns true if the date is after the <d>.
1973 * @extra [margin] is to allow extra margin of error (in ms). <d> will accept a date object, timestamp, or text format. If not specified, <d> is assumed to be now. See @date_format for more.
1974 * @example
1975 *
1976 * new Date().isAfter('tomorrow') -> false
1977 * new Date().isAfter('yesterday') -> true
1978 *
1979 ***/
1980 'isAfter': function(d, margin, utc) {
1981 return this.getTime() > date.create(d).getTime() - (margin || 0);
1982 },
1983
1984 /***
1985 * @method isBefore(<d>, [margin] = 0)
1986 * @returns Boolean
1987 * @short Returns true if the date is before <d>.
1988 * @extra [margin] is to allow extra margin of error (in ms). <d> will accept a date object, timestamp, or text format. If not specified, <d> is assumed to be now. See @date_format for more.
1989 * @example
1990 *
1991 * new Date().isBefore('tomorrow') -> true
1992 * new Date().isBefore('yesterday') -> false
1993 *
1994 ***/
1995 'isBefore': function(d, margin) {
1996 return this.getTime() < date.create(d).getTime() + (margin || 0);
1997 },
1998
1999 /***
2000 * @method isBetween(<d1>, <d2>, [margin] = 0)
2001 * @returns Boolean
2002 * @short Returns true if the date falls between <d1> and <d2>.
2003 * @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.
2004 * @example
2005 *
2006 * new Date().isBetween('yesterday', 'tomorrow') -> true
2007 * new Date().isBetween('last year', '2 years ago') -> false
2008 *
2009 ***/
2010 'isBetween': function(d1, d2, margin) {
2011 var t = this.getTime();
2012 var t1 = date.create(d1).getTime();
2013 var t2 = date.create(d2).getTime();
2014 var lo = min(t1, t2);
2015 var hi = max(t1, t2);
2016 margin = margin || 0;
2017 return (lo - margin < t) && (hi + margin > t);
2018 },
2019
2020 /***
2021 * @method isLeapYear()
2022 * @returns Boolean
2023 * @short Returns true if the date is a leap year.
2024 * @example
2025 *
2026 * Date.create('2000').isLeapYear() -> true
2027 *
2028 ***/
2029 'isLeapYear': function() {
2030 var year = callDateGet(this, 'FullYear');
2031 return (year % 4 === 0 && year % 100 !== 0) || (year % 400 === 0);
2032 },
2033
2034 /***
2035 * @method daysInMonth()
2036 * @returns Number
2037 * @short Returns the number of days in the date's month.
2038 * @example
2039 *
2040 * Date.create('May').daysInMonth() -> 31
2041 * Date.create('February, 2000').daysInMonth() -> 29
2042 *
2043 ***/
2044 'daysInMonth': function() {
2045 return 32 - callDateGet(new date(callDateGet(this, 'FullYear'), callDateGet(this, 'Month'), 32), 'Date');
2046 },
2047
2048 /***
2049 * @method format(<format>, [locale] = currentLocale)
2050 * @returns String
2051 * @short Formats and outputs the date.
2052 * @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 specified 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.
2053 *
2054 * @set
2055 * short
2056 * long
2057 * full
2058 *
2059 * @example
2060 *
2061 * Date.create().format() -> ex. July 4, 2003
2062 * Date.create().format('{Weekday} {d} {Month}, {yyyy}') -> ex. Monday July 4, 2003
2063 * Date.create().format('{hh}:{mm}') -> ex. 15:57
2064 * Date.create().format('{12hr}:{mm}{tt}') -> ex. 3:57pm
2065 * Date.create().format(Date.ISO8601_DATETIME) -> ex. 2011-07-05 12:24:55.528Z
2066 * Date.create('last week').format('short', 'ja') -> ex. 先週
2067 * Date.create('yesterday').format(function(value,unit,ms,loc) {
2068 * // value = 1, unit = 3, ms = -86400000, loc = [current locale object]
2069 * }); -> ex. 1 day ago
2070 *
2071 ***/
2072 'format': function(f, localeCode) {
2073 return formatDate(this, f, false, localeCode);
2074 },
2075
2076 /***
2077 * @method relative([fn], [locale] = currentLocale)
2078 * @returns String
2079 * @short Returns a relative date string offset to the current time.
2080 * @extra [fn] can be passed to provide for more granular control over the resulting string. [fn] is passed 4 arguments: the adjusted value, unit, offset in milliseconds, and a localization object. As an alternate syntax, [locale] can also be passed as the first (and only) parameter. For more, see @date_format.
2081 * @example
2082 *
2083 * Date.create('90 seconds ago').relative() -> 1 minute ago
2084 * Date.create('January').relative() -> ex. 5 months ago
2085 * Date.create('January').relative('ja') -> 3ヶ月前
2086 * Date.create('120 minutes ago').relative(function(val,unit,ms,loc) {
2087 * // value = 2, unit = 3, ms = -7200, loc = [current locale object]
2088 * }); -> ex. 5 months ago
2089 *
2090 ***/
2091 'relative': function(fn, localeCode) {
2092 if(isString(fn)) {
2093 localeCode = fn;
2094 fn = null;
2095 }
2096 return formatDate(this, fn, true, localeCode);
2097 },
2098
2099 /***
2100 * @method is(<d>, [margin] = 0)
2101 * @returns Boolean
2102 * @short Returns true if the date is <d>.
2103 * @extra <d> will accept a date object, timestamp, or text format. %is% additionally understands more generalized expressions like month/weekday names, 'today', etc, and compares to the precision implied in <d>. [margin] allows an extra margin of error in milliseconds. For more, see @date_format.
2104 * @example
2105 *
2106 * Date.create().is('July') -> true or false?
2107 * Date.create().is('1776') -> false
2108 * Date.create().is('today') -> true
2109 * Date.create().is('weekday') -> true or false?
2110 * Date.create().is('July 4, 1776') -> false
2111 * Date.create().is(-6106093200000) -> false
2112 * Date.create().is(new Date(1776, 6, 4)) -> false
2113 *
2114 ***/
2115 'is': function(d, margin, utc) {
2116 var tmp, comp;
2117 if(!this.isValid()) return;
2118 if(isString(d)) {
2119 d = d.trim().toLowerCase();
2120 comp = this.clone().utc(utc);
2121 switch(true) {
2122 case d === 'future': return this.getTime() > getNewDate().getTime();
2123 case d === 'past': return this.getTime() < getNewDate().getTime();
2124 case d === 'weekday': return callDateGet(comp, 'Day') > 0 && callDateGet(comp, 'Day') < 6;
2125 case d === 'weekend': return callDateGet(comp, 'Day') === 0 || callDateGet(comp, 'Day') === 6;
2126 case (tmp = English['weekdays'].indexOf(d) % 7) > -1: return callDateGet(comp, 'Day') === tmp;
2127 case (tmp = English['months'].indexOf(d) % 12) > -1: return callDateGet(comp, 'Month') === tmp;
2128 }
2129 }
2130 return compareDate(this, d, null, margin, utc);
2131 },
2132
2133 /***
2134 * @method reset([unit] = 'hours')
2135 * @returns Date
2136 * @short Resets the unit passed and all smaller units. Default is "hours", effectively resetting the time.
2137 * @example
2138 *
2139 * Date.create().reset('day') -> Beginning of today
2140 * Date.create().reset('month') -> 1st of the month
2141 *
2142 ***/
2143 'reset': function(unit) {
2144 var params = {}, recognized;
2145 unit = unit || 'hours';
2146 if(unit === 'date') unit = 'days';
2147 recognized = DateUnits.some(function(u) {
2148 return unit === u.name || unit === u.name + 's';
2149 });
2150 params[unit] = unit.match(/^days?/) ? 1 : 0;
2151 return recognized ? this.set(params, true) : this;
2152 },
2153
2154 /***
2155 * @method clone()
2156 * @returns Date
2157 * @short Clones the date.
2158 * @example
2159 *
2160 * Date.create().clone() -> Copy of now
2161 *
2162 ***/
2163 'clone': function() {
2164 var d = new date(this.getTime());
2165 d.utc(!!this._utc);
2166 return d;
2167 }
2168
2169 });
2170
2171
2172 // Instance aliases
2173 extend(date, true, true, {
2174
2175 /***
2176 * @method iso()
2177 * @alias toISOString
2178 *
2179 ***/
2180 'iso': function() {
2181 return this.toISOString();
2182 },
2183
2184 /***
2185 * @method getWeekday()
2186 * @returns Number
2187 * @short Alias for %getDay%.
2188 * @set
2189 * getUTCWeekday
2190 *
2191 * @example
2192 *
2193 + Date.create().getWeekday(); -> (ex.) 3
2194 + Date.create().getUTCWeekday(); -> (ex.) 3
2195 *
2196 ***/
2197 'getWeekday': date.prototype.getDay,
2198 'getUTCWeekday': date.prototype.getUTCDay
2199
2200 });
2201
2202
2203
2204 /***
2205 * Number module
2206 *
2207 ***/
2208
2209 /***
2210 * @method [unit]()
2211 * @returns Number
2212 * @short Takes the number as a corresponding unit of time and converts to milliseconds.
2213 * @extra Method names can be singular or plural. Note that as "a month" is ambiguous as a unit of time, %months% will be equivalent to 30.4375 days, the average number in a month. Be careful using %months% if you need exact precision.
2214 *
2215 * @set
2216 * millisecond
2217 * milliseconds
2218 * second
2219 * seconds
2220 * minute
2221 * minutes
2222 * hour
2223 * hours
2224 * day
2225 * days
2226 * week
2227 * weeks
2228 * month
2229 * months
2230 * year
2231 * years
2232 *
2233 * @example
2234 *
2235 * (5).milliseconds() -> 5
2236 * (10).hours() -> 36000000
2237 * (1).day() -> 86400000
2238 *
2239 ***
2240 * @method [unit]Before([d], [locale] = currentLocale)
2241 * @returns Date
2242 * @short Returns a date that is <n> units before [d], where <n> is the number.
2243 * @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 last day of the month. Be careful using %monthsBefore% if you need exact precision. See @date_format for more.
2244 *
2245 * @set
2246 * millisecondBefore
2247 * millisecondsBefore
2248 * secondBefore
2249 * secondsBefore
2250 * minuteBefore
2251 * minutesBefore
2252 * hourBefore
2253 * hoursBefore
2254 * dayBefore
2255 * daysBefore
2256 * weekBefore
2257 * weeksBefore
2258 * monthBefore
2259 * monthsBefore
2260 * yearBefore
2261 * yearsBefore
2262 *
2263 * @example
2264 *
2265 * (5).daysBefore('tuesday') -> 5 days before tuesday of this week
2266 * (1).yearBefore('January 23, 1997') -> January 23, 1996
2267 *
2268 ***
2269 * @method [unit]Ago()
2270 * @returns Date
2271 * @short Returns a date that is <n> units ago.
2272 * @extra 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 last day of the month. Be careful using %monthsAgo% if you need exact precision.
2273 *
2274 * @set
2275 * millisecondAgo
2276 * millisecondsAgo
2277 * secondAgo
2278 * secondsAgo
2279 * minuteAgo
2280 * minutesAgo
2281 * hourAgo
2282 * hoursAgo
2283 * dayAgo
2284 * daysAgo
2285 * weekAgo
2286 * weeksAgo
2287 * monthAgo
2288 * monthsAgo
2289 * yearAgo
2290 * yearsAgo
2291 *
2292 * @example
2293 *
2294 * (5).weeksAgo() -> 5 weeks ago
2295 * (1).yearAgo() -> January 23, 1996
2296 *
2297 ***
2298 * @method [unit]After([d], [locale] = currentLocale)
2299 * @returns Date
2300 * @short Returns a date <n> units after [d], where <n> is the number.
2301 * @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 last day of the month. Be careful using %monthsAfter% if you need exact precision. See @date_format for more.
2302 *
2303 * @set
2304 * millisecondAfter
2305 * millisecondsAfter
2306 * secondAfter
2307 * secondsAfter
2308 * minuteAfter
2309 * minutesAfter
2310 * hourAfter
2311 * hoursAfter
2312 * dayAfter
2313 * daysAfter
2314 * weekAfter
2315 * weeksAfter
2316 * monthAfter
2317 * monthsAfter
2318 * yearAfter
2319 * yearsAfter
2320 *
2321 * @example
2322 *
2323 * (5).daysAfter('tuesday') -> 5 days after tuesday of this week
2324 * (1).yearAfter('January 23, 1997') -> January 23, 1998
2325 *
2326 ***
2327 * @method [unit]FromNow()
2328 * @returns Date
2329 * @short Returns a date <n> units from now.
2330 * @extra 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 last day of the month. Be careful using %monthsFromNow% if you need exact precision.
2331 *
2332 * @set
2333 * millisecondFromNow
2334 * millisecondsFromNow
2335 * secondFromNow
2336 * secondsFromNow
2337 * minuteFromNow
2338 * minutesFromNow
2339 * hourFromNow
2340 * hoursFromNow
2341 * dayFromNow
2342 * daysFromNow
2343 * weekFromNow
2344 * weeksFromNow
2345 * monthFromNow
2346 * monthsFromNow
2347 * yearFromNow
2348 * yearsFromNow
2349 *
2350 * @example
2351 *
2352 * (5).weeksFromNow() -> 5 weeks ago
2353 * (1).yearFromNow() -> January 23, 1998
2354 *
2355 ***/
2356 function buildNumberToDateAlias(u, multiplier) {
2357 var name = u.name, methods = {};
2358 function base() { return round(this * multiplier); }
2359 function after() { return createDate(arguments)[u.addMethod](this); }
2360 function before() { return createDate(arguments)[u.addMethod](-this); }
2361 methods[name] = base;
2362 methods[name + 's'] = base;
2363 methods[name + 'Before'] = before;
2364 methods[name + 'sBefore'] = before;
2365 methods[name + 'Ago'] = before;
2366 methods[name + 'sAgo'] = before;
2367 methods[name + 'After'] = after;
2368 methods[name + 'sAfter'] = after;
2369 methods[name + 'FromNow'] = after;
2370 methods[name + 'sFromNow'] = after;
2371 number.extend(methods);
2372 }
2373
2374 extend(number, true, true, {
2375
2376 /***
2377 * @method duration([locale] = currentLocale)
2378 * @returns String
2379 * @short Takes the number as milliseconds and returns a unit-adjusted localized string.
2380 * @extra This method is the same as %Date#relative% without the localized equivalent 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 included.
2381 * @example
2382 *
2383 * (500).duration() -> '500 milliseconds'
2384 * (1200).duration() -> '1 second'
2385 * (75).minutes().duration() -> '1 hour'
2386 * (75).minutes().duration('es') -> '1 hora'
2387 *
2388 ***/
2389 'duration': function(localeCode) {
2390 return getLocalization(localeCode).getDuration(this);
2391 }
2392
2393 });
2394
2395
2396 English = CurrentLocalization = date.addLocale('en', {
2397 'plural': true,
2398 'timeMarker': 'at',
2399 'ampm': 'am,pm',
2400 'months': 'January,February,March,April,May,June,July,August,September,October,November,December',
2401 'weekdays': 'Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday',
2402 'units': 'millisecond:|s,second:|s,minute:|s,hour:|s,day:|s,week:|s,month:|s,year:|s',
2403 'numbers': 'one,two,three,four,five,six,seven,eight,nine,ten',
2404 'articles': 'a,an,the',
2405 'tokens': 'the,st|nd|rd|th,of',
2406 'short': '{Month} {d}, {yyyy}',
2407 'long': '{Month} {d}, {yyyy} {h}:{mm}{tt}',
2408 'full': '{Weekday} {Month} {d}, {yyyy} {h}:{mm}:{ss}{tt}',
2409 'past': '{num} {unit} {sign}',
2410 'future': '{num} {unit} {sign}',
2411 'duration': '{num} {unit}',
2412 'modifiers': [
2413 { 'name': 'sign', 'src': 'ago|before', 'value': -1 },
2414 { 'name': 'sign', 'src': 'from now|after|from|in|later', 'value': 1 },
2415 { 'name': 'edge', 'src': 'last day', 'value': -2 },
2416 { 'name': 'edge', 'src': 'end', 'value': -1 },
2417 { 'name': 'edge', 'src': 'first day|beginning', 'value': 1 },
2418 { 'name': 'shift', 'src': 'last', 'value': -1 },
2419 { 'name': 'shift', 'src': 'the|this', 'value': 0 },
2420 { 'name': 'shift', 'src': 'next', 'value': 1 }
2421 ],
2422 'dateParse': [
2423 '{month} {year}',
2424 '{shift} {unit=5-7}',
2425 '{0?} {date}{1}',
2426 '{0?} {edge} of {shift?} {unit=4-7?}{month?}{year?}'
2427 ],
2428 'timeParse': [
2429 '{num} {unit} {sign}',
2430 '{sign} {num} {unit}',
2431 '{0} {num}{1} {day} of {month} {year?}',
2432 '{weekday?} {month} {date}{1?} {year?}',
2433 '{date} {month} {year}',
2434 '{date} {month}',
2435 '{shift} {weekday}',
2436 '{shift} week {weekday}',
2437 '{weekday} {2?} {shift} week',
2438 '{num} {unit=4-5} {sign} {day}',
2439 '{0?} {date}{1} of {month}',
2440 '{0?}{month?} {date?}{1?} of {shift} {unit=6-7}'
2441 ]
2442 });
2443
2444 buildDateUnits();
2445 buildDateMethods();
2446 buildCoreInputFormats();
2447 buildFormatTokens();
2448 buildFormatShortcuts();
2449 buildAsianDigits();
2450 buildRelativeAliases();
2451 buildUTCAliases();
2452 setDateProperties();
2453