引言

在过去的Java版本中,日期和时间的处理主要依赖于java.util.Datejava.util.Calendar类,然而随着业务系统的复杂以及技术层面的提升,这些传统的日期时间类暴露出了若干显著的不足之处。随着Java8的发布,其引入了一套全新的日期时间API,彻底改变了我们处理日期和时间的方式。

传统的日期时间类

相比较Java8中新引入的java.time包下的时间处理类,传统的日期时间处理类在易用性,线程安全,不支持市时区等缺点。

  1. 设计复杂性Date类的设计较为简单,但它实际上混合了日期和时间信息,并且没有提供直观的方法来单独操作日期或时间部分。Calendar类虽然提供了更多灵活性,但由于其内部状态和方法的复杂性,使得开发者在使用过程中容易出现错误和混淆,尤其是在进行日期时间计算和格式化时。比如:
Date currentDate = new Date();
// 输出原始的日期时间,通常不是人类可读格式 Fri Mar 08 03:13:47 CST 2024
System.out.println(currentDate); // 要改变日期的某个部分,必须先将其转换为 Calendar,然后设置
Calendar calendar = Calendar.getInstance();
calendar.setTime(currentDate);
// 修改日期的天数
calendar.add(Calendar.DAY_OF_MONTH, 1);
  1. 线程安全性Date和 Calendar类,以及格式化日期的SimpleDateFormat类都不是线程安全的,这意味着在多线程环境下的并发访问可能会导致数据不一致。

    • Date类内部维护了一个 long 类型的瞬时值,当调用如setTime()方法来更新这个瞬时值时,不同的线程同时调用就会互相覆盖彼此的值,造成数据不一致。

    • Calendar类不仅包含了日期和时间信息,还有一系列内部状态变量,如年、月、日、小时、分钟、秒等。Calendar类的方法通常会修改这些内部状态,例如 add()set() 等。在多线程环境下,若多个线程尝试同时修改同一个 Calendar 实例,也会导致不可预期的结果。

    • SimpleDateFormat类在执行格式化和解析日期时间操作时,内部会维护一个 Calendar对象以及其他一些状态变量。在 format() 或 parse() 方法执行过程中,这些状态会被更新以完成格式转换。并且SimpleDateFormat中的方法并非原子操作,因此在多线程并发调用时,可能在一个线程还未完成整个操作时就被另一个线程打断,导致错误的日期时间处理结果。

  2. 时区处理能力Date类虽能表示时间戳,但它不直接关联时区信息,难以进行时区相关的转换。而 Calendar 虽然支持时区,但操作过程相当复杂。

  3. 精度差异Date类精度只到毫秒级。

Java8中日期时间类

Java8中引入的LocalDateLocalTimeLocalDateTime这几个位于java.time下的类克服了上述传统类别的局限性,提供了更强大、直观和精准的日期时间处理能力,成为现代Java开发中处理日期时间首选的工具类。相比较传统的日期时间类,具备以下显著优势:

  1. 功能丰富

    java.time包下的类如 LocalDateLocalTimeLocalDateTimeZonedDateTime 等都有明确的职责划分,分别处理日期、时间、日期时间以及带时区的日期时间,结构清晰,易于理解和使用。并且它们提供了一系列直观、面向对象的API,如 plusXxx()minusXxx()withXxx()等方法,使日期时间操作变得简单明了。

  2. 时区支持

    除此之外,还支持时区,通过ZonedDateTime和 ZoneId等类提供了对时区的更好支持,可以方便地进行时区转换和处理。

  3. 线程安全

    这些类都是不可变对象,线程安全,可以在多线程环境下安全使用,不会出现因并发操作导致的数据不一致问题。

  4. 更高的精度

    支持纳秒级精度,相比 Date 类的毫秒精度更胜一筹。

java.time下主要有如下一些关键类:

  1. LocalDate

    LocalDate类表示一个不包含时间信息的日期,只包含年、月、日三个部分,且不关联任何特定时区。

  2. LocalTime

    LocalTime类表示一个不包含日期信息的具体时间,包含时、分、秒和纳秒四个部分。

  3. LocalDateTime

    LocalDateTime类结合了日期和时间,表示一个具体的日期和时间点,但是不包含时区信息。

  4. ZonedDateTime

    ZonedDateTime类表示一个带有时区的日期时间,它可以明确表示某一特定时区内的某一确切时间点。

  5. Instant

    Instant类表示时间线上某一瞬时点,通常以Unix纪元(1970-01-01T00:00:00Z)以来的秒数和纳秒数表示,它是全球通用的时间点表示。

  6. Period

    Period类用于表示两个日期之间的期间,包括年、月、日的数量。

  7. Duration

    Duration类表示两个时间点之间的时间差,包含秒和纳秒的持续时间,主要用于表示时间间隔而非日历单位。

  8. DateTimeFormatter

    DateTimeFormatter类用于日期时间的格式化和解析,提供了标准化和自定义的日期时间格式化方式。

  9. TemporalAdjusters

    TemporalAdjusters类提供了一系列实用方法,用于调整日期时间,例如获取下一个工作日、月初、月末等。

这些类共同构成了一个强大、灵活且易于使用的日期时间处理体系,大大改善了Java在处理日期时间问题时的效率和准确性。接下来我们在使用上分别介绍这些类,以及使用他们的方式,感受他们的强大。

Java8中日期时间类使用

创建

NOW方法获取当前 时刻、日期、时间
LocalTime localTime = LocalTime.now();
System.out.println("localTime:"+localTime); LocalDate localDate = LocalDate.now();
System.out.println("localDate:"+localDate); LocalDateTime localDateTime = LocalDateTime.now();
System.out.println("localDateTime:"+localDateTime);

输出为:

localTime:15:28:45.241181
localDate:2024-03-11
localDateTime:2024-03-11T15:28:45.260655

