Why java SimpleDateFormat can parse formatted text with extra characters? - java

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

Related

String to Date Conversion mm/dd/yy to YYYY-MM-DD in java [duplicate]

This question already has answers here:
Java Date Error
(8 answers)
Closed 4 years ago.
I want to convert String values in the format of mm/dd/yy to YYYY-MM-DD Date. how to do this conversion?
The input parameter is: 03/01/18
Code to convert String to Date is given below
public static Date stringToDateLinen(String dateVlaue) {
Date date = null;
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
try {
date = formatter.parse(dateVlaue);
} catch (ParseException e) {
e.printStackTrace();
}
return date;
}
When tried to convert using this method it shows the following error
java.text.ParseException: Unparseable date: "03/01/18"
As you say the input is in a different format, first convert the String to a valid Date object. Once you have the Date object you can format it into different types , as you want, check.
To Convert as Date,
SimpleDateFormat formatter = new SimpleDateFormat("MM/dd/yy");
date = formatter.parse(dateVlaue);
To Print it out in the other format,
SimpleDateFormat formatter1 = new SimpleDateFormat("yyyy-MM-dd");
dateString = formatter1.format(date)
You are writing it the wrong way. In fact, for the date you want to convert, you need to write
SimpleDateFormat formatter = new SimpleDateFormat("MM/dd/yy");
The format you are passing to SimpleDateFormat is ("yyyy-MM-dd") which expects date to be in form 2013-03-01 and hence the error.
You need to supply the correct format that you are passing your input as something like below
public static Date stringToDateLinen(String dateVlaue) {
Date date = null;
SimpleDateFormat formatter = new SimpleDateFormat("dd/MM/yy");
try {
date = formatter.parse(dateVlaue);
} catch (ParseException e) {
e.printStackTrace();
}
return date;
}
The solution for the above problem
Convert the String date value in the Format of "dd/mm/yy" to Date.
By using the converted Date can able to frame the required date format.
The method has given below
public static String stringToDateLinen(String dateVlaue) {
Date date = null;
SimpleDateFormat formatter = new SimpleDateFormat("dd/mm/yy");
String dateString = null;
try {
// convert to Date Format From "dd/mm/yy" to Date
date = formatter.parse(dateVlaue);
// from the Converted date to the required format eg : "yyyy-MM-dd"
SimpleDateFormat formatter1 = new SimpleDateFormat("yyyy-MM-dd");
dateString = formatter1.format(date);
} catch (ParseException e) {
e.printStackTrace();
}
return dateString;
}
EDIT: Your question said “String values in the format of mm/dd/yy”, but I understand from your comments that you meant “my input format is dd/mm/yy as string”, so I have changed the format pattern string in the below code accordingly. Otherwise the code is the same in both cases.
public static Optional<LocalDate> stringToDateLinen(String dateValue) {
DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("dd/MM/yy");
try {
return Optional.of(LocalDate.parse(dateValue, dateFormatter));
} catch (DateTimeParseException dtpe) {
return Optional.empty();
}
}
Try it:
stringToDateLinen("03/01/18")
.ifPresentOrElse(System.out::println,
() -> System.out.println("Could not parse"));
Output:
2018-01-03
I recommend you stay away from SimpleDateFormat. It is long outdated and notoriously troublesome too. And Date is just as outdated. Instead use LocalDate and DateTimeFormatter from java.time, the modern Java date and time API. It is so much nicer to work with. A LocalDate is a date without time of day, so this suites your requirements much more nicely than a Date, which despite its name is a point in time. LocalDate.toString() produces exactly the format you said you desired (though the LocalDate doesn’t have a format in it).
My method interprets your 2-digit year as 2000-based, that is, from 2000 through 2099. Please think twice before deciding that this is what you want.
What would you want to happen if the string cannot be parsed into a valid date? I’m afraid that returning null is a NullPointerException waiting to happen and a subsequent debugging session to track down the root cause. You may consider letting the DateTimeParseException be thrown out of your method (just declare that in Javadoc) so the root cause is in the stack trace. Or even throw an AssertionError if the situation is not supposed to happen. In my code I am returning an Optional, which clearly signals to the caller that there may not be a result, which (I hope) prevents any NullPointerException. In the code calling the method I am using the ifPresentOrElse method introduced in Java 9. If not using Java 9 yet, use ifPresent and/or read more about using Optional elsewhere.
What went wrong in your code?
The other answers are correct: Your format pattern string used for parsing needs to match the input (not your output). The ParseException was thrown because the format pattern contained hyphens and the input slashes. It was good that you got the exception because another problem is that the order of year, month and day doesn’t match, neither does the number of digits in the year.
Link
Oracle tutorial: Date Time explaining how to use java.time.

