如果我运行以下程序,它解析引用时间间隔为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);
}
输出是:
为什么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
这是12月31日在上海的时区变化.
有关1927年在上海的详细信息,请参阅此页.基本上在1927年底的午夜,时钟倒流了5分52秒.因此"1927-12-31 23:54:08"实际上发生了两次,看起来Java正在将其解析为当地日期/时间的后续可能时刻 - 因此存在差异.
只是在经常奇怪和奇妙的时区世界的另一集.
编辑:停止按!历史变迁......
原来的问题将不再表现出不尽相同的行为,如果与重建版本2013a TZDB.在2013a中,结果为358秒,转换时间为23:54:03而不是23:54:08.
我只是注意到了这一点,因为我正在Noda Time中以单元测试的形式收集这样的问题......测试现在已经改变了,但它只是显示 - 甚至历史数据都不安全.
编辑:历史再次发生变化......
在TZDB 2014f中,更改的时间已经变为1900-12-31,现在只有343秒的变化(因此,如果您看到我的意思,则介于t
和之间的时间t+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
假设这是一个有效的概念),因此其他库不需要引入这种人工转换.
您遇到了当地时间不连续:
这并不是特别奇怪,并且由于政治或行政行为导致时区被切换或改变,因此在任何时候都发生过这种情况.
这种陌生感的道德是:
在递增时间时,您应该转换回UTC然后加或减.仅使用当地时间进行显示.
通过这种方式,您可以在几小时或几分钟发生两次的任何时段走过.
如果转换为UTC,请添加每秒,然后转换为本地时间进行显示.你会经历下午11时54分08秒LMT -下午11:59:59 LMT然后下午11时54分08秒CST -下午11:59:59 CST.
您可以使用以下代码,而不是转换每个日期
long difference = (sDt4.getTime() - sDt3.getTime()) / 1000;
System.out.println(difference);
并看到结果是:
1
我很遗憾地说,但是时间的不连续性已经发生了变化
两年前的JDK 6,以及最近在更新25中的JDK 7.
要学习的课程:不惜一切代价避免非UTC时间,除了显示.
正如其他人所解释的那样,那里有一段时间不连续.1927-12-31 23:54:08
at 有两种可能的时区偏移Asia/Shanghai
,但只有一种偏移1927-12-31 23:54:07
.因此,根据使用的偏移量,存在一秒差异或5分53秒差异.
这种轻微的偏移转移,而不是我们习惯的通常的一小时夏令时(夏令时),有点模糊了问题.
请注意,时区数据库的2013a更新在几秒钟之前移动了此不连续性,但效果仍然可以观察到.
java.time
Java 8上的新包使用更清楚,并提供处理它的工具.鉴于:
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:59
有1928-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 backport的Java 7的人.
恕我直言,Java中普遍存在的隐式本地化是其最大的单一设计缺陷.它可能适用于用户界面,但坦率地说,现在谁真正使用Java作为用户界面,除了一些IDE,你基本上可以忽略本地化,因为程序员并不是它的目标受众.您可以通过以下方式修复它(特别是在Linux服务器上):
对于Java Community Process成员,我建议:
我的意思是,来吧,不是全局静态变量是反OO模式吗?没有其他东西是由一些基本的环境变量给出的那些普遍的默认值.......
就像其他人所说的,这是1927年在上海的时间更改。
基本上,23:54:07
上海时间是当地的标准时间,过了一会儿又转到了第二天的时间00:00:00
。但随后又切换回23:54:08
当地标准时间。因此,这就是为什么时差为343秒而不是1秒的原因。
这也可能与美国等其他地方搞混。在美国,他们有夏令时。夏令时开始时,时间会提前1个小时。但是过了一会儿,夏令时结束了,它向后退了1个小时回到标准时区。因此,有时在比较美国的时间时,差异大约是3600
几秒钟而不是1秒。
最好在时间不改变的情况下使用UTC,除非需要使用非UTC时间(如在显示中)。