SimpleDateFormat doesn't work as expected [duplicate] - java

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

Related

Prevent SimpleDateFormat From Matching Strings With Extraneous Characters

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

Exception while validating a date string in Java

I have found nice function which is validating the format and correctness of date String. I wanted to upgrade it to validate only >= 1900 years.
So this is what I found:
public boolean isDateValid(String date) {
try {
DateFormat df = new SimpleDateFormat("dd-MM-yyyy");
df.setLenient(false);
df.parse(date);
return true;
} catch (ParseException e) {
return false;
}
}
And this is my upgraded version:
public boolean isDateValid(String date) {
try {
DateFormat df = new SimpleDateFormat("dd-MM-yyyy");
df.setLenient(false);
df.parse(date);
Integer year = Integer.parseInt(date.substring(6, 10));
if (year>= 1900)
return true;
else
return false;
} catch (ParseException e) {
return false;
}
}
So instead of returning true I am checking if the year variable is greater or equal to 1900. The problem is when I run this function with "12-12-1xxx" (edit: or "12-12-1abc"). NumberFormatException is thrown while parsing year String to int. It definitely should not happen because ParseException should be thrown first, breaking the try {} block.
It looks like validation from first listing does not work properly because it accepts every "yyyy" part which begins with a number. Yet everything works fine for "12-12-xxxx" (edit: or "12-12-abcd").
EDIT:
Stop voting down my question and focus while you are reading. The question is very clear: why new SimpleDateFormat("dd-MM-yyyy").parse("12-12-1xxx") does not throw a ParseException?
As I understand from javadoc the SimpleDateFormat will take 1 as valid part of the year and will parse it. Instead you can try to validate date with regular expressions. You can find some examples here Regex to validate date format dd/mm/yyyy
The documentation of the parse method is:
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.
Because the whole string does not need to be used, then "12-12-1xxx" is actually parsed as "12-12-1", and you get a year 1 date.
Instead of using the substring, you could use the result of the parse method to get the year. getYear is depreciated and returns an offset from 1900 so you might want to convert to a Calendar or LocalDate first.
public boolean isDateValid(String date) {
try {
DateFormat df = new SimpleDateFormat("dd-MM-yyyy");
df.setLenient(false);
Date parsed = df.parse(date);
int year = parsed.getYear() + 1900;
if (year >= 1900)
return true;
else
return false;
} catch (ParseException e) {
return false;
}
}
I have check your function, and find below details.
If you will pass 12-12-1xxx in main function then also you will get true it will not return false even when I print the date output which is converted by df.parse(date) is Oct 10 00:00:00 GMT 2.
So you will not get parse exception anytime for this,
Suggestion
Either you change ParseException by Exception or also use catch for NumberFormatException as below.
public boolean isDateValid(String date) {
try {
DateFormat df = new SimpleDateFormat("dd-MM-yyyy");
df.setLenient(false);
df.parse(date);
Integer year = Integer.parseInt(date.substring(6, 10));
if (year>= 1900)
return true;
else
return false;
} catch (Exception e) { // Use Exception or if you want ParseException then use below commented code
return false;
}
/*catch (NumberFormatException nfe) { // Use this if you use ParseException
return false;
}*/
}
If anybody is interested in how the code should look like, here it is:
public boolean isDateValid(String date, String format) {
if (date.length()!=format.length())
return false;
try {
DateFormat df = new SimpleDateFormat(format);
df.setLenient(false);
df.parse(date); // exception is not thrown if day and month is
// correct AND the first char of year is a digit
// so if we have correct day and correct month
// and we know the year has 4 chars we can try to parse it
Integer year = Integer.parseInt(date.substring(6, 10));
if (year>= 1900 && year<=2015) // here we know that the year is 4 digit integer
return true; // and we can limit it
else
return false;
} catch (ParseException e) {
return false;
} catch (NumberFormatException e) {
return false;
}
}

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;

Regex pattern for date format yyyy/MM/dd

