java.text.ParseException: Unparseable date: yyyy-MM-dd HH:mm:ss.SSSSSS - java

I am getting ParseException for the following code
String dateStr = "2011-12-22 10:56:24.389362";
String formatStr = "yyyy-MM-dd HH:mm:ss.SSSSSS";
Date testDate = null;
SimpleDateFormat sdf= new SimpleDateFormat(formatStr);
sdf.setLenient(false);
testDate = sdf.parse(dateStr);
System.out.println("CHECK DATE " + sdf.format(testDate));
Exception in thread "main" java.text.ParseException: Unparseable date: "2011-12-22 10:56:24.389362"
at java.text.DateFormat.parse(DateFormat.java:337)
If I comment out the line sdf.setLenient(false), then I see a time difference in the ouput
CHECK DATE 2011-12-22 11:02:53.000362
What am I doing wrong??

'S' is for millisecond. There are 1000 (0 to 999) milliseconds in a second. 389362 is greater than 999. The extra 389000 milliseconds are getting converted to 389 seconds, or 6 minutes 29 seconds and added to the time.

The S format specifier refers to milliseconds. When you allow lenient parsing, the last part is interpreted as 389362 milliseconds. When that's added to the date so far, the last 3 digits (actually, the value % 1000) become the actual milliseconds, and you wind up with a date about 389 seconds (~6 1/2 minutes) later than you're expecting. (With strict parsing, the parser knows that 389362 milliseconds doesn't make sense, so it throws an error.)
The simplest way around that, if you can guarantee the date will always look like that, would be to chop the last 3 digits off. (This will about half the time give you a date that's off by a millisecond. But that's better than having to write a date parser...)

Your date input for milliseconds is incorrect. It should be:-
String dateStr = "2011-12-22 10:56:24.389";
You also do not need the extra number of "S"s in the pattern. The following should suffice:
String formatStr = "yyyy-MM-dd HH:mm:ss.S";
It is clearly mentioned in the java docs for presentation type of Number:
Number: For formatting, the number of pattern letters is the minimum
number of digits, and shorter numbers are zero-padded to this amount.
For parsing, the number of pattern letters is ignored unless it's
needed to separate two adjacent fields.
It works when you set lenient to be true (or comment out the line which defaults it true) since you are asking the parser to be not strict about the parsing. From java docs on setLenient():-
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.

S is only to be used for milliseconds. If you want microseconds, you will have to write your own parser.

Use toISOString('HH:mm:ss.S') to get milliseconds (3 digits), then complete as you need with 0.
For example:
new Date().toISOString('HH:mm:ss.S')
returns "2012-02-10T12:16:39.124Z"

Related

SimpleDateFormat [0] issue

I've below SimpleDateFormat Code
Date date = new Date();
DateFormat inpuDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SS'Z'");
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
String dateStr = inpuDateFormat.format(cal.getTime());
It works perfectly on my dev servers but it fails on sandbox instances with following error.
org.junit.ComparisonFailure: expected:<...20-08-12T19:06:02.85[0]Z> but was:<...20-08-12T19:06:02.85[]Z>
I've handled it as
dateStr = dateStr.replace("[0]","");
dateStr = dateStr.replace("[]","");
But, I still didn't get the logic why my date is different on different server instances and is there any better way to handle it
java.time
There certainly is a much better way to handle it. Use java.time, the modern Java date and time API, for your date and time work, not Date, DateFormat, SimpleDateFormat nor Calendar.
Instant now = Instant.now();
String dateStr1 = now.toString();
System.out.println(dateStr1);
Output in one run was:
2020-07-24T18:06:07.988093Z
You notice that six decimals on the seconds were output, not two. In other runs you may have three decimals or no fraction at all. Don’t worry, for the majority of purposes you’ll be just fine. The format printed is ISO 8601, and according to ISO 8601 the count of decimals on the seconds, even the presence of seconds at all, is optional. So whatever you need the string for, as long as ISO 8601 format is expected, the string from the above code snippet should be accepted.
I am exploiting the fact that Instant.toString() produces ISO 8601 format, so we don’t need any formatter.
If for some strange reason you do need exactly two decimals on the seconds, use a formatter for specifying so (edit: now outputting Z):
DateTimeFormatter formatter2 = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss.SSX")
.withZone(ZoneOffset.UTC);
String dateStr2 = formatter2.format(now);
System.out.println(dateStr2);
2020-07-24T18:06:07.98Z
To a DateTimeFormatter (opposite a SimpleDateFormat) uppercase S in the format pattern string means fraction of second, and you are free to place from one through nine of them to get from one to nine decimals.
What went wrong in your code?
First, the message that you got from your JUnit test was:
org.junit.ComparisonFailure: expected:<...20-08-12T19:06:02.85[0]Z> but was:<...20-08-12T19:06:02.85[]Z>
The square brackets is JUnit’s way of drawing our attention to the difference between the expected and the actual value. So they are not part of those values. What JUnit tells us is that the value was expected to end in .850Z but instead ended in just .85Z. So a zero was missing. Your test is probably too strict since as I said, it shouldn’t matter whether there are two or three decimals. And 02.85 and 02.850 are just different ways of denoting the exact same value.
This role of the square brackets also explains why replacing [0] and [] in the string didn’t help: the square brackets were never in the strings, so the replacements never made any change to the strings.
Second, to SimpleDateFormat (opposite DateTimeFormatter) format pattern letter uppercase S means millisecond. So putting any other number than three of them makes no sense and gives you incorrect results. In your code you put two. In nine of ten cases the millisecond value is in the interval 100 through 999, and in this case SimpleDateFormat prints all three digits in spite of the only two pattern letters S. This probably explains why your unit test passed in your development environment. On your sandbox incidentally the time ended in 2.085 seconds. The correct ways to render this include 02.08 and 02.085. Your SimpleDateFormat chose neither. To it the millisecond value of 85 was to be rendered in two positions, so it produces 02.85, which is the wrong value, 765 milliseconds later. And your unit test objected while this once there were only two decimals, not three.
Third, not what you asked, but no matter if using the troublesome SimpleDateFormat or the modern DateTimeFormatter you must never hardcode Z as a literal in the format pattern string. The trailing Z means UTC or offset zero from UTC. It needs to be printed (and parsed if that were the case) as an offset, or you get wrong results. The way to make sure you get a Z and not for example an offset of +02:00 is to make sure that an offset of 0 is specified. This was why I put .withZone(ZoneOffset.UTC) on my formatter.
Links
Oracle tutorial: Date Time explaining how to use java.time.
Wikipedia article: ISO 8601
Try to remove the quotes around the 'Z', as 'Z' is a constant whilst without quotes it means 'time zone':
DateFormat inpuDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
(By the way, in most cases you want to use three decimal places for milliseconds: "SSS".)

Correct way to parse date

I'm trying to parse the following date ut get a runtime error saying:
java.time.format.DateTimeParseException: Text '2019-11-21-05:00' could not be parsed, unparsed text found at index 10
My input:
String inpDate = "2019-11-20-05:00"
I also tried the following date formats but no luck.
yyyy-MM-ddZ
yyyy-MM-dd Z
Code:
public static final String DATE_FORMAT = "yyyy-MM-dd";
public static final DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern(DATE_FORMAT);
LocalDate localDate = LocalDate.parse(inpDate, dateFormatter);
return Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant());
How can I get my input to parse correctly?
Use the following format:
public static final String DATE_FORMAT = "yyyy-MM-ddZZZZZ";
It has 5 times the letter "Z". To parse a timezone offset with a colon, you need to provide the letter "Z" 5 times. This may be somewhat hidden away in the Javadoc.
From the Javadoc:
Offset Z: This formats the offset based on the number of pattern letters.
One, two or three letters outputs the hour and minute, without a colon, such as '+0130'. The output will be '+0000' when the offset is zero.
Four letters outputs the full form of localized offset, equivalent to four letters of Offset-O. The output will be the corresponding localized offset text if the offset is zero.
Five letters outputs the hour, minute, with optional second if non-zero, with colon.
It outputs 'Z' if the offset is zero. Six or more letters throws IllegalArgumentException.
It’s easier than you think. The formatter you need is built in. So don’t struggle with writing your own format pattern string.
String inpDate = "2019-11-20-05:00";
LocalDate localDate = LocalDate.parse(inpDate, DateTimeFormatter.ISO_OFFSET_DATE);
System.out.println(Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant()));
Output in my time zone:
Wed Nov 20 00:00:00 EST 2019
I am assuming that you are only converting to a Date because you need one for a legacy API that you cannot afford to upgrade to java.time just now. Otherwise you should not use Date but stick to the modern API.
Start of day in your time zone or at the offset given in the string?
Edit: Basil Bourque in a comment asked this very knowledgeable question:
Perhaps it would be more true to the intent of the publisher of this
input string to get the first moment of the day as seen in that offset
of 5 hours behind UTC. Is it possible to parse as an OffsetDateTime
with the time-of-day defaulting to first moment of the day (00:00:00)?
(And then convert to java.util.Date if required.)
It’s definitely true. I have chosen to give code that gives the result that I think the code in the question was trying to obtain. If we understand 2019-11-20-05:00 as some unspecified time in the half-open interval from 2019-11-20T00:00-05:00 to 2019-11-21T00:00-05:00, then for some offsets and some default time zones the result from the above code snippet will actually lie outside that interval (typically before it, may also in corner cases fall after it). So this is true to the question (if I understood it correctly) and untrue to the publisher of the original string. If instead we want the start of day at the UTC offset given in the string I would go like this:
TemporalAccessor parsed = DateTimeFormatter.ISO_OFFSET_DATE.parse(inpDate);
Instant startOfDayAtSpecifiedOffset = LocalDate.from(parsed)
.atStartOfDay(ZoneOffset.from(parsed))
.toInstant();
System.out.println(Date.from(startOfDayAtSpecifiedOffset));
Output in my time zone:
Wed Nov 20 06:00:00 CET 2019
I am at offset +01:00 in November, which is why the time here is 06:00 when the day begins at offset -05:00. To illustrate that the choice of time zone or offset may make a great difference, here’s the output from running the latter snippet with Pacific/Kiritimati as default time zone:
Wed Nov 20 19:00:00 LINT 2019
Or in Pacific/Pago_Pago time zone:
Tue Nov 19 18:00:00 SST 2019
So pick carefully what you really want.
What went wrong in your code?
First, LocalDate.parse() and similar parsing methods insist on parsing the entire string or they will throw the exception you saw mentioning unparsed text found at index (some index) (you may use the overloaded DateTimeFormatter.parse​(CharSequence, ParsePosition) method for parsing only as much as possible of the string without that exception).
Second, one Z in the format pattern string matches offset without colon, so -0500, not -05:00.
Link: Documentation of DateTimeFormatter.parse​(CharSequence, ParsePosition)
String inpDate = "2019-11-20-05:00";
System.out.println(LocalDate.parse(inpDate, DateTimeFormatter.ofPattern("yyyy-MM-dd-hh:mm")));
Will print 2019-11-20.

