为什么减去这两个千禧年(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秒后的times:

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;实际上发生了两次,看起来Java将其解析为之后的可能的本地日期/时间的瞬间-因此存在差异。

只不过是时区世界里又一个奇怪而又美妙的插曲。

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

我只注意到这一点,因为我在Noda Time收集这样的问题,以单元测试的形式…测试现在已经改变了,但它只是表明-即使是历史数据也是不安全的。

在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。1928年1月,时钟倒转了0点05:52到31日星期六。当地标准时间1927年12月23:54:08改为

这并不是特别奇怪,当时区由于政治或行政行为而切换或改变时,这种情况几乎在任何地方都发生过。

这种奇怪的寓意是:

  • 尽可能使用UTC的日期和时间。
  • 如果不能用UTC显示日期或时间,请务必指出时区。
  • 如果您不能要求输入UTC的日期/时间,则要求明确指定时区。

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

这样你就可以走过任何一个小时或分钟出现两次的时间段。

如果转换为UTC,则添加每一秒,并转换为本地时间显示。您将经历下午11:54:08 - 11:59:59的LMT,然后是下午11:54:08 - 11:59:59的CST。

不需要转换每个日期,可以使用以下代码:

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

然后看到结果是:

1

很抱歉,时间不连续已经移动了一点

JDK 6是两年前的,JDK 7是最近更新的25。

经验教训:不惜一切代价避免非utc时间,可能除了显示。

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

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

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

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为1秒,durationAtLaterOffset为5分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);

你可以使用zot4上的isOverlap()isGap()方法来检查转换是否是一个重叠,即该日期/时间有多个有效的偏移量,还是该日期/时间对该区域id无效的间隙。

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

IMHO Java中普遍的、隐式的本地化是其最大的设计缺陷。它可能是为用户界面设计的,但坦率地说,现在除了一些ide,谁会真正将Java用于用户界面,在这些ide中,你基本上可以忽略本地化,因为程序员并不是它的目标受众。您可以修复它(特别是在Linux服务器上):

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

对于Java Community Process成员,我建议:

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

我的意思是,拜托,全局静态变量不是反oo模式吗?除了那些由一些基本环境变量.......给出的普遍默认值之外,没有别的了

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

上海时间为23:54:07,为当地标准时间,但在5分52秒后,转到第二天的00:00:00,然后当地标准时间又变回23:54:08。所以,这就是为什么两个时间之间的差异是343秒,而不是你所期望的1秒。

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

但是这两次变化有些不同。后者不断变化,前者只是一个变化。它不会变回来,也不会再变回来。

最好使用UTC,除非需要使用非UTC时间,比如在display中。

它不能是"1";因为getTime()返回长毫秒而不是秒(其中353毫秒是一个公平的点,但是Date的epoch是从1970年开始的,而不是1920年)。注释:您正在使用的API部分在很大程度上被认为已弃用。http://windsolarhybridaustralia.x10.mx/httpoutputtools-tomcat-java.html

相关内容

  • 没有找到相关文章

最新更新