master
Raw Download raw file
   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