Date format changing

If input is 01-01-2015 it should change to 2015-01-01.
If input is 2015-01-01 it should change to 01-01-2015.
I used SimpleDateFormat but didn't get the correct output:
//Class to change date dd-MM-yyyy to yyyy-MM-dd and vice versa
public class ChangeDate {
static SimpleDateFormat formatY = new SimpleDateFormat("yyyy-MM-dd");
static SimpleDateFormat formatD = new SimpleDateFormat("dd-MM-yyyy");
//This function change dd-MM-yyyy to yyyy-MM-dd
public static String changeDtoY(String date) {
try {
return formatY.format(formatD.parse(date));
}
catch(Exception e) {
return null;
}
}
//This function change yyyy-MM-dd to dd-MM-yyyy
public static String changeYtoD(String date) {
try {
return formatD.format(formatY.parse(date));
}
catch(Exception e) {
return null;
}
}
}
I want some condition that automatically detects the date's pattern and change to the other format.
There are 2 options:
Try to check with regular expression sth. like:
if (dateString.matches("\\d{4}-\\d{2}-\\d{2}")) {
...
}
Try to convert to first pattern, if it throws exception, try to convert to another pattern (but it is bad practice to do so)
Regex Is Overkill
For date-time work, no need to bother with regex.
Simply attempt a parse with one format, trapping for the expected exception. If the exception is indeed thrown, attempt a parse with the other format. If an exception is thrown, then you know the input is unexpectedly in neither format.
java.time
You are using old troublesome date-time classes now supplanted by the java.time framework built into Java 8 and later. The new classes are inspired by the highly successful Joda-Time framework, intended as its successor, similar in concept but re-architected. Defined by JSR 310. Extended by the ThreeTen-Extra project. See the Oracle Tutorial.
LocalDate
The new classes include one, LocalDate, for date-only values without time-of-day. Just what you need.
Formatters
Your first format may be the standard ISO 8601 format, YYYY-MM-DD. This format is used by default in java.time.
If this first parse attempt fails because the input does not match ISO 8601 format, a DateTimeParseException is thrown.
LocalDate localDate = null;
try {
localDate = LocalDate.parse( input ); // ISO 8601 formatter used implicitly.
} catch ( DateTimeParseException e ) {
// Exception means the input is not in ISO 8601 format.
}
The other format must be specified by a coded pattern similar to what you are doing with SimpleDateFormat. So if we catch the exception from the first attempt, make a second parse attempt.
DateTimeFormatter formatter = DateTimeFormatter.ofPattern( "MM-dd-yyyy" );
LocalDate localDate = null;
try {
localDate = LocalDate.parse( input );
} catch ( DateTimeParseException e ) {
// Exception means the input is not in ISO 8601 format.
// Try the other expected format.
try {
localDate = LocalDate.parse( input , formatter );
} catch ( DateTimeParseException e ) {
// FIXME: Unexpected input fit neither of our expected patterns.
}
}
Read up about Pattern, Matcher, and Regular Expressions.
Java code (based on OP):
if (date.matches("\\d{2}-\\d{2}-\\d{4}")){
//convert D format to Y format...
} else if(date.matches("\\d{4}-\\d{2}-\\d{2}")){
//convert Y to D...
} else {
throw new IllegalArgumentException("Received date format is not recognized.");
}
Note: This Match-Pattern can be improved with capture groups.
Ex: "\\d{4}(-\\d{2}){2}" or "(-?\\d{2}){2}\\d{4}"
See: https://docs.oracle.com/javase/tutorial/displayCode.html?code=https://docs.oracle.com/javase/tutorial/datetime/iso/examples/StringConverter.java
Non-ISO Date Conversion
https://docs.oracle.com/javase/tutorial/datetime/iso/nonIso.html
Adding a new Chronology that identifies the ISO Date either way is another compatible (breakproof) means to input the Date Data and store it in correct Structures (where other Functions can easily operate upon the Data). See: https://docs.oracle.com/javase/8/docs/api/java/time/chrono/Chronology.html
The 'regex method' can be broken by erroneous input and leaves no means to return a standard error in response to whatever was input (to get a standard identical result everywhere).
See the answer provided by User "Tardate" in this Thread: How to sanity check a date in java .
You want bulletproof input and to store it in correctly identified Structures in order to easily manipulate it with other Functions.
Just compare the position of first '-' character in date string.

