精通 Java 日期与时间 API(java.time):使用 LocalDate、LocalTime、ZonedDateTime 等新类,解决日期时间处理的复杂性。

好的,各位尊敬的码农、攻城狮、程序媛们,欢迎来到本期“Java时间旅行指南”节目!我是你们的导游,人称“时间掌控者”的Java老司机。今天,咱们要一起告别Java日期时间处理的“远古时代”,拥抱光鲜亮丽的java.time新纪元!

前言:时间都去哪儿了?(以及Java日期时间的痛点)

话说当年,Java的日期时间API那是出了名的“难用”。你问我有多难用?嗯…大概就像用石头刀切牛排,不是不能吃,但吃得人心里苦啊!

想想那些年,我们被java.util.Datejava.util.Calendar支配的恐惧:

  • 可变性: Date对象是可以被修改的,这意味着你一不小心就会“穿越时空”,导致程序出现难以追踪的bug。 就像你改了别人的日记,历史都被你篡改了,程序当然要出问题!
  • 线程安全: 这些类不是线程安全的,在多线程环境下需要额外的同步措施,增加了代码的复杂性。 多个线程同时修改一个Date对象,那场面简直比春运还混乱!
  • 设计缺陷: 月份从0开始计数(1月是0,2月是1…),星期也是从周日开始计数。这种反人类的设计,简直是程序员的噩梦!
  • 时区处理: 时区处理简直就是个大坑,一不小心就掉进去爬不出来。

总之,旧的日期时间API用起来就像踩地雷,处处是坑。程序员们对它们是又爱又恨,爱的是它们能用,恨的是它们太难用!

好在,Java 8 带来了全新的java.time API,它就像一把瑞士军刀,优雅地解决了旧API的各种问题,让时间处理变得简单、高效、有趣!🎉

java.time:时间旅行的新装备

java.time API的设计理念是:清晰、不变、易用。它引入了一系列新的类,每个类都有明确的职责,让你可以像搭积木一样构建复杂的日期时间逻辑。

下面,我们就来认识一下java.time家族的主要成员:

  • LocalDate: 只包含日期,不包含时间和时区。例如:2023-10-27。
  • LocalTime: 只包含时间,不包含日期和时区。例如:10:30:00。
  • LocalDateTime: 包含日期和时间,但不包含时区。例如:2023-10-27T10:30:00。
  • ZonedDateTime: 包含日期、时间和时区。例如:2023-10-27T10:30:00+08:00[Asia/Shanghai]。
  • Instant: 代表时间轴上的一个瞬时点,精确到纳秒。
  • Duration: 表示两个时间点之间的时间差,例如:30分钟。
  • Period: 表示两个日期之间的间隔,例如:2年3个月5天。
  • DateTimeFormatter: 用于格式化和解析日期时间对象。

这些类都是不可变的(immutable),这意味着一旦创建,它们的值就不能被修改。这保证了线程安全,也避免了意外修改导致的问题。

实战演练:java.time 的正确打开方式

光说不练假把式,接下来我们就通过一些实际的例子,来感受一下java.time的魅力。

1. 创建日期时间对象

  • LocalDate
// 获取当前日期
LocalDate today = LocalDate.now();
System.out.println("Today is: " + today); // 输出:Today is: 2023-10-27 (假设今天是2023年10月27日)

// 指定日期
LocalDate birthday = LocalDate.of(1990, 5, 20);
System.out.println("Birthday is: " + birthday); // 输出:Birthday is: 1990-05-20

// 从字符串解析日期
LocalDate dateFromString = LocalDate.parse("2023-12-25");
System.out.println("Christmas is: " + dateFromString); // 输出:Christmas is: 2023-12-25
  • LocalTime
// 获取当前时间
LocalTime now = LocalTime.now();
System.out.println("Now is: " + now); // 输出:Now is: 11:00:00.123 (假设现在是11点)

// 指定时间
LocalTime meetingTime = LocalTime.of(14, 30);
System.out.println("Meeting time is: " + meetingTime); // 输出:Meeting time is: 14:30

// 从字符串解析时间
LocalTime timeFromString = LocalTime.parse("23:59:59");
System.out.println("End of day: " + timeFromString); // 输出:End of day: 23:59:59
  • LocalDateTime
// 获取当前日期和时间
LocalDateTime currentDateTime = LocalDateTime.now();
System.out.println("Current date and time: " + currentDateTime); // 输出:Current date and time: 2023-10-27T11:00:00.123

// 指定日期和时间
LocalDateTime graduation = LocalDateTime.of(2024, 6, 30, 9, 0);
System.out.println("Graduation date: " + graduation); // 输出:Graduation date: 2024-06-30T09:00

// 从字符串解析日期和时间
LocalDateTime dateTimeFromString = LocalDateTime.parse("2023-11-11T00:00:00");
System.out.println("Singles' Day: " + dateTimeFromString); // 输出:Singles' Day: 2023-11-11T00:00
  • ZonedDateTime
// 获取当前带时区的日期和时间
ZonedDateTime zonedDateTime = ZonedDateTime.now();
System.out.println("Current Zoned Date Time: " + zonedDateTime); // 输出:例如 Current Zoned Date Time: 2023-10-27T11:00:00.123+08:00[Asia/Shanghai]

// 指定时区
ZoneId shanghaiZone = ZoneId.of("Asia/Shanghai");
ZonedDateTime shanghaiTime = ZonedDateTime.now(shanghaiZone);
System.out.println("Shanghai time: " + shanghaiTime); // 输出:例如 Shanghai time: 2023-10-27T11:00:00.123+08:00[Asia/Shanghai]

// 从字符串解析带时区的日期和时间
ZonedDateTime zonedDateTimeFromString = ZonedDateTime.parse("2023-12-25T00:00:00+09:00[Asia/Tokyo]");
System.out.println("Christmas in Tokyo: " + zonedDateTimeFromString); // 输出:Christmas in Tokyo: 2023-12-25T00:00+09:00[Asia/Tokyo]

2. 日期时间的计算

java.time提供了丰富的API来进行日期时间的计算,例如加减天数、月份、年份等。这些操作都是不可变的,会返回一个新的日期时间对象。

LocalDate today = LocalDate.now();

// 加上10天
LocalDate tenDaysLater = today.plusDays(10);
System.out.println("10 days later: " + tenDaysLater);

// 减去1个月
LocalDate lastMonth = today.minusMonths(1);
System.out.println("Last month: " + lastMonth);

// 加上2年
LocalDate twoYearsLater = today.plusYears(2);
System.out.println("Two years later: " + twoYearsLater);

LocalTime now = LocalTime.now();

// 加上30分钟
LocalTime thirtyMinutesLater = now.plusMinutes(30);
System.out.println("30 minutes later: " + thirtyMinutesLater);

3. 日期时间的比较

java.time提供了isBefore(), isAfter(), isEqual()等方法来比较日期时间的大小。

LocalDate date1 = LocalDate.of(2023, 1, 1);
LocalDate date2 = LocalDate.of(2023, 12, 31);

System.out.println("date1 is before date2: " + date1.isBefore(date2)); // 输出:true
System.out.println("date1 is after date2: " + date1.isAfter(date2));  // 输出:false
System.out.println("date1 is equal to date2: " + date1.isEqual(date2)); // 输出:false

4. 日期时间的格式化和解析

DateTimeFormatter是java.time中用于格式化和解析日期时间对象的类。你可以使用预定义的格式,也可以自定义格式。

LocalDateTime now = LocalDateTime.now();

// 使用预定义的格式
DateTimeFormatter formatter1 = DateTimeFormatter.ISO_DATE_TIME;
String formattedDateTime1 = now.format(formatter1);
System.out.println("Formatted date time (ISO): " + formattedDateTime1); // 输出:例如 2023-10-27T11:00:00.123

// 使用自定义格式
DateTimeFormatter formatter2 = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss");
String formattedDateTime2 = now.format(formatter2);
System.out.println("Formatted date time (custom): " + formattedDateTime2); // 输出:例如 2023年10月27日 11:00:00

// 解析日期时间字符串
String dateTimeString = "2023-11-11 11:11:11";
DateTimeFormatter formatter3 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime parsedDateTime = LocalDateTime.parse(dateTimeString, formatter3);
System.out.println("Parsed date time: " + parsedDateTime); // 输出:2023-11-11T11:11:11

5. Duration和Period

  • Duration: 用于表示两个时间点之间的时间差,精确到纳秒。
LocalTime time1 = LocalTime.of(10, 0);
LocalTime time2 = LocalTime.of(10, 30);

