Java 时间日期
Java 8 推出了全新的日期时间API。
Java处理日期、日历和时间的不足之处:将 java.util.Date 设定为可变类型,以及 SimpleDateFormat 的非线程安全使其应用非常受限。然后就在 java8 上面增加新的特性。
在旧版的 Java 中,日期时间 API 存在诸多问题,例如:

  • 非线程安全java.util.Date 是非线程安全的,所有的日期类都是可变的,这是Java日期类最大的问题之一。
  • 设计很差:Java的日期/时间类的定义并不一致,在java.utiljava.sql的包中都有日期类,此外用于格式化和解析的类被定义在java.text包中。java.util.Date同时包含日期和时间,而java.sql.Date仅包含日期,将其纳入java.sql包并不合理。另外这两个类都有相同的名字,这本身就是一个非常糟糕的设计。
  • 时区处理麻烦:日期类并不提供国际化,没有时区支持,因此 Java 引入了java.util.Calendarjava.util.TimeZone类,但他们同样存在上述所有的问题。

因为上面这些原因,诞生了第三方库Joda-Time,可以替代 Java 的时间管理 API 。
Java 8 中新的时间和日期管理 API 深受Joda-Time影响,并吸收了很多Joda-Time的精华,新的java.time包包含了所有关于日期、时间、时区、Instant(跟日期类似但是精确到纳秒)、Duration(持续时间)和时钟操作的类。
新设计的 API 认真考虑了这些类的不变性,如果某个实例需要修改,则返回一个新的对象。
全新API的众多好处之一就是,明确了日期时间概念,例如:瞬时(instant)长短(duration)日期时间时区周期
同时继承了Joda 库按人类语言和计算机各自解析的时间处理方式。不同于老版本,新API基于ISO标准日历系统,java.time包下的所有类都是不可变类型而且线程安全。

  • Instant:瞬时实例。
  • LocalDate:本地日期,不包含具体时间 例如:2014-01-14 可以用来记录生日、纪念日、加盟日等。
  • LocalTime:本地时间,不包含日期。
  • LocalDateTime:组合了日期和时间,但不包含时差和时区信息。
  • ZonedDateTime:最完整的日期时间,包含时区和相对UTC或格林威治的时差。

新API还引入了 ZoneOffSetZoneId 类,使得解决时区问题更为简便。解析、格式化时间的 DateTimeFormatter类也全部重新设计。

1、Clock类——获取当前的纳秒时间和日期

Clock类使用时区来返回当前的纳秒时间和日期。Clock可以替代System.currentTimeMillis()TimeZone.getDefault(),实例如下:

  1. final Clock clock = Clock.systemUTC();
  2. System.out.println( clock.instant() );
  3. System.out.println( clock.millis() );

输出结果是

  1. 2021-02-24T12:24:54.678Z
  2. 1614169494678

2、LocalDateLocalTimeLocalDateTime

LocalDateLocalTimeLocalDateTime类的实例是不可变的对象,分别表示使用ISO-8601日历系统的日期、时间、日期和时间。它们提供了简单的日期或时间,并不包含当前的时间信息,也不包含与时区相关的信息。
注:ISO-8601日历系统是国际标准化组织指定的现代公民的日期和时间的表示法。
核心类

  1. LocalDate:不包含具体时间的日期。
  2. LocalTime:不含日期的时间。
  3. LocalDateTime:包含了日期及时间。

LocalDate常用API——只获取年月日

  • getYear() int 获取当前日期的年份
  • getMonth() Month 获取当前日期的月份对象
  • getMonthValue() int 获取当前日期是第几月
  • getDayOfWeek() DayOfWeek 表示该对象表示的日期是星期几
  • getDayOfMonth() int 表示该对象表示的日期是这个月第几天
  • getDayOfYear() int 表示该对象表示的日期是今年第几天
  • withYear(int year) LocalDate 修改当前对象的年份
  • withMonth(int month) LocalDate 修改当前对象的月份
  • withDayOfMonth(int dayOfMonth) LocalDate 修改当前对象在当月的日期
  • plusYears(long yearsToAdd) LocalDate 当前对象增加指定的年份数
  • plusMonths(long monthsToAdd) LocalDate 当前对象增加指定的月份数
  • plusWeeks(long weeksToAdd) LocalDate 当前对象增加指定的周数
  • plusDays(long daysToAdd) LocalDate 当前对象增加指定的天数
  • minusYears(long yearsToSubtract) LocalDate 当前对象减去指定的年数
  • minusMonths(long monthsToSubtract) LocalDate 当前对象减去注定的月数
  • minusWeeks(long weeksToSubtract) LocalDate 当前对象减去指定的周数
  • minusDays(long daysToSubtract) LocalDate 当前对象减去指定的天数
  • compareTo(ChronoLocalDate other) int 比较当前对象和other对象在时间上的大小,返回值如果为正,则当前对象时间较晚,
  • isBefore(ChronoLocalDate other) boolean 比较当前对象日期是否在other对象日期之前
  • isAfter(ChronoLocalDate other) boolean 比较当前对象日期是否在other对象日期之后
  • isEqual(ChronoLocalDate other) boolean 比较两个日期对象是否相等

    创建LocalDate

    1. //获取当前年月日
    2. LocalDate localDate = LocalDate.now();
    3. //构造指定的年月日
    4. LocalDate localDate1 = LocalDate.of(2019, 9, 10);

    获取年、月、日、星期几

    1. int year = localDate.getYear();
    2. int year1 = localDate.get(ChronoField.YEAR);
    3. Month month = localDate.getMonth();
    4. int month1 = localDate.get(ChronoField.MONTH_OF_YEAR);
    5. int day = localDate.getDayOfMonth();
    6. int day1 = localDate.get(ChronoField.DAY_OF_MONTH);
    7. DayOfWeek dayOfWeek = localDate.getDayOfWeek();
    8. int dayOfWeek1 = localDate.get(ChronoField.DAY_OF_WEEK);

    LocalTime——只获取几点几分几秒

    创建LocalTime

    1. LocalTime localTime = LocalTime.of(13, 51, 10);
    2. LocalTime localTime1 = LocalTime.now();

    获取时分秒

    1. //获取小时
    2. int hour = localTime.getHour();
    3. int hour1 = localTime.get(ChronoField.HOUR_OF_DAY);
    4. //获取分
    5. int minute = localTime.getMinute();
    6. int minute1 = localTime.get(ChronoField.MINUTE_OF_HOUR);
    7. //获取秒
    8. int second = localTime.getSecond();
    9. int second1 = localTime.get(ChronoField.SECOND_OF_MINUTE);

    LocalDateTime——获取年月日时分秒,等于LocalDate+LocalTime

    创建LocalDateTime

    1. LocalDateTime localDateTime = LocalDateTime.now();
    2. LocalDateTime localDateTime1 = LocalDateTime.of(2019, Month.SEPTEMBER, 10, 14, 46, 56);
    3. LocalDateTime localDateTime2 = LocalDateTime.of(localDate, localTime);
    4. LocalDateTime localDateTime3 = localDate.atTime(localTime);
    5. LocalDateTime localDateTime4 = localTime.atDate(localDate);

    获取LocalDate

    1. LocalDate localDate2 = localDateTime.toLocalDate();

    获取LocalTime

    1. LocalTime localTime2 = localDateTime.toLocalTime();

    3、Instant类——时间戳、获取秒数

    创建Instant对象

    1. Instant instant = Instant.now();

    获取秒数

    1. long currentSecond = instant.getEpochSecond();

    获取毫秒数

    1. long currentMilli = instant.toEpochMilli();
    1. // 2.Instant:时间戳(以Unix元年:1970年1月1日 00:00:00 到某个时间之间的毫秒值
    2. @Test
    3. public void instantTest() {
    4. // 默认获取UTC时区
    5. Instant instant = Instant.now();
    6. System.out.println(instant);
    7. // 在原来的时间上加8个小时
    8. OffsetDateTime offsetDateTime = instant.atOffset(ZoneOffset.ofHours(8));
    9. System.out.println(offsetDateTime);
    10. System.out.println(instant.toEpochMilli());
    11. // 在Unix元年时间戳加60秒
    12. Instant ofEpochSecond = Instant.ofEpochSecond(60);
    13. System.out.println(ofEpochSecond);
    14. }

    4、时间计算

    Duration:计算两个时间的时间差

    1. /**
    2. * Period:计算两个日期之间的差
    3. */
    4. @Test
    5. public void calTimeTest() throws InterruptedException {
    6. System.out.println("------------计算两个时间之间的间隔-----------");
    7. Instant now = Instant.now();
    8. Thread.sleep(1000);
    9. Instant end = Instant.now();
    10. Duration duration = Duration.between(now, end);
    11. System.out.println(duration.toMillis());
    12. System.out.println("--------------------------");
    13. LocalTime localTime = LocalTime.now();
    14. Thread.sleep(1000);
    15. LocalTime l = LocalTime.now();
    16. Duration between = Duration.between(localTime, l);
    17. System.out.println(between.toMillis());
    18. }

    Period:计算两个日期之间的差

    1. /**
    2. * Duration:计算两个时间的时间差
    3. */
    4. @Test
    5. public void calTimeTest() throws InterruptedException {
    6. System.out.println("------------计算两个日期之间的间隔-----------");
    7. LocalDate localDate = LocalDate.of(2025, 6, 12);
    8. LocalDate date = LocalDate.now();
    9. Period period = Period.between(date, localDate);
    10. System.out.println(period.getYears());
    11. System.out.println(period.getMonths());
    12. System.out.println(period.getDays());
    13. }

    5、日期的操纵-时间校正器

    TemporalAdjuster:时间校正器

    有时可能需要获取将日期调整至“下个周日”等操作。

    1. LocalDate nextSunday = LocalDate.now().with(
    2. TemporalAdjusters.next(DayOfWeek.SUNDAY)
    3. );
    4. // 修改年为2019
    5. localDateTime = localDateTime.withYear(2020);
    6. // 修改为2022
    7. localDateTime = localDateTime.with(ChronoField.YEAR, 2022);

    TemporalAdjusters:该类通过静态方法提供了大量的常用TemporalAdjuster的实现。

    1. // TemporalAdjuster : 时间校正器
    2. @Test
    3. public void temporalAdjusterTest() {
    4. LocalDateTime now = LocalDateTime.now();
    5. System.out.println(now);
    6. LocalDateTime localDateTime = now.withDayOfMonth(10);
    7. System.out.println(localDateTime);
    8. LocalDateTime friday = now.with(TemporalAdjusters.next(DayOfWeek.FRIDAY));
    9. System.out.println(friday);
    10. // 自定义:下一个工作日
    11. LocalDateTime nextWorkDay = now.with((d) -> {
    12. LocalDateTime dateTime = (LocalDateTime) d;
    13. DayOfWeek dayOfWeek = dateTime.getDayOfWeek();
    14. if (dayOfWeek.equals(DayOfWeek.FRIDAY)) {
    15. return dateTime.plusDays(3);
    16. } else if (dateTime.equals(DayOfWeek.FRIDAY)) {
    17. return dateTime.plusDays(2);
    18. } else {
    19. return dateTime.plusDays(1);
    20. }
    21. });
    22. System.out.println(nextWorkDay);
    23. }

    通过firstDayOfYear()返回了当前日期的第一天日期

    ```java import java.time.LocalDate; import static java.time.temporal.TemporalAdjusters.firstDayOfYear;

public class TestTemporalAdjusters {

  1. public static void main(String[] args) {
  2. LocalDate.now().with(firstDayOfYear());
  3. }

}

  1. <a name="NOLtb"></a>
  2. ## 6、`DateTimeFormatter`——格式化时间和日期
  3. <a name="37cef521"></a>
  4. ### 解析时间
  5. ```java
  6. LocalDate localDate1 = LocalDate.parse("20190910", DateTimeFormatter.BASIC_ISO_DATE);
  7. LocalDate localDate2 = LocalDate.parse("2019-09-10", DateTimeFormatter.ISO_LOCAL_DATE);