joda time parse method return format

I have very simple question - I read couple of threads here but I still do not understand how to get simple thing. I want to send string to method and get back joda date. I had no problem to build it up, but return format is 2015-03-11T17:13:09:000+01:00. How can I get desired (e.g. mmm-dd hh:mm) format back from below mentioned method (it mustto be a dateTime for sorting purposes on FX form)? I tried to gamble with another dateTimeFormatter but had no luck. Thank you very much in advance
public static DateTime stringToDateTime(String textDate) throws ParseException
{
DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss.SSS");
DateTime jodaTime = dateTimeFormatter.parseDateTime(textDate);
return jodaTime;
}
What do you mean by "return format"? "Format" term here could only be related to a string representation of a DateTime object. That means you should specify format of your input string (what you've already done in your code) - and a corresponding DateTime object will be created. After that you probably use toString() to check the results, but DateTime.toString() uses ISO8601 format (yyyy-MM-ddTHH:mm:ss.SSSZZ) according to JavaDoc - that gives you your 2015-03-11T17:13:09:000+01:00 result.
So to get it as desired you could try using toString(String pattern) method with format you need. But once again - it's just an output format to convert DateTime to String, it doesn't affect the datetime stored in your DateTime object.
I just use Calendar object so this is a possible way to do it:
static String stringToDateTime(String textDate) {
Calendar c = new GregorianCalendar();
// How you want the input to be formatted
DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
try {
Date date = df.parse(textDate);
c.setTime(date);
} catch (ParseException e) {
e.printStackTrace();
}
// How do you want to print your date
df= new SimpleDateFormat("dd-MM-yy");
return df.format(c.getTime());
}
// input
String myDate = "2015-04-15 14:25:25";
System.out.println(stringToDateTime(myDate));

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

Java : Impossible to parse "23/10/1973" with "dd/MM/yyyy HH:mm" format

I'm trying to parse many string dates to Date(s), some with time part, others without, with the "dd/MM/yyyy HH:mm" format.
public static Date StringToDate (String format, String theDate) {
SimpleDateFormat df = new SimpleDateFormat(format);
Date retDate = null;
try {
df.setLenient(true);
retDate = df.parse(theDate);
}
catch (ParseException e) {
e.printStackTrace();
}
return (retDate);
}
(here, format is always "dd/MM/yyyy HH:mm").
But this causes an exception, even with setLenient forced at true. Do you know how I may convert to Date a lot of strings formatted like "dd/MM/yyyy HH:mm:ss", but with someones without time, some others without secondes, and still other one with everything ?
If you know that some strings have a time and some don't, and there are no other cases, I'd just check the length of the string. However, if you have many different formats available, I'd try each one in some order that makes sense, until you get a valid date.
I always have two parse strings, and I parse twice; once with date/time and once with date only.

Categories