Duration duration = Duration.between(time1, time2);
System.out.println("Duration: " + duration); // 输出:PT30M (表示30分钟)

System.out.println("Duration in minutes: " + duration.toMinutes()); // 输出:30
  • Period: 用于表示两个日期之间的间隔,例如:2年3个月5天。
LocalDate date1 = LocalDate.of(2020, 1, 1);
LocalDate date2 = LocalDate.of(2023, 4, 6);

Period period = Period.between(date1, date2);
System.out.println("Period: " + period); // 输出:P3Y3M5D (表示3年3个月5天)

System.out.println("Years: " + period.getYears());   // 输出: 3
System.out.println("Months: " + period.getMonths());  // 输出: 3
System.out.println("Days: " + period.getDays());    // 输出: 5

6. 时区处理的正确姿势

时区处理是日期时间处理中最复杂的部分之一,但java.time使它变得更加简单。

// 获取所有可用的时区
// ZoneId.getAvailableZoneIds().forEach(System.out::println); // 打印所有时区

// 获取指定时区的当前时间
ZoneId tokyoZone = ZoneId.of("Asia/Tokyo");
ZonedDateTime tokyoTime = ZonedDateTime.now(tokyoZone);
System.out.println("Tokyo time: " + tokyoTime);

// 将LocalDateTime转换为ZonedDateTime
LocalDateTime localDateTime = LocalDateTime.now();
ZonedDateTime zonedDateTime = localDateTime.atZone(tokyoZone);
System.out.println("Zoned date time: " + zonedDateTime);

// 将ZonedDateTime转换为LocalDateTime (会丢失时区信息)
LocalDateTime localDateTimeFromZoned = zonedDateTime.toLocalDateTime();
System.out.println("Local date time from zoned: " + localDateTimeFromZoned);

// 不同时区之间的转换
ZoneId newYorkZone = ZoneId.of("America/New_York");
ZonedDateTime newYorkTime = zonedDateTime.withZoneSameInstant(newYorkZone);
System.out.println("New York time: " + newYorkTime);

表格总结:java.time 常用类对比

类名 描述 是否可变 线程安全
LocalDate 只包含日期,不包含时间和时区
LocalTime 只包含时间,不包含日期和时区
LocalDateTime 包含日期和时间,但不包含时区
ZonedDateTime 包含日期、时间和时区
Instant 代表时间轴上的一个瞬时点,精确到纳秒
Duration 表示两个时间点之间的时间差
Period 表示两个日期之间的间隔
DateTimeFormatter 用于格式化和解析日期时间对象

java.time vs 旧的日期时间API:一场跨时代的对话

特性 java.time (新) java.util.Date / Calendar (旧)
可变性 不可变 (immutable) 可变 (mutable)
线程安全 线程安全 线程不安全
API设计 清晰、易用、职责明确 复杂、混乱、容易出错
时区处理 强大、方便 麻烦、容易出错
易用性 简单易懂,学习曲线平缓 复杂难懂,需要花费大量时间学习和调试
性能 经过优化,性能更好 性能较差

避坑指南:java.time 使用注意事项

  • 避免使用旧的API: 尽量避免在新的代码中使用java.util.Datejava.util.Calendar,拥抱java.time吧!
  • 理解时区: 时区处理是日期时间处理的关键,一定要理解时区的概念,并正确处理时区转换。
  • 使用合适的类: 根据实际需求选择合适的类,例如只需要日期就使用LocalDate,只需要时间就使用LocalTime
  • 格式化和解析: 使用DateTimeFormatter进行格式化和解析时,要确保格式字符串与日期时间对象的格式一致,否则会抛出异常。
  • 注意时区缩写: 时区缩写(例如EST, CST)具有歧义性,尽量使用完整的时区ID(例如America/New_York, Asia/Shanghai)。

总结:时间旅行的终点,也是新的起点

恭喜各位,经过今天的学习,你们已经掌握了java.time API的精髓,可以自信地进行Java时间旅行了!🚀 java.time不仅解决了旧API的各种问题,还提供了更加强大、灵活、易用的日期时间处理能力。

记住,时间是最宝贵的资源,我们要珍惜时间,更要用好java.time,让我们的代码更加优雅、高效、可靠!

希望本期“Java时间旅行指南”对大家有所帮助。下次再见! 👋

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注