2

Please correct or explain how my over-simplification is incorrect as I am not a JavaScript expert.

But I just need to know if an object is a valid date. This will only come from user input (ie, text box).

var is_valid_date = function(date) {
    try {
        var d = new Date(date);
        return true;
    }
    catch(e) {
        return false;
    }
}
cbmeeks
  • 11,248
  • 22
  • 85
  • 136

3 Answers3

12

YOU have to decide what form of dates you want to accept.

Then, once you know what forms you want to accept, you can then check the spec for new Date(str) or date.parse() on MDN and see if it supports exactly what you want and if it does the right things on error conditions (it probably will not). If not, then you will have to do some manual parsing.

If you want further help from us, you will need to specify what forms of date you want to accept.

There are also some browser differences as javascript has moved to support additional date formats and earlier browsers had some inconstencies between them which all means you'll want to build yourself a simple test script with a bunch of legal and illegal date format strings and see if your validity detection does what you want in several browsers. This isn't rocket science to get it right, but it's not trivial either and requires some work unless you only want to accept what the original date object supported (which is unlikely).

If this were my code, I'd probably decide that it's far less work to do manual parsing of your desired input format that you know with 100% certainty will work in all browsers because it's your own manual parsing. I'd probably use a regex to parse the date and then convert each component to a number and check each component for validity. You can then feed those numeric components to the Date constructor to create the Date object.

If you can tell by now, the built-in date class isn't very useful for user entered input. If you're willing to use a library for this, the date.js library has a ton of useful functionality in this regard.

Here's an example of a manual parsing function that accepts these US formats:

mm-dd-yyyy
mm dd yyyy
mm/dd/yyyy

JS Code:

function checkDate(str) {
    var matches = str.match(/(\d{1,2})[- \/](\d{1,2})[- \/](\d{4})/);
    if (!matches) return;

    // parse each piece and see if it makes a valid date object
    var month = parseInt(matches[1], 10);
    var day = parseInt(matches[2], 10);
    var year = parseInt(matches[3], 10);
    var date = new Date(year, month - 1, day);
    if (!date || !date.getTime()) return;

    // make sure we have no funny rollovers that the date object sometimes accepts
    // month > 12, day > what's allowed for the month
    if (date.getMonth() + 1 != month ||
        date.getFullYear() != year ||
        date.getDate() != day) {
            return;
        }
    return(date);
}

And a demo with some test cases: http://jsfiddle.net/jfriend00/xZmBY/

If you want the Euro format, it's a trivial matter to switch the code to that. In either case, you have to decide which format you accept, code for it and then communicate to the user which format is required. If you think this is messy, then perhaps you will see why so many sites use a date calendar picker that doesn't have this complexity.

jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • I think there's a bit more to it. `Date(string)` should really never be used unless you know exactly what you're doing. Even if his desired format is supported by all browsers, you have the problem that other formats are also supported; that may not be desirable, and having some formats supported in some browsers and not others is almost definitely not desirable. Better to check the format by hand and create the date with the "numbers" version of the constructor. – Dagg Nabbit Apr 21 '12 at 21:49
  • @GGG - please see how I finished off my answer. – jfriend00 Apr 21 '12 at 21:51
  • I added a sample function for parsing your own dates. – jfriend00 Apr 21 '12 at 22:28
  • @GGG - As I said in the very beginning of my answer, the OP has to decide what formats they accept, code for that and communicate that to the user who is entering the date. It's a trivial matter to modify this code to accept day first, but the code can't auto-detect which is being used. – jfriend00 Apr 21 '12 at 22:31
  • I'm just pulling your chain. You may want to change the `dd-mm-yyyy` stuff to reflect the actual format, though. – Dagg Nabbit Apr 21 '12 at 22:42
  • I think your example code could be much simpler. Take a look at my version of the fiddle - http://jsfiddle.net/yQ88y/1/ A few things are different. I think you only need to check that the month is the same to check for "rolling over." I changed the regex to disallow dates like `10/11-2001`. Also I allowed dates before 1970, can you explain why that was disallowed in your code? I assume because it's represented as a negative number? – Dagg Nabbit Apr 21 '12 at 23:13
  • 1
    @GGG - I didn't want to allow any rollovers, not in day or month. I find `parseInt()` much more descriptive/readable in the code than just `+`. I didn't want to have to test how browsers behaved with various inputs into the `Date()` constructor so I wrote more code to be able to avoid that. I also just discovered that `if (date)` isn't enough to detect a valid date. It may be a valid object with an invalid date in it. Anyway, I incorporated some of your suggestions here: http://jsfiddle.net/jfriend00/xZmBY/ – jfriend00 Apr 21 '12 at 23:41
  • 1
    what I'm saying is that if *anything* rolls over, month will change, so you can just check month, you don't have to check day and year. The parseInt stuff is just a matter of personal taste, I guess... to me it's a distraction, I didn't actually mean for you to change it, I just changed it because it helps me mentally process the code better. Also, I'm not sure if you noticed my change to the regex, but I think it's probably better to only allow all slashes or all dashes, not mixed. – Dagg Nabbit Apr 21 '12 at 23:45
  • @DaggNabbit Nice suggestion about the month only changes if *anything* rolls over. Using the `+` operator to convert strings to integer is faster & shorter but less descriptive. I only use it in personal projects :) – A1rPun Dec 30 '15 at 14:54
