深入理解时间戳

时间戳的本质

时间戳的确是一个绝对的概念,它通常表示从1970年1月1日00:00:00 UTC(协调世界时)到当前UTC时间所经过的毫秒数。这种计算方式提供了一种全球统一的时间度量标准,不受时区变化的影响。以下是对时间戳的详细解释:

一、时间戳的定义

时间戳是指从1970年1月1日(通用协调时间)至当前时间的总毫秒数。它也被称为Unix时间戳(Unix Timestamp)或POSIX时间。在网络通信、数据库存储、日志记录等场景中,时间戳被广泛应用于标识事件发生的时间。

二、时间戳的计算方式

  1. 基准时间:1970年1月1日00:00:00 UTC被设定为时间戳的基准时间。
  2. 时间差计算:通过计算当前UTC时间与基准时间之间的时间差,可以得到一个以秒或毫秒为单位的时间戳。
  3. 单位转换:由于时间戳通常以毫秒为单位表示,因此需要将计算得到的时间差从秒转换为毫秒(即乘以1000)。

三、时间戳的特点

  1. 绝对性:时间戳是基于一个固定的起点(1970年1月1日00:00:00 UTC)计算的,因此它具有绝对性,不受时区变化的影响。
  2. 全球统一:由于采用UTC作为基准时间,时间戳在全球范围内具有统一性和可比性。
  3. 精确性:时间戳通常以毫秒为单位表示,具有较高的精确性。

四、时间戳的应用场景

  1. 网络通信:在HTTP请求中,时间戳常被用于标识请求发出的时间,以帮助服务器处理请求时考虑时间因素。
  2. 数据库存储:在数据库中,时间戳常被用作记录插入或更新操作的时间标记。
  3. 日志记录:在应用程序中,时间戳常被用于记录事件的发生时间,以便于后续分析和调试。
  4. 系统时间同步:时间戳也被用于网络时间协议(NTP)等系统时间同步机制中,以确保不同系统之间的时间一致性。

综上所述,时间戳是一个基于固定起点(1970年1月1日00:00:00 UTC)计算的、具有绝对性和全球统一性的时间度量标准。它在网络通信、数据库存储、日志记录等场景中有着广泛的应用。

绝对性,永远都是相对于UTC时间的1970-01-01 00:00:00 到当前UTC时间的所经过的毫秒数。拿到一个时间戳之后,可以计算出这个时间戳在任何一个时区所表示的时间。

Java 应用程序对时间戳的相关操作

一、将一个毫秒数转为日期字符串

  • 使用默认时区
    @Test
    public void testTimeStampToDateStr() {
        long timeStamp = 5000;
        Date date = new Date(timeStamp);
        System.out.println(date.getTime()); //5000
        System.out.println(date.toString()); //Thu Jan 01 08:00:05 CST 1970
    }

在上述代码中,定义了一个时间戳5000,相当于5秒,然后创建了一个Date对象,一个Date对象的本质就是自1970-01-01 00:00:00 UTC时间后经历的时间戳,就相当于System.currentTimeMillis();方法。无论在哪个时区的操作系统运行这个方法,同一时刻返回的毫秒数都是一致的,因为时间戳是绝对的,不会因为时区改变而改变。而时间是个相对的,东八区的时间和东九区的时间是不一样的,但是所经历的时间戳都是一样的。

date.getTime() 返回的是绝对的时间戳,5000没有问题。
date.toString() 返回的是Thu Jan 01 08:00:05 CST 1970,这个时间是上午八点零5秒,理论上来说,既然时间戳是从1970-01-01 00:00:00开始计算的,那么5秒之后应该是1970-01-01 00:00:05,这里很明显,是快了8小时,因为操作系统的默认时区是东八区,所以在调用相关类库格式化时,一般会在标准时区的基础上,根据所属时区做调整,这样就得到了本地时间。

  • 使用UTC时区
    @Test
    public void testTimeStampToDateStrUTC() {
        long timeStamp = 5000;
        Date date = new Date(timeStamp);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
        String dateStr = sdf.format(date); 
        System.out.println(dateStr); //1970-01-01 00:00:05
    }

声明了SimpleDateFormat 日期格式化,关键是通过setTimeZone设置格式化的时区为0时区,那么格式化对象在拿到date对象的时间戳之后,自然就会从1970-01-01 00:00:00 + 5秒,得到UTC时间为1970-01-01 00:00:05,而格式化对象设置的时区恰好为0时区,也就是UTC时间,所以不需要再进行额外的时区处理,输出的就是UTC时间。

二、将一个时间字符串转为Date对象

    @Test
    public void testStrToDate() throws ParseException {
        String dateStr = "1970-01-01 00:00:10";
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date date = sdf.parse(dateStr);
        System.out.println(date.getTime()); //-28790000
        System.out.println(date.toString()); //Thu Jan 01 00:00:10 CST 1970
    }

