Java8-新的日期和时间API
# Java8-新的日期和时间API
# LocalDate-简单日期类
LocalDate
类只包含日期信息,即年、月和日,而不包含任何时间(时、分、秒)或时区信息。LocalDate
的设计目的是为了表示纯日期的概念,不涉及具体的时间点或地理位置的影响。
LocalDate的主要特点
- 无时间信息:
LocalDate
仅包含年、月、日的信息,没有时间部分。 - 无时区信息:
LocalDate
不涉及任何时区的概念,因此它是一个纯粹的日历日期对象。 - 不可变性:
LocalDate
对象是不可变的,这意味着一旦创建后,其值不能被更改。 - 线程安全:由于不可变性,
LocalDate
对象是线程安全的。
# 代码示例
// 获取系统日期
LocalDate now = LocalDate.now();
System.out.println(now);
// 输出 2024-10-18
// 指定日期创建
LocalDate specificDate = LocalDate.of(2024, 1, 1);
//字符串解析创建
LocalDate dateFromString = LocalDate.parse("2024-01-01");
LocalDate date = LocalDate.of(2014, 3, 18); // 2014-03-18
int year = date.getYear();
// 2014
Month month = date.getMonth();
// MARCH
int day = date.getDayOfMonth();
// 18
DayOfWeek dow = date.getDayOfWeek();
// TUESDAY
int len = date.lengthOfMonth();
// 31 (days in March)
boolean leap = date.isLeapYear(); // false (not a leap year)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# LocalTime-简单时间类
它是专门用于表示时间(即时、分、秒),而不包含日期信息或时区信息。跟localDate类似,它——LocalTime
对象也是不可变的,这意味着一旦创建后,其值不能被修改,从而保证了线程安全性。
你可以使用of重载的两个工厂方法创建LocalTime的实例。第一个重载函数接收小时和分钟,第二个重载函数同时还接收秒。
LocalTime time = LocalTime.of(17, 20, 20); // 17:20:20
int hour = time.getHour();
// 17
int minute = time.getMinute(); // 20
int second = time.getSecond(); // 20
//
LocalTime time = LocalTime.parse("17:45:20");
2
3
4
5
6
7
8
9
# LocalDateTime
localDateTime这个复合类从名称上看,就能明白其作用,它是LocalDate和LocalTime的合体,同时表示了日期和时间,但请注意,它也不带有时区信息,你可以直接创建,也可以通过合并日期和时间对象构造。
// 2014-03-18T13:45:20
LocalDateTime dt1 = LocalDateTime.of(2014, Month.MARCH, 18, 13, 45, 20);
LocalDateTime dt2 = LocalDateTime.of(date, time);
LocalDateTime dt3 = date.atTime(13, 45, 20);
LocalDateTime dt4 = date.atTime(time);
LocalDateTime dt5 = time.atDate(date);
2
3
4
5
6
当然,它也可以直接转换成localDate或者localTime。
LocalDate date1 = dt1.toLocalDate(); // 2014-03-18
LocalTime time1 = dt1.toLocalTime(); // 13:45:20
2
# LocalDateTime跟String的互转
LocalDateTime now = LocalDateTime.now();
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String format = now.format(dateTimeFormatter);
System.out.println("当前时间为:"+format);
String temp = "2023-04-07 09:09:09";
LocalDateTime parse = LocalDateTime.parse(temp, dateTimeFormatter);
System.out.println("parse:"+parse);
2
3
4
5
6
7
8
最后再补一个时间戳转LocalDateTime
// 创建 Timestamp 对象
Timestamp timestamp = new Timestamp(System.currentTimeMillis());
// 转换为 LocalDateTime
LocalDateTime localDateTime = timestamp.toLocalDateTime();
// 格式化为字符串
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String formattedDate = localDateTime.format(formatter);
// 输出结果
System.out.println("时间戳转字符串: " + formattedDate);
2
3
4
5
6
7
8
9
10
11
12
# Duration和Period
前面介绍的都是某个时间点,但时间还有一种表示,就是一段。
# Duration-时间
Duration
类是java.time
包的一部分,用于表示两个时间点之间的时间间隔。Duration
类可以用来衡量两个时刻之间的时间差,而不涉及具体的日期或时间点。它主要关注的是时间跨度,如秒、毫秒、分钟、小时等。
LocalTime startTime = LocalTime.of(12, 0);
LocalTime endTime = LocalTime.of(13, 30);
Duration duration = Duration.between(startTime, endTime);
System.out.println("Duration between start and end times: " + duration.getSeconds() + " seconds");
2
3
4
或者直接设置时长。创建一个指定小时数、分钟数、纳秒数等的Duration
对象:
Duration twoHours = Duration.ofHours(2);
Duration fiveMinutes = Duration.ofMinutes(5);
Duration tenNanos = Duration.ofNanos(10);
2
3
转换成长整型:
long seconds = duration.getSeconds();
long minutes = Math.abs(duration.toMinutes());
long hours = Math.abs(duration.toHours());
2
3
# Period-日期
Period
类同样是java.time
包的一部分,用于表示两个日期之间的间隔,即表示一段时间的长度,但它专注于日期而非时间点。Period
类主要用于表示日期间隔,例如年、月和日的数量差异。
LocalDate startDate = LocalDate.of(2024, 1, 1);
LocalDate endDate = LocalDate.of(2024, 12, 31);
Period period = Period.between(startDate, endDate);
System.out.println("Period between start and end dates: " + period.getYears() + " years, " + period.getMonths() + " months, " + period.getDays() + " days");
2
3
4
获取日期的各个部分:
int years = period.getYears();
int months = period.getMonths();
int days = period.getDays();
2
3
注意:以上的5种时间类,它们的对象是不可变的,这意味着一旦创建后,其值不会改变。性质类似String。
# 解析格式化以及输出时间
DateTimeFormatter
是java.time
包中的一个类,用于格式化和解析日期时间字符串。它是现代日期时间API的一部分,和DateFormat
(java.util
包下的传统日期时间API)相比, 它更加符合现代编程的需求。
同时它的对象也是不可变的,因此是线程安全的。此外,它还支持更多的格式化选项。
LocalDateTime now = LocalDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String formattedDateTime = now.format(formatter);
System.out.println("Formatted date and time: " + formattedDateTime);
LocalDateTime parsedDateTime = LocalDateTime.parse("2024-01-01 13:45:30", formatter);
System.out.println("Parsed date and time: " + parsedDateTime);
2
3
4
5
6
7
一些其它格式:
LocalDate date = LocalDate.of(2020, 3, 18);
String s1 = date.format(DateTimeFormatter.BASIC_ISO_DATE); // 20200318
String s2 = date.format(DateTimeFormatter.ISO_LOCAL_DATE); // 2020-03-18
2
3
# 不同的时区
时间除了年月日,时分秒,这些数字部分,它还有一个部分————时区。
Java 8中,ZoneId
是java.time
包中的一个类,用于表示时区。ZoneId
类提供了管理和获取时区的功能,可以用来处理不同地区的日期时间差异,特别是在涉及跨时区的应用程序中尤为重要。新的java.time.ZoneId类是老版java.util.TimeZone的替代品。它的设计目标就是要让你无需为时区处理的复杂和繁琐而操心。
ZoneId
表示一个时区,可以用来描述地理位置的时区,如"Asia/Shanghai"、"America/New_York"等。
每个特定的ZoneId对象都由一个地区ID标识,使用ZoneId.of
方法可以创建一个指定时区的ZoneId
对象,比如:
ZoneId shanghaiZone = ZoneId.of("Asia/Shanghai");
// 获取系统默认时区
ZoneId defaultZone = ZoneId.systemDefault();
2
3
地区ID都为“{区域}/{城市}”的格式,这些地区集合的设定都由英特网编号分配机构(IANA)的时区数据库提供。获取后,我们就可以使用ZoneId
与其他日期时间类一起使用,如ZonedDateTime
、LocalDateTime
等,以便进行更复杂的日期时间表达以及计算,还有时区转换。
// 当前本地日期时间
LocalDateTime localDateTime = LocalDateTime.now();
System.out.println("Local Date Time: " + localDateTime);
// 将本地日期时间转换为特定时区的日期时间
ZoneId newYorkZone = ZoneId.of("America/New_York");
ZonedDateTime newYorkZonedDateTime = ZonedDateTime.of(localDateTime, newYorkZone);
System.out.println("New York Date Time: " + newYorkZonedDateTime);
// 将ZonedDateTime转换为另一个时区
ZoneId londonZone = ZoneId.of("Europe/London");
ZonedDateTime londonZonedDateTime = newYorkZonedDateTime.withZoneSameInstant(londonZone);
System.out.println("London Date Time: " + londonZonedDateTime);
2
3
4
5
6
7
8
9
10
11
12
13
再来一个简单例子:
// 创建指定时区的ZoneId对象
ZoneId shanghaiZone = ZoneId.of("Asia/Shanghai");
// 获取时区信息
String id = shanghaiZone.getId();
String displayName = shanghaiZone.getDisplayName(TextStyle.FULL_STANDALONE, Locale.CHINA);
System.out.println("Shanghai Zone ID: " + id + ", Display Name: " + displayName);
// 创建一个指定时区的ZonedDateTime对象
ZonedDateTime zonedDateTime = ZonedDateTime.of(2024, 1, 1, 10, 0, 0, 0, shanghaiZone);
System.out.println("Shanghai Time: " + zonedDateTime);
2
3
4
5
6
7
8
9
10
11
当然,zoneID结合localDate 或者LocalDateTime的方法(API)还有,这里就不一一展示了。
提示:为了兼容以前的旧方法,开发者也做了努力。你可以通过Java 8的新方法toZoneId将一个老的时区对象转换为ZoneId:
ZoneId zoneId = TimeZone.getDefault().toZoneId();
# 时间戳-Instant
我们习惯于以星期几、几号、几点、几分这样的方式理解日期和时间。毫无疑问,这种方式对于计算机而言并不容易理解。从计算机的角度来看,建模时间最自然的格式是表示一个持续时间段上某个点的单一大整型数。这也是新的java.time.Instant类对时间建模的方式,基本上它是以Unix元年时间(传统的设定为UTC时区1970年1月1日午夜时分)开始所经历的秒数进行计算。
更简单的讲,它就是时间戳。它————Instant
类是java.time
包的一部分,用于表示时间轴上的一个瞬间点,通常指的是Unix纪元以来的秒数加上纳秒数的组合。Instant
类非常适合用来表示与特定时区无关的时间戳,常用于网络传输或存储时间戳。
// 使用Instant.now()方法获取当前时间的Instant对象:
Instant now = Instant.now();
// 获取时间戳的各个部分
long epochSecond = now.getEpochSecond(); // 获取秒数
int nanoOfSecond = now.getNano(); // 获取纳秒数
// 转换为其他时区的时间
ZoneId zoneId = ZoneId.of("Asia/Shanghai");
ZonedDateTime zonedDateTime = now.atZone(zoneId);
System.out.println("In Shanghai: " + zonedDateTime);
2
3
4
5
6
7
8
9
10
11
# Instant在实际应用中的作用
- 网络传输:在网络传输中,通常使用
Instant
来表示服务器端的时间戳,这样可以保证时间戳的一致性和准确性。 - 数据库存储:在存储时间戳时,可以使用
Instant
来表示一个精确的时间点,然后在读取时根据需要转换为特定时区的时间。 - 定时任务:在定时任务中,可以使用
Instant
来表示未来某一时刻的任务执行时间,然后根据当前时间计算剩余时间。 - 日志记录:在日志记录中,使用
Instant
可以记录事件发生的确切时间点,方便后续分析。
# 1.8前后的区别
以下是关于1.8之前和之后用于日期时间处理的主要包及其区别。
1.8之前:
- 对应的包:
java.util.Date
和java.util.Calendar
1.8及、之后
- 对应的包:
java.time
包
# 区别总结
特性 | Java 1.8之前 (java.util.Date , java.util.Calendar ) | Java 1.8及之后 (java.time.* ) |
---|---|---|
不可变性 | 可变对象,非线程安全 | 不可变对象,线程安全 |
时区处理 | 处理复杂,容易出错 | 提供了专门的ZonedDateTime 类,易于理解 |
API友好性 | API设计不够直观 | 更加现代化和直观的设计 |
性能与准确性 | 存在精度丢失的风险 | 高精度,支持纳秒级别 |
扩展性 | 功能有限 | 强大的扩展能力,如Duration , Period |
总的来说,Java 8引入的新日期时间API极大地改善了开发者处理日期和时间的方式,使得代码更加简洁、易读且不易出错。截至2025年,对于任何新的Java项目,推荐优先考虑使用java.time
包中的类来进行日期和时间的操作。
# 一个问题
那么后续我们在进行数据库设计时,遇到对sql表设计时间字段时,该如何选择呢?它每一种类型又对应着Java中的哪种类型?
以下是2个可供参考的分析表:
# MySQL 时间类型与对应场景
MySQL 类型 | 范围/特性 | 适用场景 |
---|---|---|
DATE | 仅日期,格式 YYYY-MM-DD | 仅需存储日期(如生日、事件日期) |
TIME | 仅时间,格式 HH:MM:SS[.微秒] | 仅需存储时间(如会议开始时间) |
DATETIME | 日期+时间,范围 1000-01-01 00:00:00 到 9999-12-31 23:59:59 ,无时区 | 需要存储与时区无关的日期时间(如用户注册时间、计划任务时间) |
TIMESTAMP | 日期+时间,范围 1970-01-01 00:00:01 UTC 到 2038-01-19 03:14:07 UTC,自动转换时区 | 需要自动时区转换或记录行更新时间(如日志时间、订单更新时间) |
# Java 类型映射
MySQL 类型 | 推荐 Java 类型 | 说明 |
---|---|---|
DATE | java.time.LocalDate | 直接映射日期,无需时区信息。 |
TIME | java.time.LocalTime | 直接映射时间,支持微秒精度。 |
DATETIME | java.time.LocalDateTime | 适合存储固定时间(如用户设定的时间),与时区无关。 |
TIMESTAMP | java.time.Instant 或 java.time.OffsetDateTime | 适合存储时间点(如日志记录),需处理时区。 |
# 关于MySql的TimeStamp
如果serverTimezone=Asia/Shanghai 设置正确,Java代码层面是否可以用LocalDateTime,而数据库使用TimeStamp?
答案是:是的。
如果在 JDBC 连接中正确设置了 serverTimezone=Asia/Shanghai
,完全可以在 Java 代码中使用 LocalDateTime
类型,而数据库使用 TIMESTAMP
字段。但需要注意以下关键点:
MySQL 的 TIMESTAMP
类型会自动将存储的时间转换为 UTC,并在读取时根据连接的时区转换回本地时间。
当 JDBC 连接配置了 serverTimezone=Asia/Shanghai
时,驱动会完成以下行为:
- 写入时:将 Java 的
LocalDateTime
(如2023-10-01 08:00:00
,视为上海时间)转换为 UTC 时间(2023-10-01 00:00:00
),存储到数据库。 - 读取时:将数据库的 UTC 时间(
2023-10-01 00:00:00
)转换为上海时间(2023-10-01 08:00:00
),再映射到 Java 的LocalDateTime
。
只要时区配置一致,LocalDateTime
和 TIMESTAMP
的转换是透明且准确的。
- 配置--在连接字符串中明确指定时区:
# application.properties(Spring Boot 示例)
spring.datasource.url=jdbc:mysql://localhost:3306/db_name?serverTimezone=Asia/Shanghai
2
# 推荐使用
Instant
(推荐)
- 特点:
直接表示 UTC 时间点,与数据库
TIMESTAMP
的存储机制(UTC 时间戳)完全一致。 - 映射示例:
@Entity
public class Event {
@Column(name = "event_time")
private Instant eventTime; // 自动映射到 TIMESTAMP
}
2
3
4
5
- 行为:
- 写入时:
Instant
(UTC 时间)直接存储为数据库的 UTC 时间戳。 - 读取时:数据库的 UTC 时间戳直接转为
Instant
。
- 写入时: