Java中1927年时间戳相减结果异常原因解析

createh51个月前 (04-28)技术教程11

技术背景

在Java编程中,处理日期和时间是常见的任务。通常,我们会使用SimpleDateFormat类来解析日期字符串,并使用Date类的getTime()方法获取时间戳(自1970年1月1日午夜以来的毫秒数)。然而,在某些特殊情况下,时间戳的计算可能会出现意想不到的结果。

实现步骤

问题代码示例

以下是一段示例代码,用于解析两个日期字符串,并计算它们的时间戳差值:

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class TimeStampSubtraction {
    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);
    }
}

预期与实际结果

预期结果:由于两个日期字符串表示的时间相差1秒,所以ld4 - ld3应该为1。 实际结果:程序输出为353,与预期结果不符。

核心代码及解析

异常原因

这个异常结果是由于时区变化导致的。在1927年12月31日午夜,上海的时钟回拨了5分52秒。因此,“1927-12-31 23:54:08”这个时间点实际上出现了两次,而Java在解析时可能选择了较晚的那个时间点,从而导致时间戳差值异常。

验证时区变化

可以通过以下代码验证在1900年之前Java时区实现将所有时区视为标准时间:

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机器上运行上述代码,不会有任何输出,这表明Java在1900年之前将所有时区视为标准时间。

Java 8解决方案

Java 8引入了新的日期和时间API(java.time包),可以更清晰地处理这种情况:

import java.time.Duration;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;

public class Java8TimeZoneExample {
    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.getSeconds());
        System.out.println("Later offset duration: " + durationAtLaterOffset.getSeconds());

        ZoneOffset zo3Earlier = zdt3.withEarlierOffsetAtOverlap().getOffset();
        ZoneOffset zo3Later = zdt3.withLaterOffsetAtOverlap().getOffset();
        System.out.println("zdt3 earlier offset: " + zo3Earlier);
        System.out.println("zdt3 later offset: " + zo3Later);

        ZoneOffset zo4Earlier = zdt4.withEarlierOffsetAtOverlap().getOffset();
        ZoneOffset zo4Later = zdt4.withLaterOffsetAtOverlap().getOffset();
        System.out.println("zdt4 earlier offset: " + zo4Earlier);
        System.out.println("zdt4 later offset: " + zo4Later);
    }
}

通过上述代码,可以更清晰地看到不同偏移量下的时间差。

最佳实践

  • 使用UTC时间:在处理日期和时间时,尽量使用UTC时间,避免时区问题。只有在需要显示给用户时,才将UTC时间转换为本地时间。
  • 明确指定时区:在解析日期字符串时,明确指定时区,避免使用默认时区。
  • 使用新的日期和时间API:Java 8及以上版本建议使用java.time包中的日期和时间API,它们提供了更丰富的功能和更好的时区支持。

常见问题

为什么会出现时区变化?

时区变化通常是由于政治、行政或天文原因导致的。例如,为了节约能源,一些地区会实行夏令时;为了统一时间标准,一些地区会调整时区。

如何避免时区问题?

  • 尽量使用UTC时间进行内部计算和存储。
  • 在与用户交互时,明确告知用户使用的时区。
  • 使用支持时区的日期和时间API,如Java 8的java.time包。

不同版本的Java对时区的处理有差异吗?

是的,不同版本的Java对时区的处理可能会有所不同。例如,不同版本的时区数据库(TZDB)可能会包含不同的时区信息,导致时间戳计算结果不同。因此,在处理日期和时间时,建议使用最新版本的Java和时区数据库。

相关文章

java:日期时间操作方法(java的日期函数)

Java时间工具类TimeUtil详解:让日期操作变得简单高效在Java开发中,日期时间处理是一个高频且容易出错的需求。为了简化开发流程,提高代码复用性,写了些常见的日期时间操作的工具类。本文将详细介...

疯传!Java 日期时间底层逻辑大揭秘,看完直接拿捏面试官挖的坑!

作为Java开发者,处理日期和时间是日常工作中的常见任务。本文将带你全面掌握Java日期时间处理的方方面面,从基础概念到高级应用,从常见陷阱到最佳实践。一、Java日期时间API演进史让我们先通过一个...

Java中玩转定时任务调度:让时间为你打工

Java中玩转定时任务调度:让时间为你打工Java作为一个功能强大的编程语言,提供了多种方式来实现定时任务调度。无论你是想执行一个简单的每日问候任务,还是需要处理复杂的数据处理任务,Java都有一系列...

java 日期时间API(java 日期函数)

以下是Java日期时间API的全面解析,涵盖新旧API对比、核心类使用、时区处理及最佳实践:一、Java日期时间演进史二、新旧API核心对比特性java.util.Datejava.time API可...

Java学习需要多长时间?(学java需要多久)

每个人都不一样,数智教育Java基础班,全天学习,差不多半年左右的时间如果你是自学,只能说Java 学习所需的时间因人而异,要考虑很多因素,包括你想学成什么样,你怎么学、目前会什么以及投入的学习时间等...

读取时间的速度(读取速度的快慢)

转自:https://www.brendangregg.com/blog/2021-09-26/the-speed-of-time.html读取时间需要多长时间?你会如何计时? 这些奇怪的问题早在 2...