针对LocalTimeLocalDateTime获取当前时刻默认会带有毫秒,如果不需要毫秒的话,可以通过设置纳秒为0 保留秒 1秒 = 十亿纳秒 。例如:

LocalTime localTime = LocalTime.now().withNano(0);
System.out.println("localTime:"+localTime); LocalDateTime localDateTime = LocalDateTime.now().withNano(0);
System.out.println("localDateTime:"+localDateTime);

输出为:

localTime:15:32:31
localDateTime:2024-03-11T15:32:31

而对于LocalDateTime获取当前日期,默认toString会带有T分隔日期和时间,在项目中,可以通过全局序列化,进行统一的时间格式输出为 yyyy-MM-dd HH:mm:ss。但是一般不建议这么干,毕竟改变全局序列化配置,建议不使用toString,可以使用DateTimeFormatter进行自定义转换。

of()方法指定年、月、日、时刻创建
// of方法直接传递对应的年、月、日
LocalDate localDate = LocalDate.of(2024, 3, 11);
System.out.println("localDate:"+localDate);
localDate = LocalDate.of(2024, Month.MARCH, 11);
System.out.println("localDate:"+localDate);
localDate = LocalDate.ofYearDay(2024, 71);
System.out.println("localDate:"+localDate); // 北京时间对应的时区
ZoneId chinaTimeZone = ZoneId.of("Asia/Shanghai");
// 创建一个 Instant,这里使用当前时间的 InstantInstant instant = Instant.now();
localDate = LocalDate.ofInstant(instant, chinaTimeZone);
System.out.println("localDate:"+localDate); // 使用ofEpochDay()方法,则EpochDay为从公元1970年1月1日(Unix纪元)开始的第多少天
localDate = LocalDate.ofEpochDay(LocalDate.now().toEpochDay());
System.out.println("localDate:"+localDate); LocalTime localTime = LocalTime.of(1, 30);
System.out.println("localTime:"+localTime);
localTime = LocalTime.of(1, 30, 30);
System.out.println("localTime:"+localTime); localTime = LocalTime.ofInstant(instant, chinaTimeZone);
System.out.println("localTime:"+localTime);
// 根据一天中的总秒数构建时间
localTime = LocalTime.ofSecondOfDay(localTime.toSecondOfDay());
System.out.println("localTime:"+localTime); LocalDateTime localDateTime = LocalDateTime.of(2024, 3, 11, 1, 30, 30);
System.out.println("localDateTime:"+localDateTime);
localDateTime = LocalDateTime.of(2024, Month.MARCH, 11, 1, 30, 30);
System.out.println("localDateTime:"+localDateTime);
// 使用LocalDate和LocalTime组合构造
localDateTime = LocalDateTime.of(localDate, localTime);
System.out.println("localDateTime:"+localDateTime); localDateTime = LocalDateTime.ofInstant(instant, chinaTimeZone);
System.out.println("localDateTime:"+localDateTime);

输出为:

localDate:2024-03-11
localDate:2024-03-11
localDate:2024-03-11
localDate:2024-03-11
localDate:2024-03-11
localTime:01:30
localTime:01:30:30
localTime:16:41:37.893310
localTime:16:41:37
localDateTime:2024-03-11T01:30:30
localDateTime:2024-03-11T01:30:30
localDateTime:2024-03-11T16:41:37
localDateTime:2024-03-11T16:41:37.893310
from()方法转换

from()方法TemporalAccessor类型(如ZonedDateTime)转换为相对应的日期或者时间。TemporalAccessor接口是一个用于读取或写入日期、时间或者日期时间的通用接口。

// 创建一个ZonedDateTime实例
ZonedDateTime zonedDateTime = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));
LocalTime localTime = LocalTime.from(zonedDateTime);
System.out.println("localTime:"+localTime); LocalDate localDate = LocalDate.from(zonedDateTime);
System.out.println("localDate:"+localDate); LocalDateTime localDateTime = LocalDateTime.from(zonedDateTime);
System.out.println("localDateTime:"+localDateTime);

输出为:

localTime:17:18:27.942911
localDate:2024-03-11
localDateTime:2024-03-11T17:18:27.942911
parse()方法转换

将字符串按照指定格式(可选)解析为对应的日期时间类。

