Last active
August 9, 2024 22:45
-
-
Save alanthai/2ec6b14452d696d7302a9031e3854537 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const INCOME = 100_000; | |
// ----- 2022 ----- // | |
// https://www.canada.ca/en/revenue-agency/services/forms-publications/payroll/t4032-payroll-deductions-tables/t4032on-jan/t4032on-january-general-information.html | |
const CPP_BRACKETS = [ | |
[ 3_500, 0 ], // exemption | |
[ 61_400, 0.0570], // cap | |
[Infinity, 0 ], | |
]; | |
// Max contribution 3,499.80 | |
const EI_BRACKETS = [ | |
[ 60_300, 0.0158], // cap | |
[Infinity, 0 ] | |
]; | |
// Max contribution 952.74 | |
const FEDERAL_TAX_BRACKETS = [ | |
// [MAX amount, % tax rate] | |
[ 14_398, 0 ], | |
[ 50_197, 0.15 ], | |
[ 100_392, 0.205], | |
[ 155_625, 0.26 ], | |
[ 221_708, 0.29 ], | |
[Infinity, 0.33 ], | |
]; | |
const ON_TAX_BRACKETS = [ | |
[ 46_226, 0.0505], | |
[ 92_454, 0.0915], | |
[ 150_000, 0.1116], | |
[ 220_000, 0.1216], | |
[Infinity, 0.1316], | |
]; | |
// Tax on top of tax | |
const ON_SURTAX_BRACKETS = [ | |
[ 4_991, 0 ], | |
[ 6_387, 0.20], | |
[Infinity, 0.56] | |
]; | |
// ----- BRACKETS ----- // | |
// transform bracket caps into bracket increments that the bracket rate works on | |
// @return Array<[cap, rate]> => Array<[increment, rate]> | |
// Eg, [[10, 0.15], [20, 0.30]] => [[10, 0.15], [10, 0.30]]; | |
function getBracketIncrements(brackets) { | |
let lastCap = 0; | |
return brackets.map(([cap, rate]) => { | |
const bracket = [cap - lastCap, rate]; | |
lastCap = cap; | |
return bracket; | |
}); | |
} | |
// @result Array<Tuple<rate, amount>> | |
function calcBracketBreakdown(brackets) { | |
return (amount) => { | |
let remainder = amount; | |
const result = []; | |
for (let [increment, rate] of getBracketIncrements(brackets)) { | |
const incrementAmount = Math.min(increment, remainder); | |
result.push([rate, incrementAmount * rate]); | |
remainder -= increment; | |
if (remainder <= 0) { | |
break; | |
} | |
} | |
return result; | |
}; | |
} | |
function calcBracketsTotal(bracketBreakdown) { | |
return bracketBreakdown | |
.map(([_, amount]) => amount) | |
.reduce((sum, amount) => sum + amount, 0); | |
} | |
// ----- DISPLAY ----- // | |
// Adds comma separators, and 2 decimal places | |
function formatDollars(n) { | |
return '$' + n.toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ","); | |
} | |
function formatPercent(p) { | |
return (p * 100).toFixed(2) + '%'; | |
} | |
function displayBracketBreakdown(bracketBreakdown, spacing = 2) { | |
bracketBreakdown.forEach(([rate, amount]) => { | |
const formattedAmount = `${formatDollars(amount)}`; | |
const rateInPercent = `${formatPercent(rate)}`; | |
if (amount) { | |
console.log(`${' '.repeat(spacing)}${formattedAmount} at ${rateInPercent}`); | |
} | |
}); | |
} | |
function displayDetailedBreakdown(income) { | |
const cpp = calcBracketsTotal(calcBracketBreakdown(CPP_BRACKETS)(income)); | |
const ei = calcBracketsTotal(calcBracketBreakdown(EI_BRACKETS)(income)); | |
const federalTaxBreakdown = calcBracketBreakdown(FEDERAL_TAX_BRACKETS)(income); | |
const federalTax = calcBracketsTotal(federalTaxBreakdown); | |
const onTaxBreakdown = calcBracketBreakdown(ON_TAX_BRACKETS)(income); | |
const onTax = calcBracketsTotal(onTaxBreakdown); | |
const totalTax = federalTax + onTax; | |
const surtax = calcBracketsTotal(calcBracketBreakdown(ON_SURTAX_BRACKETS)(onTax)); | |
const totalOwing = cpp + ei + totalTax + surtax; | |
console.log('CPP:', formatDollars(cpp)); | |
console.log('EI:', formatDollars(ei)); | |
console.log(''); // break | |
console.log('Federal Tax:', formatDollars(federalTax)); | |
displayBracketBreakdown(federalTaxBreakdown); | |
console.log(''); // break | |
console.log('Provincial Tax:', formatDollars(onTax)); | |
displayBracketBreakdown(onTaxBreakdown); | |
if (surtax) { | |
console.log('Surtax:', formatDollars(surtax)); | |
} | |
console.log(''); // break | |
console.log('Total:', formatDollars(totalOwing)); | |
console.log('Average rate:', formatPercent(totalOwing / income)); | |
} | |
displayDetailedBreakdown(INCOME); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment