日期对象 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 一起使用,因为日期时间字符串的某些部分在不同的 Locales 中以不同的方式表示。

演示:

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

SimpleDateFormat日期和文本之间相互转换

时间日期相关类:Date类,DateFormat类&SimpleDateFormat类,Calendar类

SimpleDateFormat日期格式(浅面)

SimpleDateFormat日期格式(浅面)

JUC并发编程 -- 日期转换的问题(SimpleDateFormat ) & 不可变对象使用和设计 & 保护性拷贝