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

如果我运行下面的程序,该程序将引用时间间隔为1秒的两个datestringparsing并比较它们:

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 (正如我期望的那样,在时间上的一秒差距),但是353

如果我将date更改为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 

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

有关1927年在上海的详情,请参阅此页面 。 基本上在1927年底的午夜,时钟回落了5分52秒。 所以“1927-12-31 23:54:08”实际上发生了两次,它看起来像Javaparsing它作为当地date/时间的后续可能的瞬间 – 因此是差异。

在经常奇怪和奇妙的时区世界的另一个情节。

编辑:停止按! 历史变化…

原来的问题将不再performance出相同的行为,如果重build版本2013a的TZDB 。 在2013a中,结果是358秒,转换时间是23:54:03而不是23:54:08。

我只注意到这一点,因为我在野田时间以unit testing的forms收集了这样的问题……testing现在已经改变了,但它只是表明 – 甚至没有历史数据是安全的。

编辑:历史已经改变了…

在TZDB2014f中,变化的时间已经到了1900-12-31,现在只有343秒的变化(所以tt+1之间的时间是344秒,如果你明白我的意思的话)。

编辑:要回答1900年的转换周围的一个问题…它看起来像Java时区实现对待所有的时区,只是在1900年开始之前的任何时刻,在他们的标准时间:

 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假设是一个有效的概念)的任何想法,所以其他库不需要引入这种人为的转换。

您遇到了当地时间中断 :

当地方标准时间即将到达星期天,1928年1月1日00时00分00秒钟被转换为0:05:52小时到星期六,1927年12月31日,23:54:08当地标准时间

这不是特别奇怪,因为政治或行政行为而改变或改变时区,这种情况几乎在任何时候都发生。

这种奇怪的道理是:

  • 尽可能使用UTC的date和时间。
  • 如果无法以UTC显示date或时间,请始终指定时区。
  • 如果您不需要UTC的inputdate/时间,则需要明确指定的时区。

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

通过这种方式,您将能够在几小时或几分钟发生两次的任何时间内行走。

如果您转换为UTC,请每秒添加一次,然后转换为本地时间以供显示。 您将经过11:54:08 pm LMT – 11:59:59 pm LMT ,然后11:54:08 pm CST – 11:59:59 pm CST。

而不是转换每个date,您使用下面的代码

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

而看到的结果是:

 1 

我很遗憾地说,但时间的不连续性已经有所改变了

两年前的JDK 6 ,以及最近的更新25中的JDK 7 。

吸取教训:不惜一切代价避免非UTC时间,除了可能的显示。

正如其他人所解释的那样,那里有时间的不连续性。 Asia/Shanghai 1927-12-31 23:54:08有两个可能的时区偏移量,但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将是五分五十三秒。

而且,这两个偏移量是相同的:

 // 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比较相同的问题,但是在这种情况下,产生较长散度的是较早的偏移量,更早的date有两个可能的偏移量。

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

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

您可以检查转换是否重叠 – 在这种情况下,该date/时间有多个有效偏移量 – 或者间隔 – 在这种情况下,该date/时间对该区域id无效 – 通过使用isOverlap()isGap()方法。

我希望这可以帮助人们在Java 8变得广泛使用时处理这类问题,或者帮助那些使用Java 7的人们采用JSR 310的backport。

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

  • 导出LC_ALL = C TZ = UTC
  • 将您的系统时钟设置为UTC
  • 除非绝对必要,否则绝对不要使用本地化的实现(例如,

对于Java社区进程成员,我build议:

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

我的意思是,来吧,是不是全球静态variables反OO模式? 没有其他的东西是由一些基本的环境variables给出的普遍的默认值…….