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
SimpleDateFormat parse loses timezone
提问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 Date
correctly 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 Instant
class 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 ZoneId
to 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 EST
or IST
as they are nottrue time zones, not standardized, and not even unique(!).
以、、 或等格式指定正确的时区名称。永远不要使用 3-4 个字母的缩写,例如或因为它们不是真正的时区,不是标准化的,甚至不是唯一的(!)。continent/region
America/Montreal
Africa/Casablanca
Pacific/Auckland
EST
IST
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.
Notice the eight hour difference, as the time zone of Asia/Singapore
currently 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.Date
class. 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 类?
- Java SE 8, Java SE 9, and later
- Built-in.
- Part of the standard Java API with a bundled implementation.
- Java 9 adds some minor features and fixes.
- Java SE 6and Java SE 7
- Much of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport.
- Android
- The ThreeTenABPproject adapts ThreeTen-Backport(mentioned above) for Android specifically.
- See How to use ThreeTenABP….
- Java SE 8、Java SE 9及更高版本
- 内置。
- 具有捆绑实现的标准 Java API 的一部分。
- Java 9 添加了一些小功能和修复。
- Java SE 6和Java SE 7
- 多的java.time功能后移植到Java 6和7在ThreeTen-反向移植。
- 安卓
- 所述ThreeTenABP项目适应ThreeTen-反向移植(上述)为Android特异性。
- 请参阅如何使用ThreeTenABP ...。
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 的试验场。你可能在这里找到一些有用的类,比如Interval
,YearWeek
,YearQuarter
,和更多。