为什么 SimpleDateFormat.format(Date) 忽略配置的时区



鉴于我的默认时区是欧洲/巴黎:

System.out.println("Current Timezone: " + TimeZone.getDefault());
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX");
String dateStrIn = "2016-08-01T00:00:00Z";
Date date = dateFormat.parse(dateStrIn);
String dateStrOut = dateFormat.format(date);
System.out.println("Input date String: "+dateStrIn);
System.out.println("Date.toString() "+date);
System.out.println("Output date String: "+dateStrOut);

输出为:

Current Timezone: sun.util.calendar.ZoneInfo[id="Europe/Paris",offset=3600000,dstSavings=3600000,useDaylight=true,transitions=184,lastRule=java.util.SimpleTimeZone[id=Europe/Paris,offset=3600000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=2,startMonth=2,startDay=-1,startDayOfWeek=1,startTime=3600000,startTimeMode=2,endMode=2,endMonth=9,endDay=-1,endDayOfWeek=1,endTime=3600000,endTimeMode=2]]
Input date String: 2016-08-01T00:00:00Z
Date.toString() Mon Aug 01 02:00:00 CEST 2016
Output date String: 2016-08-01T02:00:00+02

现在,我重复执行,但设置不同的默认时区 (UTC(:

TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
System.out.println("Current Timezone: " + TimeZone.getDefault());
...

这是输出:

Current Timezone: sun.util.calendar.ZoneInfo[id="UTC",offset=0,dstSavings=0,useDaylight=false,transitions=0,lastRule=null]
Input date String: 2016-08-01T00:00:00Z
Date.toString() Mon Aug 01 00:00:00 UTC 2016
Output date String: 2016-08-01T02:00:00+02

Date.toString()正确地考虑了时区的变化。但是,从dateFormat.format(date);获得的字符串仍然显示 +02 而不是 Z 或 +00,为什么?

使用标准的Java API,有没有办法根据选定的时区强制格式化?

更新:

Jesper的解决方案几乎适用于所有情况,但是我遇到了这个(使用加那利群岛的夏季时区WEST(,其中它没有:

SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss z");
dateFormat.setTimeZone(TimeZone.getTimeZone("Europe/Paris"));
TimeZone.setDefault(TimeZone.getTimeZone("Europe/Paris"));
System.out.println("Current Timezone: " + TimeZone.getDefault());
String dateStrIn = "2016-08-01T08:00:00 WEST";
Date date = dateFormat.parse(dateStrIn);
String dateStrOut = dateFormat.format(date);
System.out.println("Input date String: "+dateStrIn);
System.out.println("Date.toString() "+date);
System.out.println("Output date String: "+dateStrOut);

输出:

Current Timezone: sun.util.calendar.ZoneInfo[id="Europe/Paris",offset=3600000,dstSavings=3600000,useDaylight=true,transitions=184,lastRule=java.util.SimpleTimeZone[id=Europe/Paris,offset=3600000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=2,startMonth=2,startDay=-1,startDayOfWeek=1,startTime=3600000,startTimeMode=2,endMode=2,endMonth=9,endDay=-1,endDayOfWeek=1,endTime=3600000,endTimeMode=2]]
Input date String: 2016-08-01T08:00:00 WEST
Date.toString() Mon Aug 01 09:00:00 CEST 2016
Output date String: 2016-08-01T08:00:00 WEST

您可以看到输出日期字符串仍以 WEST 表示。

例如,如果我将初始 dateStrIn 更改为:String dateStrIn = "2016-08-01T08:00:00 GMT"; ,则输出日期字符串在 CEST 中表示为:

Output date String: 2016-08-01T10:00:00 CEST

会不会是虫子?

更新 2:

另一个例子

Default TimeZone for both Date and SimpleDateFormat: "Europe/Paris"
Input String: "2016-08-01T08:00:00 WET"

输出:

Date.toString() Mon Aug 01 10:00:00 CEST 2016
Output date String: 2016-08-01T09:00:00 WEST

请注意,dateFormat.format(date); 已生成 WEST 日期字符串。从 WET -> WEST 当它应该是 WET -> CEST 时。

不要设置默认时区,而是在SimpleDateFormat对象上设置时区:

dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));

这将使SimpleDateFormat对象在UTC时区中格式化您的Date对象。

如果您使用的是 Java 8,请考虑在包java.time中使用新的日期和时间 API,而不是旧的 java.util.Datejava.text.SimpleDateFormat

通过使用麻烦的旧旧日期时间类来折磨自己,这些类现在已经被java.time框架淘汰了。

博士

ZonedDateTime.ofInstant( Instant.parse( "2016-08-01T00:00:00Z" ) , ZoneId.of( "Europe/Paris" ) )

java.time

java.time 框架内置于 Java 8 及更高版本中。这些类取代了旧的麻烦的日期时间类,如java.util.Date.Calendarjava.text.SimpleDateFormat。Joda-Time 团队还建议迁移到 java.time。

要了解更多信息,请参阅 Oracle 教程。并搜索堆栈溢出以获取许多示例和解释。

Java.time的大部分功能在ThreeTen-Backport中向后移植到Java 6和7,并在ThreeTenABP中进一步适应Android。

Instant

输入字符串 2016-08-01T00:00:00Z 末尾的ZZulu 的缩写,表示 UTC。在java.time中由Instant类表示,分辨率高达纳秒。

Instant instant = Instant.parse ( "2016-08-01T00:00:00Z" );

ZonedDateTime

通过ZoneId指定时区以获取ZonedDateTime

在夏季,巴黎时间比UTC早两个小时。因此,在巴黎墙上的时钟上看到的同一时刻是凌晨 2 点,而不是午夜,即 8 月 1 日的同一天。

ZoneId zoneId_Paris = ZoneId.of ( "Europe/Paris" );
ZonedDateTime zdt_Paris = ZonedDateTime.ofInstant ( instant , zoneId_Paris );

您可以调整到另一个时区,例如 Atlantic/Canary 。对于夏季的这个日期,时间比 UTC 早一小时。结果是凌晨 1 点而不是午夜。

ZoneId zoneId_Canary = ZoneId.of ( "Atlantic/Canary" );
ZonedDateTime zdt_Canary = zdt_Paris.withZoneSameInstant ( zoneId_Canary );

转储到控制台。

System.out.println ( "instant: " + instant + " | zdt_Paris: " + zdt_Paris + " | zdt_Canary: " + zdt_Canary );
即时: 2016-08-01T00:00:00Z | zdt_Paris: 2016-08-01T02:00+02:00[

欧洲/巴黎] | zdt_Canary: 2016-08-01T01:00+01:00[大西洋/金丝雀]

所有这三个对象(UTC,巴黎,金丝雀(都代表了历史上相同的同时时刻,时间轴上的同一点。每一个都是通过不同的挂钟时间的镜头来观察的。

实时时区

避免使用3-4个字母的区域缩写,例如WETWEST。这些不是实时时区,不是标准化的,甚至不是唯一的(!

Europe/ParisAtlantic/Canary是正确的时区名称,格式为 continent/region

这个谜团的答案在Javadoc上:

DateFormat.parse(String source, ParsePosition pos)

此分析操作使用日历生成日期。作为一个 结果,日历的日期时间字段和时区值可能 已被覆盖,具体取决于子类实现。任何 以前通过调用设置时区设置的时区值 可能需要恢复以进行进一步的操作。

解析后设置时区可以解决:

SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss z");
TimeZone.setDefault(TimeZone.getTimeZone("Europe/Paris"));
System.out.println("Current Timezone: " + TimeZone.getDefault());
String dateStrIn = "2016-08-01T08:00:00 WET";
Date date = dateFormat.parse(dateStrIn);
dateFormat.setTimeZone(TimeZone.getTimeZone("Europe/Paris"));
String dateStrOut = dateFormat.format(date);
System.out.println("Input date String: "+dateStrIn);
System.out.println("Date.toString() "+date);
System.out.println("Output date String: "+dateStrOut);

右输出:

Current Timezone: sun.util.calendar.ZoneInfo[id="Europe/Paris",offset=3600000,dstSavings=3600000,useDaylight=true,transitions=184,lastRule=java.util.SimpleTimeZone[id=Europe/Paris,offset=3600000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=2,startMonth=2,startDay=-1,startDayOfWeek=1,startTime=3600000,startTimeMode=2,endMode=2,endMonth=9,endDay=-1,endDayOfWeek=1,endTime=3600000,endTimeMode=2]]
Input date String: 2016-08-01T08:00:00 WET
Date.toString() Mon Aug 01 10:00:00 CEST 2016
Output date String: 2016-08-01T10:00:00 CEST

,这不是一个错误。

您必须首先了解日期类的工作原理。它只不过是自epoch以来毫秒数的包装,以 long 表示。因此,无论时区是什么,date 对象的基础值都保持不变。您永远无法真正更改Date类的时区。只能使用 SimpleDateFormat 类表示日期实例的String格式。此表示形式可能具有不同的时区,基于您在创建SimpleDateFormat对象时使用的时区。

同样,您需要检查 Date 类的 toString 方法。它始终使用默认时区打印日期。

编辑

您还应该查看 SimpleDateFormat.parse(( 定义。JDK说,

时区值可能会被覆盖,具体取决于给定的模式和文本中的时区值。以前通过调用 setTimeZone 设置的任何时区值都可能需要还原才能进行进一步的操作。

最新更新