SimpleDateFormat 解析丢失时区 [重复]

Posted

技术标签:

【中文标题】SimpleDateFormat 解析丢失时区 [重复]【英文标题】:SimpleDateFormat parse loses timezone [duplicate] 【发布时间】:2013-08-09 23:08:29 【问题描述】:

代码:

 SimpleDateFormat sdf = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss z");
    sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
    System.out.println(new Date());
    try 
        String d = sdf.format(new Date());
        System.out.println(d);
        System.out.println(sdf.parse(d));
     catch (Exception e) 
        e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
    

输出:

Thu Aug 08 17:26:32 GMT+08:00 2013
2013.08.08 09:26:32 GMT
Thu Aug 08 17:26:32 GMT+08:00 2013

请注意,format()Date 正确格式化为 GMT,但 parse() 丢失了 GMT 详细信息。我知道我可以使用substring() 来解决这个问题,但是造成这种现象的原因是什么?

Here is a duplicate question 没有任何答案。

编辑:让我以另一种方式提出问题,检索 Date 对象以使其始终处于 GMT 的方式是什么?

【问题讨论】:

“解析丢失了 GMT”是什么意思?您在 GMT+0800 中给它一个日期,并告诉它在时区 GMT 中对其进行格式化,因此它会这样做(注意时间已更改)。这正是你要求它做的事情。 您在 SimpleDateFormat 上设置时区,并且未链接到日期。所以 println(date) 不会引用 SimpleDateFormat 中设置的时区 您对 Date 和 DateFormat 感到困惑,无论您在哪个时区,Date 对象总是相同的。 DateFormat 对象用于格式化您可以指定时区信息的日期。当您调用 parse 方法时,您的时间值已根据您的时区发生变化 @KevinBowersox:对,Date 没有时区。但是当你使用Date#toString时,它使用JVM的当前时区进行格式化。 @T.J.Crowder,所以你说的是它只是 toString ,它采用 JVM 的时区并相应地打印它。但是当我将 Date 对象传递给数据库时会发生什么,那么这些位是如何发送的呢?我想还会有一些日期格式?那么我如何保证,无论我在哪里运行代码,无论是在纽约、欧盟还是日本,我总是在 GMT 或 UTC 中拥有相同的日期 【参考方案1】:

我只需要这个:

SimpleDateFormat sdf = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss");
sdf.setTimeZone(TimeZone.getTimeZone("GMT"));

SimpleDateFormat sdfLocal = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss");

try 
    String d = sdf.format(new Date());
    System.out.println(d);
    System.out.println(sdfLocal.parse(d));
 catch (Exception e) 
    e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.

输出:有点可疑,但我只希望日期保持一致

2013.08.08 11:01:08
Thu Aug 08 11:01:08 GMT+08:00 2013

【讨论】:

你能帮我理解答案吗? a Date 仅存储自 1970-01-01 00:00:00 UTC 以来的毫秒数,但没有关于时区的信息,因此 d 是正确的。当您将 Date 转换为字符串时,这在 println 中隐式发生,它将使用默认/本地时区进行格式化,这意味着发生的事情是:new Date() -> 在 GMT 中格式化为字符串 - > 在 GMT 中解析为字符串 -> (与 new Date() 的值相同)由本地时区 println() 中的隐式 toString() 格式化。 它对我不起作用。我想要 EST 格式的时间。它显示 2019-06-26T00:53:20Z 和 Wed Jun 26 00:53:20 IST 2019。时间很好,但这里应该是 EST。【参考方案2】:

tl;博士

有什么方法可以检索 Date 对象以使其始终处于 GMT 状态?

Instant.now() 

详情

您正在使用令人烦恼的、令人困惑的旧日期时间类,这些类现在已被 java.time 类所取代。

Instant = UTC

Instant 类表示UTC 中时间轴上的时刻,分辨率为nanoseconds(最多九 (9) 位小数)。

