日期对象 SimpleDateFormat 未在 Java(Android)环境中正确解析时间戳字符串
Posted
技术标签:
【中文标题】日期对象 SimpleDateFormat 未在 Java(Android)环境中正确解析时间戳字符串【英文标题】:Date object SimpleDateFormat not parsing timestamp string correctly in Java (Android) environment 【发布时间】:2011-08-03 22:28:32 【问题描述】:我正在使用SimpleDateFormat
对象和Date
对象,如下所示。问题在于Date
对象显示了错误的日期,与原始字符串相差几分钟。 Date
对象似乎在调试器中以总毫秒数存储时间。
对这个问题有什么想法吗?
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
【问题讨论】:
【参考方案1】:您的代码中存在三个主要问题:
-
您使用
.SSSSSS
的时间只有几分之一秒,而SimpleDateFormat
不支持超过毫秒的精度 (.SSS
)。这也意味着您需要将几分之一秒的数字限制为三个。
您已使用Z
解析时区偏移量-05:00
,而用于此的correct pattern 是XXX
。
您曾经以 24 小时格式使用 hh
,而正确的模式是 HH
。符号 hh
用于 12 小时制(即上午/下午)格式的时间。
除此之外,我建议您始终将 Locale
与日期解析/格式化 API 一起使用,因为日期时间字符串的某些部分在不同的 Locale
s 中以不同的方式表示。
演示:
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));
输出:
Tue Apr 12 04:27:18 BST 2011
2011-04-11T22:27:18.491-05:00
关于旧版日期时间 API 的一些事实:
java.util.Date
对象不像modern date-time types 那样是真正的日期时间对象;相反,它表示自称为“纪元”的标准基准时间以来的毫秒数,即January 1, 1970, 00:00:00 GMT
(或 UTC)。当你打印一个java.util.Date
的对象时,它的toString
方法返回JVM 时区中的日期时间,从这个毫秒值计算。如果您需要在不同的时区打印日期时间,则需要将时区设置为 SimpleDateFormat
并从中获取格式化字符串。
java.util
日期时间 API 及其格式化 API SimpleDateFormat
已过时且容易出错。建议完全停止使用,改用modern date-time API* 。
使用现代日期时间 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));
输出:
2011-04-11T22:27:18.491726-05:00
2011-04-11T22:27:18.491726-05:00
注意:对于DateTimeFormatter
,符号u
表示年份,而符号y
表示年代强>。在 [AD][2] 时代,这一年没有任何区别,但在 BC 时代,这一年很重要。查看this answer 了解更多信息。
从 Trail: Date Time 了解有关现代日期时间 API 的更多信息。
* 出于任何原因,如果您必须坚持使用 Java 6 或 Java 7,您可以使用 ThreeTen-Backport,它将大部分 java.time 功能向后移植到 Java 6 和 7 . 如果您正在为一个 android 项目工作并且您的 Android API 级别仍然不符合 Java-8,请检查 Java 8+ APIs available through desugaring 和 How to use ThreeTenABP in Android Project。
【讨论】:
【参考方案2】:java.time 和 ThreeTenABP
SimpleDateFormat
无法正确解析您的日期时间字符串。另一方面,现代 Java 日期和时间 API java.time 支持开箱即用的格式。
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);
输出是:
解析为 2011-04-11T22:27:18.491726-05:00
SimpleDateFormat
仅支持毫秒,精确到小数点后三位,不是两位,不是四位,不是六位(诚然,我不确定某些 Android 版本是否有可以做得更好的SimpleDateFormat
版本,但你的问题表明您的版本不能)。 SimpleDateFormat
也是出了名的麻烦和过时,所以无论如何你都不想使用它。
java.time 好用得多。您注意到我们甚至不需要显式格式化程序,因此不需要编写格式模式字符串,这总是一个容易出错的任务。您的日期时间字符串采用 ISO 8601 格式,java.time 类将 ISO 8601 解析为默认值。
问题:我可以在 Android 上使用 java.time 吗?
是的,java.time 在较旧和较新的 Android 设备上运行良好。它只需要至少 Java 6。
在 Java 8 及更高版本以及更新的 Android 设备(从 API 级别 26 起)中,现代 API 是内置的。 在 Java 6 和 7 中获得 ThreeTen Backport,这是现代类的后向端口(JSR 310 的 ThreeTen;请参阅底部的链接)。 在(较旧的)Android 上使用 ThreeTen Backport 的 Android 版本。它被称为 ThreeTenABP。并确保从org.threeten.bp
导入日期和时间类以及子包。
链接
Oracle tutorial: Date Time 解释如何使用 java.time。 Java Specification Request (JSR) 310,其中首次描述了java.time
。
ThreeTen Backport project,java.time
向后移植到 Java 6 和 7(JSR-310 的 ThreeTen)。
ThreeTenABP,Android 版 ThreeTen Backport
Question: How to use ThreeTenABP in Android Project,解释得很透彻。
Wikipedia article: ISO 8601
【讨论】:
【参考方案3】:试试这个:
dTime = new SimpleDateFormat("HH:mm:ss:SS");
String sTime = (dTime.format(new java.util.Date())).toString();
希望有帮助
【讨论】:
请添加更多详细信息。【参考方案4】:你可以试试这个方法: http://docs.oracle.com/javase/6/docs/api/java/sql/Timestamp.html#valueOf(java.lang.String)
关键是小数位是可选的,您可以使用可变数量的小数位。但是,这似乎没有考虑时区。
来自文档:
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...]
【讨论】:
【参考方案5】:尝试从格式字符串中删除小数秒。我刚刚遇到了同样的问题,但格式略有不同。我的输入格式不是 ISO 格式(没有“T”,也没有“Z”),但症状是一样的——时间相差了一些随机的分钟和秒数,但其他一切都很好。这是我的日志结果的样子:
使用小数秒格式时:
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
我通过从格式中删除小数秒来修复它。
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
我认为正在发生的是输入字符串的“小数秒”部分太长(在 OP 示例中也是如此)。它似乎只需要 三个 小数位。如果你算一下(以第一个例子为例):
小数秒 = 0.271816 秒 DateFormat 看到的是271816 / 1000
秒
271816 / 1000 == 271 秒
271 / 60 = 4 分钟
271 % 60 = 31 秒
17:11:15 到 17:15:46 正好相差 4 分 31 秒
【讨论】:
是的,SimpleDateFormat 无法处理微秒。最简单的做法是去掉多余的 3 位数字,例如date = date.substring(0, date.length()-4)+"Z"; @scottw:除了这并不总是有效,正如您在我的示例中看到的那样,最后两项只有 5 位数字,而不是 6 位;所以在某些情况下,可能只有一位小数,这是完全错误的!如果您只是将字符串截断为只有 3 个小数位,那么它也可以正常工作。在我们的例子中,我们不需要微秒(我猜很多人也会同意这一点),所以事实证明,只需从格式字符串中删除S
就足以从中获得可用时间。如果您确实需要小数秒,那么是的,您必须进行一些字符串操作。
这是一个很好的观点,乔 - 我的解决方案仅在我使用始终为小数秒提供 6 位数字(包括用零填充)的服务工作时才有效,因此我可以摆脱字符串操作。跨度>
【参考方案6】:
试试这个,为我工作Z应该在日期中使用,或者从格式字符串中删除
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");
【讨论】:
【参考方案7】:05:00 -->> 0500
和
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");
应该是
played_at_local = dateFormat.parse("2011-04-11T22:27:18.491726-0500");
【讨论】:
以上是关于日期对象 SimpleDateFormat 未在 Java(Android)环境中正确解析时间戳字符串的主要内容,如果未能解决你的问题,请参考以下文章
Java 8 进阶手册(XX):使用简单日期对象 SimpleDateFormat