一、时间基础
一、计算机时间表示基础
-
Epoch Time(时间戳)
- 定义:从 1970 年 1 月 1 日零点(GMT+00:00)至今的毫秒数(Java 中用
long
类型表示)。 - 获取方式:通过
System.currentTimeMillis()
获取当前时间戳。 - 作用:作为计算机存储时间的统一格式,与展示格式分离(如
2023-10-01 12:00:00
是展示格式,存储为时间戳1696152000000
)。
- 定义:从 1970 年 1 月 1 日零点(GMT+00:00)至今的毫秒数(Java 中用
-
存储与展示分离
- 存储:仅保存时间戳(整数),不包含时区、格式等信息。
- 展示:通过格式化方法将时间戳转换为指定时区和格式的字符串(如
SimpleDateFormat
)。
二、旧版日期时间 API(java.util
包)
1. Date
类
-
作用:表示日期和时间,内部存储毫秒级时间戳。
-
注意事项
-
与
java.sql.Date
(仅存储日期)区分。 -
获取字段需手动调整:
getYear()
需加1900
(如getYear()+1900
)。getMonth()
返回0-11
(需加1
表示 1-12 月)。getDate()
直接返回1-31
(无需调整)。
-
-
格式化 :通过
SimpleDateFormat
自定义格式- 示例:
"yyyy-MM-dd HH:mm:ss"
表示年 - 月 - 日 时:分: 秒。 - 不同语言环境下输出差异:如
"E MMM dd, yyyy"
在英文环境输出Sun Sep 15, 2019
。
- 示例:
-
缺点:
- 无法直接处理时区,默认使用系统时区。
- 日期运算复杂(如计算两天之差需手动转换)。
2. Calendar
类
-
作用:用于获取、设置和计算日期时间字段(年、月、日、时等)。
-
基本用法
- 获取当前时间:
Calendar c = Calendar.getInstance()
。 - 获取字段:
c.get(Calendar.YEAR)
(年份直接返回)、c.get(Calendar.MONTH)+1
(月份需加 1)。 - 设置时间:
c.set(Calendar.YEAR, 2023)
;需先调用c.clear()
清除默认值。
- 获取当前时间:
-
时区处理
- 通过
TimeZone
指定时区:c.setTimeZone(TimeZone.getTimeZone("America/New_York"))
。 - 格式化时需设置目标时区:
SimpleDateFormat
的setTimeZone()
方法。
- 通过
-
日期运算 :通过
add()
方法实现加减- 示例:
c.add(Calendar.DAY_OF_MONTH, 5)
(加 5 天);c.add(Calendar.HOUR_OF_DAY, -2)
(减 2 小时)。
- 示例:
3. TimeZone
类
- 作用 :表示时区,唯一标识为字符串 ID(如
"Asia/Shanghai"
、"GMT+09:00"
)。 - 获取方式 :
TimeZone.getDefault()
(当前时区);TimeZone.getTimeZone("ID")
(指定时区)。
三、新版日期时间 API(Java 8+,java.time
包)
-
设计优势:
- 不可变对象,线程安全。
- 明确区分日期(
LocalDate
)、时间(LocalTime
)、带时区时间(ZonedDateTime
)。 - 内置时区支持(
ZoneId
),简化时区转换。
-
核心类
LocalDateTime
:本地日期时间(无时区)。ZonedDateTime
:带时区的日期时间。DateTimeFormatter
:格式化工具(替代SimpleDateFormat
)。Instant
:时间戳(对应 Epoch Time,精确到纳秒)。
四、新旧 API 对比与选择
特性 | 旧 API(java.util ) |
新 API(java.time ) |
---|---|---|
线程安全 | 非线程安全(Date 、SimpleDateFormat ) |
线程安全(不可变对象) |
时区处理 | 需通过TimeZone 和Calendar 间接处理 |
直接支持ZoneId ,ZonedDateTime 内置时区 |
日期运算 | 需手动计算或依赖Calendar.add() |
提供plusDays() 、minusHours() 等链式方法 |
易用性 | 字段获取需手动调整(如月份 + 1) | 字段直接对应实际含义(如getMonthValue() 返回 1-12) |
推荐场景 | 兼容遗留代码 | 新项目开发,优先使用新 API |
五、关键代码示例
-
旧 API 格式化日期
javaDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); System.out.println(sdf.format(date)); // 输出:2023-10-01 12:30:00
-
Calendar 时区转换
javaCalendar c = Calendar.getInstance(); c.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai")); c.set(2023, 9, 1, 8, 0, 0); // 注意:10月对应9(0-based) SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); sdf.setTimeZone(TimeZone.getTimeZone("America/New_York")); System.out.println(sdf.format(c.getTime())); // 输出:2023-09-30 19:00:00(纽约时区)
二、LocalDateTime 核心操作
-
创建实例
- 获取当前时间 :
LocalDateTime.now()
(基于系统默认时区);LocalDate.now()
/LocalTime.now()
分别获取日期和时间。 - 指定日期时间 :
LocalDateTime.of(年, 月, 日, 时, 分, 秒)
;LocalDateTime.of(LocalDate, LocalTime)
。 - 解析字符串 :
LocalDateTime.parse("2023-10-01T12:30:00")
(需符合 ISO 8601 格式,日期和时间用T
分隔)。
- 获取当前时间 :
-
日期时间运算
- 加减操作 :
dt.plusDays(5).minusHours(3)
(返回新实例,原对象不变);支持Days
、Hours
、Months
等单位。 - 调整操作 :
dt.withDayOfMonth(1)
(设置为当月第 1 天);dt.with(TemporalAdjusters.lastDayOfMonth())
(获取当月最后一天)。
- 加减操作 :
-
比较先后 :使用
isBefore()
和isAfter()
方法判断两个日期时间的先后顺序,例如now.isBefore(target)
。
1、格式化与解析(DateTimeFormatter)
- 自定义格式输出 :通过
DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss")
创建自定义格式器,例如dtf.format(LocalDateTime.now())
。 - 解析非标准字符串 :
LocalDateTime.parse("2023/10/01 12:30:00", dtf)
,需确保字符串格式与DateTimeFormatter
模式一致。
2、时间间隔(Duration vs Period)
类型 | 作用 | 示例 | 格式 |
---|---|---|---|
Duration |
表示两个时刻的时间间隔(精确到秒 / 纳秒) | Duration.between(start, end) |
PT1235H10M30S (1235 小时 10 分 30 秒) |
Period |
表示两个日期的间隔(精确到天 / 月 / 年) | LocalDate.of(...).until(LocalDate.of(...)) |
P1M21D (1 个月 21 天) |
3、与旧 API 对比及设计背景
- 替代旧 API :取代
Date
、Calendar
和SimpleDateFormat
,解决旧 API 线程不安全、设计不合理等问题。 - 设计来源:借鉴开源库 Joda Time 的设计,由 Joda Time 作者参与开发,确保易用性和稳定性。
三、ZonedDateTime 概述
- 定义 :表示带时区的日期和时间,可看作
LocalDateTime
与ZoneId
的组合。 - 核心作用:用于处理不同时区的日期时间,解决跨时区的时间转换问题。
- 与旧时区类区别 :使用
ZoneId
(属于java.time
包),摒弃旧的java.util.TimeZone
,设计更简洁且线程安全。
1、创建 ZonedDateTime 的方法
-
通过
now()
获取当前时间- 默认时区 :
ZonedDateTime.now()
,会使用系统默认时区。 - 指定时区 :
ZonedDateTime.now(ZoneId.of("时区ID"))
,例如ZoneId.of("America/New_York")
。
- 默认时区 :
-
由 LocalDateTime 转换而来 :通过
LocalDateTime.atZone(ZoneId)
方法,为本地日期时间附加时区,如ldt.atZone(ZoneId.systemDefault())
。
2、时区转换操作
-
核心方法 :
withZoneSameInstant(ZoneId)
,可在不同时区间转换同一时刻的时间,转换后会自动调整日期和时间。 -
夏令时影响:转换时需考虑时区的夏令时规则,不同日期转换结果可能存在差异,例如北京时间 11 月和 9 月转换为纽约时间有时差变化,所以切勿手动计算时差。
-
与 LocalDateTime 互转
- 转 LocalDateTime :使用
toLocalDateTime()
方法,此操作会丢弃时区信息。 - 由 LocalDateTime 转 ZonedDateTime :通过
atZone(ZoneId)
方法为其添加时区。
- 转 LocalDateTime :使用
3、实战练习:计算跨时区到达时间
-
问题描述:已知北京起飞的本地时间和飞行时长,要求计算到达纽约的当地时间。
-
解决思路
- 将北京起飞的
LocalDateTime
转换为带北京时区(Asia/Shanghai
)的ZonedDateTime
。 - 给该时间加上飞行时长,得到到达时的北京时间(带时区)。
- 使用
withZoneSameInstant()
方法将其转换为纽约时区(America/New_York
)的ZonedDateTime
。 - 最后通过
toLocalDateTime()
获取纽约当地时间。
- 将北京起飞的
-
示例代码逻辑 :
LocalDateTime
→ZonedDateTime(北京时区)
→加飞行时间→ZonedDateTime(纽约时区)
→LocalDateTime
。
四、DateTimeFormatter 核心内容
1. 作用与背景
-
用途 :用于格式化
LocalDateTime
或ZonedDateTime
(Java 8 + 新日期时间 API),替代旧版非线程安全的SimpleDateFormat
。 -
核心优势:
- 不变性与线程安全 :可创建单例实例重复使用,避免
SimpleDateFormat
的线程安全问题。 - 支持国际化(Locale) :按不同地区习惯格式化日期时间。
- 不变性与线程安全 :可创建单例实例重复使用,避免
2. 创建方式
-
基础格式化字符串:
javaDateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
格式符号与
SimpleDateFormat
一致(如yyyy
年、MM
月、dd
日等)。 -
指定 Locale:
javaDateTimeFormatter usFormatter = DateTimeFormatter.ofPattern("E, yyyy-MMMM-dd HH:mm", Locale.US);
根据地区习惯调整显示(如美国使用 "Sun, September/15/2019",中国使用 "周日,2019 年 9 月 15 日")。
3. 格式化示例
-
代码演示:
javaZonedDateTime zdt = ZonedDateTime.now(); // 默认格式(含时区) System.out.println(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm ZZZZ").format(zdt)); // 中国Locale:年 月 日 星期 时分 System.out.println(DateTimeFormatter.ofPattern("yyyy MMM dd EE HH:mm", Locale.CHINA).format(zdt)); // 美国Locale:星期, 月/日/年 时分 System.out.println(DateTimeFormatter.ofPattern("E, MMMM/dd/yyyy HH:mm", Locale.US).format(zdt));
-
输出结果:
plaintext2019-09-15T23:16 GMT+08:00 2019 9月 15 周日 23:16 Sun, September/15/2019 23:16
-
固定字符输出 :
格式字符串中用单引号包裹固定内容,如
"yyyy-'年'-MM-'月'"
输出 "2023 - 年 - 06 - 月"。
五、Instant 专题:高精度时间戳
1. 基本概念
-
定义:表示计算机存储的高精度时间戳,本质是不断递增的整数。
-
作用 :替代传统的
System.currentTimeMillis()
,提供更高精度(纳秒级)。 -
获取方式 :通过
Instant.now()
获取当前时间戳,示例:javaInstant now = Instant.now(); System.out.println(now.getEpochSecond()); // 秒级时间戳 System.out.println(now.toEpochMilli()); // 毫秒级时间戳
2. 内部结构
-
核心字段:
long seconds
:以秒为单位的时间戳。int nanos
:纳秒级精度(补充秒的小数部分)。
-
对比:比
System.currentTimeMillis()
的long
类型(毫秒级)精度更高。
3. 类型转换
-
与 ZonedDateTime / 时区转换 :
通过
atZone(ZoneId)
为时间戳附加时区,生成带时区的日期时间:javaInstant ins = Instant.ofEpochSecond(1568568760); ZonedDateTime zdt = ins.atZone(ZoneId.systemDefault()); // 输出:2019-09-16T01:32:40+08:00[Asia/Shanghai]
-
转换关系图:
arduinoLocalDateTime ─┬─> ZonedDateTime ZoneId ─┘ ▲ ┌─────────┴─────────┐ │ │ ▼ ▼ Instant ◀───▶ long(注意毫秒/秒单位)
- 转换时需注意
long
类型的单位(毫秒或秒)。
- 转换时需注意