1927年时间戳相减结果异常原因解析
技术背景
在处理时间相关的计算时,有时会遇到一些奇怪的结果。例如,在Java中对1927年特定时间戳进行相减操作,可能会得到与预期不符的结果。这主要是由于时区变更、夏令时调整以及历史上的历法变更等因素导致的时间不连续性问题。
实现步骤
1. 了解1927年上海时区变更
在1927年12月31日午夜,上海的时钟回调了5分52秒。这意味着“1927 - 12 - 31 23:54:08”这个时间点实际上出现了两次。Java在解析这个本地日期/时间时,会将其解析为较晚的那个瞬间。
2. 不同版本TZDB的影响
- 2013a版本:原始问题的表现会有所不同,结果变为358秒,过渡时间为23:54:03。
- 2014f版本:变更时间移到了1900 - 12 - 31,时间变化为343秒。
3. Java对1900年之前时间的处理
Java的时区实现将1900年UTC开始之前的所有时区都视为标准时间。可以通过以下代码验证:
1 2 3 4 5 6 7 8 9 10 11 12 13
| 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机器上运行这段代码不会有输出。
4. Java 8中java.time
包的处理
使用java.time
包可以更清晰地处理时间不连续性问题:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| import java.time.Duration; import java.time.LocalDateTime; import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatterBuilder; import java.time.zone.ZoneOffsetTransition;
public class TimeDiscontinuityExample { public static void main(String[] args) { 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());
System.out.println("Earlier offset duration: " + durationAtEarlierOffset); System.out.println("Later offset duration: " + durationAtLaterOffset);
ZoneOffsetTransition zot3 = shanghai.getRules().getTransition(zdt3.toLocalDateTime()); ZoneOffsetTransition zot4 = shanghai.getRules().getTransition(zdt4.toLocalDateTime());
System.out.println("Transition for zdt3: " + zot3); System.out.println("Transition for zdt4: " + zot4); } }
|
最佳实践
- 尽量使用UTC时间:在进行时间计算和存储时,优先使用UTC时间,只有在显示时才转换为本地时间。
- 明确时区信息:如果无法使用UTC时间,在输入和输出时间时,始终明确指定时区。
- 使用可靠的日期系统:选择经过测试和验证的日期处理库,避免自行处理复杂的时间逻辑。
常见问题
1. 时间不连续性导致计算结果异常
由于时区变更、夏令时调整等原因,某些时间点可能会出现不连续性,导致时间计算结果与预期不符。可以通过明确指定时区和使用java.time
包中的相关方法来处理。
2. 不同版本的时区数据库影响
不同版本的时区数据库(如TZDB)可能会对历史时间的处理产生不同的结果。在处理时间时,需要确保使用的时区数据库版本一致。
3. 历史历法变更问题
历史上的历法变更(如1752年英国及其殖民地的历法调整)会导致日期不连续。在处理历史日期时,需要考虑这些因素。