Prevent SimpleDateFormat From Matching Strings With Extraneous Characters - java

I'm using the following code to categorize whether or not the data type of text input is a date:
private boolean isDate(String val)
{
//These are the only formats dates will have
String[] formatList = {"MM/dd/yyyy", "MM-dd-yyyy", "MMM/dd/yyyy", "dd-MMM-yyyy"};
SimpleDateFormat dateFormat = new SimpleDateFormat();
dateFormat.setLenient(false);
dateFormat.
//Loop through formats in formatList, and if one matches the string return true
for (String str:formatList)
{
try
{
dateFormat.applyPattern(str);
dateFormat.parse(val.trim());
} catch (ParseException pe)
{
continue;
}
return true;
}
return false;
}
This method is called in a switch statement along with other function like isNumber, isDatetime, isVarchar2, etc. When I pass the value '4/05/2013 23:54' which is a datetime, the SimpleDateFormat object successfully parses it. I assume this is because it performs a regex match. This means I have to call isDate after isDatetime or nothing will ever be categorized as a datetime. Is there a simple way to get SimpleDateFormat to strictly match (ie, choke when there are extra characters)?

I would like to use java.time API from Java8+ with only one pattern like so :
private boolean isDate(String dateString) {
DateTimeFormatter format =
DateTimeFormatter.ofPattern(
"[M/d/uuuu][M-d-uuuu][MMM/d/uuuu][d-MMM-uuuu]"
);
try {
LocalDate.parse(dateString, format);
return true;
}catch (DateTimeParseException e){
return false;
}
}
where [M/d/uuuu][M-d-uuuu][MMM/d/uuuu][d-MMM-uuuu] means to match either M/d/uuuu or M-d-uuuu or MMM/d/uuuu or d-MMM-uuuu

If you read the documentation, i.e. the javadoc of parse, you'd see:
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.
See the parse(String, ParsePosition) method for more information on date parsing.
The javadoc of the other method says:
Parameters:
source - The date/time string to be parsed
pos - On input, the position at which to start parsing; on output, the position at which parsing terminated, or the start position if the parse failed.
So, to ensure the entire text matches the date format, use the second method and check the ending parse position.
This also has the beneficial effect of not using exceptions for control flow.
private static boolean isDate(String val) {
String trimmedVal = val.trim();
//These are the only formats dates will have
String[] formatList = {"MM/dd/yyyy", "MM-dd-yyyy", "MMM/dd/yyyy", "dd-MMM-yyyy"};
SimpleDateFormat dateFormat = new SimpleDateFormat();
dateFormat.setLenient(false);
ParsePosition pos = new ParsePosition(0);
for (String str : formatList) {
pos.setIndex(0);
dateFormat.applyPattern(str);
dateFormat.parse(trimmedVal, pos);
if (pos.getIndex() == trimmedVal.length())
return true; // full text parsed without error
}
return false;
}
Test
System.out.println(isDate("12/31/2018"));
System.out.println(isDate("12-31-2018"));
System.out.println(isDate("Dec/31/2018"));
System.out.println(isDate("31-Dec-2018"));
System.out.println(isDate("4/05/2013"));
System.out.println(isDate("4/05/2013 23:54"));
Output
true
true
true
true
true
false

Related

SimpleDateFormat doesn't work as expected [duplicate]

This question already has answers here:
SimpleDateFormat parse(string str) doesn't throw an exception when str = 2011/12/12aaaaaaaaa?
(7 answers)
Closed 4 years ago.
I try to use this function but it doesn't work with this case '12/05/201a' somebody knows why happen this?
In my test I use this System.out.println(isThisDateValid("12/05/201a", "dd/MM/yyyy")); and the answer was true but I'm expected the result would be false because year contains letters.
public static boolean isThisDateValid(String dateToValidate, String dateFromat)
{
if (dateToValidate == null)
{
return false;
}
SimpleDateFormat sdf = new SimpleDateFormat(dateFromat);
sdf.setLenient(false);
try
{
//if not valid, it will throw ParseException
Date date = sdf.parse(dateToValidate);
System.out.println(date);
} catch (ParseException e)
{
e.printStackTrace();
return false;
}
return true;
}
DateFormat#parse doesn't necessarily use the entire 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.
(my emphasis)
SimpleDateFormat's docs tell us that yyyy doesn't necessarily mean it will require four digits for a year:
Year:
...
For parsing, if the number of pattern letters is more than 2, the year is interpreted literally, regardless of the number of digits. So using the pattern "MM/dd/yyyy", "01/11/12" parses to Jan 11, 12 A.D.
So it's correct (if possibly surprising) that it parses that string in the year 201.
You can use parse(String,ParsePosition) to figure out whether the entire string has been consumed, or validate it with a regular expression before parsing. Here's a version that will check that the whole string has been parsed, and not just the first characters:
public static boolean isThisDateValid(String dateToValidate, String dateFormat) {
if (dateToValidate == null) {
return false;
}
SimpleDateFormat sdf = new SimpleDateFormat(dateFormat);
sdf.setLenient(false);
ParsePosition position = new ParsePosition(0);
Date date = sdf.parse(dateToValidate, position);
return date != null && position.getIndex() == dateToValidate.length();
}

