SimpleDateFormatparsing失去时区

码:

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()并解决这个问题,但是这个现象背后的原因是什么?

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

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

我所需要的是这样的:

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

输出:有点可疑,但我只想要date是一致的

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

正如他所言,OP解决他的问题的方法是可疑的。 该代码仍然performance出对时间表征的混淆。 为了消除这种混淆,编写不会导致错误时间的代码,请考虑他所做的扩展:

 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(); } } 

在debugging器中检查值:

 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在EDT区域的06:11:53。 s3&s4在GMT区的10:11:53,相当于原始的EDT时间。 想象一下,我们将s3或s4保存在数据库中,我们使用GMT来保持一致性,所以我们可以在世界任何地方都有时间存储,而不需要存储不同的时区。

s5parsingGMT时间,但将其视为当地时间。 所以它说“10:11:53” – 格林尼治标准时间 – 但认为是当地时间10:11:53。 不好。

s7parsingGMT时间,但忽略string中的GMT,所以仍将其视为本地时间。

s8的作品,因为现在我们在string中包括GMT,本地区域分析器使用它来从一个时区转换到另一个时区。

现在假设你不想存储区域,你想能够parsings3,但是显示为当地时间。 答案是使用它存储在同一时区的parsing – 所以使用与sdfGMT1中创build的sdf相同的sdf。 s9,s10和s11都是原始时间的表示。 他们都是“正确的”。 那就是,d2 == d1。 那么这只是你想如何显示它的一个问题。 如果你想显示什么是存储在DB – 格林尼治标准时间 – 那么你需要格式化使用GMT sdf。 这是S10。

所以这里是最后的解决scheme,如果你不想在string中显式地存储“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(); } } 

TL;博士

什么是检索一个date对象的方式,以便它始终在GMT?

 Instant.now() 

细节

您正在使用令人讨厌的旧date时间类,这些类现在被java.time类所取代。

Instant = UTC

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

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

ISO 8601

要以文本forms交换这些数据,请专门使用标准的ISO 8601格式。 这些格式的devise非常明确,易于机器处理,易于人们阅读多种文化。

分析和生成string时,java.time类默认使用标准格式。

 String output = instant.toString() ; 

2017-01-23T12:34:56.123456789Z

时区

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

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

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

在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.fromInstant( instant ) ; 

走另一条路

 Instant instant = myJavaUtilDate.toInstant() ; 

date,只

仅限date,请使用LocalDate

 LocalDate ld = zdt.toLocalDate() ; 

关于java.time

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

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

要了解更多信息,请参阅Oracle教程 。 并search堆栈溢出了很多例子和解释。 规范是JSR 310 。

从何处获取java.time类?

  • Java SE 8Java SE 9和更高版本
    • 内置。
    • 带有捆绑实现的标准Java API的一部分。
    • Java 9增加了一些小function和修复。
  • Java SE 6Java SE 7
    • 大部分的java.timefunction都被移植到了ThreeTen-Backport中的 Java 6&7中。
  • Android的
    • ThreeTenABP项目专门针对Android,采用了ThreeTen-Backport (上文提到)。
    • 请参阅如何使用ThreeTenABP …。

ThreeTen-Extra项目将java.time扩展到其他类。 这个项目是未来可能增加java.time的一个试验场。 你可能会在这里find一些有用的类,比如IntervalYearWeekYearQuarter 等等 。