SimpleDateFormat 解析丢失时区 [重复]
Posted
技术标签:
【中文标题】SimpleDateFormat 解析丢失时区 [重复]【英文标题】:SimpleDateFormat parse loses timezone [duplicate] 【发布时间】:2013-08-09 23:08:29 【问题描述】:代码:
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()
来解决这个问题,但是造成这种现象的原因是什么?
Here is a duplicate question 没有任何答案。
编辑:让我以另一种方式提出问题,检索 Date 对象以使其始终处于 GMT 的方式是什么?
【问题讨论】:
“解析丢失了 GMT”是什么意思?您在 GMT+0800 中给它一个日期,并告诉它在时区 GMT 中对其进行格式化,因此它会这样做(注意时间已更改)。这正是你要求它做的事情。 您在 SimpleDateFormat 上设置时区,并且未链接到日期。所以 println(date) 不会引用 SimpleDateFormat 中设置的时区 您对 Date 和 DateFormat 感到困惑,无论您在哪个时区,Date 对象总是相同的。 DateFormat 对象用于格式化您可以指定时区信息的日期。当您调用 parse 方法时,您的时间值已根据您的时区发生变化 @KevinBowersox:对,Date
没有时区。但是当你使用Date#toString
时,它使用JVM的当前时区进行格式化。
@T.J.Crowder,所以你说的是它只是 toString ,它采用 JVM 的时区并相应地打印它。但是当我将 Date 对象传递给数据库时会发生什么,那么这些位是如何发送的呢?我想还会有一些日期格式?那么我如何保证,无论我在哪里运行代码,无论是在纽约、欧盟还是日本,我总是在 GMT 或 UTC 中拥有相同的日期
【参考方案1】:
我只需要这个:
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.
输出:有点可疑,但我只希望日期保持一致
2013.08.08 11:01:08
Thu Aug 08 11:01:08 GMT+08:00 2013
【讨论】:
你能帮我理解答案吗? aDate
仅存储自 1970-01-01 00:00:00 UTC 以来的毫秒数,但没有关于时区的信息,因此 d
是正确的。当您将 Date
转换为字符串时,这在 println
中隐式发生,它将使用默认/本地时区进行格式化,这意味着发生的事情是:new Date() -> 在 GMT 中格式化为字符串 - > 在 GMT 中解析为字符串 -> (与 new Date() 的值相同)由本地时区 println()
中的隐式 toString()
格式化。
它对我不起作用。我想要 EST 格式的时间。它显示 2019-06-26T00:53:20Z 和 Wed Jun 26 00:53:20 IST 2019。时间很好,但这里应该是 EST。【参考方案2】:
tl;博士
有什么方法可以检索 Date 对象以使其始终处于 GMT 状态?
Instant.now()
详情
您正在使用令人烦恼的、令人困惑的旧日期时间类,这些类现在已被 java.time 类所取代。
Instant
= UTC
Instant
类表示UTC 中时间轴上的时刻,分辨率为nanoseconds(最多九 (9) 位小数)。
Instant instant = Instant.now() ; // Current moment in UTC.
ISO 8601
要将此数据交换为文本,请仅使用标准ISO 8601 格式。这些格式经过精心设计,明确无误,易于机器处理,并且易于人们跨多种文化阅读。
java.time 类在解析和生成字符串时默认使用标准格式。
String output = instant.toString() ;
2017-01-23T12:34:56.123456789Z
时区
如果您想看到与特定地区挂钟时间相同的时刻,请应用ZoneId
以获取ZonedDateTime
。
以continent/region
的格式指定proper time zone name,例如America/Montreal
、Africa/Casablanca
或Pacific/Auckland
。切勿使用 3-4 个字母的缩写,例如 EST
或 IST
,因为它们不是真正的时区,没有标准化,甚至不是唯一的 (!)。
ZoneId z = ZoneId.of( "Asia/Singapore" ) ;
ZonedDateTime zdt = instant.atZone( z ) ; // Same simultaneous moment, same point on the timeline.
看到这个code live at 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 ) ;
……走另一条路……
Instant instant = myJavaUtilDate.toInstant() ;
仅限日期
对于仅日期,使用LocalDate
。
LocalDate ld = zdt.toLocalDate() ;
关于java.time
java.time 框架内置于 Java 8 及更高版本中。这些类取代了麻烦的旧 legacy 日期时间类,例如 java.util.Date
、Calendar
和 SimpleDateFormat
。
Joda-Time 项目现在位于maintenance mode,建议迁移到java.time 类。
要了解更多信息,请参阅Oracle Tutorial。并在 Stack Overflow 上搜索许多示例和解释。规格为JSR 310。
从哪里获得 java.time 类?
Java SE 8、Java SE 9 及更高版本 内置。 标准 Java API 的一部分,带有捆绑实现。 Java 9 添加了一些小功能和修复。 Java SE 6 和 Java SE 7 ThreeTen-Backport 中的大部分 java.time 功能都向后移植到 Java 6 和 7。 Android ThreeTenABP 项目专门针对 android 改编了 ThreeTen-Backport(如上所述)。 见How to use ThreeTenABP…。ThreeTen-Extra 项目通过附加类扩展了 java.time。该项目是未来可能添加到 java.time 的试验场。您可以在这里找到一些有用的类,例如Interval
、YearWeek
、YearQuarter
和more。
【讨论】:
【参考方案3】:正如他所说,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();
在调试器中检查值:
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 位于美国东部夏令时间 06:11:53。 s3 和 s4 位于格林威治标准时间区的 10:11:53 - 相当于原始 EDT 时间。想象一下,我们将 s3 或 s4 保存在数据库中,我们使用 GMT 来保持一致性,这样我们就可以从世界任何地方获取时间,而无需存储不同的时区。
s5 解析 GMT 时间,但将其视为本地时间。所以它说“10:11:53”——格林威治标准时间——但认为它是 local 时间的 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();
【讨论】:
以上是关于SimpleDateFormat 解析丢失时区 [重复]的主要内容,如果未能解决你的问题,请参考以下文章
在Groovy for Jira中解析日期字符串而不丢失时区
dateutil.parser.parse() 和丢失的时区信息
SimpleDateFormat 在不同时区 JVM 中的行为不同
如果手机时区已更改,SimpleDateFormat setTimeZone无效