SimpleDateFormat解析丢失时区

Ach*_*how 53 java date gmt simpledateformat

码:

 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.
    }
Run Code Online (Sandbox Code Playgroud)

输出:

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
Run Code Online (Sandbox Code Playgroud)

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

这是一个重复的问题,没有任何答案.

编辑:让我以另一种方式提出问题,检索Date对象的方式是什么,以便它始终在GMT中?

Ach*_*how 53

我只需要这样:

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.
}
Run Code Online (Sandbox Code Playgroud)

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

2013.08.08 11:01:08
Thu Aug 08 11:01:08 GMT+08:00 2013
Run Code Online (Sandbox Code Playgroud)

  • 你能帮我理解答案吗? (4认同)

Too*_*eve 8

正如他所说,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();
    }       
}
Run Code Online (Sandbox Code Playgroud)

检查调试器中的值:

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) 
Run Code Online (Sandbox Code Playgroud)

sdf2和sdfLocal2包含时区,因此我们可以看到实际发生的事情.s1和s2是在EDT区域的06:11:53.s3和s4在格林威治标准时间区域的10:11:53 - 相当于原始的EDT时间.想象一下,我们将s3或s4保存在数据库中,我们使用GMT来保持一致性,因此我们可以在世界任何地方都有时间,而不会存储不同的时区.

s5解析GMT时间,但将其视为当地时间.所以它说"10:11:53" - 格林尼治标准时间 - 但认为当地时间是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();
    }       
}
Run Code Online (Sandbox Code Playgroud)


Bas*_*que 7

TL;博士

检索Date对象的方式是什么,以便它始终在GMT中?

Instant.now() 
Run Code Online (Sandbox Code Playgroud)

细节

您正在使用麻烦的令人困惑的旧日期时间类,现在由java.time类取代.

Instant = UTC

Instant级表示时间轴上的时刻UTC,分辨率为纳秒(最多9个(9)小数的位数).

Instant instant = Instant.now() ; // Current moment in UTC.
Run Code Online (Sandbox Code Playgroud)

ISO 8601

要将此数据作为文本进行交换,请仅使用标准ISO 8601格式.这些格式设计合理,非常明确,易于通过机器处理,并且易于被人们阅读.

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

String output = instant.toString() ;  
Run Code Online (Sandbox Code Playgroud)

2017-01-23T12:34:56.123456789Z

时区

如果您想要查看特定区域的挂钟时间中显示的相同时刻,请应用a ZoneId来获取a ZonedDateTime.

指定适当的时区名称,格式continent/region,如America/Montreal,Africa/CasablancaPacific/Auckland.切勿使用3-4字母缩写,例如ESTIST因为它们不是真正的时区,不是标准化的,甚至不是唯一的(!).

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

在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 ) ;
Run Code Online (Sandbox Code Playgroud)

......走另一条路......

Instant instant = myJavaUtilDate.toInstant() ;
Run Code Online (Sandbox Code Playgroud)

日期,只

仅限日期,请使用LocalDate.

LocalDate ld = zdt.toLocalDate() ;
Run Code Online (Sandbox Code Playgroud)

关于java.time

java.time框架是建立在Java 8和更高版本.这些类取代麻烦的老传统日期时间类,如java.util.Date,Calendar,和SimpleDateFormat.

现在处于维护模式Joda-Time项目建议迁移到java.time类.

要了解更多信息,请参阅Oracle教程.并搜索Stack Overflow以获取许多示例和解释.规范是JSR 310.

从哪里获取java.time类?

ThreeTen-额外项目与其他类扩展java.time.该项目是未来可能添加到java.time的试验场.您可以在此比如找到一些有用的类Interval,YearWeek,YearQuarter,和更多.