Java 8 新特性:Date-Time API

Java 8 引入的 Date-Time API (java.time 包) 为日期和时间处理带来了显著的改进,解决了 java.util.Date 类的许多痛点:

  1. 非线程安全
  2. 时区处理麻烦
  3. 格式化和时间计算繁琐
  4. 设计有缺陷,Date 类同时包含日期和时间;还有一个 java.sql.Date,容易混淆。

本文将详细讲解 Java 8 新的 Date-Time API,并通过源码和示例代码对比 Java 8 之前的实现方式,深入剖析其设计的用意和目的。

java.time 主要类

java.util.Date 既包含日期又包含时间,而 java.time 将它们进行了分离:

  • LocalDateTime:日期和时间,格式为 yyyy-MM-ddTHH:mm:ss.SSS
  • LocalDate:仅日期,格式为 yyyy-MM-dd
  • LocalTime:仅时间,格式为 HH:mm:ss

格式化

Java 8 之前:

java

public void oldFormat(){
    Date now = new Date();
    //格式化 yyyy-MM-dd
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
    String date  = sdf.format(now);
    System.out.println(String.format("date format : %s", date));

    //格式化 HH:mm:ss
    SimpleDateFormat sdft = new SimpleDateFormat("HH:mm:ss");
    String time = sdft.format(now);
    System.out.println(String.format("time format : %s", time));

    //格式化 yyyy-MM-dd HH:mm:ss
    SimpleDateFormat sdfdt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    String datetime = sdfdt.format(now);
    System.out.println(String.format("dateTime format : %s", datetime));
}

Java 8 之后:

java

public void newFormat(){
    //格式化 yyyy-MM-dd
    LocalDate date = LocalDate.now();
    System.out.println(String.format("date format : %s", date));

    //格式化 HH:mm:ss
    LocalTime time = LocalTime.now().withNano(0);
    System.out.println(String.format("time format : %s", time));

    //格式化 yyyy-MM-dd HH:mm:ss
    LocalDateTime dateTime = LocalDateTime.now();
    DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    String dateTimeStr = dateTime.format(dateTimeFormatter);
    System.out.println(String.format("dateTime format : %s", dateTimeStr));
}

字符串转日期格式

Java 8 之前:

java

//已弃用
Date date = new Date("2021-01-26");
//替换为
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date date1 = sdf.parse("2021-01-26");

Java 8 之后:

java

LocalDate date = LocalDate.of(2021, 1, 26);
LocalDate.parse("2021-01-26");

LocalDateTime dateTime = LocalDateTime.of(2021, 1, 26, 12, 12, 22);
LocalDateTime.parse("2021-01-26T12:12:22");

LocalTime time = LocalTime.of(12, 12, 22);
LocalTime.parse("12:12:22");

Java 8 之前 的转换需要借助 SimpleDateFormat 类,而 Java 8 之后 只需要使用 LocalDateLocalTimeLocalDateTime 的 of 或 parse 方法。

日期计算

以下以计算 一周后的日期 为例,其他单位(年、月、日、时等)类似。这些单位都在 java.time.temporal.ChronoUnit 枚举中定义。

Java 8 之前:

java

public void afterDay(){
    //一周后的日期
    SimpleDateFormat formatDate = new SimpleDateFormat("yyyy-MM-dd");
    Calendar ca = Calendar.getInstance();
    ca.add(Calendar.DATE, 7);
    Date d = ca.getTime();
    String after = formatDate.format(d);
    System.out.println("一周后日期:" + after);

    //算两个日期间隔多少天,计算间隔多少年,多少月方法类似
    String dates1 = "2021-12-23";
    String dates2 = "2021-02-26";
    SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
    Date date1 = format.parse(dates1);
    Date date2 = format.parse(dates2);
    int day = (int) ((date1.getTime() - date2.getTime()) / (1000 * 3600 * 24));
    System.out.println(dates1 + "和" + dates2 + "相差" + day + "天");
}

