DateTimeFormatter ✨

在使用旧的Date对象时,我们使用SimpleDateFromat进行格式化显示。使用新的LocalDateTimeZonedDateTime时,我们要进行格式化显示,就要用DateTimeFormatter

SimpleDateFormat不同的是,DateTimeFormatter不但是不变对象,它还是线程安全的。SimpleDateFormat不是线程安全的,使用的时候,只能在方法内部创建新的局部变量。而DateTimeFormatter可以只创建一个实例,到处引用。

创建DateTimeFormatter时,通过传入格式化字符串来实现:

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

另一种创建方法是,传入格式化字符串时,同时执行Locale

LocalDateTime date = LocalDateTime.now();
DateTimeFormatter formatter1 = DateTimeFormatter.ofPattern("E, yyyy-MMMM-dd HH:mm", Locale.US);
System.out.println(formatter1.format(date));  // Mon, 2025-January-20 14:02

DateTimeFormatter formatter2 = DateTimeFormatter.ofPattern("E, yyyy-MMMM-dd HH:mm", Locale.CHINESE);
System.out.println(formatter2.format(date));  // 周一, 2025-一月-20 14:02

这种方式可以按照Locale默认习惯格式化:

ZonedDateTime zonedDateTime = ZonedDateTime.now();
var formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm ZZZZ");
System.out.println(formatter.format(zonedDateTime));    // 2023-08-26T21:50 GMT+08:00

var zhFormatter = DateTimeFormatter.ofPattern("yyyy MMM dd EE HH:mm", Locale.CHINA);
System.out.println(zhFormatter.format(zonedDateTime));  // 2023 8月 26 周六 21:50

var usFormatter = DateTimeFormatter.ofPattern("E, MMMM/dd/yyyy HH:mm", Locale.US);
System.out.println(usFormatter.format(zonedDateTime));   // Sat, August/26/2023 21:50

如果要自定义输出的格式,或者要把一个非ISO 8601格式的字符串解析成LocalDateTime,可以使用新的DateTimeFormatter

LocalDateTime dateTime = LocalDateTime.now();

// 自定义日期时间格式化
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss");
System.out.println(dateTime.format(formatter));  // 20/01/2025 14:05:53

// 自定义格式解析
LocalDateTime dateTime2 = LocalDateTime.parse("20/09/2021 10:00:00", formatter);
System.out.println(dateTime2);      // 2021-09-20T10:00

LocalDateTime提供了对日期和时间进行加减的非常简单的链式调用:

LocalDateTime now = LocalDateTime.of(2020, 12, 31, 10, 10, 10);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
System.out.println(now);  // 2020-12-31T10:10:10
// 加一天3小时5分钟
LocalDateTime after = now.plusDays(1).plusHours(3).plusMinutes(5);
System.out.println(after.format(formatter));  // 2021-01-01 13:15:10
// 减一天3小时5分钟
LocalDateTime before = now.minusDays(1).minusHours(3).minusMinutes(5);
System.out.println(before.format(formatter));  // 2020-12-30 07:05:10
// 减去1个月
LocalDateTime beforeMonth = now.minusMonths(1);
System.out.println(beforeMonth.format(formatter));  // 2020-11-30 10:10:10

注意到月份加减会自动调整日期,例如从2020-12-31减去1个月得到的结果是2020-11-30,因为11月没有31日。

对日期和时间进行调整则使用withXxx()方法,例如:withHour(15)会把10:11:12变为15:11:12

  • 调整年:withYear()

  • 调整月:withMonth()

  • 调整日:withDayOfMonth()

  • 调整时:withHour()

  • 调整分:withMinute()

  • 调整秒:withSecond()

LocalDateTime now = LocalDateTime.of(2020, 12, 31, 10, 10, 10);
System.out.println(now);                     // 2020-12-31 10:10:10
// 日期变为12
System.out.println(now.withDayOfMonth(12));  // 2020-12-12T10:10:10
// 月份变为9
System.out.println(now.withMonth(9));        // 2020-09-30T10:10:10

同样注意到调整月份时,会相应地调整日期,即把2020-12-31的月份调整为9时,日期也自动变为30

实际上,LocalDateTime还有一个通用的with()方法允许我们做更复杂的运算。例如:

// 本月第一天0:00时刻:
LocalDateTime firstDay = LocalDate.now().withDayOfMonth(1).atStartOfDay();
System.out.println(firstDay); // 2023-08-01T00:00
// 本月最后1天:
LocalDate lastDay = LocalDate.now().with(TemporalAdjusters.lastDayOfMonth());
System.out.println(lastDay);  // 2023-08-31
// 下月第1天:
LocalDate nextMonthFirstDay = LocalDate.now().with(TemporalAdjusters.firstDayOfNextMonth());
System.out.println(nextMonthFirstDay); // 2023-09-01
// 本月第1个周一:
LocalDate firstWeekday = LocalDate.now().with(TemporalAdjusters.firstInMonth(DayOfWeek.MONDAY));
System.out.println(firstWeekday);  // 2023-08-07

对于计算某个月第1个周日这样的问题,新的API可以轻松完成。

要判断两个LocalDateTime的先后,可以使用isBefore()isAfter()方法,对于LocalDateLocalTime类似:

LocalDateTime now = LocalDateTime.now();
LocalDateTime target = LocalDateTime.of(2019, 11, 19, 8, 15, 0);
System.out.println(now.isBefore(target));  // false
System.out.println(LocalDate.now().isBefore(LocalDate.of(2019, 11, 19)));  // true
System.out.println(LocalTime.now().isAfter(LocalTime.parse("08:15:00")));   // true

注意到LocalDateTime无法与时间戳进行转换,因为LocalDateTime没有时区,无法确定某一时刻。后面我们要介绍的ZonedDateTime相当于LocalDateTime加时区的组合,它具有时区,可以与long表示的时间戳进行转换。

最后更新于

这有帮助吗?