一、时间基础
一、计算机时间表示基础
-
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类型的单位(毫秒或秒)。
- 转换时需注意