Skip to content

Instantly share code, notes, and snippets.

@cherepanov
Created August 14, 2017 06:44
Show Gist options
  • Save cherepanov/4e785264fb8d9c13230620b22f56631c to your computer and use it in GitHub Desktop.
Save cherepanov/4e785264fb8d9c13230620b22f56631c to your computer and use it in GitHub Desktop.
calc.js
DETAILS
ACTIVITY
Last year
You uploaded an item
Jan 11, 2016
Javascript
calc.js
No recorded activity before January 11, 2016
var Calc = (function() {
var DEBUG = false;
Calc = function(data) {
this.data = data;
return this;
};
Calc.prototype = {
constructor: Calc,
getDepositsOptions: function() {
var res = {};
this.data.every(function(d) {
res[d.currency] = res[d.currency] || {};
res[d.currency] = {
hasCapitalize: res[d.currency].hasCapitalize || d.capitalize,
hasRefill: res[d.currency].hasRefill || d.refill,
hasLoyalty: res[d.currency].hasLoyalty || !!d.loyalty
};
return true;
});
return res;
},
_filterByCurrencyCode: function(data, code) {
return data.filter(function(d) {
return d.currency === code;
});
},
_filterByTerms: function(data, term) {
return data.filter(function(d) {
return ~~d.terms === term;
});
},
_filterByAmount: function(data, amount) {
return data.filter(function(d) {
var min = d.min,
max = d.max ? d.max : Number.POSITIVE_INFINITY;
return amount >=min && amount <= max;
});
},
_filterByOptions: function(data, options) {
return data.filter(function(d) {
var loyaltyPass = Boolean(!!options.loyalty ? !!d.loyalty : true),
refillPass = Boolean(!!options.refill ? d.refill : true),
capitalizePass = Boolean(!!options.capitalize ? d.capitalize : true);
return loyaltyPass && refillPass && capitalizePass;
});
},
_isLeapYear: function(year) {
return ((year % 4 === 0 && year % 100 !== 0) || year % 400 === 0);
},
_getDaysInYear: function(year) {
return this._isLeapYear(year) ? 366 : 365;
},
_getDaysInMonth: function(year, month) {
switch(month) {
case 2: return (this._isLeapYear(year) ? 29 : 28);
case 4:case 6:case 9:case 11: return 30;
default: return 31;
}
},
/*
* http://alcor.concordia.ca/~gpkatch/gdate-algorithm.html
* */
_fromGregorian: function(dg) {
var y = ~~((10000 * dg + 14780)/3652425),
ddd = dg - (365*y + ~~(y/4) - ~~(y/100) + ~~(y/400));
if (ddd < 0) {
y = y - 1;
ddd = dg - (365*y + ~~(y/4) - ~~(y/100) + ~~(y/400));
}
var mi = ~~((100*ddd + 52)/3060),
mm = (mi + 2) % 12 + 1;
y = y + ~~((mi + 2)/12);
var dd = ddd - ~~((mi*306 + 5)/10) + 1;
return [y, mm - 1, dd];
},
_gregorianDate: function (date) {
var y = date.getFullYear(),
m = date.getMonth() + 1,
d = date.getDate();
m = (m + 9) % 12;
y = y - ~~(m/10);
return 365*y + ~~(y/4) - ~~(y/100) + ~~(y/400) + ~~((m*306 + 5)/10) + ( d - 1 );
},
_incDays: function(date, n) {
var dateParts = this._fromGregorian(this._gregorianDate(date) + n);
return new Date(dateParts[0], dateParts[1], dateParts[2]);
},
_incMonths: function(date, n, startDay) {
var y = date.getFullYear(),
m = date.getMonth() + 1,
d = startDay || date.getDate(),
nextMonth = (m + n) % 12,
yearsToAdd = ~~((m + n) / 12),
nextYear = y + yearsToAdd,
nextMonthDays = this._getDaysInMonth(y, nextMonth),
nextMonthDay = Math.min(d, nextMonthDays),
nextDate = new Date(nextYear, nextMonth - 1, nextMonthDay),
daysToAdd = this._daysDiff(nextDate, date);
return this._incDays(date, daysToAdd);
},
_daysDiff: function(dateTwo, dateOne) {
return this._gregorianDate(dateTwo) - this._gregorianDate(dateOne);
},
_isValidDate: function(y, m, d) {
var reverse = this._fromGregorian(this._gregorianDate(new Date(y, m, d)));
return (y === reverse[0] && m === reverse[1] && d === reverse[2]);
},
_formatDate: function(date) {
return date.getDate() + '.' + (date.getMonth() + 1) + '.' + date.getFullYear();
},
_composePeriods: function(startDate, term) {
var periodStart = new Date(startDate),
startDay = startDate.getDate(),
endDate = this._incDays(startDate, term),
daysLeft = this._daysDiff(endDate, periodStart),
year = startDate.getFullYear(),
month = startDate.getMonth() + 1,
daysInMonth = this._getDaysInMonth(year, month),
periods = [],
addition = false; // summarize percents and capitalization at the end of period
DEBUG && console.log('----------------');
/*
console.log(this._fromGregorian(this._gregorianDate(new Date(2014, 0, 1))));
console.log(this._fromGregorian(this._gregorianDate(new Date(2014, 11, 31))));
console.log(this._fromGregorian(this._gregorianDate(new Date(2014, 11, 12))));
console.log(this._fromGregorian(this._gregorianDate(new Date(2014, 11, 30))));
console.log(this._incDays(startDate, 1));
console.log(this._incDays(startDate, 10));
console.log(this._incDays(startDate, 20));
console.log(this._incDays(startDate, 30));
console.log(this._incDays(startDate, 31));
console.log(this._incDays(startDate, 61));
console.log(this._incDays(startDate, 91));
console.log('---');
console.log(this._isValidDate(2014, 11, 31));
console.log(this._isValidDate(2014, 10, 30));
console.log(this._isValidDate(2014, 1, 1));
console.log('---');
console.log(this._isValidDate(2014, 10, 31));
console.log(this._isValidDate(2014, -1, 0));
console.log(this._isValidDate(2014, 1, 0));
console.log(this._isValidDate(2014, 0, -1));
console.log('---');
$.each(new Array(20), function(m) {
var newDate = this._incMonths(startDate, m),
daysDiff = this._daysDiff(newDate, startDate);
console.log(newDate, daysDiff);
}.bind(this));
*/
while(daysLeft > 0) {
var periodEnd = this._incMonths(periodStart, 1, startDay),
periodDays = this._daysDiff(periodEnd, periodStart);
//FIXME: evaluate once
if(periodDays > daysLeft) {
periodDays = daysLeft;
periodEnd = this._incDays(periodStart, daysLeft);
}
year = periodStart.getFullYear();
month = periodStart.getMonth() + 1;
daysInMonth = this._getDaysInMonth(year, month);
if(periodEnd.getFullYear() > year) {
var subDays = daysInMonth - startDay,
subEnd= this._incDays(periodStart, subDays);
periods.push({
start: this._formatDate(periodStart),
end: this._formatDate(subEnd),
days: subDays,
base: this._getDaysInYear(year),
addition: true
});
daysLeft -= subDays;
addition = false;
year++;
periodStart = new Date(subEnd);
periodDays = this._daysDiff(periodEnd, periodStart);
}
periods.push({
start: this._formatDate(periodStart),
end: this._formatDate(periodEnd),
days: periodDays,
base: this._getDaysInYear(year),
addition: addition
});
periodStart = new Date(periodEnd);
daysLeft -= periodDays;
addition = true;
}
return periods;
},
_calc: function(deposit, amount, term, options) {
var total = amount,
loyalty = 0,
percents = 0,
rate = deposit.percents / 100,
refill = options.refill,
capitalize = options.capitalize,
investments = 0,
currentDate = new Date(),
startDate = new Date(currentDate.getFullYear(), currentDate.getMonth(), currentDate.getDate()),
periods = this._composePeriods(startDate, term),
periodPercents = 0,
periodStart = amount,
daysCounted = 0;
if(options.loyalty) {
loyalty = amount * (deposit.loyalty / 100);
total += loyalty;
}
periods.forEach(function(p) {
if(p.addition) {
if(capitalize) {
periodStart += periodPercents;
}
if(refill) {
periodStart += refill;
investments += refill;
}
}
periodPercents = periodStart * p.days * rate / p.base;
daysCounted += p.days;
DEBUG && console.log('start|' + p.start, ' end|' + p.end, ' days|' + p.days, ' base|', p.base ,' sum|', periodStart, ' refill|', investments, ' |percents', periodPercents);
percents += periodPercents;
});
DEBUG && console.log(daysCounted);
DEBUG && console.log(percents);
DEBUG && console.log(investments);
DEBUG && console.log(total);
total += percents + investments;
return {
start: amount,
total: total,
loyalty: loyalty,
investments:investments,
percents: percents,
term: term
};
},
getDepositsByParams: function(currency, term, amount, options) {
var deposits = this.data;
deposits = this._filterByCurrencyCode(deposits, currency);
deposits = this._filterByTerms(deposits, term);
deposits = this._filterByAmount(deposits, amount);
deposits = this._filterByOptions(deposits, options);
return deposits;
},
calculate: function(currency, term, amount, options) {
var deposits = this.getDepositsByParams(currency, term, amount, options) || [],
max = 0,
maxPriority = Number.NEGATIVE_INFINITY,
mostValuable = null;
deposits.forEach(function(d) {
var res = this._calc(d, amount, term, options);
/*DEBUG && */console.log(res.total, d.priority, d);
if(res.total + 1 >= max && d.priority > maxPriority) {
max = res.total;
maxPriority = d.priority;
mostValuable = {
deposit: d,
request: {
currency: currency,
term: term,
amount: amount,
options: options
},
result: res
};
}
}.bind(this));
DEBUG && console.log(mostValuable);
return mostValuable;
}
};
return Calc
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment