为什么减去这两次(在1927年)给出一个奇怪的结果?

时间:2011-07-27 08:15:58

标签: java date timezone

如果我运行以下程序,该程序会解析引用时间间隔为1秒的两个日期字符串并进行比较:

public static void main(String[] args) throws ParseException {
    SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  
    String str3 = "1927-12-31 23:54:07";  
    String str4 = "1927-12-31 23:54:08";  
    Date sDt3 = sf.parse(str3);  
    Date sDt4 = sf.parse(str4);  
    long ld3 = sDt3.getTime() /1000;  
    long ld4 = sDt4.getTime() /1000;
    System.out.println(ld4-ld3);
}

输出为:

  

353

为什么ld4-ld3不是1(正如我所期望的那样,时间间隔为1秒),但是353

如果我将日期更改为1秒后的时间:

String str3 = "1927-12-31 23:54:08";  
String str4 = "1927-12-31 23:54:09";  

然后ld4-ld3将为1


Java版本:

java version "1.6.0_22"
Java(TM) SE Runtime Environment (build 1.6.0_22-b04)
Dynamic Code Evolution Client VM (build 0.2-b02-internal, 19.0-b04-internal, mixed mode)

Timezone(`TimeZone.getDefault()`):

sun.util.calendar.ZoneInfo[id="Asia/Shanghai",
offset=28800000,dstSavings=0,
useDaylight=false,
transitions=19,
lastRule=null]

Locale(Locale.getDefault()): zh_CN

11 个答案:

答案 0 :(得分:10331)

这是12月31日在上海的时区变化。

有关1927年上海的详情,请参阅this page。基本上在1927年底的午夜,时钟倒流了5分52秒。所以“1927-12-31 23:54:08”实际上发生了两次,看起来Java正在将其解析为以后可能的本地日期/时间 - 因此差异。

在经常奇怪而精彩的时区世界中又是另一集。

编辑:停止按!历史变迁......

如果使用版本2013a TZDB重建,原始问题将不再表现出完全相同的行为。在2013a,结果将是358秒,过渡时间为23:54:03而不是23:54:08。

我之所以注意到这一点,是因为我在Noda Time中以unit tests的形式收集了这样的问题......测试现在已经改变,但它只是显示 - 甚至历史数据都没有安全

编辑:历史记录再次发生变化......

