首页 文章

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

提问于
浏览
6203

如果我运行以下程序,它解析引用时间间隔为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);
}

The output is:

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 version:

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

8 回答

  • 196

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

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

    只是在经常奇怪和精彩的时区世界的另一集 .

    EDIT: 停止按!历史变迁......

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

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

    EDIT: 历史再次改变......

    在TZDB 2014f中,更改的时间已经变为1900-12-31,现在只有343秒的变化(所以 tt+1 之间的时间是344秒,如果你明白我的意思) .

    EDIT: 要回答关于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本身有一些数据早于此,并且不依赖于"fixed"标准时间的任何想法(这是 getRawOffset 假设是一个有效的概念),因此其他库不需要引入这种人工转换 .

  • 1498

    你遇到了local time discontinuity

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

    这并不是特别奇怪,并且由于政治或行政行为导致时区被切换或改变,因此在任何时候都发生过这种情况 .

  • 9994

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

    这种轻微的偏移转移,而不是我们习惯的通常的一小时夏令时(夏令时),有点模糊了问题 .

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

    Java 8上的新 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 将是一秒钟,而 durationAtLaterOffset 将是五分53秒 .

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

    // 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:591928-01-01 00:00:00 进行比较时遇到的问题相同,但在这种情况下,它是产生较长发散的较早偏移,而且它是具有两个可能偏移的较早日期 .

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

    // Null
    ZoneOffsetTransition zot3 = shanghai.getRules().getTransition(ld3.toLocalDateTime);
    
    // An overlap transition
    ZoneOffsetTransition zot4 = shanghai.getRules().getTransition(ld3.toLocalDateTime);
    

    您可以检查转换是否是重叠 - 在这种情况下,该日期/时间有多个有效偏移量 - 或者间隙 - 在这种情况下,日期/时间对于该区域ID无效 - 使用 isOverlap()isGap() zot4 上的方法 .

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

  • 144

    这种陌生感的道德是:

    • 尽可能使用UTC中的日期和时间 .

    • 如果无法以UTC格式显示日期或时间,请始终指明时区 .

    • 如果您不能要求以UTC格式输入日期/时间,则需要明确指定的时区 .

  • 175

    您可以使用以下代码,而不是转换每个日期

    long difference = (sDt4.getTime() - sDt3.getTime()) / 1000;
    System.out.println(difference);
    

    并看到结果是:

    1
    
  • 596

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

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

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

  • 330

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

    • export LC_ALL = C TZ = UTC

    • 将系统时钟设置为UTC
      除非绝对必要,否则

    • 永远不会使用本地化实现(即仅用于显示)

    我建议Java Community Process成员:

    • 使本地化方法不是默认方法,但要求用户明确请求本地化 .

    • 使用UTF-8 / UTC作为FIXED默认值,因为这只是今天的默认值 . 除非您想生成这样的线程,否则没有理由做其他事情 .

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

  • 277

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

    JDK 6两年前,JDK 7刚刚update 25 .

    需要学习的经验:不惜一切代价避免非UTC时间,可能用于显示 .

相关问题