为什么需要新的日期时间API
一句话理解:Java 8之前的 Date 和 Calendar 简直是反人类设计,新API(java.time包)终于让日期操作变得正常了
老API有多坑?
Date 的月份从0开始(0=一月,11=十二月),年份要减1900
Calendar 操作繁琐,一个简单的日期加减要写一堆代码
Date 是可变的(mutable),多线程不安全
SimpleDateFormat 线程不安全
新API的优点:不可变、线程安全、API设计合理、方法命名清晰
LocalDate(只有日期,没有时间)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| @Test public void testLocalDate() { System.out.println("=== LocalDate ===");
LocalDate today = LocalDate.now(); LocalDate birthday = LocalDate.of(2000, 6, 15); LocalDate parsed = LocalDate.parse("2024-03-15");
System.out.println("今天:" + today); System.out.println("生日:" + birthday); System.out.println("解析:" + parsed);
System.out.println("年:" + today.getYear()); System.out.println("月:" + today.getMonthValue()); System.out.println("日:" + today.getDayOfMonth()); System.out.println("星期:" + today.getDayOfWeek()); System.out.println("今年第几天:" + today.getDayOfYear());
System.out.println("是闰年?" + today.isLeapYear()); System.out.println("之前?" + birthday.isBefore(today)); System.out.println("之后?" + birthday.isAfter(today)); }
|
LocalTime(只有时间,没有日期)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| @Test public void testLocalTime() { System.out.println("=== LocalTime ===");
LocalTime now = LocalTime.now(); LocalTime lunch = LocalTime.of(12, 0, 0); LocalTime parsed = LocalTime.parse("14:30:00");
System.out.println("现在:" + now); System.out.println("午饭:" + lunch); System.out.println("解析:" + parsed);
System.out.println("时:" + now.getHour()); System.out.println("分:" + now.getMinute()); System.out.println("秒:" + now.getSecond()); }
|
LocalDateTime(日期+时间,最常用)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| @Test public void testLocalDateTime() { System.out.println("=== LocalDateTime ===");
LocalDateTime now = LocalDateTime.now(); LocalDateTime specific = LocalDateTime.of(2024, 12, 25, 20, 30, 0); LocalDateTime combined = LocalDateTime.of( LocalDate.of(2024, 12, 25), LocalTime.of(20, 30) );
System.out.println("现在:" + now); System.out.println("圣诞:" + specific);
LocalDate date = now.toLocalDate(); LocalTime time = now.toLocalTime(); System.out.println("日期部分:" + date); System.out.println("时间部分:" + time); }
|
日期时间的加减操作
新API的操作方法非常直观,而且都是不可变的——每次操作返回新对象,不修改原对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| @Test public void testDateTimeOperations() { System.out.println("=== 日期时间操作 ===");
LocalDate today = LocalDate.now();
LocalDate nextWeek = today.plusDays(7); LocalDate nextMonth = today.plusMonths(1); LocalDate nextYear = today.plusYears(1); System.out.println("一周后:" + nextWeek); System.out.println("一月后:" + nextMonth); System.out.println("一年后:" + nextYear);
LocalDate lastWeek = today.minusDays(7); LocalDate lastMonth = today.minusMonths(1); System.out.println("一周前:" + lastWeek); System.out.println("一月前:" + lastMonth);
LocalDate firstDayOfMonth = today.withDayOfMonth(1); LocalDate inYear2000 = today.withYear(2000); System.out.println("本月第一天:" + firstDayOfMonth); System.out.println("换成2000年:" + inYear2000);
System.out.println("today还是:" + today); }
|
| 方法 |
作用 |
示例 |
plusDays(n) |
加n天 |
today.plusDays(7) |
plusMonths(n) |
加n月 |
today.plusMonths(1) |
plusYears(n) |
加n年 |
today.plusYears(1) |
minusDays(n) |
减n天 |
today.minusDays(7) |
minusMonths(n) |
减n月 |
today.minusMonths(1) |
withDayOfMonth(n) |
设置为n号 |
today.withDayOfMonth(1) |
withMonth(n) |
设置为n月 |
today.withMonth(6) |
withYear(n) |
设置年份 |
today.withYear(2000) |
Duration 和 Period(时间间隔)
Duration:时间维度的间隔(时分秒),用于 LocalTime / LocalDateTime
Period:日期维度的间隔(年月日),用于 LocalDate
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| @Test public void testDurationAndPeriod() { System.out.println("=== Duration 和 Period ===");
LocalTime start = LocalTime.of(9, 0); LocalTime end = LocalTime.of(17, 30); Duration duration = Duration.between(start, end); System.out.println("工作时长:" + duration); System.out.println("工作了" + duration.toHours() + "小时"); System.out.println("工作了" + duration.toMinutes() + "分钟");
LocalDate born = LocalDate.of(2000, 1, 1); LocalDate now = LocalDate.now(); Period period = Period.between(born, now); System.out.println("年龄:" + period.getYears() + "岁" + period.getMonths() + "月" + period.getDays() + "天");
Duration twoHours = Duration.ofHours(2); Period threeMonths = Period.ofMonths(3);
LocalDateTime meeting = LocalDateTime.now().plus(twoHours); LocalDate deadline = LocalDate.now().plus(threeMonths); System.out.println("会议时间:" + meeting); System.out.println("截止日期:" + deadline); }
|
| 类 |
维度 |
适用于 |
示例 |
Duration |
时分秒纳秒 |
LocalTime, LocalDateTime |
Duration.between(t1, t2) |
Period |
年月日 |
LocalDate |
Period.between(d1, d2) |
DateTimeFormatter(格式化和解析)
线程安全的格式化器,替代老的 SimpleDateFormat
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| @Test public void testDateTimeFormatter() { System.out.println("=== DateTimeFormatter ===");
LocalDateTime now = LocalDateTime.now();
String iso = now.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME); System.out.println("ISO格式:" + iso);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); String formatted = now.format(formatter); System.out.println("自定义格式:" + formatted);
DateTimeFormatter cnFormatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH时mm分ss秒"); System.out.println("中文格式:" + now.format(cnFormatter));
LocalDateTime parsed = LocalDateTime.parse("2024-12-25 20:00:00", formatter); System.out.println("解析结果:" + parsed);
LocalDate dateParsed = LocalDate.parse("2024-03-15"); System.out.println("日期解析:" + dateParsed); }
|
常用格式符号
| 符号 |
含义 |
示例 |
yyyy |
四位年份 |
2024 |
MM |
两位月份 |
03 |
dd |
两位日期 |
15 |
HH |
24小时制时 |
14 |
hh |
12小时制时 |
02 |
mm |
分钟 |
30 |
ss |
秒 |
00 |
E |
星期 |
周五 |
a |
上午/下午 |
PM |
新旧API对比
| 对比项 |
旧API (Date/Calendar) |
新API (java.time) |
| 所在包 |
java.util |
java.time |
| 可变性 |
可变(mutable) |
不可变(immutable) |
| 线程安全 |
不安全 |
安全 |
| 月份 |
0-11(反人类) |
1-12(正常) |
| 日期和时间 |
混在一起 |
LocalDate/LocalTime/LocalDateTime分开 |
| 格式化 |
SimpleDateFormat(线程不安全) |
DateTimeFormatter(线程安全) |
| 时区 |
TimeZone |
ZoneId/ZonedDateTime |
| 日期计算 |
Calendar操作繁琐 |
plus/minus直观简洁 |
| API设计 |
混乱 |
清晰、一致 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| @Test public void testOldVsNew() { System.out.println("=== 新旧API对比 ===");
Calendar cal = Calendar.getInstance(); cal.set(2024, 2, 15); Date oldDate = cal.getTime(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); System.out.println("旧API:" + sdf.format(oldDate));
LocalDate newDate = LocalDate.of(2024, 3, 15); System.out.println("新API:" + newDate);
Date date = new Date(); LocalDateTime ldt = date.toInstant() .atZone(ZoneId.systemDefault()) .toLocalDateTime();
Date back = Date.from(ldt.atZone(ZoneId.systemDefault()).toInstant()); }
|
时区处理 ZonedDateTime(了解)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| @Test public void testZonedDateTime() { System.out.println("=== 时区处理 ===");
ZonedDateTime shanghaiNow = ZonedDateTime.now(ZoneId.of("Asia/Shanghai")); ZonedDateTime tokyoNow = ZonedDateTime.now(ZoneId.of("Asia/Tokyo")); ZonedDateTime londonNow = ZonedDateTime.now(ZoneId.of("Europe/London")); ZonedDateTime newYorkNow = ZonedDateTime.now(ZoneId.of("America/New_York"));
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss z"); System.out.println("上海:" + shanghaiNow.format(fmt)); System.out.println("东京:" + tokyoNow.format(fmt)); System.out.println("伦敦:" + londonNow.format(fmt)); System.out.println("纽约:" + newYorkNow.format(fmt));
ZonedDateTime converted = shanghaiNow.withZoneSameInstant(ZoneId.of("America/New_York")); System.out.println("上海时间转纽约:" + converted.format(fmt));
}
|
日常开发如果不涉及跨时区,用 LocalDateTime 就够了
如果需要处理时区(比如国际化项目),才需要用 ZonedDateTime
常见坑
坑1:新API的对象是不可变的,操作后要接收返回值
1 2 3
| LocalDate today = LocalDate.now(); today.plusDays(7); LocalDate nextWeek = today.plusDays(7);
|
坑2:DateTimeFormatter的大小写有含义
MM 是月份,mm 是分钟
HH 是24小时制,hh 是12小时制
写错了不会报错,但结果会很离谱
坑3:parse字符串格式必须严格匹配
1 2 3 4 5 6
|
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy/MM/dd"); LocalDate date = LocalDate.parse("2024/03/15", fmt);
|
坑4:Period.between的参数顺序
1 2 3
| Period p = Period.between(LocalDate.of(2025, 1, 1), LocalDate.of(2024, 1, 1)); System.out.println(p.getYears());
|
练习题
题1:计算你的年龄(精确到年月日),用 Period.between
题2:写一个方法,接收一个日期字符串(格式:yyyy/MM/dd),返回这个日期是星期几
题3:计算两个日期之间相差多少天(提示:用 ChronoUnit.DAYS.between())
题4:写一个方法,输出本月的第一天和最后一天
提示:today.withDayOfMonth(1) 和 today.withDayOfMonth(today.lengthOfMonth())
题5:把字符串 "2024-12-25 20:00:00" 转成 "2024年12月25日 晚上8点整" 格式输出