【问题标题】:SimpleDateFormat parse loses timezone [duplicate]SimpleDateFormat 解析丢失时区 [重复]
【发布时间】: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 中拥有相同的日期

标签: java date simpledateformat gmt


【解决方案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 类?

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

【讨论】:

    【解决方案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();
        }       
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-02-04
      • 2014-06-08
      • 2011-12-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多