9
\$\begingroup\$

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 getElementById instead 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
\$\endgroup\$
0

5 Answers 5

7
\$\begingroup\$

Your code is really clean, but it could be improved in places:


  • You could consider placeholder attributes in the input fields on your HTML to display the format of the data that should be entered.

  • Your closing </style> tag isn't indented on the same level as the opening tag, it should be.

  • Style-wise, It's easier on people if you can align all your <input>s to the same vertical values, rather than based on the length of the description.

  • You have extraneous whitespace in some of your brackets: if ( finalRefund < 0.0 ), should be:

    if (finalRefund < 0.0)
    
  • The naming behind outputPar could be improved; outputParagraph: for example.

  • Rather than repeating outputPar.appendChild() over and over, move it to an array, and do it in a loop:

    outputPar.appendChild(document.createTextNode("Product Purchase date: " + formatDate(productPurchaseDate)));
    outputPar.appendChild(document.createElement("br"));
    

    like:

    var contentToBuild = [
        "Product Purchase date: " + formatDate(productPurchaseDate),
        // More here
        ];
    contentToBuild.forEach(function(element){
        outputParagraph.appendChild(document.createTextNode(element));
        outputParagraph.appendChild(document.createElement("br"));
    });
    
  • Your usage of brackets around math operations is inconsistent:

    var msPerDay = (1000 * 60 * 60 * 24); 
    var termLeft = 1.0 - termUsed;
    

    I'd suggest sticking to the same usage throughout, the brackets seem clearer, but often aren't required.

  • isValidDate() could use some re-arranging.

    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;
        }
    }
    

    Rather than testing isNaN(date.getTime()) second, test it first, and remove it from the if-else block.

    Then, you can simply return the boolean value of

    Object.prototype.toString.call(date) !== "[object Date]"
    

    into:

    function isValidDate(date) {
        "use strict";
        if (isNaN(date.getTime())) {
            return false;
        } 
        return (Object.prototype.toString.call(date) === "[object Date]");
    }
    

    Note that, in your code, you test against negatively (!==) the variable, and return false. It'd be easier, in future, if you test against positively (===) and returned true.


I'm not familiar with the level of JavaScript expertise your colleagues have, but, this code is well written, the code is consistent, and it contains descriptive @params and @returns. Good Work!

\$\endgroup\$
1
  • 1
    \$\begingroup\$ Nice review! +1 But you didn't need to flip the isNaN and the type check, you could just as well do type check first, and then return ! isNaN(...). You could also blend the if in the return statement itself. (Sometimes that might make the expression complex and hurt readability, but in this case I think it would be fine.) \$\endgroup\$
    – janos
    Commented Jul 14, 2015 at 6:44
5
\$\begingroup\$

Functions

Use more functions.

