ParseException when trying to parse Date string - java

I have a piece of code written to parse date string -
DateFormat cal = new SimpleDateFormat("yyyy-MM-dd hh:mm");
cal.setLenient(false);
try {
cal.parse("2018-01-01 14:42");
}
catch (Exception e)
{
e.printStackTrace();
}
}
But I get an exception saying -
java.text.ParseException: Unparseable date: "2018-01-01 14:42"
at java.base/java.text.DateFormat.parse(DateFormat.java:388)
at MyClass.main(MyClass.java:10)
I am not sure why I am seeing this error as the date string and the format given is right. Please help

From the documentation https://docs.oracle.com/javase/7/docs/api/java/text/SimpleDateFormat.html:
Lowercase h in the SimpleDateFormat indicates hour in the 12 hour format, whereas 24-hour format is indicated with uppercase H. As 14 > 12, the date 14:42 fails to be parsed.

You should be using HH instead of hh for hour pattern if the hour is displayed in 24 hour format.
See the documentation below for more information.
https://docs.oracle.com/javase/6/docs/api/java/text/SimpleDateFormat.html

hh is used for the time of the day with hours going from 1 to 12. 14 is not a valid hour for this kind of hour-representation, so you have to use HH or kk. The former is used for times that are shown from 0-23, the latter for times shown between 1-24. Most likely you have to use HH or H/k if the single digit hours aren't preceded by a 0.

TL;DR
DateTimeFormatter cal = DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm");
LocalDateTime.parse("2018-01-01 14:42", cal);
This runs without exception or other error.
java.time
The date-time classes you use, DateFormat and SimpleDateFormat, are long outdated and furthermore notoriously troublesome. I recommend you stop using them immediately. Instead use java.time, the modern Java date and time API. It came out nearly four years ago after having been described in Java Specification Request (JSR) 310 (a name that somehow still clings to the API).
As others have correctly pointed out, your error was that you used lowercase hh in your format pattern string, where you should have used uppercase HH for hour of day. Just one little example of where the modern classes try to be more helpful, try the same. If I insert hh in the format pattern in the code above, my program crashes (because there is no try-catch construct) with an DateTimeParseException with the following message:
Text '2018-01-01 14:42' could not be parsed: Invalid value for
ClockHourOfAmPm (valid values 1 - 12): 14
While perhaps still a bit esoteric, it is very precise. And I would dare hope that in combination with the documentation it would tell you what you did wrong.
The exception is unchecked, so no try-catch is required around the parsing. On the other hand, you may use one if you like (and if you are not very certain that the format of your date-time string is correct, you should).
Links
Oracle tutorial: Date Time, explaining how to use java.time.
Java Specification Request (JSR) 310, where the modern date and time API was first described.

Related

SimpleDateFormat leniency leads to unexpected behavior