1

Please correct or explain how my over-simplification is incorrect as I am not a JavaScript expert.

But I just need to know if an object is a valid date. This will only come from user input (ie, text box).

Here's why it's an oversimplification.

First of all, it sounds like you really want to check the validity of a string representation of a Date object. This is not particularly useful by itself, because you are going to want to use the date for something in your script, send it to the server, etc.

If you want to use the date in your script, there are caveats.

new Date('2020-10-10') // Fri Oct 09 2020 20:00:00 GMT-0400 (EDT)

If you want to pass it to the server, you'll need to do more than just check validity– you'll need to use a format that your server side code can interpret.

If that's the case, you could consider normalizing the string into a format of your choice. You'd want to be able to create equivalent dates from the normalized strings in both your client and server side code. For simplicity, the format can be human-readable (not a timestamp), and you can replace the value of the text input with the normalized string.

Checking the validity of the string can simply be a part of normalization... have the function return false or an empty string if the input was bad, don't change the text input's value, and instead show a message indicating that the value is invalid:

// assume `birthday` is a text input.

birthday.onblur = function() {
    
    var dateString = normalizeDate(birthday.value);
    
    if (dateString) {
        validator.style.display = 'none';
        birthday.value = dateString;
    } else {
        validator.style.display = 'block';
    }
        
}; 

Here's an example of what the normalizeDate function might look like. This example uses the format 'yyyy-mm-dd', you can change it to suit your needs.

function normalizeDate(dateString) {

    // If it's not at least 6 characters long (8/8/88), give up.
    if (dateString.length && dateString.length < 6) {
        return '';
    }

    var date = new Date(dateString), 
        month, day;

    // If input format was in UTC time, adjust it to local.
    if (date.getHours() || date.getMinutes()) {
        date.setMinutes(date.getTimezoneOffset());
    }
    
    month = date.getMonth() + 1;
    day = date.getDate();
    
    // Return empty string for invalid dates
    if (!day) {
        return '';
    }
    
    // Return the normalized string.
    return date.getFullYear() + '-' +
        (month > 9 ? '' : '0') + month + '-' + 
        (day > 9 ? '' : '0') + day;
}

Here's the obligatory live demo.

Community
  • 1
  • 1
Dagg Nabbit
  • 75,346
  • 19
  • 113
  • 141
0

new Date() doesn't throw an exception if month>12 for example, you can use Date.parse() and test the returned value with isNaN()

Soufiane Hassou
  • 17,257
  • 2
  • 39
  • 75
  • `+ new Date('1980-13-10')` gives me `NaN` in Chrome, same as `Date.parse`. What's the difference? – Dagg Nabbit Apr 21 '12 at 21:33
  • `Date.parse` will return non-NaN responses for strings with single integer values, e.g., `Date.parse("2")` => `981003600000`. Standalone numbers are read as a month values without day and year. (The browser uses default values of `1` for day and year when they're omitted.) – apsillers Apr 21 '12 at 21:35
  • @apsillers `+ new Date('2')` gives the same result in my browser, `981003600000`. – Dagg Nabbit Apr 21 '12 at 21:37
  • I think `Date.parse` is nearly the same as `+new Date` (except that zero arguments to `+new Date()` results in the current time, and `Date.parse()` is `NaN`, but that's only difference). – apsillers Apr 21 '12 at 21:39
  • @apsillers so this answer doesn't solve the problem, it just shuffles it around a bit. – Dagg Nabbit Apr 21 '12 at 21:42