新时间日期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 并掌握其使用场景,可以帮助我们编写更简洁、优雅和健壮的代码。