和SimpleDateFormat相比,DateTimeFormatter是线程安全的。

  1. // DateTimeFormatter:格式化时间日期
  2. @Test
  3. public void dateTimeFormatterTest() {
  4. // 使用官方提供的格式
  5. DateTimeFormatter ISO_DATE = DateTimeFormatter.ISO_DATE;
  6. LocalDateTime now = LocalDateTime.now();
  7. String formatDate = now.format(ISO_DATE);
  8. System.out.println(formatDate);
  9. // 自定义格式
  10. DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss");
  11. String selfDate = dateTimeFormatter.format(now);
  12. System.out.println(selfDate);
  13. // 字符串日期转回日期类型
  14. LocalDateTime parseDate = now.parse(selfDate, dateTimeFormatter);
  15. System.out.println(parseDate);
  16. }

Date类型的日期转换为LocalDateTime

  1. /**
  2. * 将Date类型的日期转换为LocalDateTime
  3. *
  4. * @param date Date类型的对象
  5. * @return LocalDateTime
  6. * @author @author Fcant 下午 18:27 2020/6/30/0030
  7. */
  8. public static LocalDateTime dateToLocalDateTime(Date date) {
  9. return LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
  10. }

LocalDate日期进行格式化

  1. /**
  2. * 将LocalDate日期进行格式化
  3. *
  4. * @param date 格式化的Date对象
  5. * @param format 格式化的样式:"yyyy-MM-dd HH:mm:ss"
  6. * @return String
  7. * @author @author Fcant 下午 18:27 2020/6/30/0030
  8. */
  9. public static String formatDateTime(Date date, String format) {
  10. LocalDateTime localDateTime = dateToLocalDateTime(date);
  11. return formatLocalDateTime(localDateTime, format);
  12. }
  13. /**
  14. * 格式化LocalDateTime
  15. *
  16. * @param localDateTime 要格式化的localDateTime
  17. * @param format 格式化的样式:"yyyy-MM-dd HH:mm:ss"
  18. * @return String
  19. * @author @author Fcant 下午 18:27 2020/6/30/0030
  20. */
  21. public static String formatLocalDateTime(LocalDateTime localDateTime, String format) {
  22. return DateTimeFormatter.ofPattern(format).format(localDateTime);
  23. }

