程序员scholar 程序员scholar
首页
  • Java 基础

    • JavaSE
    • JavaIO
    • JavaAPI速查
  • Java 高级

    • JUC
    • JVM
    • Java新特性
    • 设计模式
  • Web 开发

    • Servlet
    • Java网络编程
  • Web 标准

    • HTML
    • CSS
    • JavaScript
  • 前端框架

    • Vue2
    • Vue3
    • Vue3 + TS
    • 微信小程序
    • uni-app
  • 工具与库

    • jQuery
    • Ajax
    • Axios
    • Webpack
    • Vuex
    • WebSocket
    • 第三方登录
  • 后端与语言扩展

    • ES6
    • Typescript
    • node.js
  • Element-UI
  • Apache ECharts
  • 数据结构
  • HTTP协议
  • HTTPS协议
  • 计算机网络
  • Linux常用命令
  • Windows常用命令
  • SQL数据库

    • MySQL
    • MySQL速查
  • NoSQL数据库

    • Redis
    • ElasticSearch
  • 数据库

    • MyBatis
    • MyBatis-Plus
  • 消息中间件

    • RabbitMQ
  • 服务器

    • Nginx
  • Spring框架

    • Spring6
    • SpringMVC
    • SpringBoot
    • SpringSecurity
  • SpringCould微服务

    • SpringCloud基础
    • 微服务之DDD架构思想
  • 日常必备

    • 开发常用工具包
    • Hutoll工具包
    • IDEA常用配置
    • 开发笔记
    • 日常记录
    • 项目部署
    • 网站导航
    • 产品学习
    • 英语学习
  • 代码管理

    • Maven
    • Git教程
    • Git小乌龟教程
  • 运维工具

    • Docker
    • Jenkins
    • Kubernetes
  • 算法笔记

    • 算法思想
    • 刷题笔记
  • 面试问题常见

    • 十大经典排序算法
    • 面试常见问题集锦
关于
GitHub (opens new window)
首页
  • Java 基础

    • JavaSE
    • JavaIO
    • JavaAPI速查
  • Java 高级

    • JUC
    • JVM
    • Java新特性
    • 设计模式
  • Web 开发

    • Servlet
    • Java网络编程
  • Web 标准

    • HTML
    • CSS
    • JavaScript
  • 前端框架

    • Vue2
    • Vue3
    • Vue3 + TS
    • 微信小程序
    • uni-app
  • 工具与库

    • jQuery
    • Ajax
    • Axios
    • Webpack
    • Vuex
    • WebSocket
    • 第三方登录
  • 后端与语言扩展

    • ES6
    • Typescript
    • node.js
  • Element-UI
  • Apache ECharts
  • 数据结构
  • HTTP协议
  • HTTPS协议
  • 计算机网络
  • Linux常用命令
  • Windows常用命令
  • SQL数据库

    • MySQL
    • MySQL速查
  • NoSQL数据库

    • Redis
    • ElasticSearch
  • 数据库

    • MyBatis
    • MyBatis-Plus
  • 消息中间件

    • RabbitMQ
  • 服务器

    • Nginx
  • Spring框架

    • Spring6
    • SpringMVC
    • SpringBoot
    • SpringSecurity
  • SpringCould微服务

    • SpringCloud基础
    • 微服务之DDD架构思想
  • 日常必备

    • 开发常用工具包
    • Hutoll工具包
    • IDEA常用配置
    • 开发笔记
    • 日常记录
    • 项目部署
    • 网站导航
    • 产品学习
    • 英语学习
  • 代码管理

    • Maven
    • Git教程
    • Git小乌龟教程
  • 运维工具

    • Docker
    • Jenkins
    • Kubernetes
  • 算法笔记

    • 算法思想
    • 刷题笔记
  • 面试问题常见

    • 十大经典排序算法
    • 面试常见问题集锦
关于
GitHub (opens new window)
npm

(进入注册为作者充电)

  • Java8新特性

    • Java8新特性简介
    • Lambda 表达式
    • 函数式接口
    • 方法的引用
    • 构造器和数组的引用
    • Stream 流式编程
    • 并行流与串行流
    • Optional 类的使用
    • 对反射的支持增强
    • 接口中的默认方法与静态方法
    • 新时间日期API
      • 1. 新时间日期 API 出现的原因
      • 2. 三大核心类:LocalDate、LocalTime、LocalDateTime
      • 3. Instant 类:时间戳
      • 4. Duration 和 Period
      • 5. 日期调整器(TemporalAdjuster)
      • 6. 解析与格式化
      • 7. 时区处理
      • 8. 与旧 API 的转换
    • Try-with-Resources 升级
    • 重复注解与类型注解
  • Java9新特性

  • Java10新特性

  • Java11新特性

  • Java12新特性

  • Java13新特性

  • Java14新特性

  • Java15新特性

  • Java新特性
  • Java8新特性