LocalTime localTime = LocalTime.parse("17:25:30");
System.out.println("localTime:"+localTime);
localTime = LocalTime.parse("17:25:30", DateTimeFormatter.ofPattern("HH:mm:ss"));
System.out.println("localTime:"+localTime); LocalDate localDate = LocalDate.parse("2024-03-11");
System.out.println("localDate:"+localDate);
localDate = LocalDate.parse("2024/03/11", DateTimeFormatter.ofPattern("yyyy/MM/dd"));
System.out.println("localDate:"+localDate); LocalDateTime localDateTime = LocalDateTime.parse("2024-03-11T17:25:30");
System.out.println("localDateTime:"+localDateTime);
localDateTime = LocalDateTime.parse("2024/03/11 17:25:30", DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss"));
System.out.println("localDateTime:"+localDateTime);

输出为:

localTime:17:25:30
localTime:17:25:30
localDate:2024-03-11
localDate:2024-03-11
localDateTime:2024-03-11T17:25:30
localDateTime:2024-03-11T17:25:30

日期时间类的相互转换

LocalTime、LocalDate、LocalDateTime 相互转化
// LocalTime + LocalDate = LocalDateTime
LocalDateTime localDateTime = LocalTime.now().atDate(LocalDate.now());
System.out.println("localDateTime:"+localDateTime);
localDateTime = LocalDate.now().atTime(LocalTime.now());
System.out.println("localDateTime:"+localDateTime);
localDateTime = LocalDateTime.of(LocalDate.now(), LocalTime.now());
System.out.println("localDateTime:"+localDateTime); // LocalDateTime 转 LocalDate
LocalDate localDate = LocalDateTime.now().toLocalDate();
System.out.println("localDate:"+localDate);
// LocalDateTime 转 LocalTime
LocalTime localTime = LocalDateTime.now().toLocalTime();
System.out.println("localTime:"+localTime); // 获取今日开始时间 2024-03-11T00:00
localDateTime = LocalDate.now().atStartOfDay();
System.out.println("localDateTime:"+localDateTime);
// 获取今日开始时间 2024-03-11T00:00
LocalDateTime startDateTime = LocalDateTime.of(LocalDate.now(), LocalTime.MIN);
System.out.println("startDateTime:"+ startDateTime);
// 获取今日结束时间 2024-03-11T23:59:59.999999999
LocalDateTime endDateTime = LocalDateTime.of(LocalDate.now(), LocalTime.MAX);
System.out.println("endDateTime:"+ endDateTime);

输出为:

localDateTime:2024-03-11T18:04:22.348539
localDateTime:2024-03-11T18:04:22.370562
localDateTime:2024-03-11T18:04:22.370768
localDate:2024-03-11
localTime:18:04:22.371062
localDateTime:2024-03-11T00:00
startDateTime:2024-03-11T00:00
endDateTime:2024-03-11T23:59:59.999999999
String 与 LocalTime、LocalDate、LocalDateTime 相互转化

主要使用format 和 parse 进行转换,使用方法基本相同。使用 DateTimeFormatter.ofPattern() 定义时间格式,再进行转换。

DateTimeFormatter线程安全。

// LocalTime 转 String 自定义输出格式,例如:**时**分**秒 该转化的 00 不会被省略
String localTimeStr = LocalTime.now().format(DateTimeFormatter.ofPattern("HH时mm分ss秒"));
System.out.println("localTimeStr:"+localTimeStr); String localDateStr = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
System.out.println("localDateStr:"+localDateStr); // LocalDateTime 转 String
String localDateTimeStr = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
System.out.println("localDateTimeStr:"+localDateTimeStr); // String 转 LocalDateTime
LocalDateTime localDateTime = LocalDateTime.parse("2023-04-14 15:59:40", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
System.out.println("localDateTime:"+localDateTime);

输出结果:

localTimeStr:19时02分58秒
localDateStr:2024-03-11
localDateTimeStr:2024-03-11 19:02:58
localDateTime:2023-04-14T15:59:40
Date 与 LocalDate、LocalDateTime 相互转化
// Date 转 LocalDateTime
Date currentDate = new Date();
// 转换为Instant
Instant instant = currentDate.toInstant();
// 通过zoneId设置时区(这里使用系统时区),转换为带带时区的 ZoneDateTime
ZonedDateTime zonedDateTime = instant.atZone(ZoneId.systemDefault());
// 然后通过ZonedDateTime转换为LocalDateTime
LocalDateTime localDateTime = zonedDateTime.toLocalDateTime();
System.out.println("localDateTime:"+localDateTime);
// LocalDateTime 转 Date,同理也是通过ZonedDateTime转换为Date
Date localDateTimeToDate = Date.from(LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant());
System.out.println(localDateTimeToDate);
// Date转LocalDate 同理 LocalDateTime转换
LocalDate localDate = currentDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
System.out.println("localDate:"+localDate);
// LocalDate 转 Date 需要先将 LocalDate 转 LocalDateTime
Date localDateToDate = Date.from(LocalDate.now().atStartOfDay(ZoneId.systemDefault()).toInstant());

这里介绍一下ZoneId。java.time.ZoneId是Java 8中java.time包中用于表示时区的类。时区是地球上的地理位置,用于确定在该位置观察太阳升落以及规定当地居民生活和商业活动时间的标准时间。ZoneId使用IANA时区数据库提供的时区标识符,这个标识符是唯一的,这些标识符通常是地区/城市对,例如“Asia/Shanghai”代表中国上海所在的时区,America/New_York代表美国纽约城市。

其实例获取有两种方式:

  • ZoneId.systemDefault():获取系统默认的时区ID。
  • ZoneId.of(String zoneId):根据提供的时区ID字符串获取ZoneId实例。至于zoneId的值,可以查看源码。可以通过ZoneId.getAvailableZoneIds()查看获取。
Long 与 LocalDate、LocalDateTime 相互转化

时间戳转换。

long timeMillis = System.currentTimeMillis();

// 时间戳(Long) 转 LocalDateTime
LocalDateTime localDateTime = Instant.ofEpochMilli(timeMillis).atZone(ZoneOffset.ofHours(8)).toLocalDateTime();
System.out.println("localDateTime:"+localDateTime);
localDateTime = Instant.ofEpochMilli(timeMillis).atZone(ZoneId.systemDefault()).toLocalDateTime();
System.out.println("localDateTime:"+localDateTime);
// LocalDateTime 转 时间戳(Long) 秒级
long localDateTimeToSecond = LocalDateTime.now().toEpochSecond(ZoneOffset.ofHours(8));
System.out.println("localDateTimeToSecond:"+ localDateTimeToSecond);
// LocalDateTime 转 时间戳(Long) 毫秒级
long localDateTimeToMilliSecond = LocalDateTime.now().toInstant(ZoneOffset.ofHours(8)).toEpochMilli();
System.out.println("localDateTimeToMilliSecond:"+ localDateTimeToMilliSecond); // 时间戳(Long) 转 LocalDate
LocalDate localDate = Instant.ofEpochMilli(timeMillis).atZone(ZoneOffset.ofHours(8)).toLocalDate();
System.out.println("localDate:"+ localDate);
// LocalDate 转 时间戳(Long) 秒级
long localDateToSecond = LocalDate.now().atStartOfDay().toEpochSecond(ZoneOffset.ofHours(8));
System.out.println("localDateToSecond:"+ localDateToSecond);
// LocalDate 转 时间戳(Long) 毫秒级
long localDateToMilliSecond = LocalDate.now().atStartOfDay().toInstant(ZoneOffset.ofHours(8)).toEpochMilli();
System.out.println("localDateToMilliSecond:"+ localDateToMilliSecond);

输出结果为:

localDateTime:2024-03-11T19:37:02.335
localDateTime:2024-03-11T19:37:02.335
localDateTimeToSecond:1710157022
localDateTimeToMilliSecond:1710157022365
localDate:2024-03-11
localDateToSecond:1710086400
localDateToMilliSecond:1710086400000

java.time.ZoneOffset是Java8中java.time包内用来表示时区偏移量的类,它表示的是格林尼治标准时间或协调世界时间(UTC)基础上的固定偏移量。每一个时区都可以通过一个或多个偏移量来表示,比如“+02:00”表示比UTC时间快两个小时的时区偏移。

其实例创建有如下方式:

  • ZoneOffset.ofHours(int hours):根据小时数创建偏移量,例如 ZoneOffset.ofHours(2) 表示比UTC早2小时的时区。
  • ZoneOffset.ofHoursMinutes(int hours, int minutes):根据小时数和分钟数创建偏移量。
  • ZoneOffset.ofHoursMinutesSeconds(int hours, int minutes, int seconds):根据小时、分钟和秒数创建偏移量。
  • ZoneOffset.ofTotalSeconds(int totalSeconds):根据相对于UTC的总秒数创建偏移量。
  • ZoneOffset.of(String offsetId):根据偏移量ID(如 "+02:00")创建实例。

日期时间类的操作

日期时间的增减

java.time 包中日期时间类(如 LocalDateTimeLocalDate 和 LocalTime)可以通过plusXxx() 和 minusXxx() 方法,用于对日期时间对象进行加减操作,以增加或减少指定的时间或日期单位。

1、LocalDateTime 加减

  • plusHours(int hours)plusMinutes(int minutes)plusSeconds(int seconds):分别用于向 LocalDateTime 对象添加指定的小时数、分钟数和秒数。
  • plus(1, ChronoUnit.XXX):这里的 ChronoUnit 参数可以是 HOURSMINUTESSECONDS 等,也可以是 YEARSMONTHSDAYSWEEKS 等,用于向日期时间对象添加指定单位的数量。
  • plus(Duration duration):使用 Duration 对象来增加时间,Duration 可以包含秒和纳秒的精度。
  • plus(Period period):使用 Period 对象来增加日期,Period 可以表示年、月、日的数量。
  • plusXxx() 方法相对应,minusXxx() 方法用于从日期时间对象中减少指定的单位。例如 minusHours(int hours)minusMinutes(int minutes)minusSeconds(int seconds) 等方法用于减少小时、分钟、秒数。
// LocalDateTime 加减
LocalDateTime localDateTime = LocalDateTime.now();
// 以下为增加时、分、秒
LocalDateTime plusLocalDateTime = localDateTime.plusHours(1).plusMinutes(1).plusSeconds(1);
System.out.println("plusLocalDateTime:"+plusLocalDateTime);
plusLocalDateTime = localDateTime.plus(1, ChronoUnit.HOURS).plus(1, ChronoUnit.MINUTES).plus(1, ChronoUnit.SECONDS);
System.out.println("plusLocalDateTime:"+plusLocalDateTime);
plusLocalDateTime = localDateTime.plus(Duration.ofHours(1)).plus(Duration.of(1, ChronoUnit.MINUTES)).plus(Duration.of(1, ChronoUnit.SECONDS));
System.out.println("plusLocalDateTime:"+plusLocalDateTime); // 以下为增加年、月、日
plusLocalDateTime = localDateTime.plusYears(1).plusMonths(1).plusWeeks(1).plusDays(1);
System.out.println("plusLocalDateTime:"+plusLocalDateTime);
plusLocalDateTime = localDateTime.plus(1, ChronoUnit.YEARS).plus(1, ChronoUnit.MONTHS).plus(1, ChronoUnit.WEEKS).plus(1, ChronoUnit.DAYS);
System.out.println("plusLocalDateTime:"+plusLocalDateTime);
plusLocalDateTime = localDateTime.plus(Duration.of(1, ChronoUnit.YEARS)).plus(Duration.of(1, ChronoUnit.MONTHS)).plus(Duration.of(1, ChronoUnit.WEEKS)).plus(Duration.ofDays(1));
System.out.println("plusLocalDateTime:"+plusLocalDateTime);
plusLocalDateTime = localDateTime.plus(Period.ofYears(1)).plus(Period.ofMonths(1)).plus(Period.ofWeeks(1)).plus(Period.ofDays(1));
System.out.println("plusLocalDateTime:"+plusLocalDateTime); // 以下为减少时、分、秒
LocalDateTime minusLocalDateTime = localDateTime.minusHours(1).minusMinutes(1).minusSeconds(1);
System.out.println("minusLocalDateTime:"+minusLocalDateTime);
minusLocalDateTime = localDateTime.minus(1, ChronoUnit.HOURS).minus(1, ChronoUnit.MINUTES).minus(1, ChronoUnit.SECONDS);
System.out.println("minusLocalDateTime:"+minusLocalDateTime);
minusLocalDateTime = localDateTime.minus(Duration.ofHours(1)).minus(Duration.of(1, ChronoUnit.MINUTES)).minus(Duration.of(1, ChronoUnit.SECONDS));
System.out.println("minusLocalDateTime:"+minusLocalDateTime); // 以下为减少年、月、日
minusLocalDateTime = localDateTime.minusYears(1).minusMonths(1).minusWeeks(1).minusDays(1);
System.out.println("minusLocalDateTime:"+minusLocalDateTime);
minusLocalDateTime = localDateTime.minus(1, ChronoUnit.YEARS).minus(1, ChronoUnit.MONTHS).minus(1, ChronoUnit.WEEKS).minus(1, ChronoUnit.DAYS);
System.out.println("minusLocalDateTime:"+minusLocalDateTime);
minusLocalDateTime = localDateTime.minus(Duration.of(1, ChronoUnit.YEARS)).minus(Duration.of(1, ChronoUnit.MONTHS)).minus(Duration.of(1, ChronoUnit.WEEKS)).minus(Duration.ofDays(1));
System.out.println("minusLocalDateTime:"+minusLocalDateTime);
minusLocalDateTime = localDateTime.minus(Period.ofYears(1)).minus(Period.ofMonths(1)).minus(Period.ofWeeks(1)).minus(Period.ofDays(1));
System.out.println("plusLocalDateTime:"+minusLocalDateTime);

2、LocalDate 加减

  • 同样的,plusYears(int years)plusMonths(int months)plusDays(int days) 分别用于增加年、月、日。
  • plus(1, ChronoUnit.XXX)plus(Duration/Period duration/period) 方法在此同样适用,用于增加指定的日期单位。
  • plusXxx() 方法相对应,minusXxx() 方法用于从日期时间对象中减少指定的单位。例如 minusYears(int years)minusMonths(int months)minusWeeks(int weeks)minusDays(int days) 等方法用于减少年、月、周、天数。
// LocalDate 加减
LocalDate localDate = LocalDate.now();
LocalDate plusLocalDate = localDate.plusYears(1).plusMonths(1).plusWeeks(1).plusDays(1);
System.out.println("plusLocalDate:"+plusLocalDate);
plusLocalDate = localDate.plus(1, ChronoUnit.YEARS).plus(1, ChronoUnit.MONTHS).plus(1, ChronoUnit.WEEKS).plus(1, ChronoUnit.DAYS);
System.out.println("plusLocalDate:"+plusLocalDate);
plusLocalDate = localDate.plus(Duration.of(1, ChronoUnit.YEARS)).plus(Duration.of(1, ChronoUnit.MONTHS)).plus(Duration.of(1, ChronoUnit.WEEKS)).plus(Duration.ofDays(1));
System.out.println("plusLocalDate:"+plusLocalDate);
plusLocalDate = localDate.plus(Period.ofYears(1)).plus(Period.ofMonths(1)).plus(Period.ofWeeks(1)).plus(Period.ofDays(1));
System.out.println("plusLocalDate:"+plusLocalDate); LocalDate minusLocalDate = localDate.minusYears(1).minusMonths(1).minusWeeks(1).minusDays(1);
System.out.println("minusLocalDate:"+minusLocalDate);
minusLocalDate = localDate.minus(1, ChronoUnit.YEARS).minus(1, ChronoUnit.MONTHS).minus(1, ChronoUnit.WEEKS).minus(1, ChronoUnit.DAYS);
System.out.println("minusLocalDate:"+minusLocalDate);
minusLocalDate = localDate.minus(Duration.of(1, ChronoUnit.YEARS)).minus(Duration.of(1, ChronoUnit.MONTHS)).minus(Duration.of(1, ChronoUnit.WEEKS)).minus(Duration.ofDays(1));
System.out.println("minusLocalDate:"+minusLocalDate);
minusLocalDate = localDate.minus(Period.ofYears(1)).minus(Period.ofMonths(1)).minus(Period.ofWeeks(1)).minus(Period.ofDays(1));
System.out.println("minusLocalDate:"+minusLocalDate);

3、LocalTime 加减

  • plusHours(int hours)plusMinutes(int minutes)plusSeconds(int seconds):分别用于向 LocalTime 对象添加指定的小时数、分钟数和秒数。
  • 同样支持 plus(1, ChronoUnit.XXX) 和 plus(Duration duration) 方法,用于增加时间单位。
  • 与 plusXxx() 方法相对应,minusXxx() 方法用于从日期时间对象中减少指定的单位。minus(1, ChronoUnit.XXX)minus(Duration duration)minus(Period period) 方法也分别用于减少指定的日期或时间单位。例如 minusHours(int hours)minusMinutes(int minutes)minusSeconds(int seconds) 等方法用于减少小时、分钟、秒数。
// LocalTime 加减
LocalTime localTime = LocalTime.now();
LocalTime plusLocalTime = localTime.plusHours(1).plusMinutes(1).plusSeconds(1);
System.out.println("plusLocalTime:"+plusLocalTime);
plusLocalTime = localTime.plus(1, ChronoUnit.HOURS).plus(1, ChronoUnit.MINUTES).plus(1, ChronoUnit.SECONDS);
System.out.println("plusLocalTime:"+plusLocalTime);
plusLocalTime = localTime.plus(Duration.ofHours(1)).plus(Duration.of(1, ChronoUnit.MINUTES)).plus(Duration.of(1, ChronoUnit.SECONDS));
System.out.println("plusLocalTime:"+plusLocalTime); LocalTime minusLocalTime = localTime.minusHours(1).minusMinutes(1).minusSeconds(1);
System.out.println("minusLocalTime:"+minusLocalTime);
minusLocalTime = localTime.minus(1, ChronoUnit.HOURS).minus(1, ChronoUnit.MINUTES).minus(1, ChronoUnit.SECONDS);
System.out.println("minusLocalDateTime:"+minusLocalTime);
minusLocalTime = localTime.minus(Duration.ofHours(1)).minus(Duration.of(1, ChronoUnit.MINUTES)).minus(Duration.of(1, ChronoUnit.SECONDS));
System.out.println("minusLocalDateTime:"+minusLocalTime);
日期时间修改指定值

LocalDateLocalTimeLocalDateTimeZonedDateTime可以通过相对应的withXxx()方法修改指定的值。

1、LocalDate

  • LocalDate.withYear(int year):修改年份字段。
  • LocalDate.withMonth(int month):修改月份字段(注意月份是从1开始计数的)。
  • LocalDate.withDayOfMonth(int dayOfMonth):修改日期字段。
LocalDate localDate = LocalDate.of(2024, 3, 12);
LocalDate newDate = localDate.withYear(2025).withMonth(4).with(ChronoField.DAY_OF_MONTH, 13);
System.out.println("newDate:"+newDate);

2、LocalTime

  • LocalTime.withHour(int hour):修改小时字段。
  • LocalTime.withMinute(int minute):修改分钟字段。
  • LocalTime.withSecond(int second):修改秒字段。
  • LocalTime.withNano(int nanoOfSecond):修改纳秒字段。
LocalTime localTime = LocalTime.of(17, 25, 30);
LocalTime newTime = localTime.withHour(18).withMinute(26).with(ChronoField.SECOND_OF_MINUTE, 31);
System.out.println("newTime:"+newTime);

3、LocalDateTime

  • LocalDateTime.withYear(int year)
  • LocalDateTime.withMonth(int month)
  • LocalDateTime.withDayOfMonth(int dayOfMonth)
  • LocalDateTime.withHour(int hour)
  • LocalDateTime.withMinute(int minute)
  • LocalDateTime.withSecond(int second)
  • LocalDateTime.withNano(int nanoOfSecond)
LocalDateTime localDateTime = LocalDateTime.of(2024, 3, 12, 17, 25, 30);
LocalDateTime newDateTime = localDateTime.withYear(2025).withMonth(4).with(ChronoField.DAY_OF_MONTH, 13).withHour(18).withMinute(26).with(ChronoField.SECOND_OF_MINUTE, 31);
System.out.println("newDateTime:"+ newDateTime);

4、ZonedDateTime

  • 除了上述的日期和时间字段外,还有时区相关的 withZoneSameInstant(ZoneId zone) 方法,可以改变时区的同时保持同一瞬间不变。
ZonedDateTime zonedDateTime = ZonedDateTime.of(2024, 3, 12, 17, 25, 30, 0, ZoneId.of("Europe/London"));
ZonedDateTime newZonedDateTime = zonedDateTime.withZoneSameInstant(ZoneId.of("America/New_York"));
System.out.println("newZonedDateTime:"+ newZonedDateTime);

除此之外,调整日期时间还可以通过TemporalAdjustersTemporalAdjuster 是一个函数式接口,用于根据给定的规则调整日期时间对象。Java8的 java.time.temporal 包中预定义了一系列常用的 TemporalAdjuster 实现,例如获取下一个工作日、月初、月末等。

LocalDate date = LocalDate.of(2024, 3, 11);
// 下一个工作日
LocalDate nextWorkingDay = date.with(TemporalAdjusters.next(DayOfWeek.MONDAY)); // 如果11号不是周一,则返回下一个周一的日期
// 下一个月的第一天
LocalDate firstDayNextMonth = date.with(TemporalAdjusters.firstDayOfMonth()); // 返回4月1日
// 当月的最后一个工作日
LocalDate lastWorkingDay = date.with(TemporalAdjusters.lastInMonth(DayOfWeek.FRIDAY)); // 返回3月最后一个周五的日期 // 自定义 TemporalAdjuster
TemporalAdjuster adjuster = temporal -> {
return temporal.plusDays(10).with(TemporalAdjusters.lastDayOfMonth());
};
LocalDate tenthDayNextMonthEnd = date.with(adjuster); // 返回4月最后一个日期,前提是先加10天
日期时间的比较

在Java8及其以后版本的日期时间API中,isBefore() 和 isAfter() 方法是 java.time 包中的 LocalDateLocalTimeLocalDateTimeZonedDateTime 等日期时间类所共有的方法,用于比较两个日期时间对象的先后顺序。

isBefore()

  • 此方法用于判断当前对象是否早于另一个日期时间对象。
  • 如果当前对象的时间点在参数对象之前,则返回 true;否则返回 false
LocalDate date1 = LocalDate.of(2024, 3, 11);
LocalDate date2 = LocalDate.of(2024, 3, 12);
boolean isEarlier = date1.isBefore(date2); // 返回 true,因为 date1 在 date2 之前

isAfter()

  • 此方法用于判断当前对象是否晚于另一个日期时间对象。
  • 如果当前对象的时间点在参数对象之后,则返回 true;否则返回 false
LocalDateTime time1 = LocalDateTime.of(2024, 3, 11, 10, 0);
LocalDateTime time2 = LocalDateTime.of(2024, 3, 11, 9, 0);
boolean isLater = time1.isAfter(time2); // 返回 true,因为 time1 在 time2 之后

compareTo()

在Java 8的 java.time 包中,大部分日期时间类如 LocalDateLocalTimeLocalDateTimeZonedDateTime 都实现了 Comparable 接口,从而可以直接使用 compareTo() 方法进行比较。compareTo() 方法用于比较两个日期时间对象的先后顺序,返回值含义如下:

  • 如果当前对象早于(时间点在前)参数对象,返回负数。
  • 如果当前对象等于参数对象,返回0。
  • 如果当前对象晚于(时间点在后)参数对象,返回正数。
LocalDate date1 = LocalDate.of(2024, 3, 11);
LocalDate date2 = LocalDate.of(2024, 3, 12); int comparisonResult = date1.compareTo(date2); if (comparisonResult < 0) {
System.out.println("date1 is before date2");
} else if (comparisonResult > 0) {
System.out.println("date1 is after date2");
} else {
System.out.println("date1 is equal to date2");
} LocalDateTime dateTime1 = LocalDateTime.of(2024, 3, 11, 10, 30);
LocalDateTime dateTime2 = LocalDateTime.of(2024, 3, 11, 11, 00); int timeComparisonResult = dateTime1.compareTo(dateTime2);
其他操作

在Java8的 java.time 包中,各个日期时间类如 LocalDateLocalTimeLocalDateTime 提供了一系列 get 方法,用于获取特定字段的值。

获取日期中的特定字段:

LocalDate date = LocalDate.of(2024, 3, 11);
int dayOfMonth = date.getDayOfMonth(); // 获取当月的第几天,此处返回11
int monthValue = date.getMonthValue(); // 获取月份值,此处返回3
Month month = date.getMonth(); // 获取Month枚举,此处返回March
int year = date.getYear(); // 获取年份,此处返回2024

对于时间部分,类似地可以获取小时、分钟、秒和纳秒:

LocalTime time = LocalTime.of(19, 30, 45);
int hour = time.getHour(); // 获取小时数,此处返回10
int minute = time.getMinute(); // 获取分钟数,此处返回30
int second = time.getSecond(); // 获取秒数,此处返回45
int nano = time.getNano(); // 获取纳秒数

在SpringBoot中使用

SpringBoot默认集成了Jackson作为JSON处理库,Jackson已经能自动处理 LocalDateLocalTime 和 LocalDateTime 类型。

如果需要使用自定义日期时间格式,我们有两种方式:

手动更改全局配置: 如果需要自定义日期格式,可以通过 ObjectMapper 的配置类来注册自定义的日期格式化器:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
</dependency>
@Configuration
public class JacksonConfig {
@Bean
public Jackson2ObjectMapperBuilderCustomizer jsonCustomizer() {
return builder -> {
builder.simpleDateFormat("yyyy-MM-dd");
// 使用Java 8时间API的日期格式器
builder.dateFormat(new StdDateFormat().withColonInTimeZone(true));
// 注册LocalDateTime的序列化和反序列化模块
builder.modules(new JavaTimeModule());
};
}
}

手动绑定格式化配置

SpringBoot支持自动绑定HTTP请求参数到控制器方法参数中,包括 LocalDateLocalTime 和 LocalDateTime 类型。客户端需发送符合日期格式的字符串,Spring Boot会自动转换成相应类型。

@PostMapping("/events")
public ResponseEntity<Event> createEvent(@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate date,
@RequestParam @DateTimeFormat(pattern = "HH:mm:ss") LocalTime startTime,
@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime timestamp) {
// ...
}

或者请求或者响应VO中:

 public static class ResponseVO{

		@DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalDate date; @DateTimeFormat(pattern = "HH:mm:ss")
private LocalTime startTime; @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime timestamp;
}

Mybatis中使用

在MyBatis中查询MySQL数据库时,使用Java 8的 java.time.LocalDatejava.time.LocalTime 和 java.time.LocalDateTime类型。

  1. 数据库表结构: 在MySQL数据库中,通常需要使用适合的日期时间类型来存储这些Java 8的日期时间对象。例如:

    • LocalDate 对应MySQLDATE类型。
    • LocalTime 对应MySQL 的 TIME 类型。
    • LocalDateTime 对应MySQL的 DATETIMETIMESTAMP类型。
CREATE TABLE `test_date`(
`id` BIGINT ( 20 ) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增主键',
test_local_date DATE ,
test_local_time TIME,
test_local_date_time DATETIME,
PRIMARY KEY ( `id` )
)
ENGINE = INNODB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8mb4 COMMENT = '日期时间测试';
  1. 实体类映射: 在Java实体类中,对应字段应声明为 LocalDateLocalTime 或 LocalDateTime 类型。
@Data
public class TestDate implements Serializable {
/**
* 自增主键
*/
private Long id; private LocalDate testLocalDate; private LocalTime testLocalTime; private LocalDateTime testLocalDateTime; private static final long serialVersionUID = 1L;
}

3.MyBatis配置

  • 自动类型转换:如果你使用的是较新的MyBatis版本(>=3.4.5),MyBatis已经内置了对Java 8日期时间类型的处理。这意味着在执行SQL查询时,MyBatis会自动将数据库中的日期时间字段转换为相应的Java8类型。
@Test
public void testInsertDate(){
TestDate testDate = new TestDate();
testDate.setTestLocalDate(LocalDate.of(2024, 3, 12));
testDate.setTestLocalTime(LocalTime.of(20,10,30));
testDate.setTestLocalDateTime(LocalDateTime.of(2024, 3, 12,20,10,30,0));
testDateMapper.insert(testDate);
} @Test
public void testQueryDate(){
TestDate testDate = testDateMapper.selectByPrimaryKey(1L);
System.out.println("testLocalDate:"+testDate.getTestLocalDate());
System.out.println("testLocalTime:"+testDate.getTestLocalTime());
System.out.println("testLocalDateTime:"+testDate.getTestLocalDateTime());
}

结论

综上所述,本文深入探讨了Java 8引入的全新日期时间API相较于传统的Date和Calendar类的优势及实际应用。鉴于Java 8新日期时间API在设计上的先进性和易用性,我们强烈建议开发者积极采纳并替换掉陈旧的Date和Calendar类,转而采用如LocalDate、LocalDateTime、ZonedDateTime等现代日期时间类。

Java 8新日期时间API提供了更为清晰、直观的操作接口,支持不可变对象设计模式,增强了类型安全性,并具备丰富的日期时间运算、解析与格式化功能,显著提高了代码质量与可读性。此外,新API对日期时间单位的精确度控制、时区管理以及与其他日期时间规范的兼容性等方面均表现出卓越的表现力和灵活性,使得开发者在处理各类复杂日期时间逻辑时能够更加得心应手,提升开发效率。

因此,无论是处于对代码现代化改造的需求,还是出于提高开发效率和程序稳定性的考量,迁移到Java 8的新日期时间API无疑是明智之举。通过充分利用这些强大且功能完备的工具,开发者将在日期时间处理领域实现飞跃,为项目的长期维护和发展打下坚实基础。

本文已收录于我的个人博客:码农Academy的博客,专注分享Java技术干货,包括Java基础、Spring Boot、Spring Cloud、Mysql、Redis、Elasticsearch、中间件、架构设计、面试题、程序员攻略等

还在用Calendar操作Date?Java8都弃用了,还不知道它的这款强大的工具吗?的更多相关文章

  1. java中的日期操作Calendar和Date

    1. Calendar转Date Calendar calendar = Calendar.getInstance(); Date date = calendar.getTime(); 2. Date ...

  2. Java 时间类-Calendar、Date、LocalDate/LocalTime

    1.Date 类 java.util.Date是一个"万能接口",它包含日期.时间,还有毫秒数,如果你只想用java.util.Date存储日期,或者只存储时间,那么,只有你知道哪 ...

  3. Java 中Calendar、Date、SimpleDateFormat学习总结

    在之前的项目中,经常会遇到Calendar,Date的一些操作时间的类,并且总会遇到时间日期之间的格式转化问题,虽然做完了但是总是忘记,记不清楚,每次还都要查找资料.今天总结一下,加深印象. Cale ...

  4. Calendar代替Date常用方法小记

    业务逻辑中遇到日期的相关操作,整理一下小做总结 日期操作离不开 java.util.Calendar 和 java.util.Date ,实体类中日期字段设计也可能用到java.sql.Date.但目 ...

  5. Calendar /String /Date 转换

    Calendar 转化 String Calendar calendat = Calendar.getInstance(); SimpleDateFormat sdf = new SimpleDate ...

  6. 核心思想:百度网盘怎么盈利(互联网的高速更新决定了:亏钱你还有点机会,放弃连门都进不了),战略预备队 good

    百度做网盘很大程度就是为了防止别人依靠网盘做大和积累点技术储备.腾讯邮箱怎么赚钱?腾讯影音怎么赚钱?互联网的高速更新决定了,一些你看不起眼的软件很可能就会席卷整个市场,所以互联网大佬宁愿一些项目亏钱也 ...

  7. 推荐Calendar操作日期

    package com.example.demo.Calender; import java.text.SimpleDateFormat;import java.util.Calendar;impor ...

  8. 对每一个IO操作的返回都要进行判断

    对每一个IO操作的返回都要进行判断 我们业务代码中有很多进行mysql.redis.文件.curl等的io操作,对每一个io操作我们都要对其返回值进行判断,然后做对应的处理,加日志信息或者抛出异常状态 ...

  9. calendar merge date

    calendar merge date componentDidMount () { const { monthDays, // monthDates, } = this.props; const d ...

  10. 还在争论WPS、Office哪个更好用?这款云办公工具才是真的香!

    最近,金山WPS更新狠狠的刷了一波存在感.尤其是xlookup函数,着实是有被惊艳到,也让大家看到了国产办公软件的进步.甚至有人认为WPS已经超越了传统的办公软件--微软office.WPS的优点固然 ...

随机推荐

  1. 小知识:在Exadata平台上使用ExaWatcher收集信息

    在非Exadata平台上,我们通常会使用DBA已经很熟悉的OSW,如果有不熟悉的朋友可以参考我之前的随笔初步了解OSW: OSW 快速安装部署 OSW Analyzer分析oswbb日志发生异常 而在 ...

  2. mysql 索引优化 explain,复合索引,联合索引,优化 user_base 和 log_login 实战

    本节是关于MySQL的复合索引相关的知识,两个或更多个列上的索引被称作复合索引,本文主要介绍了mysql 联合索引生效的条件及失效的条件 对于复合索引:Mysql从左到右的使用索引中的字段,一个查询可 ...

  3. [JVM]关于swap的理解

    关于swap的理解 概念 swap就是内存交换的意思. 计算机内存分为物理内存和虚拟内存.物理内存就是计算机实际内存的大小:虚拟内存是磁盘空间里开辟出一部分,是虚拟出来的内存空间,所以也叫磁盘缓存. ...

  4. NC19857 最后的晚餐(dinner)

    题目链接 题目 题目描述 ​ **YZ(已被和谐)的食堂实在是太挤辣!所以Apojacsleam现在想邀请他的一些好友去校外吃一顿饭,并在某酒店包下了一桌饭. ​ 当Apojacsleam和他的同学们 ...

  5. PL/SQL相关的数据字典

    PL/SQL相关的数据字典 http://www.oracle.com/technetwork/issue-archive/2012/12-nov/o62plsql-1851968.html 有时候, ...

  6. 【图论#02】岛屿系列题(数量、周长、最大面积),flood fill算法的代码实现与优化

    岛屿数量 给你一个由 '1'(陆地)和 '0'(水)组成的的二维网格,请你计算网格中岛屿的数量. 岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成. 此外,你可以假设该网 ...

  7. 详细的BoltDB学习记录文档

    最近项目中用到了boltdb这个go开发的key/value 数据库,但是之前并有接触过,所以特意去看了官方,也找了些资料,网上找的资料要不就是官方文档的翻译,要不就是简单的介绍一点,都不是很全,所以 ...

  8. 第一百一十五篇: JS集合引用类型Map

    好家伙,本篇为<JS高级程序设计>第六章"集合引用类型"学习笔记   1.Map ECMAScript6以前,在JavaScript中实现"键/值" ...

  9. Python实现ARP攻击

    目录 概述 ARP协议 IP转MAC 结构 ARP扫描 开始欺骗 中间人 其他 ARP老化 防御 windows linux 概述 高中的时候,学校有一个商店,会放开WIFI给偷偷带手机去学校的我们使 ...

  10. 三: MySQL的数据目录

    # MySQL的数据目录 1. MySQL8的主要目录结构 1.1 数据库文件的存放路径 MySQL数据库文件的存放路径:/var/lib/mysql/ MySQL服务器程序在启动时会到文件系统的某个 ...