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/Montreal
, Africa/Casablanca
, Pacific/Auckland
等continent/region
的格式指定适当的时区名称 。 切勿使用3-4字母缩写(如EST
或IST
因为它们不是真正的时区,不是标准化的,甚至不是唯一的(!)。
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.Date
, Calendar
和SimpleDateFormat
。
Joda-Time项目现在处于维护模式 ,build议迁移到java.time类。
要了解更多信息,请参阅Oracle教程 。 并search堆栈溢出了很多例子和解释。 规范是JSR 310 。
从何处获取java.time类?
- Java SE 8 , Java SE 9和更高版本
- 内置。
- 带有捆绑实现的标准Java API的一部分。
- Java 9增加了一些小function和修复。
- Java SE 6和Java SE 7
- 大部分的java.timefunction都被移植到了ThreeTen-Backport中的 Java 6&7中。
- Android的
- ThreeTenABP项目专门针对Android,采用了ThreeTen-Backport (上文提到)。
- 请参阅如何使用ThreeTenABP …。
ThreeTen-Extra项目将java.time扩展到其他类。 这个项目是未来可能增加java.time的一个试验场。 你可能会在这里find一些有用的类,比如Interval
, YearWeek
, YearQuarter
等等 。
你会发现这个网站非常有用https://www.programcreek.com/java-api-examples/index.php?class=java.text.SimpleDateFormat&method=applyLocalizedPattern此外我已经发布了一个方法,你可以重构满足您的需求。; javadateparsingexception,同时将UTC传递到本地时区