格式化LocalDateTime

  1. /**
  2. * 格式化LocalDateTime
  3. *
  4. * @param localDateTime 要格式化的localDateTime
  5. * @param format 格式化的样式:"yyyy-MM-dd HH:mm:ss"
  6. * @return String
  7. * @author @author Fcant 下午 18:27 2020/6/30/0030
  8. */
  9. public static String formatLocalDateTime(LocalDateTime localDateTime, String format) {
  10. return DateTimeFormatter.ofPattern(format).format(localDateTime);
  11. }

String类型的日期转换为LocalDateTime

  1. /**
  2. * String类型的日期转换为LocalDateTime
  3. *
  4. * @param date String类型的日期参数
  5. * @param fmt String类型的日期的格式
  6. * @return LocalDateTime
  7. * @author Fcant 下午 17:22 2020/7/9/0009
  8. */
  9. public static LocalDateTime stringToLocal(String date, String fmt) {
  10. DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(fmt);
  11. return LocalDateTime.parse(date, dateTimeFormatter);
  12. }

LocalDateTime转换为Date类型的日期

  1. /**
  2. * 将LocalDateTime转换为Date类型的日期
  3. *
  4. * @param localDateTime 要转换的LocalDateTime参数
  5. * @return Date
  6. * @author Fcant 下午 17:21 2020/7/9/0009
  7. */
  8. public static Date localToDate(LocalDateTime localDateTime) {
  9. ZoneId zoneId = ZoneId.systemDefault();
  10. ZonedDateTime zonedDateTime = localDateTime.atZone(zoneId);
  11. return Date.from(zonedDateTime.toInstant());
  12. }