Instant instant = Instant.now() ; // Current moment in UTC.

ISO 8601

要将此数据交换为文本,请仅使用标准ISO 8601 格式。这些格式经过精心设计,明确无误,易于机器处理,并且易于人们跨多种文化阅读。

java.time 类在解析和生成字符串时默认使用标准格式。

String output = instant.toString() ;  

2017-01-23T12:34:56.123456789Z

时区

如果您想看到与特定地区挂钟时间相同的时刻,请应用ZoneId 以获取ZonedDateTime

continent/region 的格式指定proper time zone name,例如America/MontrealAfrica/CasablancaPacific/Auckland。切勿使用 3-4 个字母的缩写,例如 ESTIST,因为它们不是真正的时区,没有标准化,甚至不是唯一的 (!)。

ZoneId z = ZoneId.of( "Asia/Singapore" ) ;
ZonedDateTime zdt = instant.atZone( z ) ;  // Same simultaneous moment, same point on the timeline.

看到这个code live at IdeOne.com。

请注意八小时的差异,因为Asia/Singapore 的时区当前与 UTC 的偏移量为 +08:00。同一时刻,不同的挂钟时间。

instant.toString(): 2017-01-23T12:34:56.123456789Z

zdt.toString(): 2017-01-23T20:34:56.123456789+08:00[亚洲/新加坡]

转换

避免使用旧的 java.util.Date 类。但是,如果必须,您可以转换。查看添加到旧类的新方法。

java.util.Date date = Date.from( instant ) ;

……走另一条路……

Instant instant = myJavaUtilDate.toInstant() ;

仅限日期

对于仅日期,使用LocalDate

LocalDate ld = zdt.toLocalDate() ;

关于java.time

java.time 框架内置于 Java 8 及更高版本中。这些类取代了麻烦的旧 legacy 日期时间类,例如 java.util.DateCalendarSimpleDateFormat

Joda-Time 项目现在位于maintenance mode,建议迁移到java.time 类。

要了解更多信息,请参阅Oracle Tutorial。并在 Stack Overflow 上搜索许多示例和解释。规格为JSR 310。

从哪里获得 java.time 类?

Java SE 8Java SE 9 及更高版本 内置。 标准 Java API 的一部分,带有捆绑实现。 Java 9 添加了一些小功能和修复。 Java SE 6Java SE 7 ThreeTen-Backport 中的大部分 java.time 功能都向后移植到 Java 6 和 7。 Android ThreeTenABP 项目专门针对 android 改编了 ThreeTen-Backport(如上所述)。 见How to use ThreeTenABP…

ThreeTen-Extra 项目通过附加类扩展了 java.time。该项目是未来可能添加到 java.time 的试验场。您可以在这里找到一些有用的类,例如IntervalYearWeekYearQuarter 和more。

【讨论】:

【参考方案3】:

正如他所说,OP 对他的问题的解决方案具有可疑的输出。该代码仍然显示出对时间表示的混淆。为了消除这种困惑,并编写不会导致错误时间的代码,请考虑他所做的扩展:

public static void _testDateFormatting() 
    SimpleDateFormat sdfGMT1 = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss");
    sdfGMT1.setTimeZone(TimeZone.getTimeZone("GMT"));
    SimpleDateFormat sdfGMT2 = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss z");
    sdfGMT2.setTimeZone(TimeZone.getTimeZone("GMT"));

    SimpleDateFormat sdfLocal1 = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss");
    SimpleDateFormat sdfLocal2 = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss z");

    try 
        Date d = new Date();
        String s1 = d.toString();
        String s2 = sdfLocal1.format(d);
        // Store s3 or s4 in database.
        String s3 = sdfGMT1.format(d);
        String s4 = sdfGMT2.format(d);
        // Retrieve s3 or s4 from database, using LOCAL sdf.
        String s5 = sdfLocal1.parse(s3).toString();
        //EXCEPTION String s6 = sdfLocal2.parse(s3).toString();
        String s7 = sdfLocal1.parse(s4).toString();
        String s8 = sdfLocal2.parse(s4).toString();
        // Retrieve s3 from database, using GMT sdf.
        // Note that this is the SAME sdf that created s3.
        Date d2 = sdfGMT1.parse(s3);
        String s9 = d2.toString();
        String s10 = sdfGMT1.format(d2);
        String s11 = sdfLocal2.format(d2);
     catch (Exception e) 
        e.printStackTrace();
           

