Date object SimpleDateFormat not parsing timestamp string correctly in Java (Android) environment
I'm using the SimpleDateFormat
object with the Date
object as shown below. The problem lis that the Date
object shows the wrong date, which is a few minutes off from the original string. The Date
object appears to store the time in total milliseconds in the debugger.
Any ideas on the problem?
import java.text.SimpleDateFormat;
import java.util.Date;
Date played_at_local;
dateFormat 开发者_如何学运维= new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss.SSSSSSZ");
played_at_local = dateFormat.parse("2011-04-11T22:27:18.491726-05:00");
//played_at_local shows "Mon Apr 11 22:35:29 America/Chicago 2011" in debugger
Try removing the fractional seconds from the format string. I just ran into the same issue, but with a slightly different format. My input format wasn't in ISO format (no "T", and no "Z"), but the symptom was the same -- time was off by some random number of minutes and seconds, but everything else was fine. This is what my log results looked like:
When using the fractional second format:
SimpleDateFormat dateFormater = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSSSS");
# Parsed date: 2011-05-27 17:11:15.271816 => Fri May 27 17:15:46 EDT 2011
# Parsed date: 2011-05-27 17:09:37.750343 => Fri May 27 17:22:07 EDT 2011
# Parsed date: 2011-05-27 17:05:55.182921 => Fri May 27 17:08:57 EDT 2011
# Parsed date: 2011-05-27 16:55:05.69092 => Fri May 27 16:56:14 EDT 2011
# Parsed date: 2011-05-27 16:38:35.50348 => Fri May 27 16:39:25 EDT 2011
I fixed it by removing the fractional seconds from the format.
SimpleDateFormat dateFormater = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
# Parsed date: 2011-05-27 17:11:15.271816 => Fri May 27 17:11:15 EDT 2011
# Parsed date: 2011-05-27 17:09:37.750343 => Fri May 27 17:09:37 EDT 2011
# Parsed date: 2011-05-27 17:05:55.182921 => Fri May 27 17:05:55 EDT 2011
# Parsed date: 2011-05-27 16:55:05.69092 => Fri May 27 16:55:05 EDT 2011
# Parsed date: 2011-05-27 16:38:35.50348 => Fri May 27 16:38:35 EDT 2011
What I think is happening is that my "fractional seconds" part of the input string is too long (the same is true in the OP example). It appears to be expecting only three decimal places. If you do the math (take the first example):
- fractional seconds = 0.271816 seconds
- What DateFormat sees is
271816 / 1000
of a second - 271816 / 1000 == 271 seconds
- 271 / 60 = 4 minutes
- 271 % 60 = 31 seconds
- 17:11:15 to 17:15:46 is exactly 4 minutes, 31 seconds off
Try this, working for me Z should be useed in date, or rmove from Format String
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss.SSSSSS'Z'");
played_at_local = dateFormat.parse("2011-04-11T22:27:18.491726Z-05:00");
There are three major problems in your code:
- You have used
.SSSSSS
for the fraction of a second whereas theSimpleDateFormat
does not support a precision beyond milliseconds (.SSS
). It also means that you need to limit the digits in the fraction of a second to three. - You have used
Z
to parse the timezone offset,-05:00
whereas the correct pattern for this isXXX
. - You have used
hh
for a time in 24-Hour format whereas the correct pattern for this isHH
. The symbol,hh
is used for a time in 12-Hour (i.e. with am/pm) format.
Apart from this, I recommend you always use Locale
with a date parsing/formatting API because parts of a date-time string are represented in different ways in different Locale
s.
Demo:
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
public class Main {
public static void main(String[] args) throws ParseException {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX", Locale.ENGLISH);
Date date = sdf.parse("2011-04-11T22:27:18.491-05:00");
// Print the default string i.e. Date#toString
System.out.println(date);
// Print the date-time in a custom format
sdf.setTimeZone(TimeZone.getTimeZone("GMT-05:00"));
System.out.println(sdf.format(date));
}
}
Output:
Tue Apr 12 04:27:18 BST 2011
2011-04-11T22:27:18.491-05:00
Some facts about legacy date-time API:
- The
java.util.Date
object is not a real date-time object like the modern date-time types; rather, it represents the number of milliseconds since the standard base time known as "the epoch", namelyJanuary 1, 1970, 00:00:00 GMT
(or UTC). When you print an object ofjava.util.Date
, itstoString
method returns the date-time in the JVM's timezone, calculated from this milliseconds value. If you need to print the date-time in a different timezone, you will need to set the timezone toSimpleDateFormat
and obtain the formatted string from it. - The
java.util
date-time API and their formatting API,SimpleDateFormat
are outdated and error-prone. It is recommended to stop using them completely and switch to the modern date-time API* .
Using modern date-time API:
import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;
public class Main {
public static void main(String[] args) {
OffsetDateTime odt = OffsetDateTime.parse("2011-04-11T22:27:18.491726-05:00");
// Print the default string i.e. OffsetDateTime#toString
System.out.println(odt);
// Print the date-time in a custom format. Note: OffsetDateTime#toString drops
// seconds if it is zero
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss.SSSSSSXXX");
System.out.println(dtf.format(odt));
}
}
Output:
2011-04-11T22:27:18.491726-05:00
2011-04-11T22:27:18.491726-05:00
Note: For DateTimeFormatter
, the symbol, u
means year whereas the symbol, y
means year-of-era. It doesn't make any difference for a year in the [AD][2] era, but it matters for a year in the BC era. Check this answer to learn more about it.
Learn more about the modern date-time API from Trail: Date Time.
* For any reason, if you have to stick to Java 6 or Java 7, you can use ThreeTen-Backport which backports most of the java.time functionality to Java 6 & 7. If you are working for an Android project and your Android API level is still not compliant with Java-8, check Java 8+ APIs available through desugaring and How to use ThreeTenABP in Android Project.
05:00 -->> 0500
and
hh --> HH // error not because of this ,but date is in 24hr format.
played_at_local = dateFormat.parse("2011-04-11T22:27:18.491726-05:00");
should be
played_at_local = dateFormat.parse("2011-04-11T22:27:18.491726-0500");
You could try this method: http://docs.oracle.com/javase/6/docs/api/java/sql/Timestamp.html#valueOf(java.lang.String)
The key thing is that fractional digits are optional and you can use a variable number of them. However, this does not seem to account for the time zone.
From the docs:
valueOf
public static Timestamp valueOf(String s)
Converts a String object in JDBC timestamp escape format to a Timestamp value.
Parameters:
s - timestamp in format yyyy-mm-dd hh:mm:ss[.f...]. The fractional seconds may be omitted.
Returns:
corresponding Timestamp value
Throws:
IllegalArgumentException - if the given argument does not have the format yyyy-mm-dd hh:mm:ss[.f...]
Try this :
dTime = new SimpleDateFormat("HH:mm:ss:SS");
String sTime = (dTime.format(new java.util.Date())).toString();
Hope this help
java.time and ThreeTenABP
There is no way that SimpleDateFormat
can parse your datetime string correctly. On the other hand java.time, the modern Java date and time API, supports your format out of the box.
import org.threeten.bp.OffsetDateTime;
String dateTimeString = "2011-04-11T22:27:18.491726-05:00";
OffsetDateTime playedAtLocal = OffsetDateTime.parse(dateTimeString);
System.out.println("Parsed into " + playedAtLocal);
Output is:
Parsed into 2011-04-11T22:27:18.491726-05:00
SimpleDateFormat
only supports milliseconds, exactly three decimals on the seconds, not two, not four, not six (admittedly I’m unsure whether some Android versions have a version of SimpleDateFormat
that can do better, but your question shows that your version cannot). SimpleDateFormat
is also notoriously troublesome and long outdated, so you wouldn’t want to use it anyway.
java.time is so much nicer to work with. You notice that we didn’t even need an explicit formatter and thus didn’t need to write a format pattern string, which is always an error-prone task. Your date time string is in ISO 8601 format, and java.time classes parse ISO 8601 as their default.
Question: Can I use java.time on Android?
Yes, java.time works nicely on older and newer Android devices. It just requires at least Java 6.
- In Java 8 and later and on newer Android devices (from API level 26) the modern API comes built-in.
- In Java 6 and 7 get the ThreeTen Backport, the backport of the modern classes (ThreeTen for JSR 310; see the links at the bottom).
- On (older) Android use the Android edition of ThreeTen Backport. It’s called ThreeTenABP. And make sure you import the date and time classes from
org.threeten.bp
with subpackages.
Links
- Oracle tutorial: Date Time explaining how to use java.time.
- Java Specification Request (JSR) 310, where
java.time
was first described. - ThreeTen Backport project, the backport of
java.time
to Java 6 and 7 (ThreeTen for JSR-310). - ThreeTenABP, Android edition of ThreeTen Backport
- Question: How to use ThreeTenABP in Android Project, with a very thorough explanation.
- Wikipedia article: ISO 8601
精彩评论