I just wrote this unit tests :
#Test
public void testGetDateFromString() throws ParseException{
String date = "52/29/2500";
Date dateFromString = DateHelper.getDateFromString(date, DateHelper.DD_MM_YYYY_FORMAT);
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(DateHelper.DD_MM_YYYY_FORMAT);
Date dateWithSimpleFormat = simpleDateFormat.parse(date);
Assert.assertNotNull(dateFromString);
Assert.assertNotNull(dateWithSimpleFormat);
Assert.assertTrue(dateFromString.equals(dateWithSimpleFormat));
System.out.println("dateFromString " + dateFromString);
System.out.println("dateWithSimpleFormat " + dateWithSimpleFormat);
}
And the output is :
dateFromString Wed Jun 21 00:00:00 CEST 2502
dateWithSimpleFormat Wed Jun 21 00:00:00 CEST 2502
The DateHelper.DD_MM_YYYY_FORMAT pattern is dd/MM/yyyy and getDateFromString is a method that parses a String date to a Date object using commons-lang library.
Why des the java.util.Date object verifies the date validity?
You need to set simpleDateFormat.setLenient(false); to make the SimpleDateFormat to validate your input strictly.
You can refer the setLenient documentation for further understanding. By the definition,
Specify whether or not date/time parsing is to be lenient. With lenient parsing,
the parser may use heuristics to interpret inputs that do not precisely match this
object's format. With strict parsing, inputs must match this object's format.
Use simpleDateFormat.setLenient(false); to enable strict parsing.
java.time
DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("dd/MM/uuuu");
String date = "52/29/2500";
try {
LocalDate dateWithJavaTime = LocalDate.parse(date, dateFormatter);
System.out.println("dateWithJavaTime " + dateWithJavaTime);
} catch (DateTimeParseException dtpe) {
System.out.println("Invalid date. " + dtpe);
}
The output from this code is:
Invalid date. java.time.format.DateTimeParseException: Text
'52/29/2500' could not be parsed: Invalid value for MonthOfYear (valid
values 1 - 12): 29
Please enjoy not only that the validation works, but also the precision of the error message.
Other results:
For the string 52/11/2500 the result is “Invalid date. java.time.format.DateTimeParseException: Text '52/11/2500' could not be parsed: Invalid value for DayOfMonth (valid values 1 - 28/31): 52”.
For the string 29/02/2019 we get “dateWithJavaTime 2019-02-28”, which may surprise. To have this string rejected use
DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("dd/MM/uuuu")
.withResolverStyle(ResolverStyle.STRICT);
Now we get
Invalid date. java.time.format.DateTimeParseException: Text
'29/02/2019' could not be parsed: Invalid date 'February 29' as '2019'
is not a leap year
Again enjoy how precise the message is.
Related
I have three date formats: YYYY-MM-DD, DDMMYYYY, MMDDYYYY this is how I pass date format in Spark to parse.
scala> val formatter = DateTimeFormatter.ofPattern("[MMddyyyy][yyyy-MM-dd][yyyyMMdd]")
formatter: java.time.format.DateTimeFormatter = [Value(MonthOfYear,2)Value(DayOfMonth,2)Value(YearOfEra,4,19,EXCEEDS_PAD)][Value(YearOfEra,4,19,EXCEEDS_PAD)'-'Value(MonthOfYear,2)'-'Value(DayOfMonth,2)][Value(YearOfEra,4,19,EXCEEDS_PAD)Value(MonthOfYear,2)Value(DayOfMonth,2)]
For format MMddyyyy it's working
scala> LocalDate.parse("10062019",formatter)
res2: java.time.LocalDate = 2019-10-06
For format yyyyMMdd it's working
scala> LocalDate.parse("2019-06-20",formatter)
res3: java.time.LocalDate = 2019-06-20
For format yyyyMMdd, it's giving me an error
scala> LocalDate.parse("20190529",formatter)
java.time.format.DateTimeParseException: Text '20190529' could not be parsed: Invalid value for MonthOfYear (valid values 1 - 12): 20
at java.time.format.DateTimeFormatter.createError(DateTimeFormatter.java:1920)
at java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1855)
at java.time.LocalDate.parse(LocalDate.java:400)
... 66 elided
Caused by: java.time.DateTimeException: Invalid value for MonthOfYear (valid values 1 - 12): 20
at java.time.temporal.ValueRange.checkValidIntValue(ValueRange.java:330)
at java.time.temporal.ChronoField.checkValidIntValue(ChronoField.java:722)
at java.time.chrono.IsoChronology.resolveYMD(IsoChronology.java:550)
at java.time.chrono.IsoChronology.resolveYMD(IsoChronology.java:123)
at java.time.chrono.AbstractChronology.resolveDate(AbstractChronology.java:472)
at java.time.chrono.IsoChronology.resolveDate(IsoChronology.java:492)
at java.time.chrono.IsoChronology.resolveDate(IsoChronology.java:123)
at java.time.format.Parsed.resolveDateFields(Parsed.java:351)
at java.time.format.Parsed.resolveFields(Parsed.java:257)
at java.time.format.Parsed.resolve(Parsed.java:244)
at java.time.format.DateTimeParseContext.toResolved(DateTimeParseContext.java:331)
at java.time.format.DateTimeFormatter.parseResolved0(DateTimeFormatter.java:1955)
at java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1851)
... 67 more
If I pass 2 format yyyyMMdd, yyyy-MM-dd it's working fine
scala> val formatter = DateTimeFormatter.ofPattern("[yyyy-MM-dd][yyyyMMdd]")
scala> LocalDate.parse("20190529",formatter)
res5: java.time.LocalDate = 2019-05-29
scala> LocalDate.parse("2019-06-20",formatter)
res6: java.time.LocalDate = 2019-06-20
Same as yyyy-MM-dd, mmddyyy date format
scala> val formatter = DateTimeFormatter.ofPattern("[yyyy-MM-dd][MMddyyyy]")
scala> LocalDate.parse("10062019",formatter)
res7: java.time.LocalDate = 2019-10-06
scala> LocalDate.parse("2019-06-20",formatter)
res8: java.time.LocalDate = 2019-06-20
Is there any way that I can pass three different formats?
Java's own DateTimeFormatterBuilder#appendOptional can handle it without without relying on exception handling. From the documentation:
The formatter will format if data is available for all the fields contained within it. The formatter will parse if the string matches, otherwise no error is returned.
Which means you will not have to handle the multiple cases with exceptions.
Here is an example from a real code-base where I had to do this handling:
def dtf: DateTimeFormatter =
new DateTimeFormatterBuilder()
.appendOptional(DateTimeFormatter.ofPattern("dd/MM/yy"))
.appendOptional(DateTimeFormatter.ofPattern("dd/MM/yyyy"))
.toFormatter()
Here are two unit tests (in ScalaTest) demonstrating the usage:
"Short form is parsed" in {
assert(LocalDate.parse("28/08/20", dtf) == LocalDate.of(2020, 8, 28))
}
"Long form is parsed" in {
assert(LocalDate.parse("02/10/2020", dtf) == LocalDate.of(2020, 10, 2))
}
You can´t have in the formatter [yyyyMMdd] and [MMddyyyy] at the same time.
My idea is to normalize so you have [yyyy-MM-dd] and [MM-dd-yyyy], instead of 3 formats .
Hope this helped
Edited:
If you don´t have a chance you can do something like this, but it´s not very pretty.
val formatter1 = DateTimeFormatter.ofPattern("[yyyy-MM-dd][MMddyyyy]")
val formatter2 = DateTimeFormatter.ofPattern("[yyyy-MM-dd][yyyyMMdd]")
val time = "20190529"
if (time.matches("2+\\d*")) LocalDate.parse(time,formatter2) else
LocalDate.parse(time,formatter1)
With only the information in the question this is not possible. The string 10111213 could denote December 13, 1011 or October 11, 1213. However, assuming that your dates are always after year 1300, you’re lucky because then the YYYY part of the string cannot be parsed as MMDD becuase the month will be 13 or greater, that is, not valid. You can use this for deciding which of the formats is the correct one to use.
I would use three formatters and try them in turn:
private static final DateTimeFormatter[] DATE_FORMATTERS = {
DateTimeFormatter.ofPattern("uuuuMMdd"),
DateTimeFormatter.ofPattern("MMdduuuu"),
DateTimeFormatter.ofPattern("uuuu-MM-dd")
};
With these just do:
String dateString = "20190529";
LocalDate result = null;
for (DateTimeFormatter df : DATE_FORMATTERS) {
try {
result = LocalDate.parse(dateString, df);
break;
} catch (DateTimeParseException dtpe) {
// Ignore; try next formatter
}
}
System.out.println("" + dateString + " was parsed to " + result);
Output is:
20190529 was parsed to 2019-05-29
Let’s try the two other formats too:
10062019 was parsed to 2019-10-06
2019-06-20 was parsed to 2019-06-20
I recommend you add a null check to catch any unparseable date string and a range check on the parsed date so that 10111213 doesn’t slip through as valid. For example:
if (result == null) {
System.out.println(dateString + " could not be parsed");
}
else if (result.isBefore(LocalDate.now(ZoneId.of("Asia/Aden")))) {
System.out.println("Date should be in the future, was " + result);
}
PS I assume a typo in the first sentence of your question:
I have three date formats: YYYY-MM-DD, DDMMYYYY, MMDDYYYY this is how
I pass date format in Spark to parse.
The middle format should have been YYYYMMDD (otherwise you’ve got no chance).
I have date strings in various formats like Oct 10 11:05:03 or 12/12/2016 4:30 etc
If I do
// some code...
getDate("Oct 10 11:05:03", "MMM d HH:mm:ss");
// some code ...
The date gets parsed, but I am getting the year as 1970 (since the year is not specified in the string.) But I want the year as current year if year is not specidied. Same applies for all fields.
here is my getDate function:
public Date getDate(dateStr, pattern) {
SimpleDateFormat parser = new SimpleDateFormat(pattern);
Date date = parser.parse(myDate);
return date;
}
can anybody tell me how to do that inside getDate function (because I want a generic solution)?
Thanks in advance!
If you do not know the format in advance, you should list the actual formats you are expecting and then try to parse them. If one fails, try the next one.
Here is an example of how to fill in the default.
You'll end up with something like this:
DateTimeFormatter f = new DateTimeFormatterBuilder()
.appendPattern("ddMM")
.parseDefaulting(YEAR, currentYear)
.toFormatter();
LocalDate date = LocalDate.parse("yourstring", f);
Or even better, the abovementioned formatter class supports optional elements. Wrap the year specifier in square brackets and the element will be optional. You can then supply a default with parseDefaulting.
Here is an example:
String s1 = "Oct 5 11:05:03";
String s2 = "Oct 5 1996 13:51:56"; // Year supplied
String format = "MMM d [uuuu ]HH:mm:ss";
DateTimeFormatter f = new DateTimeFormatterBuilder()
.appendPattern(format)
.parseDefaulting(ChronoField.YEAR, Year.now().getValue())
.toFormatter(Locale.US);
System.out.println(LocalDate.parse(s1, f));
System.out.println(LocalDate.parse(s2, f));
Note: Dates and times are not easy. You should take into consideration that date interpreting is often locale-dependant and this sometimes leads to ambiguity. For example, the date string "05/12/2018" means the 12th of May, 2018 when you are American, but in some European areas it means the 5th of December 2018. You need to be aware of that.
One option would be to concatenate the current year onto the incoming date string, and then parse:
String ts = "Oct 10 11:05:03";
int currYear = Calendar.getInstance().get(Calendar.YEAR);
ts = String.valueOf(currYear) + " " + ts;
Date date = getDate(ts, "yyyy MMM d HH:mm:ss");
System.out.println(date);
Wed Oct 10 11:05:03 CEST 2018
Demo
Note that we could have used StringBuilder above, but the purpose of brevity of code, I used raw string concatenations instead. I also fixed a few typos in your helper method getDate().
I'm trying to format a Date in Grails, here is my code in the controller:
SimpleDateFormat sdf = new SimpleDateFormat("dd-MM-yyyy");
empRefInstance.startDate=sdf.parse(params.startDate)
empRefInstance.endDate=sdf.parse(params.endDate)
println ("dates " + empRefInstance.startDate +" "+empRefInstance.endDate)
the output supposed to be 01-05-2016 as per the format i defined but the output of the both date in this format
Sun May 01 00:00:00 EEST 2016
is there something wrong in the formater?
There is nothing wrong with the formatter. You're not using one for the output. Something like this will give you the expected output:
println empRefInstance.startDate.format('dd-MM-yyyy')
You are not formatting the output instead you have only parsed.
Formatting: Converting the Date to String (the format method)
Parsing: Converting the String to Date (the parse method)
To format, you need to do like this:
SimpleDateFormat sdf = new SimpleDateFormat("dd-MM-yyyy");
// First you are converting the incoming date string to a date
empRefInstance.startDate = sdf.parse(params.startDate)
empRefInstance.endDate=sdf.parse(params.endDate)
// Now we have to conert the date object to string and print it
println ("dates " + sdf.format(empRefInstance.startDate) + " "+sdf.format(empRefInstance.endDate))
When you print a Date object in Groovy/Java, it's default implementation of toString() will be invoked hence you were getting output like Sun May 01 00:00:00 EEST 2016
Also, Groovy adds a format method in Date class to direct allow formatting. You can even use that as well.
println("dates " + empRefInstance.startDate.format("dd-MM-yyyy") + " " + empRefInstance.endDate.format("dd-MM-yyyy"))
String selectedDate = "2012-" + createdMonth + "-" + createdDay;
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
try {
createdDate = dateFormat.parse(selectedDate);
} catch (ParseException e1) {
e1.printStackTrace();
}
System.out.println(createdDate);
Basically when I print createdDate, it will display something like this :
Thu Mar 08 00:00:00 CST 2012
Instead of something of this format "yyyy-MM-dd". Thanks a bunch!
The parse method returns a java.util.Date and that is the what the Date implementation of toString() returns.
You need to print as below. Point is that you need to use the formatter object you have created while printing as well.
System.out.println(dateFormat.format(createdDate));
use dateFormat.format(createdDate)
You seem to think that createdDate, which is a Date object, has the format yyyy-MM-dd. It doesn't. Date objects don't have a format - they just contain a timestamp, just like numbers are just numbers, which don't have a format by themselves.
A SimpleDateFormat object is used to parse a String into a Date object, or format a Date object into a String.
If you have a Date object and you want to display the date in a particular format, then convert it to a String with the appropriate format using a SimpleDateFormat object:
SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd");
String text = fmt.format(createdDate);
System.out.println("Created: " + text);
If you print a Date object without explicitly formatting it, it will be formatted using a default format, which is why you see Thu Mar 08 00:00:00 CST 2012.
A Date object does not somehow remember what the format was of the String that you parsed it from.
I'm trying to convert an input date string to a date format and then to a datetime format.
As a test, I gave an input of an incorrect date format, but this doesn't seem to be throwing any parse exceptions and gives me the wrong output. Any thoughts on what my code below is doing wrong?
String OLD_FORMAT ="MM/dd/yyyy";
String NEW_FORMAT ="yyyyMMdd HHmmss";
SimpleDateFormat sdf = new SimpleDateFormat(OLD_FORMAT);
String oldDateString = "03/01211/2012"; //Incorrect input
Date myOldDate;
Datetime myNewDate;
try {
myOldoldDate = sdf.parse(oldDateString);
//Returns Wed Jun 24 00:00:00 IST 2015...why??
//Shouldn't this be throwing a parse exception?
} catch (ParseException e) {
logger.error("Error while parsing Date");
}
sdf.applyPattern(NEW_FORMAT);
//Converting date to datetime format
try {
myNewDate= DateHelper.toDatetime(sdf.parse((sdf.format(myOldDate))));
//Returns 2015-06-24 00:00:00.0
} catch (ParseException e) {
logger.error("Error while parsing Date");
}
"03/01211/2012" => Jun 24 00:00:00 IST 2015 ... why?
My guess is that June 24th, 2015 is 1211 days from March 1st, 2012.
Excessive rollover, reads it as March 1211th.
You should be able to turn this off with:
sdf.setLenient(false)
public void setLenient(boolean lenient)
Specify whether or not date/time interpretation is to be lenient. With lenient interpretation, a date such as "February 942, 1996" will be treated as being equivalent to the 941st day after February 1, 1996. With strict interpretation, such dates will cause an exception to be thrown.
You can set strict format
SimpleDateFormat.setLenient(false)
Specify whether or not date/time parsing is to be lenient. With lenient parsing, the parser may use heuristics to interpret inputs that do not precisely match this object's format. With strict parsing, inputs must match this object's format.
Without looking at the source code, I assume 01211 is parsed to 1211 days which are added to 2012-03-01 thus resulting in 2015-06-24. As #Thilo said sdf.setLenient(false) should help here.
The problem is that by default the parser is more tolerant to wrong input (lenient mode is on by default) and thus won't throw an exception here.