Java SimpleDateFormat 解析丢失时区

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/18122608/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 22:32:06  来源:igfitidea点击:

SimpleDateFormat parse loses timezone

javadatesimpledateformatgmt

提问by Achow

Code:

代码:

 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.
    }

Output:

输出:

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

Note that format()formats the Datecorrectly to GMT, but parse()lost the GMT details. I know I can use substring()and work around this, but what is the reason underlying this phenomenon?

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

Here is a duplicate questionwhich doesn't have any answers.

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

Edit: Let me put the question in another way, what is the way to retrieve a Date object so that its always in GMT?

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

采纳答案by Achow

All I needed was this :

我只需要这个:

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.
}

Output : slightly dubious, but I want only the date to be consistent

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

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

回答by ToolmakerSteve

OP's solution to his problem, as he says, has dubious output. That code still shows confusion about representations of time. To clear up this confusion, and make code that won't lead to wrong times, consider this extension of what he did:

正如他所说,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();
    }       
}

examining values in a debugger:

检查调试器中的值:

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 and sdfLocal2 include time zone, so we can see what is really going on. s1 & s2 are at 06:11:53 in zone EDT. s3 & s4 are at 10:11:53 in zone GMT -- equivalent to the original EDT time. Imagine we save s3 or s4 in a data base, where we are using GMT for consistency, so we can have times from anywhere in the world, without storing different time zones.

sdf2 和 sdfLocal2 包括时区,所以我们可以看到真正发生了什么。s1 和 s2 位于 EDT 区域的 06:11:53。s3 和 s4 位于格林威治标准时间的 10:11:53 - 相当于原始 EDT 时间。想象一下,我们将 s3 或 s4 保存在一个数据库中,我们使用 GMT 来保持一致性,这样我们就可以拥有来自世界任何地方的时间,而无需存储不同的时区。

s5 parses the GMT time, but treats it as a local time. So it says "10:11:53" -- the GMT time -- but thinks it is 10:11:53 in localtime. Not good.

s5 解析 GMT 时间,但将其视为本地时间。因此,它说:“10时11分53秒” - GMT时间-但认为它是在10时11分53秒的本地时间。不好。

s7 parses the GMT time, but ignores the GMT in the string, so still treats it as a local time.

s7 解析 GMT 时间,但忽略字符串中的 GMT,因此仍将其视为本地时间。

s8 works, because now we include GMT in the string, and the local zone parser uses it to convert from one time zone to another.

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

Now suppose you don't want to store the zone, you want to be able to parse s3, but display it as a local time. The answer is to parse using the same time zone it was stored in-- so use the same sdf as it was created in, sdfGMT1. s9, s10, & s11 are all representations of the original time. They are all "correct". That is, d2 == d1. Then it is only a question of how you want to DISPLAY it. If you want to display what is stored in DB -- GMT time -- then you need to format it using a GMT sdf. Ths is s10.

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

So here is the final solution, if you don't want to explicitly store with " GMT" in the string, and want to display in GMT format:

所以这是最终的解决方案,如果您不想在字符串中显式存储“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();
    }       
}

回答by Basil Bourque

tl;dr

tl;博士

what is the way to retrieve a Date object so that its always in GMT?

检索 Date 对象以使其始终在 GMT 中的方法是什么?

Instant.now() 

Details

细节

You are using troublesome confusing old date-time classes that are now supplanted by the java.time classes.

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

Instant= UTC

Instant= UTC

The Instantclass represents a moment on the timeline in UTCwith a resolution of nanoseconds(up to nine (9) digits of a decimal fraction).

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

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

ISO 8601

ISO 8601

To exchange this data as text, use the standard ISO 8601formats exclusively. These formats are sensibly designed to be unambiguous, easy to process by machine, and easy to read across many cultures by people.

要将此数据作为文本交换,请专门使用标准ISO 8601格式。这些格式被巧妙地设计为明确的,易于机器处理,并且易于人们在多种文化中阅读。

The java.time classes use the standard formats by default when parsing and generating strings.

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

String output = instant.toString() ;  

2017-01-23T12:34:56.123456789Z

2017-01-23T12:34:56.123456789Z

Time zone

时区

If you want to see that same moment as presented in the wall-clock time of a particular region, apply a ZoneIdto get a ZonedDateTime.

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

Specify a proper time zone namein the format of continent/region, such as America/Montreal, Africa/Casablanca, or Pacific/Auckland. Never use the 3-4 letter abbreviation such as ESTor ISTas they are nottrue time zones, not standardized, and not even unique(!).

以、、 或等格式指定正确的时区名称。永远不要使用 3-4 个字母的缩写,例如或因为它们不是真正的时区,不是标准化的,甚至不是唯一的(!)。continent/regionAmerica/MontrealAfrica/CasablancaPacific/AucklandESTIST

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

See this code live at IdeOne.com.

在 IdeOne.com 上实时查看此代码

Notice the eight hour difference, as the time zone of Asia/Singaporecurrently has an offset-from-UTC of +08:00. Same moment, different wall-clock time.

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

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

zdt.toString(): 2017-01-23T20:34:56.123456789+08:00[Asia/Singapore]

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

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

Convert

转变

Avoid the legacy java.util.Dateclass. But if you must, you can convert. Look to new methods added to the old classes.

避免遗留java.util.Date类。但如果你必须,你可以转换。寻找添加到旧类中的新方法。

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

…going the other way…

……反其道而行之……

Instant instant = myJavaUtilDate.toInstant() ;

Date-only

仅限日期

For date-only, use LocalDate.

对于仅限日期,请使用LocalDate.

LocalDate ld = zdt.toLocalDate() ;


About java.time

关于 java.time

The java.timeframework is built into Java 8 and later. These classes supplant the troublesome old legacydate-time classes such as java.util.Date, Calendar, & SimpleDateFormat.

java.time框架是建立在Java 8和更高版本。这些类取代了麻烦的旧的遗留日期时间类,例如java.util.Date, Calendar, & SimpleDateFormat

The Joda-Timeproject, now in maintenance mode, advises migration to the java.timeclasses.

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

To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.

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

Where to obtain the java.time classes?

从哪里获得 java.time 类?

The ThreeTen-Extraproject extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.

ThreeTen-额外项目与其他类扩展java.time。该项目是未来可能添加到 java.time 的试验场。你可能在这里找到一些有用的类,比如IntervalYearWeekYearQuarter,和更多