I need to validate a date with format yyyy/MM/dd using a regex pattern. I already have a regex for the format dd/MM/yyyy.
(0?[1-9]|[12][0-9]|3[01])/(0?[1-9]|1[012])/((19|20)\\d\\d)
I'm using the validation from this link http://www.mkyong.com/regular-expressions/how-to-validate-date-with-regular-expression. I need the validation for yyyy/MM/dd. Can anyone help me?
I have found the answer:
((?:19|20)\\d\\d)/(0?[1-9]|1[012])/([12][0-9]|3[01]|0?[1-9])
Old question, I know, but as I've just had cause to do this:
\d{4}\\\d{2}\\\d{2}
Looks a lot neater to my eyes than the other suggestions, and works outside of the 20th/21st century (unclear if OP required this or not).
However, my implementation will match for dates such as 9999/99/99 (clearly invalid) - but as a commenter suggested, SimpleDateFormat is the tool for that kind of validation, in my opinion.
If OP were able to use SimpleDateFormat as well as regex, then this would suffice:
public static final boolean isDateValid(String date) {
if (!date.matches("\d{4}\\\d{2}\\\d{2}"))
return false;
SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
try {
sdf.parse(date);
return true;
} catch (ParseExceptione) {
return false;
}
}
NB. Usual caveats about SimpleDateFormat not being thread safe etc.
Here is my take on it:
((19|20)[0-9]{2})/((0?[1-9])|1[012])/((0?[1-9])|(1[0-9])|(3[01]))
i think this helpful for u...
var regdate = /^(19[0-9][0-9]|20[0-9][0-9])\/(0[1-9]|1[012])\/(0[1-9]|[12][0-9]|3[01])$/;
A simple regex plus a SimpleDateFormat doesn't filter a String date like "2014\26\26", then it would not suffice.
So maybe the best approach is a fully strict regex pattern.
If you turn over your regex (Java) expression from the mkyong website you should be done. As you were already suggested:
String pattern = "((?:19|20)\\d\\d)/(0?[1-9]|1[012])/(0?[1-9]|[12][0-9]|3[01])";
And for extra assurance you can add a SimpleDateFormat. Here is my extra verbose approach:
if(stringDate.length()==10 && stringDate.matches("((?:19|20)\\d\\d)/(0?[1-9]|1[012])/(0?[1-9]|[12][0-9]|3[01])")){
Date d=null;
String checkDate=null;
DateFormat df = new SimpleDateFormat("yyyy/MM/dd");
try {
//Also parses the String as a Date with the format "yyyy/MM/dd"
//to be sure that the passed string is correct
d=df.parse(stringDate);
} catch (ParseException e) {
e.printStackTrace();
}
if (d != null) {
//Transforms the Date into a String with the format "yyyy/MM/dd"
checkDate=df.format(d);
System.out.println(checkDate);
}
}
Here is an another simple approach, write a small method that converts the string input to date and convert back the date to string. Then compare both string are equal. If equal then date is correct else its incorrect. All validations are taken care by this approach.
import java.text.SimpleDateFormat;
import java.util.Date;
public class TestDate {
public static boolean isValidDateFormat(String value, String format) {
Date date = null;
try {
SimpleDateFormat sdf = new SimpleDateFormat(format);
date = sdf.parse(value);
if (!value.equals(sdf.format(date))) {
date = null;
}
} catch (Exception ex) {
return false;
}
return date != null;
}
public static void main(String[] args) {
String format = "yyyy/MM/dd";
System.out.println("2017/02/31 isValidDateFormat: " + isValidDateFormat("2017/02/31", format));
System.out.println("2017/03/31 isValidDateFormat: " + isValidDateFormat("2017/03/31", format));
System.out.println("2017/04/31 isValidDateFormat: " + isValidDateFormat("2017/04/31", format));
System.out.println("2017/13/31 isValidDateFormat: " + isValidDateFormat("2017/04/31", format));
System.out.println("2017/01/35 isValidDateFormat: " + isValidDateFormat("2017/01/35", format));
System.out.println("017/01/30 isValidDateFormat: " + isValidDateFormat("017/01/30", format));
// output :
//2017/02/31 isValidDateFormat: false
// 2017/03/31 isValidDateFormat: true
// 2017/04/31 isValidDateFormat: false
// 2017/13/31 isValidDateFormat: false
// 2017/01/35 isValidDateFormat: false
// 017/01/30 isValidDateFormat: false
}
}
Just for fun, I wanted to see how the regex Jon Skeet's very valid comment mentioned (that a regex handling leap years correctly would be horrendous) would look like. And look - he was right ;)
^
(?:(?:19|2[01])\d\d\/(?:1[02]|0[13578])\/(?:[0-2]\d|3[01])) # 31 day months
|
(?:(?:19|2[01])\d\d\/(?:(?:11|0[469])\/(?:[0-2]\d|30))) # 30 day months
|
(?:(?:19|2[01])(?:[02468][1235679]|[13579][01345789])|1900|2100)\/02\/(?:[01]\d|2[0-8]) # Non leap year
|
(?:(?:(?:19|21)(?!00)|20)(?:[02468][048]|[13579][26]))\/02\/(?:[01]\d|2[0-9]) # Leap year
$
See it here at regex101.
It tests using four alternations:
31 day months
30 day months
February non-leap years
February leap years
Note that leap years are the years that are a multiple of four. An exception to that rule are multiples of 100 except if it's a multiple of 400. (Phew... This is what makes it horrendous.)
It's regexp date format(dd.MM.yyyy). You can adapt this one for you.
(((0[1-9]{1}|1[0-9]|2[0-9]).(0[1-9]|1[0-2]))|(30.(04|06|09|11))|((30|31).(01|03|05|07|08|10|12))).[0-9]{4}

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

Categories