Title: SimpleDateFormat Unexpected Success with Phone Number
Tags: java;string;datetime
Question: Accidentally passing in a phone number string into the format method of SimpleDateFormat sometimes results in a valid date being parsed.
As an example, passing the number "518-123-4567" (literal, with hyphens) somehow results in the date "11/23/0517 05:27 AM"
We are using this in an area where a String could represent a number of different things, and we were under the assumption that a string with digits and hyphens in the way that a phone number is typically written would fail when parsed as a date.  Our code simply checks to ParseException, and accepts anything that does not throw such an exception as valid.  Why doesn't this sort of string fail parsing?  Is there a better way to check to see if a string could potentially be or not be a date?
private static Date getPromisedDate(String promisedText) {
SimpleDateFormat promsiedDateTimeFormat = new SimpleDateFormat("yyyyMMddHHmm");
if(null != promisedText) {
try {
return promsiedDateTimeFormat.parse(promisedText);
}
catch (ParseException e) { }
}
return null;
Comment: @Martin yessir, done.

Comment: I do get java.text.ParseException with the given input with empty SimpleDateFormat constructor. Give us the code where date format object is instantiated.

Comment: There has got to be a better way to "validate" input than trying to parse it into a date string and expecting it to fail. As you discovered there is always the edge case where the validation succeeds.

Comment: Can you share us a sample code where this happens? Especially the SimpleDateFormat declaration you're using...

Comment: Thanks, will take a look... :)

Comment: The answer from Jon Skeet will do just what you need... :)

Here is the accepted answer: Your SimpleDateFormat is in "lenient" mode - which is very lenient indeed. If you use
promsiedDateTimeFormat.setLenient(false);
it will throw an exception as you'd expect when you try to parse the bogus data.
Personally I think it should be strict by default, but...
Comment for this answer: Wow, I never noticed or knew about that option.  Fantastic!  Thank you.

Here is another answer: From DateFormat#parse(String):
Parses text from the beginning of the given string to produce a date. The method may not use the entire text of the given string.
So, the method might not parse the entire string. It will stop at the position, where the pattern stops matching. In your case, the matching is done in these ways:
yyyy  MM   dd  HH   mm
518   -1   23  -4   567
The year parsing yyyy stops at first -, as it can't be parsed as year. So, the year is 518. Then month is taken as -1, then 23 goes as dd, so on.
You can use the overloaded version of parse method and pass a ParsePosition instance to see the details.
From DateFormat#parse(String, ParsePosition):
By default, parsing is lenient: If the input is not in the form used by this object's format method but can still be parsed as a date, then the parse succeeds. Clients may insist on strict adherence to the format by calling setLenient(false)
So, just set the leniency to false, to stop it from parsing date not matching the format:
promsiedDateTimeFormat.setLenient(false);
For example, on using ParsePosition, suppose you pass the date string as - "518-123-4567abc". Surprisingly, it would also be parsed with leniency set to true, because the last part abc would not be cwilliams@example.org. To test this, you can try the following code:
private static Date getPromisedDate(String promisedText) throws Exception {
ParsePosition pp = new ParsePosition(0);
SimpleDateFormat promsiedDateTimeFormat = new SimpleDateFormat("yyyyMMddHHmm");
if(null != promisedText) {
try {
Date date = promsiedDateTimeFormat.parse(promisedText);
// If complete string is not parsed, throw ParseException
if (pp.getIndex() != promisedText.length()) {
throw new ParseException("Unparseable date given: " + promisedText, pp.getIndex());
}
return date;
}
catch (ParseException e) { throw e; }
}
return null;

Here is another answer: To explain what happened: Year 581, Month -1, day 23, Hour -4, Minute 567. Sum everything up and you will get the resulting date. To avid such results, see Post of JonSkeet
Comment for this answer: I guess that makes sense.

{'source': 'StackExchangeDataset', 'subset': 'stackoverflow.com', 'metadata': {'AnswerCount': 3, 'CommentCount': 6, 'ContentLicense': 'CC BY-SA 3.0', 'CreationDate': '2013:00:10/22/13', 'FavoriteCount': None, 'PostTypeId': 1, 'Tags': '<java><string><datetime>', 'Title': 'SimpleDateFormat Unexpected Success with Phone Number', 'ViewCount': 93, 'id': 19521361, 'user_AboutMe': "<ul>\n<li>Professional Programmer\n\n<ul>\n<li>Java</li>\n<li>Python</li>\n<li>COBOL</li>\n<li>C/C++</li>\n<li>Angular.js</li>\n</ul></li>\n<li>Game Developer\n\n<ul>\n<li>Pygame</li>\n<li>Ren'py</li>\n</ul></li>\n<li>Competitive Gaming Enthusiast\n\n<ul>\n<li>Yomi</li>\n<li>Super Smash Bros</li>\n<li>Marvel vs Capcom</li>\n<li>Dragon Ball FighterZ</li>\n<li>Street Fighter</li>\n<li>Civilization IV</li>\n<li>Divekick</li>\n</ul></li>\n<li>Challenge Game Lover\n\n<ul>\n<li>XCOM 2 (+ Long War 2)</li>\n<li>Xenonauts</li>\n<li>Dwarf Fortress</li>\n<li>RimWorld</li>\n<li>Minecraft: Terrafirmacraft</li>\n<li>Don't Starve</li>\n<li>Pokemon: Nuzlocke</li>\n<li>Artemis: Starship Bridge Simulator</li>\n<li>Gauntlet</li>\n<li>Darkest Dungeon</li>\n</ul></li>\n<li>Roleplaying Game Enthusiast\n\n<ul>\n<li>Dungeon World</li>\n<li>Anima: Beyond Fantasy</li>\n<li>Tails of Equestria</li>\n<li>D&amp;D 3rd Edition / 3.5 / Pathfinder</li>\n<li>D&amp;D 5th Edition</li>\n<li>D&amp;D 2nd Edition</li>\n<li>Old World of Darkness</li>\n<li>Street Fighter: The Storytelling Game</li>\n</ul></li>\n<li>Fandom Member\n\n<ul>\n<li>My Little Pony: Friendship is Magic</li>\n<li>Furries</li>\n</ul></li>\n</ul>\n", 'user_AccountId': 2222816.0, 'user_DisplayName': 'Southpaw Hare', 'user_Reputation': 1525.0, 'user_Views': 275.0, 'user_id': 1961815.0}}