scholar
2024-08-24
目录

新时间日期API

# 新时间日期 API 总结

前言

Java 8 引入了全新的时间日期 API,旨在解决旧的 java.util.Date 和 java.util.Calendar 存在的线程不安全、复杂性高和不可读性问题。新 API 位于 java.time 包下,通过不可变对象和流畅的 API 提供了更简洁、直观和强大的时间日期处理能力。

  • 1. 新时间日期 API 出现的原因
  • 2. 三大核心类:LocalDate、LocalTime、LocalDateTime
  • 3. Instant 类:时间戳
  • 4. Duration 和 Period
  • 5. 日期调整器(TemporalAdjuster)
  • 6. 解析与格式化
  • 7. 时区处理
  • 8. 与旧 API 的转换

# 1. 新时间日期 API 出现的原因

在 Java 8 之前,旧的日期 API 存在以下问题:

  • 线程不安全:SimpleDateFormat 类在多线程环境下可能引发线程安全问题,导致数据不一致。
  • 设计缺陷:Date 类月份从 0 开始,年份从 1900 开始,容易引发混淆和错误。
  • 可变性:Date 和 Calendar 类的实例是可变的,容易引发线程安全问题和不必要的复杂性。

以下示例展示了旧的日期 API 在多线程环境下存在的安全隐患:

