This is a follow-up question to original Prorated Refund Calculator. Here is the summary of the changes I made since last, thanks to the good answers and some study of my own.
- Did away with bleeding-edge ECMAScript 6 code that was causing compatibility issues with some browsers.
- Added more input validation.
- Added a utility function to format dates to MM/DD/YYYY format.
- Input values are now obtained using
getElementByIdinstead of getting them from a numbered list. - Output is appended into the existing page, rather than updating placeholder HTML tags.
- Documentation throughout.
- Output is more comprehensive and user-friendly.
Is there anything else that I could improve before I share this with my colleagues?
<!DOCTYPE html> <html> <head> <title>Prorated Refund Calculator</title> <style> body { font-family: Calibri, Arial, sans-serif; font-size: 1.0em; } h1 { font-size: 1.2em; } </style> </head> <body> <h1>Prorated Refund Calculator</h1> <p>Input into the following fields and press Calculate.</p> <p style="color: red;">Enter date format as MM/DD/YYYY</p> <form> <label>Product Purchase Date: <input type="text" id="productPurchaseDate" /> </label><br/> <label>Contract Purchase Date: <input type="text" id="contractPurchaseDate" /> </label><br/> <label>Purchase Price: <input type="text" id="purchasePrice" /> </label><br/> <label>Term (in years): <input type="text" id="termInYears" value="10" /> </label><br/> <label>Cancel Date: <input type="text" id="cancelDate" /> </label><br/> <label>Amount paid in claims: <input type="text" id="amtPaidInClaims" value="0" /> </label><br/> <label>Grace period (in days): <input type="text" id="gracePeriodInDays" value="60" /></label><br/> </form> <br/> <button onclick="calculateProratedRefund()">Calculate</button> <script type="text/javascript"> /** * Main function called by HTML form. * Validates input values, then performs calculations * and adds the result into the HTML document to display to the user. */ function calculateProratedRefund() { "use strict"; var productPurchaseDate = new Date(document.getElementById("productPurchaseDate").value); var contractPurchaseDate = new Date(document.getElementById("contractPurchaseDate").value); var purchasePrice = parseFloat(document.getElementById("purchasePrice").value).toFixed(2); var termInYears = parseInt(document.getElementById("termInYears").value, 10); var cancelDate = new Date(document.getElementById("cancelDate").value); var amtPaidInClaims = parseFloat(document.getElementById("amtPaidInClaims").value).toFixed(2); var gracePeriodInDays = parseInt(document.getElementById("gracePeriodInDays").value, 10); /** * Underwriting policies specify that a Year of coverage is exactly 365 Days, regardless of Leap Years. * Hence the following calculations: */ var totalDays = (termInYears * 365); var expirationDate = new Date(productPurchaseDate); expirationDate.setDate(productPurchaseDate.getDate() + (totalDays)); /** * Confirm that all dates are valid Date values and that they follow business rules before proceeding to calculations. */ if ( termInYears < 0 ) { window.alert("Term in years must be greater than zero.") } else if ( !isValidDate(productPurchaseDate) ) { window.alert("Invalid ProductPurchase Date."); } else if ( !isValidDate(contractPurchaseDate) ) { window.alert("Invalid Contract Purchase Date."); } else if ( !isValidDate(cancelDate) ) { window.alert("Invalid Cancel Date."); } else if ( cancelDate < productPurchaseDate || cancelDate < contractPurchaseDate ) { window.alert("Cancel date cannot be prior to Product or Contract purchase date."); } else if ( cancelDate > expirationDate ) { window.alert("Cancel date cannot be past Expiration date."); } /** * Confirm that all numbers are valid numbers for calculations. */ else if ( isNaN(purchasePrice) || isNaN(termInYears) || isNaN(amtPaidInClaims) || isNaN(gracePeriodInDays) ) { window.alert("Invalid Number Entry. Please check your entries and try again."); } /** * If all the input values are valid, we proceed to calculations. */ else { /** * All terms are calculated in Days, hence the following conversion from JavaScript's millisecond precision to Days. * (ms * secs * mins * hours) = 1 Day */ var msPerDay = (1000 * 60 * 60 * 24); var daysElapsed = Math.floor(( Date.parse(cancelDate) - Date.parse(contractPurchaseDate) ) / msPerDay); /** * Policy holder is entitled to a full refund within the grace period. */ var termUsed = 0.0; if ( daysElapsed <= gracePeriodInDays ) { termUsed = 0.0; } else { termUsed = (daysElapsed / totalDays); } var termLeft = 1.0 - termUsed; /** * Finally we calculate the refund amount */ var proratedRefund = (purchasePrice * termLeft).toFixed(2); var finalRefund = (proratedRefund - amtPaidInClaims).toFixed(2); if ( finalRefund < 0.0 ) { finalRefund = 0.0; } /** * Output calculation results to HTML document. */ var outputPar = document.createElement("p"); outputPar.appendChild(document.createTextNode("Product Purchase date: " + formatDate(productPurchaseDate))); outputPar.appendChild(document.createElement("br")); outputPar.appendChild(document.createTextNode("Contract Purchase date: " + formatDate(contractPurchaseDate))); outputPar.appendChild(document.createElement("br")); outputPar.appendChild(document.createTextNode("Total days: " + totalDays + " (" + termInYears + " years)")); outputPar.appendChild(document.createElement("br")); outputPar.appendChild(document.createTextNode("Expiration date: " + formatDate(expirationDate))); outputPar.appendChild(document.createElement("br")); outputPar.appendChild(document.createTextNode("Cancel date: " + formatDate(cancelDate))); outputPar.appendChild(document.createElement("br")); outputPar.appendChild(document.createTextNode("Grace period: " + gracePeriodInDays + " days")); outputPar.appendChild(document.createElement("br")); outputPar.appendChild(document.createTextNode("Days elapsed: " + daysElapsed + " (" + parseFloat(daysElapsed / 365).toFixed(2) + " years)")); outputPar.appendChild(document.createElement("br")); outputPar.appendChild(document.createTextNode("Refund percent: " + (termLeft * 100).toFixed(2) + " %")); outputPar.appendChild(document.createElement("br")); outputPar.appendChild(document.createTextNode("Purchase price: $ " + purchasePrice)); outputPar.appendChild(document.createElement("br")); outputPar.appendChild(document.createTextNode("Prorated refund: $ " + proratedRefund)); outputPar.appendChild(document.createElement("br")); outputPar.appendChild(document.createTextNode("Paid in claims: $ " + amtPaidInClaims)); outputPar.appendChild(document.createElement("br")); outputPar.appendChild(document.createTextNode("Final refund: $ " + finalRefund)); outputPar.appendChild(document.createElement("br")); document.body.appendChild(outputPar); } } /** * Utility function to make sure an input is a correct Date format * @param date Date - A date or date-time value. * @returns boolean - Whether the input date is a valid date. */ function isValidDate(date) { "use strict"; if ( Object.prototype.toString.call(date) !== "[object Date]" ) { return false; } else if ( isNaN(date.getTime()) ) { return false; } else { return true; } } /** * Utility function to convert a Date to MM/DD/YYYY formatted String. * @param date Date - A date or date-time value. * @returns String - A String formatted to MM/DD/YYYY date if date is valid, otherwise a String with error message. */ function formatDate(date) { "use strict"; var inputDate = new Date(date); if ( isValidDate(inputDate) ) { var year = inputDate.getFullYear(); var month = (1 + inputDate.getMonth()).toString(); // months are 0-indexed hence the 1 + getMonth() var day = inputDate.getDate().toString(); return(month + '/' + day + '/' + year); } else { return("Invalid date: " + inputDate); } } </script> </body> </html> Sample input:
Product Purchase date: 6/1/2013
Contract Purchase date: 1/15/2014
Purchase Price: 699
Term (in years): 10
Cancel date: 07/13/2015
Paid in claims: 147
Grace period: 60
Resulting output:
Product Purchase date: 6/1/2013 Contract Purchase date: 1/15/2014 Total days: 3650 (10 years) Expiration date: 5/30/2023 Cancel date: 7/13/2015 Grace period: 60 days Days elapsed: 543 (1.49 years) Refund percent: 85.12 % Purchase price: $ 699.00 Prorated refund: $ 595.01 Paid in claims: $ 147.00 Final refund: $ 448.01
