SimpleDateFormat - 时区不正确

3

我在Java中解析时间戳时遇到了问题。

我希望两个时间戳都在同一个时区(CET)。

    SimpleDateFormat sdaf = new SimpleDateFormat ("dd.MM.yyyy HH:mm:ss");

    String str = "30.03.2013 06:00:00";
    sdaf.setTimeZone (TimeZone.getTimeZone ("CET"));
    java.util.Date dat = sdaf.parse (str);
    System.out.println (str + " = " + dat);

    str = "31.03.2013 05:00:00";
    sdaf.setTimeZone (TimeZone.getTimeZone ("CET"));
    dat = sdaf.parse (str);
    System.out.println (str + " = " + dat);

但事实并非如此-请查看输出。
30.03.2013 06:00:00 = Sat Mar 30 06:00:00 CET 2013
31.03.2013 05:00:00 = Sun Mar 31 05:00:00 CEST 2013

编辑:

如果我把CET改成GMT+1,就会得到这个结果。

 03.2013 06:00:00 = Sat Mar 30 06:00:00 CET 2013
 31.03.2013 05:00:00 = Sun Mar 31 06:00:00 CEST 2013

看起来是正确的。但为什么CET没有起作用呢?

UTC+1产生了:

30.03.2013 06:00:00 = Sat Mar 30 07:00:00 CET 2013
31.03.2013 05:00:00 = Sun Mar 31 07:00:00 CEST 2013

GMT+1与其他时区有何不同?


2
我建议您避免使用SimpleDateFormat类。它不仅过时,而且出了名的麻烦。今天我们有更好的选择java.time,现代Java日期和时间API和它的DateTimeFormatter。我还建议您避免依赖三个和四个字母的时区缩写。它们没有标准化,因此存在歧义,其中许多,包括CET,都不是真正的时区,如AxelH的答案中已经解释过。 - Ole V.V.
3个回答

4
由于该时间不再在该时区上,因此无法获取CET时区的31.03.2013 05:00:00。要理解,只需检查两个时区的名称即可:
  • CET:中欧时间(UTC+1GMT+1
  • CEST:中欧夏令时(UTC+2GMT+2
这是在2013年3月31日02:00:00发生的节约日光时间。因此,对于第二个日期,您无法获得CET时区。
如果解析31.03.2013 02:00:00,则会得到:

31.03.2013 02:00:00 = Sun Mar 31 03:00:00 CEST 2013

因为在那天的02:00:00发生了夏令时改变,时间变为03:00:00。
您可以使用TimeZone.inDaylightTime(Date)来检查这一点。
String str = "30.03.2013 06:00:00";
java.util.Date dat = sdaf.parse (str);
System.out.println (str + " = " + dat);
System.out.println("SDT : " + TimeZone.getTimeZone("CET").inDaylightTime(dat));

30.03.2013 06:00:00 = Sat Mar 30 06:00:00 CET 2013
SDT : false

30.03.2013 06:00:00 = 2013年3月30日 星期六 06:00:00 中欧标准时间
SDT:false
str = "31.03.2013 02:00:00";
dat = sdaf.parse (str);
System.out.println (str + " = " + dat);
System.out.println("SDT : " + TimeZone.getTimeZone("CET").inDaylightTime(dat));

31.03.2013 02:00:00 = Sun Mar 31 03:00:00 CEST 2013
SDT : true

由于CETUTC+1GMT+1相同,CEST变为UTC+2GMT+2,当您将日期强制设置为GMT+1时,您会得到等效于CET但不考虑SDT参数的结果。

注意:这是LocalDateTime在大多数处理中不考虑TimeZone的原因之一。


2
AxelH的回答是正确的。因此,我认为你在3月31日并不需要中欧(标准)时间来表示日期和时间。
我想提出另外三点:
  • 永远不要依赖任何三个或四个字母的时区缩写,如CET。
  • 如果你真的需要中欧时间,那么是可以的,因为两个非洲国家全年都使用CET,分别是阿尔及利亚和突尼斯。
  • 避免使用过时的SimpleDateFormat、TimeZone和Date,而使用现代Java日期和时间API——java.time。
所以我的代码建议是:
    ZoneId cetAllYear = ZoneId.of("Africa/Algiers");
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd.MM.uuuu HH:mm:ss");
    DateTimeFormatter demoFormatter 
            = DateTimeFormatter.ofPattern("EEE MMM dd HH:mm:ss z yyyy", Locale.ROOT);

    String str = "31.03.2013 05:00:00";
    ZonedDateTime dateTime = LocalDateTime.parse(str, formatter).atZone(cetAllYear);
    System.out.println (str + " = " + dateTime 
                            + " = " + dateTime.format(demoFormatter));

输出:

2013年3月31日05:00:00 = 2013-03-31T05:00+01:00[非洲/阿尔及尔] = 2013年3月31日星期日05:00:00 CET

同样可以使用Africa/Tunis时区。

避免使用三个字母的时区缩写。中欧时间是欧洲许多时区在一年中使用5个月的标准时间的通用名称。因此,在某种程度上,它只是半个时区,在另一方面,它是许多时区,你不知道你得到哪一个。而且,当你给出一个落在夏令时(一年中的DST时间)的日期时间时,更是如此。很多三个和四个字母的缩写是有歧义的。相反,将时区表示为例如Europe/Rome或Africa/Tunis,作为region/city

避免使用SimpleDateFormat及其过时的朋友们。这个类极具问题性。java.time要好得多。

链接


1
你可以使用Java 8,它比这个更清晰简单。
DateTimeFormatter parse = DateTimeFormatter.ofPattern("dd.MM.yyyy hh:mm:ss.XXX");
LocalDate localDate = LocalDate.of(2013,3,30);
LocalTime localTime = LocalTime.of(6,0);

LocalDateTime dateTime = LocalDateTime.of(localDate, localTime);
ZonedDateTime zonedDateTime = dateTime.atZone(ZoneId.of("CET"));

System.out.println(zonedDateTime.format(parse));

输出:30.03.2013 06:00:00.+01:00


我正在考虑为java.time的有用使用点赞,但犹豫不决,因为您仍在使用不幸的三个字母时区缩写CET。这些缩写通常是模棱两可的,而且经常不是真正的时区。请避免使用它们。 - Ole V.V.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接