public class TestSimpleDateFormat {
    public static void main(String[] args) throws Exception {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");

        Callable<Date> task = () -> sdf.parse("20230822");

        ExecutorService pool = Executors.newFixedThreadPool(10);
        List<Future<Date>> results = new ArrayList<>();

        for (int i = 0; i < 10; i++) {
            results.add(pool.submit(task));
        }

        for (Future<Date> future : results) {
            System.out.println(future.get()); // 可能抛出线程安全问题导致的异常
        }

        pool.shutdown();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

Java 8 之前的解决方案:

在旧的 API 中,可以使用 ThreadLocal 来避免线程安全问题:

public class DateFormatThreadLocal {
    private static final ThreadLocal<DateFormat> df = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyyMMdd"));

    public static Date convert(String source) throws ParseException {
        return df.get().parse(source);
    }
}

public class TestThreadLocal {
    public static void main(String[] args) throws Exception {
        Callable<Date> task = () -> DateFormatThreadLocal.convert("20230822");

        ExecutorService pool = Executors.newFixedThreadPool(10);
        List<Future<Date>> results = new ArrayList<>();

        for (int i = 0; i < 10; i++) {
            results.add(pool.submit(task));
        }

        for (Future<Date> future : results) {
            System.out.println(future.get());
        }

        pool.shutdown();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

Java 8 的解决方案:

Java 8 引入了 LocalDate 和 DateTimeFormatter,可以直接替代 ThreadLocal 来实现线程安全的日期处理:

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class TestNewDateAPI {
    public static void main(String[] args) throws Exception {
        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyyMMdd");

        Callable<LocalDate> task = () -> LocalDate.parse("20230822", dtf);

        ExecutorService pool = Executors.newFixedThreadPool(10);
        List<Future<LocalDate>> results = new ArrayList<>();

        for (int i = 0; i < 10; i++) {
            results.add(pool.submit(task));
        }

        for (Future<LocalDate> future : results) {
            System.out.println(future.get());
        }

        pool.shutdown();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

# 2. 三大核心类:LocalDate、LocalTime、LocalDateTime

Java 8 引入了三个核心时间日期类:

  • LocalDate:表示日期(年、月、日),不包含时间信息。
  • LocalTime:表示时间(时、分、秒、纳秒),不包含日期信息。
  • LocalDateTime:表示日期和时间(年、月、日、时、分、秒、纳秒),不包含时区信息。

这些类的实例是不可变的,线程安全。

常用方法及其作用:

方法 描述
now() 获取当前日期/时间
of() 创建指定日期/时间
plusDays(), plusMonths(), plusYears() 向日期/时间添加指定的天、月、年
minusDays(), minusMonths(), minusYears() 从日期/时间减去指定的天、月、年
withDayOfMonth(), withMonth(), withYear() 设置指定的天、月、年
getDayOfMonth() 获取月份中的天数
getMonthValue() 获取月份(1-12)
isBefore(), isAfter() 判断两个日期/时间的先后顺序
isLeapYear() 判断是否为闰年

代码示例:

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;

public class TestLocalDateTime {
    public static void main(String[] args) {
        // 获取当前日期、时间和日期时间
        LocalDate localDate = LocalDate.now();
        LocalTime localTime = LocalTime.now();
        LocalDateTime localDateTime = LocalDateTime.now();

        System.out.println("当前日期:" + localDate);
        System.out.println("当前时间:" + localTime);
        System.out.println("当前日期时间:" + localDateTime);

        // 指定日期和时间
        LocalDate specifiedDate = LocalDate.of(2023, 8, 22);
        LocalTime specifiedTime = LocalTime.of(10, 30, 45);
        LocalDateTime specifiedDateTime = LocalDateTime.of(2023, 8, 22, 10, 30, 45);

        System.out.println("指定日期:" + specifiedDate);
        System.out.println("指定时间:" + specifiedTime);
        System.out.println("指定日期时间:" + specifiedDateTime);

        // 日期加减操作
        LocalDate plusDays = localDate.plusDays(10);
        LocalDate minusMonths = localDate.minusMonths(1);

        System.out.println("当前日期加 10 天:" + plusDays);
        System.out.println("当前日期减 1 个月:" + minusMonths);

        // 获取日期和时间的各个部分
        int year = localDateTime.getYear();
        int month = localDateTime.getMonthValue();
        int day = localDateTime.getDayOfMonth();
        int hour = localDateTime.getHour();
        int minute = localDateTime.getMinute();
        int second = localDateTime.getSecond();

        System.out.println("年:" + year);
        System.out.println("月:" + month);
        System.out.println("日:" + day);
        System.out.println("时:" + hour);
        System.out.println("分:" + minute);
        System.out.println("秒:" + second);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

# 3. Instant 类:时间戳

Instant 类用于表示时间戳,即以 Unix 元年(1970 年 1 月 1 日 00:00:00)为基准的毫秒数。Instant 通常用于计算两个时间点之间的差异。

常用方法及其作用:

  • now():获取当前时间戳。
  • ofEpochSecond():根据秒数创建时间戳。
  • atOffset(ZoneOffset):将时间戳转换为指定时区的时间。

代码示例:

import java.time.Instant;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;

public class TestInstant {
    public static void main(String[] args) {
        // 获取当前时间戳
        Instant instant = Instant.now();
        System.out.println("当前时间戳:" + instant);

        // 转换为指定时区的时间
        OffsetDateTime offsetDateTime = instant.atOffset(ZoneOffset.ofHours(8));
        System.out.println("北京时间:" + offsetDateTime);

        // 创建指定秒数的时间戳
        Instant instantFromEpoch = Instant.ofEpochSecond(1000);
        System.out.println("从 Epoch 开始的 1000 秒时间戳:" + instantFromEpoch);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 4. Duration 和 Period

  • Duration:用于计算两个时间点之间的时间间隔,通常用于时间操作(小时、分钟、秒)。
  • Period:用于计算两个日期之间的时间间隔,通常用于日期操作(年、月、日)。

代码示例:

import java.time.Duration;
import java.time.Instant;
import java.time.LocalDate;
import java.time.Period;

public class TestDurationAndPeriod {
    public static void main(String[] args) throws InterruptedException {
        // 计算两个时间点之间的间隔
        Instant start = Instant.now();
        Thread.sleep(1000); // 模拟耗

时操作
        Instant end = Instant.now();

        Duration duration = Duration.between(start, end);
        System.out.println("耗时:" + duration.toMillis() + " 毫秒");

        // 计算两个日期之间的间隔
        LocalDate date1 = LocalDate.of(2020, 1, 1);
        LocalDate date2 = LocalDate.now();

        Period period = Period.between(date1, date2);
        System.out.println("相差时间:" + period.getYears() + " 年 " + period.getMonths() + " 月 " + period.getDays() + " 日");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# 5. 日期调整器(TemporalAdjuster)

TemporalAdjuster 接口允许对日期进行灵活的调整,如获取下一个周日、调整到月初或年末。TemporalAdjusters 工具类提供了一些常用的日期调整器。

代码示例:

import java.time.DayOfWeek;
import java.time.LocalDateTime;
import java.time.temporal.TemporalAdjusters;

public class TestTemporalAdjuster {
    public static void main(String[] args) {
        LocalDateTime dateTime = LocalDateTime.now();

        // 调整到本月的第 10 天
        LocalDateTime adjustedDate = dateTime.withDayOfMonth(10);
        System.out.println("本月第 10 天:" + adjustedDate);

        // 调整到下一个周日
        LocalDateTime nextSunday = dateTime.with(TemporalAdjusters.next(DayOfWeek.SUNDAY));
        System.out.println("下一个周日:" + nextSunday);

        // 自定义调整:下一个工作日
        LocalDateTime nextWorkingDay = dateTime.with((temporal) -> {
            LocalDateTime dt = LocalDateTime.from(temporal);
            DayOfWeek dayOfWeek = dt.getDayOfWeek();

            if (dayOfWeek == DayOfWeek.FRIDAY) {
                return dt.plusDays(3);
            } else if (dayOfWeek == DayOfWeek.SATURDAY) {
                return dt.plusDays(2);
            } else {
                return dt.plusDays(1);
            }
        });
        System.out.println("下一个工作日:" + nextWorkingDay);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

# 6. 解析与格式化

DateTimeFormatter 提供了强大的日期时间格式化和解析功能。支持预定义格式、语言环境相关格式和自定义格式。

代码示例:

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

public class TestDateTimeFormatter {
    public static void main(String[] args) {
        // 自定义日期格式
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss");

        // 获取当前日期时间
        LocalDateTime dateTime = LocalDateTime.now();
        String formattedDate = dateTime.format(formatter);
        System.out.println("格式化后的日期时间:" + formattedDate);

        // 解析日期时间字符串
        LocalDateTime parsedDate = LocalDateTime.parse(formattedDate, formatter);
        System.out.println("解析后的日期时间:" + parsedDate);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 7. 时区处理

Java 8 提供了对时区的全面支持,通过 ZoneId 和 ZonedDateTime 等类,可以轻松处理带时区的时间。

代码示例:

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Set;

public class TestTimeZone {
    public static void main(String[] args) {
        // 获取所有时区 ID
        Set<String> zoneIds = ZoneId.getAvailableZoneIds();
        zoneIds.forEach(System.out::println);

        // 获取当前时区时间
        LocalDateTime dateTime = LocalDateTime.now(ZoneId.of("Asia/Shanghai"));
        System.out.println("上海时区当前时间:" + dateTime);

        // 获取带时区的日期时间
        ZonedDateTime zonedDateTime = ZonedDateTime.now(ZoneId.of("US/Pacific"));
        System.out.println("美国太平洋时区当前时间:" + zonedDateTime);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 8. 与旧 API 的转换

Java 8 提供了与旧的日期时间 API 互相转换的工具,方便将旧代码迁移到新 API。

新 API 旧 API 转换方法
java.time.Instant java.util.Date Date.from(instant)
java.time.Instant java.sql.Timestamp Timestamp.from(instant)
java.time.ZonedDateTime java.util.GregorianCalendar GregorianCalendar.from(zonedDateTime)
java.time.LocalDate java.sql.Date Date.valueOf(localDate)
java.time.LocalTime java.sql.Time Time.valueOf(localTime)
java.time.LocalDateTime java.sql.Timestamp Timestamp.valueOf(localDateTime)
java.time.ZoneId java.util.TimeZone TimeZone.toZoneId()
java.time.format.DateTimeFormatter java.text.DateFormat formatter.toFormat()

总结

Java 8 的新时间日期 API 通过引入不可变对象、流畅的 API 和丰富的实用工具类,解决了旧 API 的诸多问题,如线程不安全、复杂性高和不可读性差等。在实际开发中,了解这些新 API 并掌握其使用场景,可以帮助我们编写更简洁、优雅和健壮的代码。

编辑此页 (opens new window)
上次更新: 2024/12/28, 18:32:08
接口中的默认方法与静态方法
Try-with-Resources 升级

← 接口中的默认方法与静态方法 Try-with-Resources 升级→

Theme by Vdoing | Copyright © 2019-2025 程序员scholar
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式