时间戳的本质
时间戳的确是一个绝对的概念,它通常表示从1970年1月1日00:00:00 UTC(协调世界时)到当前UTC时间所经过的毫秒数。这种计算方式提供了一种全球统一的时间度量标准,不受时区变化的影响。以下是对时间戳的详细解释:
一、时间戳的定义
时间戳是指从1970年1月1日(通用协调时间)至当前时间的总毫秒数。它也被称为Unix时间戳(Unix Timestamp)或POSIX时间。在网络通信、数据库存储、日志记录等场景中,时间戳被广泛应用于标识事件发生的时间。
二、时间戳的计算方式
- 基准时间:1970年1月1日00:00:00 UTC被设定为时间戳的基准时间。
- 时间差计算:通过计算当前UTC时间与基准时间之间的时间差,可以得到一个以秒或毫秒为单位的时间戳。
- 单位转换:由于时间戳通常以毫秒为单位表示,因此需要将计算得到的时间差从秒转换为毫秒(即乘以1000)。
三、时间戳的特点
- 绝对性:时间戳是基于一个固定的起点(1970年1月1日00:00:00 UTC)计算的,因此它具有绝对性,不受时区变化的影响。
- 全球统一:由于采用UTC作为基准时间,时间戳在全球范围内具有统一性和可比性。
- 精确性:时间戳通常以毫秒为单位表示,具有较高的精确性。
四、时间戳的应用场景
- 网络通信:在HTTP请求中,时间戳常被用于标识请求发出的时间,以帮助服务器处理请求时考虑时间因素。
- 数据库存储:在数据库中,时间戳常被用作记录插入或更新操作的时间标记。
- 日志记录:在应用程序中,时间戳常被用于记录事件的发生时间,以便于后续分析和调试。
- 系统时间同步:时间戳也被用于网络时间协议(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小时之后的时间。
三、时间戳相关的转换过程
- 获取当前时间,首先要获取时间戳,而时间戳是一个绝对的数值,得到这个时间戳之后,类库往往会根据所属默认时区再进行时间的增减,最终得到当前时区的正确时间。
- 根据时间获取时间戳,例如 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时间所花费的毫秒数。