在调试器中检查值:

s1  "Mon Sep 07 06:11:53 EDT 2015" (id=831698113128)    
s2  "2015.09.07 06:11:53" (id=831698114048) 
s3  "2015.09.07 10:11:53" (id=831698114968) 
s4  "2015.09.07 10:11:53 GMT+00:00" (id=831698116112)   
s5  "Mon Sep 07 10:11:53 EDT 2015" (id=831698116944)    
s6  -- omitted, gave parse exception    
s7  "Mon Sep 07 10:11:53 EDT 2015" (id=831698118680)    
s8  "Mon Sep 07 06:11:53 EDT 2015" (id=831698119584)    
s9  "Mon Sep 07 06:11:53 EDT 2015" (id=831698120392)    
s10 "2015.09.07 10:11:53" (id=831698121312) 
s11 "2015.09.07 06:11:53 EDT" (id=831698122256) 

sdf2 和 sdfLocal2 包含时区,因此我们可以看到实际情况。 s1 和 s2 位于美国东部夏令时间 06:11:53。 s3 和 s4 位于格林威治标准时间区的 10:11:53 - 相当于原始 EDT 时间。想象一下,我们将 s3 或 s4 保存在数据库中,我们使用 GMT 来保持一致性,这样我们就可以从世界任何地方获取时间,而无需存储不同的时区。

s5 解析 GMT 时间,但将其视为本地时间。所以它说“10:11:53”——格林威治标准时间——但认为它是 local 时间的 10:11:53。不好。

s7解析了GMT时间,但是忽略了字符串中的GMT,所以仍然把它当作本地时间。

s8 有效,因为现在我们在字符串中包含 GMT,本地时区解析器使用它从一个时区转换为另一个时区。

现在假设您不想存储区域,您希望能够解析 s3,但将其显示为本地时间。答案是使用它存储在的同一时区进行解析——因此使用与它在 sdfGMT1 中创建时相同的 sdf。 s9、s10 和 s11 都是原始时间的表示。他们都是“正确的”。也就是说,d2 == d1。那么这只是你想如何显示它的问题。如果你想显示存储在 DB 中的内容——GMT 时间——那么你需要使用 GMT sdf 对其进行格式化。这是 s10。

所以这里是最后的解决方案,如果你不想在字符串中显式存储“GMT”,并且想以GMT格式显示:

public static void _testDateFormatting() 
    SimpleDateFormat sdfGMT1 = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss");
    sdfGMT1.setTimeZone(TimeZone.getTimeZone("GMT"));

    try 
        Date d = new Date();
        String s3 = sdfGMT1.format(d);
        // Store s3 in DB.
        // ...
        // Retrieve s3 from database, using GMT sdf.
        Date d2 = sdfGMT1.parse(s3);
        String s10 = sdfGMT1.format(d2);
     catch (Exception e) 
        e.printStackTrace();
           

【讨论】:

以上是关于SimpleDateFormat 解析丢失时区 [重复]的主要内容,如果未能解决你的问题,请参考以下文章

在Groovy for Jira中解析日期字符串而不丢失时区

dateutil.parser.parse() 和丢失的时区信息

SimpleDateFormat 在不同时区 JVM 中的行为不同

如果手机时区已更改,SimpleDateFormat setTimeZone无效

设置时区的 SimpleDateFormat 获得正确的值但区域错误

SimpleDateFormat 与时区