Reputation: 739
I want to parse a timestamp, like this - "2016-03-16 01:14:21.6739"
. But when I use the SimpleDateFormat
to parse it, I find that it outputs an incorrect parsed value. It will covert 6739 milliseconds to 6 seconds with 739 millseconds left. It converted the date to this format - Wed Mar 16 01:14:27 PDT 2016
. Why the seconds part has changed from 21 seconds to 27 seconds(an addition of 6 seconds?). The following is my code snippet:
final SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSS");
String parsedate="2016-03-16 01:14:21.6739";
try {
Date outputdate = sf.parse(parsedate);
String newdate = outputdate.toString(); //==output date is: Wed Mar 16 01:14:27 PDT 2016
System.out.println(newdate);
} catch (ParseException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Upvotes: 9
Views: 33309
Reputation: 338835
LocalDateTime.parse(
"2016-03-16 01:14:21.6739".replace( " " , "T" ) // Comply with ISO 8601 standard format.
)
As others noted, java.util.Date
has millisecond resolution. That means up to 3 digits of a decimal fraction of second.
You have 4 digits in your input string, one too many. Your input value demands finer resolution such as microseconds or nanoseconds.
Instead of using the flawed, confusing, and troublesome java.util.Date/.Calendar classes, move on to their replacement: the java.time framework built into Java 8 and later.
The java.time classes have a resolution of nanosecond, up to nine digits of decimal fraction of a second. For example:
2016-03-17T05:19:24.123456789Z
Your string input is almost in standard ISO 8601 format used by default in java.time when parsing/generating textual representations of date-time values. Replace that space in the middle with a T
to comply with ISO 8601.
String input = "2016-03-16 01:14:21.6739".replace( " " , "T" );
A LocalDateTime
is an approximation of a date-time, without any time zone context. Not a moment on the timeline.
LocalDateTime ldt = LocalDateTime.parse( input );
Make that LocalDateTime
an actual moment on the timeline by applying the intended time zone. If meant for UTC, make an Instant
.
Instant instant = ldt.toInstant( ZoneOffset.UTC );
If meant for a particular time zone, specify a ZoneId
to get a ZoneDateTime
.
ZoneId zoneId = ZoneId.of( "America/Montreal" );
ZonedDateTime zdt = ldt.atZone( zoneId );
Upvotes: 4
Reputation: 10964
As mentioned in the comments above your question already contains the answer: milliseconds must not have more than 3 digits otherwise it represents at least one full second already.
That your code is working is simply due to a very questionable feature of java.text.SimpleDateFormat
, the lenient (see here) option. By default a SimpleDateFormat
has setLenient
set to true
wich means that the parser will try to interpret strings that do not match the pattern 100% and will convert them to date objects by means of some heuristics. For example it will accept a date 31.04.2016
and convert it to 01.05.2016
. This feature may be nice in some situations but produces questionable results in most of the cases.
If you set lenient to false
in your code, the date string won't be parsed anymore. Making the error in the pattern more obvious:
final SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSS");
sf.setLenient(false);
String parsedate="2016-03-16 01:14:21.6739";
...
As java.util.Date
is not able to represent any precision lower than milliseconds, I think the best option for you to parse your date would be to simply strip of the last digits of your input date, if the part after the dot has more than four digits. Your code might look something like this:
final SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
sf.setLenient(false);
String parsedate="2016-03-16 01:14:21.6739";
try {
// 23 is the length of the date pattern
if (parsedate.length() > 23) {
parsedate = parsedate.substring(0, 23);
}
Date outputdate = sf.parse(parsedate);
String newdate = sf.format(outputdate); //==output date is: 2016-03-16 01:14:21.673
System.out.println(newdate);
} catch (ParseException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
You could try to add some rounding logic as well in order to not loose all the information of the 4th digit...
Upvotes: 0
Reputation: 3225
If you have to get string as final output why not use format
instead of parse
final SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSZ");
sf.setTimeZone(TimeZone.getTimeZone("UTC"));
Date curDate = new Date();
String outputdate = sf.format(curDate);
// 2016-03-17 09:45:28.658+0000
System.out.println(outputdate);
Date strToDate = new Date();
try {
strToDate = sf.parse(outputdate);
} catch (ParseException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//Thu Mar 17 17:11:30 MYT 2016
System.out.println(strToDate);
and instead of "yyyy-MM-dd HH:mm:ss.SSSS"
use "yyyy-MM-dd HH:mm:ss.SSSZ"
check it here https://docs.oracle.com/javase/7/docs/api/java/text/SimpleDateFormat.html
"yyyy-MM-dd'T'HH:mm:ss.SSSZ" 2001-07-04T12:08:56.235-0700
Upvotes: 1
Reputation: 91
It seems that is not possible to use SimpleDateFormat to express times with a finer grain than the millisecond. What is happening is that as you put 6739, Java understands it as 6739 milliseconds i.e. 6 seconds and 739 milliseconds hence the 6 seconds difference observed.
Check these ones, it is explained quite well: String-Date conversion with nanoseconds Java date parsing with microsecond or nanosecond accuracy
Upvotes: 5
Reputation: 233
You can use String newdate = sf.format(outputdate); in place of String newdate = outputdate.toString();
Upvotes: 0
Reputation: 59111
SS
in SimpleDateFormat
is milliseconds. You have 6739 milliseconds, which means you are adding an extra 6.7 seconds onto your time. Perhaps you can truncate the 6739
to 673
(or if you prefer, round it to 674
) so it can be parsed correctly as milliseconds.
Upvotes: 5