Java 8 之后:

java

public void pushWeek(){
    //一周后的日期
    LocalDate localDate = LocalDate.now();
    //方法1
    LocalDate after = localDate.plus(1, ChronoUnit.WEEKS);
    //方法2
    LocalDate after2 = localDate.plusWeeks(1);
    System.out.println("一周后日期:" + after);

    //算两个日期间隔多少天,计算间隔多少年,多少月
    LocalDate date1 = LocalDate.parse("2021-02-26");
    LocalDate date2 = LocalDate.parse("2021-12-23");
    Period period = Period.between(date1, date2);
    System.out.println("date1 到 date2 相隔:" + period.getYears() + "年" + period.getMonths() + "月" + period.getDays() + "天");

    //获取总天数
    long day = date2.toEpochDay() - date1.toEpochDay();
    System.out.println(date1 + "和" + date2 + "相差" + day + "天");
}

获取指定日期

获取特定一个日期,如本月最后一天或第一天。

Java 8 之前:

java

public void getDay() {
    SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
    //获取当前月第一天:
    Calendar c = Calendar.getInstance();
    c.set(Calendar.DAY_OF_MONTH, 1);
    String first = format.format(c.getTime());
    System.out.println("first day:" + first);

    //获取当前月最后一天
    Calendar ca = Calendar.getInstance();
    ca.set(Calendar.DAY_OF_MONTH, ca.getActualMaximum(Calendar.DAY_OF_MONTH));
    String last = format.format(ca.getTime());
    System.out.println("last day:" + last);

    //当年最后一天
    Calendar currCal = Calendar.getInstance();
    Calendar calendar = Calendar.getInstance();
    calendar.clear();
    calendar.set(Calendar.YEAR, currCal.get(Calendar.YEAR));
    calendar.roll(Calendar.DAY_OF_YEAR, -1);
    Date time = calendar.getTime();
    System.out.println("last day:" + format.format(time));
}

Java 8 之后:

java

public void getDayNew() {
    LocalDate today = LocalDate.now();
    //获取当前月第一天:
    LocalDate firstDayOfThisMonth = today.with(TemporalAdjusters.firstDayOfMonth());
    //获取当前月最后一天:
    LocalDate lastDayOfThisMonth = today.with(TemporalAdjusters.lastDayOfMonth());
    //获取下一天:
    LocalDate nextDay = lastDayOfThisMonth.plusDays(1);
    //获取当年最后一天:
    LocalDate lastDayOfYear = today.with(TemporalAdjusters.lastDayOfYear());
    //2021年最后一个周日
    LocalDate lastSundayOf2021 = LocalDate.parse("2021-12-31").with(TemporalAdjusters.lastInMonth(DayOfWeek.SUNDAY));
}

java.time.temporal.TemporalAdjusters 提供了很多便捷的方法来获取特定日期。

JDBC 和 Java 8

现在 JDBC 时间类型和 Java 8 时间类型的对应关系是:

  1. Date ---> LocalDate
  2. Time ---> LocalTime
  3. Timestamp ---> LocalDateTime

而之前统统对应 Date 类型。

时区

java.util.Date 对象实际上存储的是 1970 年 1 月 1 日 0 点(GMT)至 Date 对象所表示时刻所经过的毫秒数。因此,它记录的毫秒数与时区无关,但在使用上需要转换成当地时间,这就涉及到了时间的国际化。java.util.Date 本身并不支持国际化,需要借助 TimeZone

java

//北京时间
Date date = new Date();
SimpleDateFormat bjSdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//北京时区
bjSdf.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
System.out.println("毫秒数:" + date.getTime() + ", 北京时间:" + bjSdf.format(date));

//东京时区
SimpleDateFormat tokyoSdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
tokyoSdf.setTimeZone(TimeZone.getTimeZone("Asia/Tokyo"));
System.out.println("毫秒数:" + date.getTime() + ", 东京时间:" + tokyoSdf.format(date));

//直接打印会自动转成当前时区时间
System.out.println(date);

在新特性中引入了 java.time.ZonedDateTime 来表示带时区的时间。它可以看成是 LocalDateTime + ZoneId

java

//当前时区时间
ZonedDateTime zonedDateTime = ZonedDateTime.now();
System.out.println("当前时区时间: " + zonedDateTime);

//东京时间
ZoneId zoneId = ZoneId.of(ZoneId.SHORT_IDS.get("JST"));
ZonedDateTime tokyoTime = zonedDateTime.withZoneSameInstant(zoneId);
System.out.println("东京时间: " + tokyoTime);

// ZonedDateTime 转 LocalDateTime
LocalDateTime localDateTime = tokyoTime.toLocalDateTime();
System.out.println("东京时间转当地时间: " + localDateTime);

//LocalDateTime 转 ZonedDateTime
ZonedDateTime localZoned = localDateTime.atZone(ZoneId.systemDefault());
System.out.println("本地时区时间: " + localZoned);

java.time 包与 Joda-Time 的对比

Java 8 引入的 java.time 包是对日期和时间处理的一次重大改进,它借鉴了许多 Joda-Time 的设计理念,并在此基础上进行了优化。那么,java.time 包与 Joda-Time 相比有哪些优势和劣势呢?

1. 设计理念和 API 风格

Joda-Time

  • 设计理念:Joda-Time 旨在提供一个更直观和更强大的日期时间处理库,弥补 java.util.Date 和 java.util.Calendar 的不足。
  • API 风格:Joda-Time 的 API 风格非常直观,类名和方法名都非常清晰易懂。例如,DateTime 类表示日期和时间,LocalDate 类表示仅日期。

Java 8 java.time

  • 设计理念java.time 包是基于 JSR 310 的规范实现,吸收了 Joda-Time 的优点,并在此基础上进行了优化和改进。
  • API 风格java.time 包的 API 风格与 Joda-Time 类似,但在命名和结构上更加规范。例如,LocalDateTime 表示日期和时间,LocalDate 表示仅日期,LocalTime 表示仅时间。

2. 集成和兼容性

Joda-Time

  • 集成:Joda-Time 是一个独立的第三方库,需要手动添加依赖。
  • 兼容性:Joda-Time 与 Java SE 8 之前的版本兼容,适用于所有版本的 Java。

Java 8 java.time

  • 集成java.time 包是 Java SE 8 的一部分,无需额外添加依赖。
  • 兼容性java.time 包仅适用于 Java SE 8 及以上版本,对于 Java 8 之前的版本,需要额外的兼容性库(如 ThreeTen-Backport)。

3. 性能和线程安全

Joda-Time

  • 性能:Joda-Time 的性能相对较好,但在某些操作上可能不如 java.time 包高效。
  • 线程安全:Joda-Time 的大多数类是不可变的,因此是线程安全的。

Java 8 java.time

  • 性能java.time 包在设计时考虑了性能优化,尤其是在日期计算和格式化等高频操作上性能更优。
  • 线程安全java.time 包中的所有类都是不可变的,因此也是线程安全的。

4. 功能和扩展性

Joda-Time

  • 功能:Joda-Time 提供了丰富的功能,支持各种日期时间操作、格式化、解析、时区转换等。
  • 扩展性:Joda-Time 提供了一些扩展功能,例如自定义时间单位和字段。

Java 8 java.time

  • 功能java.time 包提供了与 Joda-Time 类似的功能,并在某些方面进行了增强。例如,提供了更丰富的日期调整器(TemporalAdjuster)和更强大的时间量(Duration 和 Period)。
  • 扩展性java.time 包也提供了良好的扩展性,允许用户自定义时间单位和字段。

示例对比

Joda-Time 示例

java

import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;

public class JodaTimeExample {
    public static void main(String[] args) {
        // 当前时间
        DateTime now = DateTime.now();
        System.out.println("当前时间: " + now);

        // 格式化
        DateTimeFormatter formatter = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss");
        String formattedDate = now.toString(formatter);
        System.out.println("格式化后的时间: " + formattedDate);

        // 解析
        DateTime parsedDate = DateTime.parse("2021-01-26 12:12:22", formatter);
        System.out.println("解析后的时间: " + parsedDate);

        // 日期计算
        DateTime oneWeekLater = now.plusWeeks(1);
        System.out.println("一周后的时间: " + oneWeekLater);
    }
}

Java 8 java.time 示例

java

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

public class JavaTimeExample {
    public static void main(String[] args) {
        // 当前时间
        LocalDateTime now = LocalDateTime.now();
        System.out.println("当前时间: " + now);

        // 格式化
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        String formattedDate = now.format(formatter);
        System.out.println("格式化后的时间: " + formattedDate);

        // 解析
        LocalDateTime parsedDate = LocalDateTime.parse("2021-01-26 12:12:22", formatter);
        System.out.println("解析后的时间: " + parsedDate);

        // 日期计算
        LocalDateTime oneWeekLater = now.plusWeeks(1);
        System.out.println("一周后的时间: " + oneWeekLater);
    }
}

总结

优势
  1. 集成java.time 包是 Java 8 的一部分,无需额外依赖,集成更方便。
  2. 性能java.time 包在性能上进行了优化,尤其是在高频日期时间操作上。
  3. 线程安全java.time 包中的所有类都是不可变的,因此线程安全。
  4. 标准化java.time 包是基于 JSR 310 的规范实现,更加标准化。
劣势
  1. 兼容性java.time 包仅适用于 Java SE 8 及以上版本,对于 Java 8 之前的版本需要额外的兼容性库。
  2. 学习成本:对于熟悉 Joda-Time 的开发者来说,迁移到 java.time 包可能需要一定的学习成本。

使用 java.time 包中的类处理时间间隔和持续时间

在 Java 8 的 java.time 包中,处理时间间隔和持续时间有两个主要的类:Duration 和 PeriodDuration 用于表示基于时间的间隔(以秒和纳秒为单位),而 Period 用于表示基于日期的间隔(以年、月、日为单位)。

1. Duration 类

Duration 类用于表示两个时间点之间的时间间隔,精确到秒和纳秒。它常用于计算精确的时间差,如秒、分钟、小时等。

示例代码
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;