I have found that SimpleDateFormat::parse(String source)'s behavior is (unfortunatelly) defaultly set as lenient: setLenient(true).
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.
If I set the leniency to false, the documentation said that with strict parsing, inputs must match this object's format. I have used paring with SimpleDateFormat without the lenient mode and by mistake, I had a typo in the date (letter o instead of number 0). (Here is the brief working code:)
// PASSED (year 199)
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("dd.mm.yyyy");
System.out.println(simpleDateFormat.parse("03.12.199o"));
simpleDateFormat.setLenient(false);
System.out.println(simpleDateFormat.parse("03.12.199o")); //WTF?
In my surprise, this has passed and no ParseException has been thrown. I'd go further:
// PASSED (year 1990)
String string = "just a String to mess with SimpleDateFormat";
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("dd.mm.yyyy");
System.out.println(simpleDateFormat.parse("03.12.1990" + string));
simpleDateFormat.setLenient(false);
System.out.println(simpleDateFormat.parse("03.12.1990" + string));
Let's go on:
// FAILED on the 2nd line
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("dd.mm.yyyy");
System.out.println(simpleDateFormat.parse("o3.12.1990"));
simpleDateFormat.setLenient(false);
System.out.println(simpleDateFormat.parse("o3.12.1990"));
Finally, the exception is thrown: Unparseable date: "o3.12.1990". I wonder where is the difference in the leniency and why the last line of my first code snippet has not thrown an exception? The documentation says:
With strict parsing, inputs must match this object's format.
My input clearly doesn't strictly match the format - I expect this parsing to be really strict. Why does this (not) happen?
Why does this (not) happen?
It’s not very well explained in the documentation.
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.
The documentation does help a bit, though, by mentioning that it is the Calendar object that the DateFormat uses that is lenient. That Calendar object is not used for the parsing itself, but for interpreting the parsed values into a date and time (I am quoting DateFormat documentation since SimpleDateFormat is a subclass of DateFormat).
SimpleDateFormat, no matter if lenient or not, will accept 3-digit year, for example 199, even though you have specified yyyy in the format pattern string. The documentation says about 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.
DateFormat, no matter if lenient or not, accepts and ignores text after the parsed text, like the small letter o in your first example. It objects to unexpected text before or inside the text, as when in your last example you put the letter o in front. The documentation of DateFormat.parse says:
The method may not use the entire text of the given string.
As I indirectly said, leniency makes a difference when interpreting the parsed values into a date and time. So a lenient SimpleDateFormat will interpret 29.02.2019 as 01.03.2019 because there are only 28 days in February 2019. A strict SimpleDateFormat will refuse to do that and will throw an exception. The default lenient behaviour can lead to very surprising and downright inexplicable results. As a simple example, giving the day, month and year in the wrong order: 1990.03.12 will result in August 11 year 17 AD (2001 years ago).
The solution
VGR already in a comment mentioned LocalDate from java.time, the modern Java date and time API. In my experience java.time is so much nicer to work with than the old date and time classes, so let’s give it a shot. Try a correct date string first:
DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("dd.mm.yyyy");
System.out.println(LocalDate.parse("03.12.1990", dateFormatter));
We get:
java.time.format.DateTimeParseException: Text '03.12.1990' could not
be parsed: Unable to obtain LocalDate from TemporalAccessor:
{Year=1990, DayOfMonth=3, MinuteOfHour=12},ISO of type
java.time.format.Parsed
This is because I used your format pattern string of dd.mm.yyyy, where lowercase mm means minute. When we read the error message closely enough, it does state that the DateTimeFormatter interpreted 12 as minute of hour, which was not what we intended. While SimpleDateFormat tacitly accepted this (even when strict), java.time is more helpful in pointing out our mistake. What the message only indirectly says is that it is missing a month value. We need to use uppercase MM for month. At the same time I am trying your date string with the typo:
DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("dd.MM.yyyy");
System.out.println(LocalDate.parse("03.12.199o", dateFormatter));
We get:
java.time.format.DateTimeParseException: Text '03.12.199o' could not
be parsed at index 6
Index 6 is where is says 199. It objects because we had specified 4 digits and are only supplying 3. The docs say:
The count of letters determines the minimum field width …
It would also object to unparsed text after the date. In short it seems to me that it gives you everything that you had expected.
Links
DateFormat.setLenient documentation
Oracle tutorial: Date Time explaining how to use java.time.
Leniency is not about whether the entire input matches but whether the format matches. Your input can still be 3.12.1990somecrap and it would work.
The actual parsing is done in parse(String, ParsePosition) which you could use as well. Basically parse(String) will pass a ParsePosition that is set up to start at index 0 and when the parsing is done the current index of that position is checked.
If it's still 0 the start of the input didn't match the format, not even in lenient mode.
However, to the parser 03.12.199 is a valid date and hence it stops at index 8 - which isn't 0 and thus the parsing succeeded. If you want to check whether everything was parsed you'd have to pass your own ParsePosition and check whether the index is matches to the length of the input.
If you use setLenient(false) it will still parse the date till the desired pattern is meet. However, it will check the output date is a valid date or not. In your case, 03.12.199 is a valid date, so it will not throw an exception. Lets take an example to understand where the setLenient(false) different from setLenient(true)/default.
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("dd.MM.yyyy");
System.out.println(simpleDateFormat.parse("31.02.2018"));
The above will give me output: Sat Mar 03 00:00:00 IST 2018
But the below code throw ParseException as 31.02.2018 is not a valid/possible date:
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("dd.MM.yyyy");
simpleDateFormat.setLenient(false);
System.out.println(simpleDateFormat.parse("31.02.2018"));

