提问人:Freewind 提问时间:7/27/2011 最后编辑:cellepoFreewind 更新时间:11/11/2023 访问量:794365
为什么减去这两个纪元毫次(1927 年)会得到一个奇怪的结果?
Why is subtracting these two epoch-milli Times (in year 1927) giving a strange result?
问:
如果我运行以下程序,该程序解析两个引用间隔 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
答:
这是 12 月 31 日上海的时区变更。
有关1927年在上海的详细信息,请参阅此页面。基本上在 1927 年底的午夜,时钟倒退了 5 分 52 秒。因此,“1927-12-31 23:54:08”实际上发生了两次,看起来 Java 正在将其解析为该本地日期/时间的较晚可能的时刻 - 因此存在差异。
只是时区这个经常奇怪而美妙的世界中的另一集。
如果使用 TZDB 的 2013a 版本重建,则原始问题将不再表现出完全相同的行为。在 2013a 中,结果为 358 秒,过渡时间为 23:54:03,而不是 23:54:08。
我之所以注意到这一点,是因为我在 Noda Time 中以单元测试的形式收集了这样的问题......测试现在已经改变,但它只是表明 - 即使是历史数据也不安全。
在 TZDB 2014f 中,更改的时间已移至 1900-12-31,现在仅更改了 343 秒(因此,如果您明白我的意思,则 和 之间的时间是 344 秒)。t
t+1
回答一个关于 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月, 00:00:00 时钟向后拨 0:05:52 小时到 31 日星期六。 1927 年 12 月 23:54:08 当地标准时间
这并不特别奇怪,而且由于政治或行政行为而改变时区,几乎在任何地方都发生过。
评论
这种奇怪现象的寓意是:
- 尽可能使用 UTC 格式的日期和时间。
- 如果无法以 UTC 显示日期或时间,请始终指明时区。
- 如果不能要求输入 UTC 格式的日期/时间,则需要明确指示的时区。
评论
增加时间时,应转换回 UTC,然后加减。仅使用本地时间进行显示。
这样,您将能够走过小时或分钟发生两次的任何时间段。
如果转换为 UTC,请添加每一秒,然后转换为本地时间进行显示。您将经历 LMT 晚上 11:54:08 - LMT 晚上 11:59:59,然后是 CST 晚上 11:54:08 - CST 晚上 11:59:59。
您可以使用以下代码来代替转换每个日期:
long difference = (sDt4.getTime() - sDt3.getTime()) / 1000;
System.out.println(difference);
然后看到结果是:
1
正如其他人所解释的那样,那里存在时间不连续性。at 有两种可能的时区偏移量,但 只有一种偏移量。因此,根据使用的偏移量,有 1 秒的差异或 5 分 53 秒的差异。1927-12-31 23:54:08
Asia/Shanghai
1927-12-31 23:54:07
这种偏移量的轻微变化,而不是我们习惯的通常的一小时夏令时(夏令时),有点掩盖了问题。
请注意,时区数据库的 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
此外,这两个偏移量是相同的:
// 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 广泛使用时处理此类问题,或者帮助那些使用 Java 7 并采用 JSR 310 向后移植的人处理此类问题。
很抱歉,时间的不连续性已经移动了一点
两年前的 JDK 6,以及最近更新的 JDK 7 25。
要吸取的教训:不惜一切代价避免使用非UTC时间,但显示除外。
评论
IMHO
Java 中无处不在的隐式本地化是其最大的设计缺陷。它可能用于用户界面,但坦率地说,今天谁真正将 Java 用于用户界面,除了一些 IDE,你基本上可以忽略本地化,因为程序员并不完全是它的目标受众。您可以通过以下方式修复它(尤其是在 Linux 服务器上):
- 出口
LC_ALL=C TZ=UTC
- 将系统时钟设置为 UTC
- 除非绝对必要,否则切勿使用本地化实现(即仅用于显示)
对于 Java Community Process 成员,我建议:
- make localized 方法,而不是默认方法,但要求用户显式请求本地化。
- 改用为 FIXED 默认值,因为这只是今天的默认值。没有理由做其他事情,除非你想产生这样的线程。
UTF-8/UTC
我的意思是,拜托,全局静态变量不是反面向对象模式吗?没有别的是由一些基本的环境变量给出的普遍默认值......
正如其他人所说,这是1927年上海的时间变化。
它在上海,以当地标准时间,但在 5 分 52 秒后,它变成了第二天的 ,然后当地标准时间又变回了 。所以,这就是为什么两次之间的差异是 343 秒,而不是您预期的 1 秒。23:54:07
00:00:00
23:54:08
在美国等其他地方,时间也可能搞砸。美国有夏令时。当夏令时开始时,时间将向前移动 1 小时。但过了一会儿,夏令时结束,它倒退 1 小时回到标准时区。因此,有时在比较美国的时间时,差异大约是几秒钟,而不是 1 秒。3600
但是,这两次更改有一些不同之处。后者不断变化,而前者只是一个变化。它没有变回或再次以相同的数量变化。
最好使用 UTC,除非需要使用非 UTC 时间,例如在显示中。
它永远不能是“1”作为结果,因为 getTime() 返回长毫秒而不是秒(其中 353 毫秒是一个公平的点,但 Date 的纪元从 1970 年开始,而不是 1920 年代)。 cmmnt:您正在使用的 API 部分在很大程度上被认为是已弃用的。http://windsolarhybridaustralia.x10.mx/httpoutputtools-tomcat-java.html
评论
💡额外信息
这是因为夏令时,正如其他人在他们的回答中提到的那样。我想指出有关日期和日历的其他事实,以向您展示:
🧠 为什么你永远不能假设日期
除了许多现有的日历和规则(如闰年和夏令时)外,纵观历史,日历还发生了许多不遵循任何顺序的变化。
例如,在 1752 年,英格兰及其殖民地使用的日历与欧洲大多数其他地区使用的公历不同步 11 天。🤷🏻♂️
因此,他们通过一系列步骤对其进行了更改:
- 1750 年 12 月 31 日之后是 1750 年 1 月 1 日(在“旧式”日历下,12 月是第 10 个月,1 月是第 11 个月)
- 1750 年 3 月 24 日之后是 1751 年 3 月 25 日(3 月 25 日是“旧式”年的第一天)
- 1751 年 12 月 31 日之后是 1752 年 1 月 1 日(从 3 月 25 日切换到 1 月 1 日作为一年的第一天)
- 1752 年 9 月 2 日之后是 1752 年 9 月 14 日(与公历相差 11 天)
- 更多信息请点击此处
另一个例子是基于月亮的日历,如回历日期,可以根据每个月的月相每年更改一次。
因此,有些日历甚至有超过10天的间隔,有些日历每年都有些不一致!在使用日历时,我们应该始终注意很多参数(如时区、区域设置、夏令时、日历标准本身等),并始终尝试使用经过测试且准确的日期系统。
⚠️ 更喜欢在线日历,因为一些日历(如伊朗回历日历)正在积极变化,而无法预测。
评论