*
@method [units]Since([d], [locale] = currentLocale)
@returns Number
@short Returns the time since [d] in the appropriate unit.
@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.

@set
millisecondsSince
secondsSince
minutesSince
hoursSince
daysSince
weeksSince
monthsSince
yearsSince

@example

Date.create().millisecondsSince('1 hour ago') -> 3,600,000
Date.create().daysSince('1 week ago')         -> 7
Date.create().yearsSince('15 years ago')      -> 15
Date.create('15 years ago').yearsAgo()        -> 15

**
@method [units]Ago()
@returns Number
@short Returns the time ago in the appropriate unit.

@set
millisecondsAgo
secondsAgo
minutesAgo
hoursAgo
daysAgo
weeksAgo
monthsAgo
yearsAgo

@example

Date.create('last year').millisecondsAgo() -> 3,600,000
Date.create('last year').daysAgo()         -> 7
Date.create('last year').yearsAgo()        -> 15

**
@method [units]Until([d], [locale] = currentLocale)
@returns Number
@short Returns the time until [d] in the appropriate unit.
@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.

@set
millisecondsUntil
secondsUntil
minutesUntil
hoursUntil
daysUntil
weeksUntil
monthsUntil
yearsUntil

@example

Date.create().millisecondsUntil('1 hour from now') -> 3,600,000
Date.create().daysUntil('1 week from now')         -> 7
Date.create().yearsUntil('15 years from now')      -> 15
Date.create('15 years from now').yearsFromNow()    -> 15

**
@method [units]FromNow()
@returns Number
@short Returns the time from now in the appropriate unit.

@set
millisecondsFromNow
secondsFromNow
minutesFromNow
hoursFromNow
daysFromNow
weeksFromNow
monthsFromNow
yearsFromNow

@example

Date.create('next year').millisecondsFromNow() -> 3,600,000
Date.create('next year').daysFromNow()         -> 7
Date.create('next year').yearsFromNow()        -> 15

**
@method add[Units](<num>, [reset] = false)
@returns Date
@short Adds <num> of the unit to the date. If [reset] is true, all lower units will be reset.
@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.

@set
addMilliseconds
addSeconds
addMinutes
addHours
addDays
addWeeks
addMonths
addYears

@example

Date.create().addMilliseconds(5) -> current time + 5 milliseconds
Date.create().addDays(5)         -> current time + 5 days
Date.create().addYears(5)        -> current time + 5 years

**
@method isLast[Unit]()
@returns Boolean
@short Returns true if the date is last week/month/year.

@set
isLastWeek
isLastMonth
isLastYear

@example

Date.create('yesterday').isLastWeek()  -> true or false?
Date.create('yesterday').isLastMonth() -> probably not...
Date.create('yesterday').isLastYear()  -> even less likely...

**
@method isThis[Unit]()
@returns Boolean
@short Returns true if the date is this week/month/year.

@set
isThisWeek
isThisMonth
isThisYear

@example

Date.create('tomorrow').isThisWeek()  -> true or false?
Date.create('tomorrow').isThisMonth() -> probably...
Date.create('tomorrow').isThisYear()  -> signs point to yes...

**
@method isNext[Unit]()
@returns Boolean
@short Returns true if the date is next week/month/year.

@set
isNextWeek
isNextMonth
isNextYear

@example

Date.create('tomorrow').isNextWeek()  -> true or false?
Date.create('tomorrow').isNextMonth() -> probably not...
Date.create('tomorrow').isNextYear()  -> even less likely...

**
@method beginningOf[Unit]()
@returns Date
@short Sets the date to the beginning of the appropriate unit.

@set
beginningOfDay
beginningOfWeek
beginningOfMonth
beginningOfYear

@example

Date.create().beginningOfDay()   -> the beginning of today (resets the time)
Date.create().beginningOfWeek()  -> the beginning of the week
Date.create().beginningOfMonth() -> the beginning of the month
Date.create().beginningOfYear()  -> the beginning of the year

**
@method endOf[Unit]()
@returns Date
@short Sets the date to the end of the appropriate unit.

@set
endOfDay
endOfWeek
endOfMonth
endOfYear

@example

Date.create().endOfDay()   -> the end of today (sets the time to 23:59:59.999)
Date.create().endOfWeek()  -> the end of the week
Date.create().endOfMonth() -> the end of the month
Date.create().endOfYear()  -> the end of the year

*
function buildDateMethods() {
    extendSimilar(date, true, false, DateUnits, function(methods, u, i) {
      var unit = u.unit, caps = simpleCapitalize(unit), multiplier = u.multiplier(), since, until;
      u.addMethod = 'add' + caps + 's';
      // "since/until now" only count "past" an integer, i.e. "2 days ago" is
      // anything between 2 - 2.999 days. The default margin of error is 0.999,
      // but "months" have an inherently larger margin, as the number of days
      // in a given month may be significantly less than the number of days in
      // the average month, so for example "30 days" before March 15 may in fact
      // be 1 month ago. Years also have a margin of error due to leap years,
      // but this is roughly 0.999 anyway (365 / 365.25). Other units do not
      // technically need the error margin applied to them but this accounts
      // for discrepancies like (15).hoursAgo() which technically creates the
      // current date first, then creates a date 15 hours before and compares
      // them, the discrepancy between the creation of the 2 dates means that
      // they may actually be 15.0001 hours apart. Milliseconds don't have
      // fractions, so they won't be subject to this error margin.
      function applyErrorMargin(ms) {
        var num      = ms / multiplier,
            fraction = num % 1,
            error    = u.error || 0.999;
        if(fraction && math.abs(fraction % 1) > error) {
          num = round(num);
        }
        return parseInt(num);
      }
      since = function(f, localeCode) {
        return applyErrorMargin(this.getTime() - date.create(f, localeCode).getTime());
      };
      until = function(f, localeCode) {
        return applyErrorMargin(date.create(f, localeCode).getTime() - this.getTime());
      };
      methods[unit+'sAgo']     = until;
      methods[unit+'sUntil']   = until;
      methods[unit+'sSince']   = since;
      methods[unit+'sFromNow'] = since;
      methods[u.addMethod] = function(num, reset) {
        var set = {};
        set[unit] = num;
        return this.advance(set, reset);
      };
      buildNumberToDateAlias(u, multiplier);
      if(i < 3) {
        ['Last','This','Next'].forEach(function(shift) {
          methods['is' + shift + caps] = function() {
            return this.is(shift + ' ' + unit);
          };
        });
      }
      if(i < 4) {
        methods['beginningOf' + caps] = function() {
          var set = {};
          switch(unit) {
            case 'year':  set['year']    = callDateGet(this, 'FullYear'); break;
            case 'month': set['month']   = callDateGet(this, 'Month');    break;
            case 'day':   set['day']     = callDateGet(this, 'Date');     break;
            case 'week':  set['weekday'] = 0; break;
          }
          return this.set(set, true);
        };
        methods['endOf' + caps] = function() {
          var set = { 'hours': 23, 'minutes': 59, 'seconds': 59, 'milliseconds': 999 };
          switch(unit) {
            case 'year':  set['month']   = 11; set['day'] = 31; break;
            case 'month': set['day']     = this.daysInMonth();  break;
            case 'week':  set['weekday'] = 6;                   break;
          }
          return this.set(set, true);
        };
      }
    });
  }