如果我运行以下程序,它会解析两个引用时间为 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 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 年的详情见this page。基本上在 1927 年底的午夜,时钟倒退了 5 分 52 秒。因此,“1927-12-31 23:54:08”实际上发生了两次,看起来 Java 将其解析为该本地日期/时间的 later 可能瞬间 - 因此存在差异。
只是时区经常奇怪而美妙的世界中的另一个插曲。
编辑:停止新闻!历史变迁...
如果使用 TZDB 的 2013a 版本进行重建,原始问题将不再表现出完全相同的行为。在 2013a 中,结果将为 358 秒,过渡时间为 23:54:03,而不是 23:54:08。
我之所以注意到这一点,是因为我在 Noda Time 以 unit tests 的形式收集类似的问题......现在测试已经改变,但它只是表明 - 甚至历史数据都不是安全的。
编辑:历史再次改变......
在 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
假定为有效的概念),因此其他库不需要引入这种人为的转换.
您遇到了 local time discontinuity:
当当地标准时间即将到达 1928 年 1 月 1 日星期日 00:00:00 时,时钟倒转 0:05:52 小时至 1927 年 12 月 31 日星期六 23:54:08 当地标准时间
这并不是特别奇怪,并且由于政治或行政行为导致时区切换或更改,几乎无处不在。
这种奇怪的寓意是:
尽可能使用 UTC 中的日期和时间。
如果不能以 UTC 显示日期或时间,请始终指明时区。
如果您不能要求输入 UTC 日期/时间,则需要明确指示的时区。
您可以使用以下代码,而不是转换每个日期:
long difference = (sDt4.getTime() - sDt3.getTime()) / 1000;
System.out.println(difference);
然后看到结果是:
1
正如其他人所解释的那样,那里存在时间不连续性。 Asia/Shanghai
处的 1927-12-31 23:54:08
有两个可能的时区偏移,但 1927-12-31 23:54:07
只有一个偏移。因此,根据使用的偏移量,有 1 秒的差异或 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
将是 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: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()
方法3}。
我希望这可以帮助人们在 Java 8 广泛使用后处理此类问题,或者帮助那些使用 Java 7 并采用 JSR 310 反向移植的人。
IMHO
Java 中普遍存在的隐式本地化是其最大的设计缺陷。它可能是为用户界面设计的,但坦率地说,除了一些 IDE,你基本上可以忽略本地化,因为程序员并不完全是它的目标受众,现在谁真正将 Java 用于用户界面。您可以通过以下方式修复它(尤其是在 Linux 服务器上):
导出 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 小时。但过了一会儿,夏令时结束,它倒退 1 小时回到标准时区。所以有时在比较美国的时间时,差异大约是 3600
秒而不是 1 秒。
但是这两次变化有一些不同之处。后者不断变化,而前者只是变化。它没有变回或再次改变相同的数量。
除非需要在显示中使用非 UTC 时间,否则最好使用 UTC。
为避免该问题,在增加时间时应转换回 UTC,然后加或减。
这样,您将能够走过小时或分钟发生两次的任何时间段。
如果您转换为 UTC,则添加每秒,然后转换为本地时间以进行显示。您将经历 11:54:08 pm LMT - 11:59:59 pm LMT,然后 11:54:08 pm CST - 11:59:59 pm CST。
结果它永远不会是“1”,因为 getTime() 返回长毫秒而不是秒(其中 353 毫秒是一个公平的点,但 Date 的纪元始于 1970 年而不是 1920 年)。 cmmnt:您正在使用的 API 部分在很大程度上被认为已弃用。 http://windsolarhybridaustralia.x10.mx/httpoutputtools-tomcat-java.html
不定期副业成功案例分享