Not able to work with Java Date Function properly

If I take current date from my application, it comes with variation like below:
scenario 1: when the date is less than 10th of the month, a month is less than 10 of the year --> example: 5/9/18
scenario 2: when the date is >= 10th of the month, a month is less >= 10 of the year --> example: 10/11/18
Note: all the examples are in MM/DD/YY format and timezone is the USA
Calendar cal = Calendar.getInstance();
cal.add(Calendar.DATE,-2);
DateFormat dateFormat = new SimpleDateFormat("MM/dd/yy HH:mm a");
String PastDate = dateFormat.format(cal.getTime());
info("Date is displayed as : "+ PastDate );
The above piece of code throwing me an error when the scenario 1 is in place. But if I format the date-time as "M/d/yy H:mm a" it works for both the scenario. I need the date add also.
Will it be a good practice to use the 2nd format? or there is any other way to get it done. Expert guidance please..
java.time
DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT)
.withLocale(Locale.US);
ZonedDateTime dayBeforeYesterday = ZonedDateTime.now(ZoneId.of("America/St_Thomas"))
.minusDays(2);
System.out.println(dayBeforeYesterday.format(formatter));
Running just now I got this output:
5/7/18, 8:44 AM
Please specify your desired time zone where I put America/St_Thomas. Think twice before you use ZoneId.systemDefault() for your JVM’s time zone setting since this setting may be changed at any time from other parts of your program or other programs running in the same JVM; but if you trust the setting reflects the user’s time zone, it’s the correct thing to use.
Rather than defining your own output format prefer using one of the built-in formats you get from DateTimeFormatter.ofLocalizedDateTime. Do specify locale (no matter if you use a built-in format or roll your own). Again, use Locale.getDefault() if you trust the JVM’s setting is correct.
Avoid the old date and time classes like Calendar, DateFormat and SimpleDateFormat. They are not only long outdated, they are also poorly designed and the last two in particular notoriously troublesome. Today we have so much better in java.time, the modern Java date and time API.
Link: Oracle tutorial: Date Time explaining how to use java.time.
The number of characters in the format MM indicates that two digits are required in the input. A single character M will match one or two digits. Use M/d/yy H:mm a to support your desired formats.

How to handle negative ISO 8601 date string in java?

I am trying to convert ISO 8601 date string to epoch time. How do I handle negative dates? Is the below code correct? Should I use something else instead of simple date format library? Negative dates are for BC.
String formatString = "yyyy-MM-dd'T'hh:mm:ssX";
SimpleDateFormat formatter = new SimpleDateFormat(formatString);
Date date = formatter.parse("-2017-01-04T12:30:00+05:00");
System.out.println(date.getTime()/1000);
Answer: -125818806600L
TL;DR: No, your code is not correct. Yes, I recommend using the modern Java date and time API instead of SimpleDateFormat.
Your first issue is defining correctness for years before the common era (BCE, “before Christ”).
As I read Wikipedia, ISO 8601 does not define clearly how to interpret a date in that range. The year itself poses no great problem: “year 0000 being equal to 1 BCE”, so -1 is 2 BCE and -2017 is 2018 BCE. You may use the proleptic Gregorian calendar, produced by extending the Gregorian calendar backward to dates preceding its official introduction in 1582, but beware that this disagrees with the Julian calendar traditionally used, so when your date-time string says January 4th this is not the same day as January 4th in the history books. Also the use of negative years and the proplectic Gregorian calendar is not a requirement by ISO 8601, it is only by agreement between the parties. Reservation: I don’t know whether there is any definition of January 4, 2018 BCE in the history books; we’re way back before the introduction of the Julian calendar too (proposed by Julius Caesar in 46 BCE).
The documentation of SimpleDateFormat does not state how it handles dates before the introduction of the Gregorian calendar. It seems to depend on a Calendar object associated with the date/time formatter. Such a Calendar object would be a GregorianCalendar on most computers and JVMs, but not always. So I take it that the output from your code is not guaranteed to be the same on all computers. And a GregorianCalendar can and usually does handle dates from before pope Gregor in the Julian calendar, so I will expect that the result you got does agree with the history books, but not with ISO 8601, when it comes to establishing which day was January 4, 2018 BCE. So on these grounds I suspect that your result is not correct.
As a test I compared the output from your code with the output from a similar use of the Java date and time API. Running your code I too got -125818806600. So I tried:
System.out.println(OffsetDateTime.parse("-2017-01-04T12:30:00+05:00")
.toInstant()
.getEpochSecond());
These classes should be ISO 8601 compliant, so I would prefer this code over yours (it’s also a bit simpler). I got
-125817294600
It’s not the same, so another sign that your code does not give the correct result. The difference is 1512000 seconds, the same as 17 days 12 hours. Let me start by admitting I don’t understand. I would readily think that the difference between Julian and Gregorian calendar could account for a difference in the range of 17 or 18 days. But the 12 hours confuse me.
Edit: The 12 hours come from your use of lowercase hh in your format pattern string. Since you don’t have an AM/PM marker, you should use uppercase HH. Correcting this error, the output from your code is
-125818763400
Now the difference between your code and mine is 1468800 seconds or precisely 17 days.
hh is for hours within AM or PM in the range 1–12. Uppercase HH is for hour in day, 0–23. It’s a very common mistake with SimpleDateFormat (not with the modern classes, they catch it so you correct it). It goes unnoticed scaringly often because for most hours the result is the same; SimpleDateFormat is happy to use AM as default and parse for example 14:30 and understand it as 2:30 PM. But since the hours in your string happen to be 12, there is a difference: 12:30 AM means 0:30 in the day, where in ISO 12:30 means 12:30 PM. Hence the 12 hours error.