Java SimpleDateFormat.format() returns incorrect date when using miliseconds

I want to parse a date string from "2016-09-23T09:14:52.555000000" format to "23-SEP-2016" format.
Here is my code :
public class Tester {
public static void main(String[] args) {
System.out.println(displayDate("2016-09-23T09:14:52.555000000"));
System.out.println(displayDate("2016-09-28T11:56:24.552000000"));
System.out.println(displayDate("2016-09-23T09:29:12.507000000"));
System.out.println(displayDate("2016-09-26T14:55:02.702000000"));
System.out.println(displayDate("2016-09-26T09:50:24.880000000"));
System.out.println(displayDate("2016-09-26T15:20:49.397000000"));
System.out.println(displayDate("2016-09-26T15:21:21.559000000"));
}
public static String displayDate(String dateString) {
String formattedDateString = "NA";
if(dateString == null) {
return formattedDateString;
}
SimpleDateFormat oracleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'kk:mm:ss.S");
SimpleDateFormat dateFormatToDisplay = new SimpleDateFormat("dd-MMM-yyyy");
try {
Date date = oracleDateFormat.parse(dateString);
formattedDateString = dateFormatToDisplay.format(date).toUpperCase();
} catch (ParseException e) {
e.printStackTrace();
}
return formattedDateString;
}
}
The problem is if I use this line
SimpleDateFormat oracleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'kk:mm:ss.S");
The output is (incorrect date values) :
29-SEP-2016
04-OCT-2016
29-SEP-2016
04-OCT-2016
06-OCT-2016
01-OCT-2016
03-OCT-2016
Whereas If use this line
SimpleDateFormat oracleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'kk:mm:ss");
The output is (correct date values)
23-SEP-2016
28-SEP-2016
23-SEP-2016
26-SEP-2016
26-SEP-2016
26-SEP-2016
26-SEP-2016
I want to know why adding "S (Millisecond)" to the format string results in incorrect values.
Edit1 :
Even "yyyy-MM-dd'T'kk:mm:ss.SSS" returns incorrect values.
Edit2 :
I am using this code in Android App. It works on perfectly on emulator(API 23). For some devices it displays incorrect date. Can it be related to Java version?
TL;DR:
In Java, it's not a fraction of seconds, it's a number of milliseconds. Don't make it larger than 999.
Change your date format in Oracle to include FF3 for the fractions of seconds to yield milliseconds.
Explanation
In Java, the S, or rather SSS, in your date format stands for milliseconds, which are thousands of a second. There are only thousand milliseconds in one second.
In Oracle, the date format doesn't specify milliseconds, but fractions of a second.
FF [1..9]
Fractional seconds; no radix character is printed. Use the X format element to add the radix character. Use the numbers 1 to 9 after FF to specify the number of digits in the fractional second portion of the datetime value returned. If you do not specify a digit, then Oracle Database uses the precision specified for the datetime datatype or the datatype's default precision. Valid in timestamp and interval formats, but not in DATE formats.
In Java, if you need a third of a second, you can't get more precise than 333 milliseconds. In Oracle however, you could express it as 333333 microseconds, or perhaps even 333333333 nanoseconds.
Oracle lets you specify the number of decimal digits you want, but if you don't, you get as much as the precision for the type allows. In your case, that seems to be 9.
Then your date format in Java interprets that as a number of milliseconds. Millions and billions of them. These are added to the rest of your date. Since there are only 86,400,000 milliseconds in a day, anything over that is another day added to your date.
Example
Let's take a look at your first test case, 2016-09-23T09:14:52.555000000.
555000000 milliseconds = 555000 seconds &approx; 154 hours &approx; 6 days and 10 hours.
Adding 6 days and 10 hours to the rest of your date, which is 2016-09-23 09:14:52, should get you to about 2016-09-29 19:00 and a bit. Change your output format (dateFormatToDisplay) to include the hours and you'll see what's happening.
Fix
Your Java date format expects no more than 3 digits for the milliseconds. Specify the number of fractional digits in Oracle. FF uses the maximal precision available for the type, FF3 only outputs 3 fractional digits — milliseconds.
If you can't alter the date format used in Oracle, trim it down to three decimal digits in Java. Note that anything less than 3 digits should be padded with zeroes to a length of three digits; 34.12 is interpreted as 34 seconds and 12 milliseconds, while you might be looking for 120 milliseconds.
The mystery stays in the correct interpretation of the S specifier.
In SimpleDateFormat, on the Date and Time Patterns section, the definition for S states:
S Millisecond
Does it strikes you as peculiar in some way? No?! It took myself by surprise as well, so let me put it in a negative way:
Not fractional part of the seconds, but Milliseconds
Still don't get it? I mean literaly the count of milliseconds.
Like in:
SimpleDateFormat oracleDateFormat =
new SimpleDateFormat("yyyy-MM-dd'T'kk:mm:ss.S");
SimpleDateFormat dateFormatToDisplay =
new SimpleDateFormat("dd-MMM-yyyy--HH:mm:ss.SSS");
Date d=oracleDateFormat.parse("2016-09-23T09:14:52.1");
System.out.println(dateFormatToDisplay.format(d));
will result in... waaait for it... boom-tshhh...
"2016-09-23--09:14:52.001"
That's right folks! "And one millisecond" not "and one tenth of a second".
So, does it go the same way if we increase the number of digits after the dot? Sure it does.
d=oracleDateFormat.parse("2016-09-23T09:14:52.1000");
System.out.println(dateFormatToDisplay.format(d));
"2016-09-23--09:14:53.000"
So if you put in T09:14:53.555000000 you just added 555 millions of milliseconds to your base time or 555000 full seconds. Which means an extra 6 days, 10 hours and 11 minutes` over your base time.
It seems that when you add 'S' then parser adds all milliseconds eg "555000000" to date. When you do calculation (555000000 ms ~ 6,4d) it matches result (incorrect date).
You should use "yyyy-MM-dd HH:mm:ss.SSS", but also trim "2016-09-23T09:14:52.555000000" to "2016-09-23T09:14:52.555", eg:
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
sdf.parse("2016-09-23T09:14:52.555000000".substring(0,23));
Milliseconds should be represented in 3 digits only. Change code to:
System.out.println(displayDate("2016-09-23T09:14:52.555"));
System.out.println(displayDate("2016-09-28T11:56:24.552"));
System.out.println(displayDate("2016-09-23T09:29:12.507"));
System.out.println(displayDate("2016-09-26T14:55:02.702"));
System.out.println(displayDate("2016-09-26T09:50:24.880"));
System.out.println(displayDate("2016-09-26T15:20:49.397"));
System.out.println(displayDate("2016-09-26T15:21:21.559"));
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");

Parsing string to local date doesn't use desired century

I am using this DateTimeFormatter:
DateTimeFormatter.ofPattern("ddMMYY")
I want to parse the string 150790 and I got this error:
Unable to obtain LocalDate from TemporalAccessor: {DayOfMonth=15, MonthOfYear=7, WeekBasedYear[WeekFields[MONDAY,4]]=2090},ISO of type java.time.format.Parsed
Obviously, I want to get the following TemporalAccessor:
{DayOfMonth=15, MonthOfYear=7, WeekBasedYear=1990}
Do you know why I got the year 2090 instead of 1990?
Thanks for your help
Since this question is really about new java.time-package and NOT SimpleDateFormat I will cite following relevant section:
Year: The count of letters determines the minimum field width below
which padding is used. If the count of letters is two, then a reduced
two digit form is used. For printing, this outputs the rightmost two
digits. For parsing, this will parse using the base value of 2000,
resulting in a year within the range 2000 to 2099 inclusive.
We see that Java-8 uses the range 2000-2099 per default, not like SimpleDateFormat the range -80 years until +20 years relative to today.
If you want to configure it then you have to use appendValueReduced(). This is designed in an inconvenient way, but possible, see here:
String s = "150790";
// old code with base range 2000-2099
DateTimeFormatter dtf1 =
new DateTimeFormatterBuilder().appendPattern("ddMMyy").toFormatter();
System.out.println(dtf1.parse(s)); // 2090-07-15
// improved code with base range 1935-2034
DateTimeFormatter dtf2 =
new DateTimeFormatterBuilder().appendPattern("ddMM")
.appendValueReduced(
ChronoField.YEAR, 2, 2, Year.now().getValue() - 80
).toFormatter();
System.out.println(dtf2.parse(s)); // 1990-07-15
By the way, if you really want week-based years then you have to use Y instead of y or the appropriate field IsoFields.WEEK_BASED_YEAR. Regarding the fact that you don't have any other week-related fields I would assume the normal calendar year, not the week-based one.

Android SimpleDateFormat Parsing Error

I know there are many other questions on stackoverflow that deal with SimpleDateFormatter in Android or Java, but I have been unable to find any questions that help me answer my own question.
I have a String with this time format 2014-06-28T21:00:00-05:00 and I am trying to convert it to 9:00 PM (or at least 9:00). I'm pretty sure my issue is with actually writing out the correct notation for the above time, but here is my code:
String input = '2014-06-28T21:00:00-05:00';
DateFormat fromFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss+|-hh:mm");
fromFormat.setLenient(false);
DateFormat toFormat = new SimpleDateFormat("hh:mm");
toFormat.setLenient(false);
try{
String output = toFormat.format(fromFormat.parse(input));
return output;
} catch(ParseException pe){
pe.printStackTrace();
}
return "No Date Listed";
If I look at the stack trace, it tells me unparseable date at offset #19.
I am fairly certain the logic behind the code does work because I switched the SimpleDateFormats to something a little simpler like yyyy-MM-dd and MMMM dd, yy and it worked perfectly. So, can anyone point me in the right direction and help me figure out the proper time notation?
I appreciate all of your help.
The main problem you're having is that you are being given a time with a time zone format which is not supported by SimpleDateFormat.
There are two supported time zone formats that it can parse,
General time zones:
General time zone: Time zones are interpreted as text if they have names. For time zones representing a GMT offset value, the following syntax is used:
GMTOffsetTimeZone:
GMT Sign Hours : Minutes
Sign: one of
+ -
Hours:
Digit
Digit Digit
Minutes:
Digit Digit
Digit: one of
0 1 2 3 4 5 6 7 8 9
Hours must be between 0 and 23, and Minutes must be between 00 and 59. The format is locale independent and digits must be taken from the Basic Latin block of the Unicode standard.
...and RFC 822 time zones:
RFC 822 time zone: For formatting, the RFC 822 4-digit time zone format is used:
RFC822TimeZone:
Sign TwoDigitHours Minutes
TwoDigitHours:
Digit Digit
TwoDigitHours must be between 00 and 23. Other definitions are as for general time zones.
As you can see, the general time zone has a colon in it, but must be prefixed with "GMT", whereas the RFC 822 format has no colon. What you are trying to parse is a sort of bastardization of the two.
One of the following would work, depending on the time zone format, if you had a legal syntax:
String generalInput = "2014-06-28T21:00:00GMT-05:00"; // legal General time zone
String rfcInput = "2014-06-28T21:00:00-0500"; // legal RFC 822 time zone
DateFormat generalFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssz"); // format for general time zone
DateFormat rfcFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ"); // format for RFC 822 time zone
Since your input is malformed, I would suggest that you simply don't try to parse the time zone part of it at all, and treat it as a local time. Since you're trying to convert 21:00 to 9:00 pm anyway, this should work for you:
String input = "2014-06-28T21:00:00-05:00"; // not a legal time zone format
DateFormat fromFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); // don't even try to parse time zone
You can try following -
String input = '2014-06-28T21:00:00.000-0500';
DateFormat fromFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");

Categories

Resources