Why do I get a date back when I pass a date string with a "HH" pattern to the SimpleDateFormat?

Here's my code:
try {
DateFormat dateFormat = new SimpleDateFormat(pattern);
dateFormat.setLenient(false);
Date date = dateFormat.parse(value);
if (date != null) {
return true;
}
} catch (ParseException e) {}
1.) When I pass value as "01/07/2015" and pattern as "HH:mm" I correctly get an exception.
2.) However when I pass value as "01/07/2015" and pattern as "HH" I get a "Thu Jan 01 01:00:00 EST 1970" Date object.
I would except scenario #2 to also throw an exception since the given string completely doesn't match the given pattern. Why do I get that strange date even when setLenient(false) is set?
http://download.java.net/jdk6/archive/b104/docs/api/java/text/Format.html#parseObject(java.lang.String)
parseObject
public Object parseObject(String source)
throws ParseException
Parses text from the beginning of the given string to produce an object. The method may not use the entire text of the given string
I suppose #1 doesn't match the separator. You put / in it although the pattern is :.
And #2 stops matching right after HH because it parses text from the beginning of the given string and DOESN'T USE the entire text of the given string.
The JavaDoc beautifully sums up why you do not get an exception:
Throws: ParseException - if the beginning of the specified string cannot be parsed.
01 can be parsed with HH, therefore no exception.
I found a solution to this problem.
In order to resolve my issue I just wrapped my entire code in an if statement where I check if the length of the pattern is the same as the length of the value because they should be when I use this code for validation:
if(StringUtils.length(pattern) == StringUtils.length(value)) {
try {
DateFormat dateFormat = new SimpleDateFormat(pattern);
dateFormat.setLenient(false);
Date date = dateFormat.parse(value);
if (date != null) {
return true;
}
} catch (ParseException e) {}
}
return false;

Why java SimpleDateFormat can parse formatted text with extra characters?