在TZDB 2014f中,更改时间已移至1900-12-31,现在只需343秒更改(因此tt+1之间的时间为344秒,如果你明白我的意思了。

编辑:要回答有关1900年转换的问题...看起来Java时区实现将所有时区视为仅仅处于标准时间在1900年UTC开始之前的任何瞬间:

import java.util.TimeZone;

public class Test {
    public static void main(String[] args) throws Exception {
        long startOf1900Utc = -2208988800000L;
        for (String id : TimeZone.getAvailableIDs()) {
            TimeZone zone = TimeZone.getTimeZone(id);
            if (zone.getRawOffset() != zone.getOffset(startOf1900Utc - 1)) {
                System.out.println(id);
            }
        }
    }
}

上面的代码在我的Windows机器上没有输出。因此,任何在1900年开始时具有除标准偏移之外的任何偏移的时区都将其视为过渡。 TZDB本身有一些数据早于此,并且不依赖于“固定”标准时间的任何想法(这是getRawOffset假设为有效概念),因此其他库无需引入此人工过渡。

答案 1 :(得分:1541)

您遇到了local time discontinuity

  

当地方标准时间即将到达星期日,1928年1月1日,   00:00:00时钟倒退0:05:52至星期六,31。   1927年12月,当地标准时间23:54:08而不是

这并不是特别奇怪,因为时区因政治或行政行为而被切换或改变,所以在任何时候都发生过这种情况。

答案 2 :(得分:621)

这种陌生感的道德是:

  • 尽可能使用UTC中的日期和时间。
  • 如果您无法以UTC格式显示日期或时间,请始终指明时区。
  • 如果您不能要求以UTC格式输入日期/时间,则需要明确指定的时区。

答案 3 :(得分:345)

当递增时间时,您应该转换回UTC然后加或减。仅使用当地时间进行显示。

通过这种方式,您可以走过几小时或几分钟发生两次的任何时期。

如果您转换为UTC,请添加每秒,然后转换为本地时间进行显示。你会经过晚上11:54:08。 LMT - 晚上11:59:59 LMT然后是晚上11:54:08 CST - 晚上11:59:59 CST。

答案 4 :(得分:290)

您可以使用以下代码

,而不是转换每个日期
long difference = (sDt4.getTime() - sDt3.getTime()) / 1000;
System.out.println(difference);

看到结果是:

1

答案 5 :(得分:210)

我很遗憾地说,但是时间的不连续性已经发生了一些变化

两年前的

JDK 6以及最近JDK 7update 25

要学习的课程:不惜一切代价避免非UTC时间,除了可能用于显示。

答案 6 :(得分:187)

正如其他人所解释的那样,那里有一段时间不连续。 1927-12-31 23:54:08的{​​{1}}有两种可能的时区偏移,但Asia/Shanghai只有一个偏移。因此,根据使用的偏移量,有一秒差异或5分53秒的差异。

这种偏移的轻微转变,而不是我们习惯的通常的一小时夏令时(夏令时),可以稍微掩盖这个问题。

请注意,时区数据库的2013a更新在几秒钟之前移动了这种不连续性,但效果仍然可以观察到。

Java 8上的新1927-12-31 23:54:07包让我们更清楚地看到它,并提供处理它的工具。给出:

java.time

然后DateTimeFormatterBuilder dtfb = new DateTimeFormatterBuilder(); dtfb.append(DateTimeFormatter.ISO_LOCAL_DATE); dtfb.appendLiteral(' '); dtfb.append(DateTimeFormatter.ISO_LOCAL_TIME); DateTimeFormatter dtf = dtfb.toFormatter(); ZoneId shanghai = ZoneId.of("Asia/Shanghai"); String str3 = "1927-12-31 23:54:07"; String str4 = "1927-12-31 23:54:08"; ZonedDateTime zdt3 = LocalDateTime.parse(str3, dtf).atZone(shanghai); ZonedDateTime zdt4 = LocalDateTime.parse(str4, dtf).atZone(shanghai); Duration durationAtEarlierOffset = Duration.between(zdt3.withEarlierOffsetAtOverlap(), zdt4.withEarlierOffsetAtOverlap()); Duration durationAtLaterOffset = Duration.between(zdt3.withLaterOffsetAtOverlap(), zdt4.withLaterOffsetAtOverlap()); 将是一秒,而durationAtEarlierOffset将是五分53秒。

此外,这两个偏移是相同的:

durationAtLaterOffset

但这两者是不同的:

// Both have offsets +08:05:52
ZoneOffset zo3Earlier = zdt3.withEarlierOffsetAtOverlap().getOffset();
ZoneOffset zo3Later = zdt3.withLaterOffsetAtOverlap().getOffset();

您可以看到将// +08:05:52 ZoneOffset zo4Earlier = zdt4.withEarlierOffsetAtOverlap().getOffset(); // +08:00 ZoneOffset zo4Later = zdt4.withLaterOffsetAtOverlap().getOffset(); 1927-12-31 23:59:59进行比较时遇到同样的问题,但在这种情况下,它是产生较长分歧的较早偏移量,而且是较早的日期,它有两个可能的偏移量

另一种解决方法是检查是否正在进行转换。我们可以这样做:

1928-01-01 00:00:00

您可以检查转换是否是重叠 - 在这种情况下,该日期/时间有多个有效偏移量 - 或者是间隙 - 在这种情况下,日期/时间对于该区域ID无效 - 通过使用// Null ZoneOffsetTransition zot3 = shanghai.getRules().getTransition(ld3.toLocalDateTime); // An overlap transition ZoneOffsetTransition zot4 = shanghai.getRules().getTransition(ld3.toLocalDateTime); 上的isOverlap()isGap()方法。

我希望这可以帮助人们在Java 8广泛使用后处理这类问题,或者使用采用JSR 310反向端口的Java 7的人。

答案 7 :(得分:154)

恕我直言,Java中普遍存在的隐式本地化是其最大的单一设计缺陷。它可能适用于用户界面,但坦率地说,现在谁真正使用Java作为用户界面,除了一些IDE基本上可以忽略本地化,因为程序员并不是它的目标受众。您可以通过以下方式修复它(尤其是在Linux服务器上):

  • export LC_ALL = C TZ = UTC
  • 将您的系统时钟设置为UTC
  • 除非绝对必要(即仅供显示)
  • ,否则永远不会使用本地化实施

我建议的Java Community Process成员:

  • 使本地化方法不是默认方法,但要求用户明确请求本地化。
  • 使用UTF-8 / UTC作为 FIXED 默认值,因为它只是今天的默认值。除非你想生成这样的线程,否则没有理由做其他事情。

我的意思是,来吧,不是全局静态变量是反OO模式吗?没有别的东西是由一些基本的环境变量给出的普遍违约.......

答案 8 :(得分:6)

发生这种情况是因为这是1927年12月31日在上海的时区规则。 如果您转到this page并选择“ 1900-1924年时区更改”,您会看到1900年的日期和时间是“整个时间UTC +8:05:43小时”。

因此,Java只是显示为该时区配置的那年的时间。

如果您将默认时区更改为香港,它将显示正确的结果。

TimeZone.setDefault(TimeZone.getTimeZone("Asia/Hong_Kong"));

请注意,时区从CST(中国标准时间, HKT(相当于亚洲/上海的3个字母)(3个字母的名称) 香港的时区。

但是更改时区不是一个好的解决方案。因此,请尽可能使用系统UTC时间。转换后,它将始终给出本地时间。

答案 9 :(得分:4)

正如其他人提到的那样,这是1927年在上海的时间变迁。

基本上,上海的23:54:07是当地的标准时间,此后不久,它又回到了第二天的00:00:00。但随后在当地标准时间切换回23:54:08。因此,这就是为什么时差为343秒而不是1秒的原因。

这也可能使美国等其他地方陷入困境。在美国,他们有夏令时。夏令时开始时,时间会提前1个小时。但是过了一会儿,夏令时结束了,它向后退了1个小时回到标准时区。因此,有时在比较美国的时间时,差异约为3600秒,而不是1秒。

最好在时间不变的情况下使用UTC,除非需要使用非UTC时间(如显示中那样)。

答案 10 :(得分:1)

为避免这个问题,当增加时间时,您应该转换回 UTC,然后添加或减去。

通过这种方式,您将能够遍历任何小时或分钟发生两次的时期。

如果转换为UTC,则加上每一秒,并转换为本地时间显示。你会经历晚上 11:54:08 LMT - 晚上 11:59:59 LMT,然后是晚上 11:54:08 CST - 晚上 11:59:59科技委。