 新时间日期API
新时间日期API
 # 新时间日期 API 总结
前言
Java 8 引入了全新的时间日期 API,旨在解决旧的 java.util.Date 和 java.util.Calendar 存在的线程不安全、复杂性高和不可读性问题。新 API 位于 java.time 包下,通过不可变对象和流畅的 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();
    }
}
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();
    }
}
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();
    }
}
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);
    }
}
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);
    }
}
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() + " 日");
    }
}
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);
    }
}
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);
    }
}
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);
    }
}
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 并掌握其使用场景,可以帮助我们编写更简洁、优雅和健壮的代码。