public class DurationExample {
    public static void main(String[] args) {
        // 当前时间
        LocalDateTime startTime = LocalDateTime.now();
        System.out.println("开始时间: " + startTime);

        // 模拟一个耗时操作
        try {
            Thread.sleep(3000); // 休眠3秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 结束时间
        LocalDateTime endTime = LocalDateTime.now();
        System.out.println("结束时间: " + endTime);

        // 计算时间间隔
        Duration duration = Duration.between(startTime, endTime);
        System.out.println("时间间隔: " + duration.getSeconds() + " 秒 " + duration.getNano() + " 纳秒");

        // 其他常用方法
        System.out.println("时间间隔(分钟): " + duration.toMinutes());
        System.out.println("时间间隔(毫秒): " + duration.toMillis());

        // 使用ChronoUnit计算时间间隔
        long secondsBetween = ChronoUnit.SECONDS.between(startTime, endTime);
        System.out.println("时间间隔(ChronoUnit): " + secondsBetween + " 秒");
    }
}

2. Period 类

Period 类用于表示两个日期之间的时间间隔,精确到年、月、日。它常用于计算日期间的差异,如天数、月数、年数等。

示例代码

java

import java.time.LocalDate;
import java.time.Period;
import java.time.temporal.ChronoUnit;

public class PeriodExample {
    public static void main(String[] args) {
        // 当前日期
        LocalDate startDate = LocalDate.now();
        System.out.println("开始日期: " + startDate);

        // 目标日期
        LocalDate endDate = startDate.plusYears(1).plusMonths(2).plusDays(3);
        System.out.println("结束日期: " + endDate);

        // 计算日期间隔
        Period period = Period.between(startDate, endDate);
        System.out.println("日期间隔: " + period.getYears() + " 年 " + period.getMonths() + " 月 " + period.getDays() + " 天");

        // 其他常用方法
        System.out.println("总月数: " + period.toTotalMonths());

        // 使用ChronoUnit计算日期间隔
        long daysBetween = ChronoUnit.DAYS.between(startDate, endDate);
        System.out.println("日期间隔(ChronoUnit): " + daysBetween + " 天");
    }
}

选择 Duration 和 Period 的适用场景

选择 Duration 的场景
  • 需要精确到秒和纳秒的时间计算。
  • 计时操作,如测量代码执行时间、任务的持续时间等。
  • 时区无关的时间计算,如两个时间点之间的间隔。
选择 Period 的场景
  • 需要基于年、月、日的日期计算。
  • 计算两个日期之间的年、月、日差异。
  • 日历相关操作,如计算某个日期之后的某年某月某日。
比较两个日期之间的差异

示例代码

java

import java.time.LocalDate;
import java.time.Period;

public class PeriodDifferenceExample {
    public static void main(String[] args) {
        // 两个日期
        LocalDate startDate = LocalDate.of(2020, 1, 1);
        LocalDate endDate = LocalDate.of(2021, 6, 15);

        // 计算日期间隔
        Period period = Period.between(startDate, endDate);
        System.out.println("日期间隔: " + period.getYears() + " 年 " + period.getMonths() + " 月 " + period.getDays() + " 天");
    }
}

使用 Duration 进行时区无关的时间计算

示例代码

java

import java.time.Duration;
import java.time.ZonedDateTime;
import java.time.ZoneId;

public class ZonedDateTimeExample {
    public static void main(String[] args) {
        // 纽约时间
        ZonedDateTime startDateTime = ZonedDateTime.now(ZoneId.of("America/New_York"));
        System.out.println("纽约开始时间: " + startDateTime);

        // 东京时间
        ZonedDateTime endDateTime = ZonedDateTime.now(ZoneId.of("Asia/Tokyo"));
        System.out.println("东京结束时间: " + endDateTime);

        // 计算时间间隔
        Duration duration = Duration.between(startDateTime, endDateTime);
        System.out.println("时间间隔: " + duration.toHours() + " 小时");
    }
}

使用 ChronoUnit 枚举

ChronoUnit 枚举提供了一系列用于时间单位的常量,支持基于时间和日期的计算。

示例代码

java

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;

public class ChronoUnitExample {
    public static void main(String[] args) {
        // 当前时间
        LocalDateTime now = LocalDateTime.now();
        System.out.println("当前时间: " + now);

        // 计算一周后的时间
        LocalDateTime oneWeekLater = now.plus(1, ChronoUnit.WEEKS);
        System.out.println("一周后的时间: " + oneWeekLater);

        // 计算一年前的日期
        LocalDate oneYearAgo = LocalDate.now().minus(1, ChronoUnit.YEARS);
        System.out.println("一年前的日期: " + oneYearAgo);

        // 计算两个时间点之间的小时数
        long hoursBetween = ChronoUnit.HOURS.between(now, oneWeekLater);
        System.out.println("时间间隔(小时): " + hoursBetween + " 小时");
    }
}

总结

使用 Java 8 java.time 包中的 Duration 和 Period 类可以方便地处理时间间隔和持续时间。Duration 用于表示基于时间的间隔,如秒、分钟、小时等;而 Period 用于表示基于日期的间隔,如年、月、日等。此外,ChronoUnit 枚举提供了一系列用于时间单位的常量,支持基于时间和日期的计算。

  • 21
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值