I am parsing date strings from user input with MM-dd-yyyy HH:mm:ss format, and I found 12-20-2012 10:10:10 abcdexxxx could be pasred as well. How can this happen? Here is my code:
SimpleDateFormat df = new SimpleDateFormat( "MM-dd-yyyy HH:mm:ss" );
String currColValue = "12-20-2012 10:10:10 abcdexxxx";
try{
d=df.parse( currColValue );
}catch( ParseException e ){
System.out.println("Error parsing date: "+e.getMessage());
}
But there is no exception, the String value is parsed to be a Date. Why?
Per the Javadoc of the parse method:
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.
(emphases mine).
Contrary to the implication of comments above, this has nothing to do with lenient parsing; rather, it's just that this method is not intended to consume the whole string. If you wish to validate that it consumed the whole string, I suppose you could set up a ParsePosition object and use the two-arg overload, and then examine the ParsePosition afterward to see if it parsed to the end of the string.
java.time
I should like to contribute the modern answer. This question was asked just the month before java.time, the modern Java date and time API, came out, which we all should be using now.
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MM-dd-yyyy HH:mm:ss");
String currColValue = "12-20-2012 10:10:10 abcdexxxx";
try {
LocalDateTime ldt = LocalDateTime.parse(currColValue, formatter);
// Do something with ldt
} catch (DateTimeParseException e) {
System.out.println("Error parsing date and time: " + e.getMessage());
}
Output:
Error parsing date and time: Text '12-20-2012 10:10:10 abcdexxxx' could not be parsed, unparsed text found at index 19
Contrary to the old SimpleDateFormat class the parse methods of the modern classes do insist on parsing the entire string (there is a way to parse only part of the string if that is what you require). Also please note the precision and clarity of the exception message. By the way, SimpleDateFormat is not only long outdated, it is also notoriously troublesome. You found just one of many surprising problems it has. I recommend that you no longer use SimpleDateFormat and Date.
Link: Oracle tutorial: Date Time explaining how to use java.time.
Check SimpleDateFormat.parse(String) doc. It clearly says it.
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.
http://docs.oracle.com/javase/7/docs/api/java/text/DateFormat.html#parse(java.lang.String)
I want to contibute to the above correct answers with examples, using the method overload
public Date parse(String text, ParsePosition pos);
To parse the exact whole string just create a new ParsePosition object (with index 0, indicating that parse needs to start from begin), pass it to the method, and inspect its index property after parse.
Index is where the parse did end. If matches with string length then the string matches exactly form start to end.
Here is a unit test demonstrating it
public class DateParseUnitTest {
#Test
public void testParse(){
Date goodDate = parseExact("2019-11-05");
Date badDate1 = parseExact("foo 2019-11-05");
Date badDate2 = parseExact("2019-11-05 foo");
assert(goodDate != null);
assert(badDate1 == null);
assert(badDate2 == null);
}
#Nullable
private Date parseExact(#NonNull String text){
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
ParsePosition pos = new ParsePosition(0);
Date date = formatter.parse(text, pos);
if (pos.getIndex() != text.length())
return null;
return date;
}
}

parse check doesn't work

hi guys i am using this method to check if a string can be converted to a date or not but it seems that it's not working, this is the code i wrote, the user inputs a date in this format dd/MM/YYYY then this is what happens for checking it
...
String date = JOptionPane.showInputDialog(frame,"Insert Date:");
if (date ==null) { return;}
while (!(isValidDate(date))) {
JOptionPane.showMessageDialog(frame, "Incorrect Date");
date = JOptionPane.showInputDialog(frame,"Insert Date:");
if (date ==null) { return;} }
String[] parts = date.split("/");
int year = Integer.parseInt(parts[2]);
int month = Integer.parseInt(parts[1]);
int day = Integer.parseInt(parts[0]);
...
and this is the method for check the date
public boolean isValidDate(String dateString) {
SimpleDateFormat df = new SimpleDateFormat("dd/MM/YYYY");
if (dateString.length() != "ddMMYYYY".length()) {
return false; }
try {
df.parse(dateString);
return true;
} catch (ParseException e) {
return false;
}
this seems not to work cause it always goes into the while block whatever i insert in the input, what is the problem with this code ?
EDIT
fixed the error on the condition
if (dateString.length() != "ddMMYYYY".length())
now i got another problem it accepts values like 54/12/2030 which obvioiusly are not a date format
Your if condition seems to be wrong... This is how it should be.
if (dateString.length() != "dd/MM/YYYY".length()) return false;
if input date is 22/07/1986 obviously it's length will be more than length of ddMMYYYY because of missing slashes.
df.setLenient(false); Will ensure that it won't roll over for invalid dates. Just put thiss line after you created df object.
This is a very good reason for why you should use a static final define rather than repeating the same string throughout your code. You are comparing against one string and parsing against another, so the two are never going to match.
I also don't understand why you would go through SimpleDateFormat to parse a Date (which has things like year, month etc available as method calls) and then throw that away in order to parse the String again by hand.
Just rename isValidDate to parseDate, have it return a Date object or null if not valid, and have the while loop continue so long as the Date returned is null.
For one thing, you want your date to be separated by / in most places, but you check if the string has the same length as "ddMMYYYY". You should probably change your isValidDate() method to include the slashes (and a trailing close-brace):
public boolean isValidDate(String dateString) {
SimpleDateFormat df = new SimpleDateFormat("dd/MM/YYYY");
if (dateString.length() != "dd/MM/YYYY".length()) {
return false;
}
try {
df.parse(dateString);
return true;
} catch (ParseException e) {
return false;
}
}
for last problem i had referring to values like 54/77/4444 i changed the YYYY to yyyy and it worked

SimpleDateFormat Unexpected Success with Phone Number

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;
}
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...
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 parsed at all. 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;
}
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

Categories