-import { daysToYears, yearsToDays } from './bubble';
+import { daysToMonths, monthsToDays } from './bubble';
import { normalizeUnits } from '../units/aliases';
import toInt from '../utils/to-int';
if (units === 'month' || units === 'year') {
days = this._days + milliseconds / 864e5;
- months = this._months + daysToYears(days) * 12;
+ months = this._months + daysToMonths(days);
return units === 'month' ? months : months / 12;
} else {
// handle milliseconds separately because of floating point math errors (issue #1867)
- days = this._days + Math.round(yearsToDays(this._months / 12));
+ days = this._days + Math.round(monthsToDays(this._months));
switch (units) {
case 'week' : return days / 7 + milliseconds / 6048e5;
case 'day' : return days + milliseconds / 864e5;
import absFloor from '../utils/abs-floor';
+import absCeil from '../utils/abs-ceil';
import { createUTCDate } from '../create/date-from-array';
export function bubble () {
var days = this._days;
var months = this._months;
var data = this._data;
- var seconds, minutes, hours, years = 0, monthDaySplit, sign;
+ var seconds, minutes, hours, years, monthsFromDays;
// if we have a mix of positive and negative values, bubble down first
// check: https://github.com/moment/moment/issues/2166
if (!((milliseconds >= 0 && days >= 0 && months >= 0) ||
(milliseconds <= 0 && days <= 0 && months <= 0))) {
- milliseconds += absFloor(yearsToDays(months / 12) + days) * 864e5;
+ milliseconds += absCeil(monthsToDays(months) + days) * 864e5;
days = 0;
months = 0;
}
days += absFloor(hours / 24);
- // Accurately convert days to years, assume start from year 0.
- years = absFloor(daysToYears(days));
- days -= absFloor(yearsToDays(years));
-
- // Use Jan 1st 1970 as anchor.
- if (days !== 0) {
- monthDaySplit = createUTCDate(1970, 0, Math.abs(days) + 1);
- sign = days < 0 ? -1 : 1;
- months += sign * monthDaySplit.getUTCMonth();
- days = sign * (monthDaySplit.getUTCDate() - 1);
- }
+ // convert days to months
+ monthsFromDays = absFloor(daysToMonths(days));
+ months += monthsFromDays;
+ days -= absCeil(monthsToDays(monthsFromDays));
// 12 months -> 1 year
- years += absFloor(months / 12);
+ years = absFloor(months / 12);
months %= 12;
data.days = days;
return this;
}
-export function daysToYears (days) {
+export function daysToMonths (days) {
// 400 years have 146097 days (taking into account leap year rules)
- return days * 400 / 146097;
+ // 400 years have 12 months === 4800
+ return days * 4800 / 146097;
}
-export function yearsToDays (years) {
- // years * 365 + absFloor(years / 4) -
- // absFloor(years / 100) + absFloor(years / 400);
- return years * 146097 / 400;
+export function monthsToDays (months) {
+ // the reverse of daysToMonths
+ return months * 146097 / 4800;
}
assert.equal(d.years(), 29227, '29227 years');
assert.equal(d.months(), 8, '8 months');
- assert.equal(d.days(), 14, '14 day'); // this should be 13, maybe
+ assert.equal(d.days(), 12, '12 day'); // if you have to change this value -- just do it
assert.equal(d.hours(), 2, '2 hours');
assert.equal(d.minutes(), 48, '48 minutes');
assert.equal(d.years(), -29227, '29653 years');
assert.equal(d.months(), -8, '8 day');
- assert.equal(d.days(), -14, '17 day'); // this should be 13, maybe
+ assert.equal(d.days(), -12, '12 day'); // if you have to change this value -- just do it
assert.equal(d.hours(), -2, '2 hours');
assert.equal(d.minutes(), -48, '48 minutes');
assert.equal(moment.duration({months: 13}).months(), 1, '13 months is 1 month left over');
assert.equal(moment.duration({months: 13}).years(), 1, '13 months makes 1 year');
- assert.equal(moment.duration({days: 29}).days(), 29, '29 days is 29 days');
- assert.equal(moment.duration({days: 29}).months(), 0, '29 days makes no month');
- assert.equal(moment.duration({days: 31}).days(), 0, '31 days is 0 day left over');
+ assert.equal(moment.duration({days: 30}).days(), 30, '30 days is 30 days');
+ assert.equal(moment.duration({days: 30}).months(), 0, '30 days makes no month');
+ assert.equal(moment.duration({days: 31}).days(), 0, '31 days is 0 days left over');
assert.equal(moment.duration({days: 31}).months(), 1, '31 days is a month');
- assert.equal(moment.duration({days: 32}).days(), 1, '32 days is 1 days left over');
+ assert.equal(moment.duration({days: 32}).days(), 1, '32 days is 1 day left over');
assert.equal(moment.duration({days: 32}).months(), 1, '32 days is a month');
assert.equal(moment.duration({hours: 23}).hours(), 23, '23 hours is 23 hours');
assert.equal(moment.duration({hours: 25}).days(), 1, '25 hours makes 1 day');
});
+test('bubbling consistency', function (assert) {
+ var days = 0, months = 0, newDays, newMonths, totalDays, d;
+ for (totalDays = 1; totalDays <= 500; ++totalDays) {
+ d = moment.duration(totalDays, 'days');
+ newDays = d.days();
+ newMonths = d.months() + d.years() * 12;
+ assert.ok(
+ (months === newMonths && days + 1 === newDays) ||
+ (months + 1 === newMonths && newDays === 0),
+ 'consistent total days ' + totalDays +
+ ' was ' + months + ' ' + days +
+ ' now ' + newMonths + ' ' + newDays);
+ days = newDays;
+ months = newMonths;
+ }
+});
+
test('effective equivalency', function (assert) {
assert.deepEqual(moment.duration({seconds: 1})._data, moment.duration({milliseconds: 1000})._data, '1 second is the same as 1000 milliseconds');
assert.deepEqual(moment.duration({seconds: 60})._data, moment.duration({minutes: 1})._data, '1 minute is the same as 60 seconds');