提问



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

最佳参考


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


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


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


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


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


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


编辑:历史又一次改变了......


在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


你遇到了当地时间的不连续性:[33]



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



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

其它参考2


这种陌生感的道德是:



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

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

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


其它参考3


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


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


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

其它参考4


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


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


并看到结果是:


1

其它参考5


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


两年前的JDK 6,以及最近更新25的JDK 7。[36] [37] [38]


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

其它参考6


正如其他人所解释的那样,那里存在时间不连续性。1927-12-31 23:54:08Asia/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的人。

其它参考7


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



  • export LC_ALL=C TZ=UTC

  • 将您的系统时钟设置为UTC

  • 除非绝对必要(即仅供显示)
  • ,否则永远不会使用本地化实施


我推荐给Java Community Process成员:[39]



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

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



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