为什么SimpleDateFormat.format(Date)会忽略配置的TimeZone?

时间:2016-08-04 09:21:23

标签: java date date-format simpledateformat

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

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

现在,我重复执行但设置了不同的默认TimeZone(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()已正确考虑TimeZone更改。但是,从dateFormat.format(date);得到的字符串仍显示+02而不是Z或+00,为什么?

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

更新

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。

4 个答案:

答案 0 :(得分:2)

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

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

这会使SimpleDateFormat对象格式化为Date时区中的UTC对象。

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

答案 1 :(得分:0)

,这不是错误。

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

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

修改

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

  

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

答案 2 :(得分:0)

这个谜的答案在于Javadoc:

DateFormat.parse(String source, ParsePosition pos)

  

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

解析后设置TimeZone可以解决问题:

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

答案 3 :(得分:0)

你通过使用现在已经过时的java.time框架的旧的遗留日期时间类来折磨自己。

TL;博士

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

java.time

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

要了解详情,请参阅Oracle Tutorial。并搜索Stack Overflow以获取许多示例和解释。

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

Instant

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

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

ZonedDateTime

通过ZoneId指定时区以获得ZonedDateTime

在夏天,Paris time比UTC早两个小时。所以在巴黎墙上的钟表上看到的同一时刻是8月1日凌晨2点而不是午夜。

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,Paris,Canary)代表历史上相同的同步时刻,即时间轴上的同一个点。每个都通过不同wall-clock time的镜头观看。

实时区域

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

Europe/ParisAtlantic/Canaryproper time zone names,格式为continent/region