为什么减去这两次(1927 年)会得到一个奇怪的结果?

如果我运行以下程序,它会解析两个相隔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(正如我从时代的一秒钟差异中所期望的那样),而是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
775329 次浏览

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

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

只是时区这个经常怪异而美妙的世界中的又一集。

编辑:停止新闻!历史改变…

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

我之所以注意到这一点,是因为我正在野田时代收集这样的问题,以单元测试的形式……测试现在已经改变了,但它只是表明——甚至历史数据也不安全。

编辑:历史再次改变…

在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认为是一个有效的概念),所以其他库不需要引入这种人工转换。

你遇到了局部时间不连续性

1928年1月1日星期日,当地标准时间就要到了,00:00:00时钟向后拨0:05:52小时到31日星期六。1927年12月当地标准时间23:54:08

这并不是特别奇怪,随着时区因政治或行政行为而切换或改变,几乎在任何地方都发生过。

这种奇怪的寓意是:

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

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

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

如果转换为UTC,请添加每秒,然后转换为本地时间以显示。您将经历11:54:08 p. m.LMT-11:59:59 p. m. LMT,然后是11:54:08 p. m.科技委-11:59:59 p. m. CST。

您可以使用以下代码来代替转换每个日期:

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

然后看到结果是:

1

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

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

请注意,时区数据库的2013a更新将此不连续性提前了几秒钟,但效果仍然可以观察到。

Java8上的新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将是1秒,而durationAtLaterOffset将是5分53秒。

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

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

但这两个是不同的:

// +08:05:52ZoneOffset zo4Earlier = zdt4.withEarlierOffsetAtOverlap().getOffset();
// +08:00ZoneOffset zo4Later = zdt4.withLaterOffsetAtOverlap().getOffset();

您可以看到将1927-12-31 23:59:591928-01-01 00:00:00进行比较的相同问题,但是,在这种情况下,产生较长散度的是较早的偏移量,并且具有两个可能偏移量的是较早的日期。

另一种方法是检查是否有过渡正在进行。我们可以这样做:

// NullZoneOffsetTransition zot3 = shanghai.getRules().getTransition(ld3.toLocalDateTime);
// An overlap transitionZoneOffsetTransition zot4 = shanghai.getRules().getTransition(ld3.toLocalDateTime);

您可以通过使用zot4上的isOverlap()isGap()方法来检查转换是否是重叠,其中该日期/时间有多个有效偏移量,或者该日期/时间对该区域id无效的间隙。

我希望这有助于人们在Java8变得广泛可用时处理此类问题,或者对于那些使用Java7并采用JSR 310反向端口的人。

我很抱歉地说,但是时间不连续性已经开始了

JDK6两年前,JDK7最近在更新25

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

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

  • 导出LC_ALL=C TZ=UTC
  • 将系统时钟设置为UTC
  • 除非绝对必要(即仅用于显示),否则永远不要使用本地化实现

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

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

我的意思是,拜托,全局静态变量不是反OO模式吗?没有别的是一些基本的环境变量给出的普遍默认值。

正如其他人所说,这是1927年上海的时间变化。

在上海,当地标准时间是0,但是在5分52秒之后,它变成了第二天的1,然后当地标准时间又回到了2。所以,这就是为什么两个时间之间的差异是343秒,而不是你想象的1秒。

在美国等其他地方,时间也会混乱。美国有夏令时。当夏令时开始时,时间向前1小时。但过了一会儿,夏令时结束,它向后1小时回到标准时区。所以有时在美国比较时间时,差异大约是0秒而不是1秒。

但这两次变化有一点不同。后者是连续变化的,前者只是一个变化。它没有变回来,也没有再变回来。

最好使用UTC,除非需要使用非UTC时间,如显示。

结果永远不可能是“1”,因为getTime()返回长毫秒而不是秒(其中353毫秒是一个公平的点,但Date的纪元从1970年开始,而不是1920年)。cmmnt:您正在使用的API部分在很大程度上被认为已弃用。http://windsolarhybridaustralia.x10.mx/httpoutputtools-tomcat-java.html