7、时区的操作

Java8加入了对时区的支持,带时区的时间分别为:
ZoneDateZoneTimeZoneDateTime,其中每个时区都对应着ID,地区ID都为“{区域}/{城市}”的格式,例如Asis/Shanghai等
ZoneId:该类中包含了所有的时区信息。
getAvailableZoneIDs():可以获取所有时区信息;
of(id):用指定的时区信息获取ZoneId对象。

  1. // ZoneDate、ZoneTime、ZoneDateTime
  2. @Test
  3. public void zoneDateTimeTest() {
  4. Set<String> availableZoneIds = ZoneId.getAvailableZoneIds();
  5. availableZoneIds.forEach(System.out::println);
  6. // 根据指定时区获取当前时间
  7. LocalDateTime now = LocalDateTime.now(ZoneId.of("Africa/Nairobi"));
  8. System.out.println(now);
  9. // 获取时间并为之指定时区
  10. LocalDateTime localDateTime = LocalDateTime.now();
  11. ZonedDateTime zonedDateTime = localDateTime.atZone(ZoneId.of("US/Pacific"));
  12. System.out.println(zonedDateTime);
  13. }
  1. // 获取当前时间日期
  2. final ZonedDateTime zonedDatetime = ZonedDateTime.now();
  3. //获取指定时钟的日期时间
  4. final ZonedDateTime zonedDatetimeFromClock = ZonedDateTime.now( clock );
  5. //获取纽约时区的当前时间日期
  6. final ZonedDateTime zonedDatetimeFromZone = ZonedDateTime.now( ZoneId.of("America/New_York") );
  7. System.out.println( zonedDatetime );
  8. System.out.println( zonedDatetimeFromClock );
  9. System.out.println( zonedDatetimeFromZone );
  1. 2021-02-24T20:42:27.238+08:00[Asia/Shanghai]
  2. 2021-02-24T12:42:27.238Z
  3. 2021-02-24T07:42:27.241-05:00[America/New_York]

8、SpringBoot中应用LocalDateTime

LocalDateTime字段以时间戳的方式返回给前端

添加日期转化类

  1. public class LocalDateTimeConverter extends JsonSerializer<LocalDateTime> {
  2. @Override
  3. public void serialize(LocalDateTime value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
  4. gen.writeNumber(value.toInstant(ZoneOffset.of("+8")).toEpochMilli());
  5. }
  6. }

并在LocalDateTime字段上添加

  1. @JsonSerialize(using = LocalDateTimeConverter.class)

注解,如下:

  1. @JsonSerialize(using = LocalDateTimeConverter.class)
  2. protected LocalDateTime gmtModified;

LocalDateTime字段以指定格式化日期的方式返回给前端

LocalDateTime字段上添加

  1. @JsonFormat(shape=JsonFormat.Shape.STRING, pattern="yyyy-MM-dd HH:mm:ss")

注解即可,如下:

  1. @JsonFormat(shape=JsonFormat.Shape.STRING, pattern="yyyy-MM-dd HH:mm:ss")
  2. protected LocalDateTime gmtModified;

对前端传入的日期进行格式化

LocalDateTime字段上添加

  1. @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")

注解即可,如下:

  1. @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
  2. protected LocalDateTime gmtModified;