date.getTime() 返回的是-28790000,上文说到,date对象本质就是一个时间戳,代表了从UTC标准起始时间到当前UTC时间经历的毫秒数,那返回-28790000 说明date的这个时间戳远远在UTC起始时间之前,那么为什么会这样? 因为 sdf.parse(dateStr); 时格式化对象默认时区为东八区,也就是会默认认为1970-01-01 00:00:10这个时间是东八区的时间,那东八区的这个时间自然对象的是UTC时间1969-12-31 16:00:10,因为东八区比标准时区快8个小时,所以要减去8个小时才能得到当前时刻的UTC时间,OK。那么得到当前时刻的UTC时间之后减去UTC标准起始时间,得到的自然是一个负数时间戳。date.toString() 会基于时间戳结合当前时区处理,所以看到的是时间戳 + 8小时之后的时间。

三、时间戳相关的转换过程

  1. 获取当前时间,首先要获取时间戳,而时间戳是一个绝对的数值,得到这个时间戳之后,类库往往会根据所属默认时区再进行时间的增减,最终得到当前时区的正确时间。
  2. 根据时间获取时间戳,例如 2024-10-10 10:10:10 得到一个时间戳,首先类库需要知道这个时间是哪个时区的时间,如果不明确,也就是时间字符传中没有表示时区的语义,则类库会认为这是个本地时间,如果为东八区,也就是东八区时间,那么在进行时间戳转换时,需要先将这个时间减8个小时,得到对应的UTC时间,再计算从UTC标准起始时间到这个UTC时间所花费的毫秒数,就可以就计算出一个绝对的时间戳了。

全局设置时区

方法一:使用 TimeZone.setDefault() 方法(Java 8 之前常用)

在 Java 8 之前,java.util.TimeZone 类用于处理时区信息。可以通过 TimeZone.setDefault() 方法来全局设置默认时区。示例代码如下:

import java.util.TimeZone;

public class SetTimeZoneUsingTimeZoneClass {
    public static void main(String[] args) {
        // 设置时区为亚洲/上海
        TimeZone.setDefault(TimeZone.getTimeZone("Asia/Shanghai"));

        // 打印当前默认时区
        System.out.println("当前默认时区: " + TimeZone.getDefault().getID());
    }
}

在上述代码中,调用 TimeZone.setDefault() 方法并传入一个 TimeZone 对象,该对象通过 TimeZone.getTimeZone() 方法根据时区 ID 创建。设置之后,后续涉及默认时区的操作都会使用这个新设置的时区。

方法二:使用系统属性(适用于 Java 8 及之后)

可以在启动 Java 应用程序时通过 -D 参数设置系统属性 user.timezone 来全局指定时区。例如,在命令行中启动 Java 程序时可以这样设置:

java -Duser.timezone=Asia/Shanghai YourMainClass

如果你是在 IDE 中运行程序,可以在运行配置中添加这个系统属性。以 IntelliJ IDEA 为例,在 Run/Debug Configurations 中,找到 VM options 输入框,添加 -Duser.timezone=Asia/Shanghai

方法三:在代码中设置系统属性(适用于 Java 8 及之后)

你也可以在 Java 代码中通过 System.setProperty() 方法来设置 user.timezone 属性。示例代码如下:

public class SetTimeZoneUsingSystemProperty {
    public static void main(String[] args) {
        // 设置时区为亚洲/上海
        System.setProperty("user.timezone", "Asia/Shanghai");

        // 刷新时区设置
        java.util.TimeZone.setDefault(null);

        // 打印当前默认时区
        System.out.println("当前默认时区: " + java.util.TimeZone.getDefault().getID());
    }
}

需要注意的是,设置系统属性后,需要调用 java.util.TimeZone.setDefault(null) 来刷新时区设置,让新的设置生效。

JDK8 LocalDateTime类库对时区的处理

public static void main(String[] args) {
        // 获取当前时间戳(以毫秒为单位)
        long timestamp = System.currentTimeMillis();

        // 将时间戳转换为 Instant 对象,Instant 表示 UTC 时间线上的一个点
        Instant instant = Instant.ofEpochMilli(timestamp);

        // 获取系统默认时区
        ZoneId zoneId = ZoneId.systemDefault();

        // 根据系统默认时区将 Instant 转换为 LocalDateTime
        LocalDateTime localDateTimeFromTimestamp = LocalDateTime.ofInstant(instant, zoneId);

        // 使用 LocalDateTime.now() 获取当前时间
        LocalDateTime localDateTimeNow = LocalDateTime.now();

        System.out.println("通过时间戳转换得到的 LocalDateTime: " + localDateTimeFromTimestamp);
        System.out.println("使用 LocalDateTime.now() 得到的 LocalDateTime: " + localDateTimeNow);
    }

切记一点,时间戳永远是绝对的,和时区无关,永远是基于1970-01-01 00:00:00 UTC 到当前UTC时间所花费的毫秒数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值