/**
 * 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.");
}

This is one example of a place you could use another function. You can split that off into an isValidInput() function, perhaps, and call that by itself and get rid of the if/else if/else structure at the beginning of calculateProratedRefund(), replacing it with something like this:

if (!isValidInput()) { return }

// other code here
\$\endgroup\$
4
\$\begingroup\$

It's not very user friendly that the validation reports one error at a time. It can be annoying that even after you correct an input, you might still get further alerts again and again.

A more common practice is to collect all the errors in an array for example. After all the validators are executed, if the array is non-empty, then print to the user all the different problems found.

For example, if only the purchase date and contract date were invalid, you could display in the alert the text:

Invalid input:

- Invalid Product Purchase Date.
- Invalid Contract Purchase Date.

For an extra nice touch, if there is only one error found, then no need for the bullet point style and the "Invalid input:" introduction. Just the text of the one error alone is enough.


The way you check if the date is valid is suspicious. Since the date parameter of isValidDate is always created with new Date(...), I don't see how it can possibly be anything else but a Date instance.

I believe you can safely simplify the function to:

function isValidDate(date) {
    "use strict";
    return ! isNaN(date.getTime());
}
\$\endgroup\$
3
  • \$\begingroup\$ That's a good idea! Being that I have two separate functions for validations, would I want that array of error messages to be within my main function, and to be called to read messages from it within my early exit block? \$\endgroup\$
    – Phrancis
    Commented Jul 14, 2015 at 15:31
  • \$\begingroup\$ So, create an empty array to hold the errors, var errors = [], and then replace the if-else chain with ifs, which errors.push(...) the errors found. After the validation, check if errors.length == 0 or not. Does this answer your question? If not ping me on chat! \$\endgroup\$
    – janos
    Commented Jul 14, 2015 at 18:05
  • \$\begingroup\$ That's perfect, I will try that! \$\endgroup\$
    – Phrancis
    Commented Jul 14, 2015 at 23:59
3
\$\begingroup\$

Just a small note.

    /**
    * 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);
    }

You assigned a value to termUsed as 0.0 already. Get rid of the first if condition, and just check if (daysElapsed > gracePeriodInDays), and assign termUsed to then be daysElapsed / totalDays. Just a redundant if statement, and it stood out to me.

Otherwise, your code looks clean and documented, after reading the other reviews.

\$\endgroup\$
0
2
\$\begingroup\$

I have applied the advice suggested by @Quill and that helped clean up the code quite a bit, especially the last part where values are appended to the HTML document. The bit about putting the content to append into an array and just iterating over it made the code a lot more DRY!

I also applied @Hosch250's suggestions and extracted the date and number validation logic in two separate functions, validateDates and validateNums, and that also really helped clean up the code. It also allowed more flexibility in catching other invalid values, which in this case is very important since finances are involved.

I added a small improvement suggested by @CanadianLuke as well.

I applied @janos' idea of adding error messages to arrays, then checking for array.length > 0 and print those values in an alert to the user, like so:

errors2

Improved code:

<!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 <strong>MM/DD/YYYY</strong><br/>
Enter whole numbers for <strong>Term (in years)</strong> and <strong>Grace Period (in days)</strong>.</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          = parseFloat (document.getElementById("termInYears").value).toFixed(2);
    var cancelDate           = new Date   (document.getElementById("cancelDate").value);
    var amtPaidInClaims      = parseFloat (document.getElementById("amtPaidInClaims").value).toFixed(2);
    var gracePeriodInDays    = parseFloat (document.getElementById("gracePeriodInDays").value).toFixed(2);

    /* 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));

    /* Validation of user input. 
     *   If any values are invalid, we return early and exit the function with error message to the user.
     *   If all the input values are valid, we proceed to calculations.
     * NOTE: The two validation functions return an array of errors pushed from the called function. 
     *   If the array's length is 0 (i.e., empty array) there are no errors. */
    var datesValid = validateDates(productPurchaseDate, contractPurchaseDate, cancelDate, expirationDate);
    var numsValid  = validateNums(purchasePrice, termInYears, amtPaidInClaims, gracePeriodInDays);
    if (datesValid.length > 0 || numsValid.length > 0) {
        window.alert("Some values are invalid:"
          + "\n - " + datesValid.join("\n - ")
          + "\n - " + numsValid.join("\n - ")
        );
        return false;
    }

    /* 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.
     * Otherwise, if days elapsed exceed the grace period, the refund becomes prorated. */
    var termUsed = 0.0;
    if (daysElapsed > gracePeriodInDays) {
        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;
    }

    /* List of content to append into document. */
    var contentToAppend = [
        "Product Purchase date: "  + formatDate(productPurchaseDate),
        "Contract Purchase date: " + formatDate(contractPurchaseDate),
        "Total days: "             + totalDays + " (" + termInYears + " years)",
        "Expiration date: "        + formatDate(expirationDate),
        "Cancel date: "            + formatDate(cancelDate),
        "Grace period: "           + gracePeriodInDays + " days",
        "Days elapsed: "           + daysElapsed + " (" + parseFloat(daysElapsed / 365).toFixed(4) + " years)",
        "Refund percent: "         + (termLeft * 100).toFixed(2) + " %",
        "Purchase price: $ "       + purchasePrice,
        "Prorated refund: $ "      + proratedRefund,
        "Paid in claims: ($ "      + amtPaidInClaims + ")",
        "Final refund: $ "         + finalRefund
    ];

    /* Append calculation results to HTML document. */
    var outputParagraph = document.createElement("p");

    contentToAppend.forEach(function(element) {
        outputParagraph.appendChild(document.createTextNode(element));
        outputParagraph.appendChild(document.createElement("br"));
    });

    document.body.appendChild(outputParagraph);
    return true;
}