Incorrect Parsed Date

My code:
DateFormat format = new SimpleDateFormat("dd/MM/YYYY");
Date Today = format.parse(today);
Date Date = format.parse(date);
difference = (Date.getTime() - Today.getTime()) / 86400000;
Math.abs(difference);
System.out.println(String.valueOf("date" + date));
System.out.println(String.valueOf("date" + Date));
System.out.println(String.valueOf("date" + today));
System.out.println(String.valueOf("date" + Today));
The output:
date29/11/2016
dateSun Dec 27 00:00:00 GMT+08:00 2015
date20/11/2016
dateSun Dec 27 00:00:00 GMT+08:00 2015
I have problems while parsing the date, the original date is 29/11/2016, however when parsed, the date becomes Sun Dec 27 00:00:00 GMT+08:00 2015.
This problem appears in my whole program whenevr it's related to date.
Format dd/MM/YYYY should be dd/MM/yyyy. See javadoc of SimpleDateFormat: y = Year, Y = Week year. – Andreas 12 mins ago
Thanks resolved.
I know you got your solution from Andreas’ comment already. What I want to do here is take a step back and suggest a few improvements to your code. Feel free to ignore.
Most importantly I suggest you throw the long outdated classes Date, DateFormat and SimpleDateFormat overboard and start using their modern replacements. These came out in the java.time package early in 2014. These also offer a much more straightforward and clear way of calculating the difference.
Follow the convention that says that a variable name begins with a lowercase letter. Specifically, in the same source file to use a class called Date and two variable called date and Date is bound to cause confusion.
Like Henry I also think you intended difference = Math.abs(difference);.
Your calls to String.valueOf() are superfluous and just seem to make the code a bit harder to read. Drop them.
For the sake of the example, in the code I suggest below, I am deliberately using your incorrect date format pattern string, dd/MM/YYYY.
DateTimeFormatter format = DateTimeFormatter.ofPattern("dd/MM/YYYY");
LocalDate todayAsLocalDate = LocalDate.parse(today, format);
LocalDate dateAsLocalDate = LocalDate.parse(date, format);
difference = ChronoUnit.DAYS.between(todayAsLocalDate, dateAsLocalDate);
difference = Math.abs(difference);
System.out.println("date " + date);
System.out.println("date " + dateAsLocalDate);
System.out.println("date " + today);
System.out.println("date " + todayAsLocalDate);
System.out.println(difference);
As the code stands now, it throws a java.time.format.DateTimeParseException: Text '20/11/2016' could not be parsed: Unable to obtain LocalDate from TemporalAccessor: {WeekBasedYear[WeekFields[SUNDAY,1]]=2016, MonthOfYear=11, DayOfMonth=20},ISO of type java.time.format.Parsed. When the code is incorrect, I much prefer an exception over an incorrect result that might go unnoticed. So this is better than what SimpleDateFormat gave you.
While the message is not easy to read, the bit to notice is WeekBasedYear. Week-based years are only useful with week numbers, you intended none of that. If you compare with the documentation, you will see that uppercase Y in the pattern is week-based-year while lowercase y is year-of-era. So let’s correct:
DateTimeFormatter format = DateTimeFormatter.ofPattern("dd/MM/yyyy");
Now the code prints:
date 29/11/2016
date 2016-11-29
date 20/11/2016
date 2016-11-20
9
We note one final advantage of the modern classes: you can have a date without a time-of-day when this is what you need, again giving code that models your requirements more precisely and thereby leaves less room for confusion.
Question: can I use the modern classes with my Java version?
If using at least Java 6, you can.
In Java 8 and later the new API comes built-in.
In Java 6 and 7 get the ThreeTen-Backport library, the backport of the new classes (that’s “ThreeTen” for JSR 310, where the modern API was first defined).
On Android, use the Android edition of ThreeTen-Backport. It’s called ThreeTenABP, and I think that there’s a wonderful explanation in this question: How to use ThreeTenABP in Android Project.