/** Confirm that all dates are valid Date values and that they follow business rules.
 *  @param {Date} productPurchaseDate  Date of purchase of the product(s) covered by the contract.
 *  @param {Date} contractPurchaseDate Date of purchase of the contract to be cancelled.
 *  @param {Date} cancelDate           Date on which the cancellation of the contract was requested.
 *  @param {Date} expirationDate       Date on which the contract expires and is no longer eligible for cancellation or claims payment. 
 *  @return {Array}                    Array of Strings containing all applicable error messages. */
function validateDates(productPurchaseDate, contractPurchaseDate, cancelDate, expirationDate) {
    "use strict";
    var dateErrors = new Array();
    if (!isValidDate(productPurchaseDate)) {
        dateErrors.push("Invalid Product Purchase Date: " + productPurchaseDate);
    } 
    if (!isValidDate(contractPurchaseDate)) {
        dateErrors.push("Invalid Contract Purchase Date: " + contractPurchaseDate);
    } 
    if (contractPurchaseDate < productPurchaseDate) {
        dateErrors.push("Contract cannot be purchased (" + formatDate(contractPurchaseDate) 
          + ") prior to the product (" + formatDate(productPurchaseDate) + ")");
    } 
    if (!isValidDate(cancelDate)) {
        dateErrors.push("Invalid Cancel Date: " + cancelDate);
    } 
    if (cancelDate < productPurchaseDate || cancelDate < contractPurchaseDate) {
        dateErrors.push("Cancel date (" + formatDate(cancelDate) 
          + ") cannot be prior to Product (" + formatDate(productPurchaseDate) 
          + ") or Contract purchase (" + formatDate(contractPurchaseDate) + ")");
    } 
    if (cancelDate > expirationDate) {
        dateErrors.push("Cancel date (" + formatDate(cancelDate) 
          + ") cannot be past Expiration date (" + formatDate(expirationDate) + ")");
    }
    return dateErrors;
}

/** Confirm that all numbers are valid numbers and that they follow business rules.
 *  @param {number} purchasePrice     The dollar amount purchase price of the contract.
 *  @param {number} termInYears       The duration term of the policy in 365-day years. Must be whole number. 
 *  @param {number} amtPaidInClaims   The dollar amount of claims paid against the contract prior to cancellation.
 *  @param {number} gracePeriodInDays Grace period during which a contract holder can cancel with full refund/credit. Must be whole number.
 *  @return {Array}                   Array of Strings containing all applicable error messages. */
function validateNums (purchasePrice, termInYears, amtPaidInClaims, gracePeriodInDays) {
    "use strict";
    var numErrors = new Array();
    if (isNaN(purchasePrice) || purchasePrice < 0) {
        numErrors.push("Invalid purchase price: " + purchasePrice);
    } 
    if (isNaN(termInYears) || termInYears < 0) {
        numErrors.push("Invalid Term (in years): " + termInYears);
    } 
    if (isNaN(amtPaidInClaims) || amtPaidInClaims < 0) {
        numErrors.push("Invalid paid claims amount: " + amtPaidInClaims);
    } 
    if (isNaN(gracePeriodInDays) || gracePeriodInDays < 0) {
        numErrors.push("Invalid grace period: " + gracePeriodInDays);
    }
    if (termInYears % 1 !== 0) {
        numErrors.push("Term (in years) " + termInYears + " is not a whole number.");
    }
    if (gracePeriodInDays % 1 !== 0) {
        numErrors.push("Grace Period (in days) " + gracePeriodInDays + " is not a whole number.");
    }
    return numErrors;
}

/** 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 (isNaN(date.getTime())) {
        return false;
    } 
    return (Object.prototype.toString.call(date) === "[object Date]");
}

/** 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 as MM/DD/YYYY 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().toString();
        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>
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.