Parse time with microseconds in Java

I am having problems parsing time strings in Java that are in the format of 2013-01-09 09:15:03.000000. In my data, the last three digits are always 0 (meaning the input strings have only millisecond precision), so I passed this format to SimpleDateFormat:
formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS'000'");
but formatter.parse("2013-01-09 09:15:02.500000"); throws an exception:
Unparseable date: "2013-01-09 09:15:02.500000"
at java.text.DateFormat.parse(DateFormat.java:357)
Anyone knows how to do it correctly? I can work around by using format yyyy-MM-dd HH:mm:ss.SSS and using substring to get rid of last three digits but that's really hacky.
EDIT: can anyone explain why the format string yyyy-MM-dd HH:mm:ss.SSS'000' can't be used to parse time "2013-01-09 09:15:02.500000"
try java.sql.Timestamp
Timestamp ts = Timestamp.valueOf("2013-01-09 09:15:03.500000");
Date date = new Date(ts.getTime())
it's also thread-safe and fast as opposed to SimpleDateFormat
java.time
I should like to contribute the modern answer. Use java.time, the modern Java date and time API. One option, you may use a formatter:
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss.SSSSSS");
LocalDateTime dateTime = LocalDateTime.parse(timeString, formatter);
System.out.println(dateTime);
When using the string from your question, "2013-01-09 09:15:02.500000", this printed:
2013-01-09T09:15:02.500
If you want the value printed with six decimals on the seconds even when the last three decimals are 0, use the same formatter to format the time back into a string:
System.out.println(dateTime.format(formatter));
The other option, you may exploit the fact that your string resembles the ISO 8601 format, the format that the modern classes parse as their default, that is, without any explicit formatter. Only ISO 8601 has a T to denote the start of the time part, but we can fix that easily:
LocalDateTime dateTime = LocalDateTime.parse(timeString.replace(' ', 'T'));
It gives the same result, 2013-01-09T09:15:02.500. It’s shorter, but also more tricky.
Why bother?
The classes Date and Timestamp are long outdated, and SimpleDateFormat in particular has proven troublesome. Its surprising behaviour in your situation is just one little story out of very many. The modern API is generally so much nicer to work with.
Why didn’t your formatter work?
While the format pattern strings used by SimpleDateFormat and DateTimeFormatter are similar, there are differences. One is that SimpleDateFormat understands uppercase S as milliseconds no matter of there are one or nine of them, whereas to DateTimeFormatter they mean fraction of second. Your SimpleDateFormat furthermore grabbed all six digits after the decimal point, ignoring the fact that you had typed only three S, so there were no zeroes left to match the '000' (by the way, the apostrophes are not necessary, only letters need them).
Link
Oracle Tutorial
I've figured out myself. Just FYI, Apache commons' FastDateFormat seems accepting the SSS000 format and parses the time correctly.

Categories