Java Time API
在 Java 8 之前时间日期处理主要使用 Date、Calendar、SimpleDateFormat 对象,但这几个对象存在明显设计缺陷
- 可变性:Date 和 Calendar 都是可变的,容易导致线程安全问题。
- 混淆性:在 Calendar 中,月份是从 0 开始计数的,这容易引起混淆。
- 设计缺陷:日期、时间和时区数据之间缺乏清晰的分离。
Java 8 引入了 Java Time API(java.time 包),受到 Joda-Time 库的启发,旨在解决前述类库的不足,提供:
- 不可变性:所有类都是不可变的,线程安全。
- 清晰的分离:不同的类分别处理日期、时间和时区。
- 流畅的 API:更具可读性和直观性的开发接口。
- 全面的功能:涵盖几乎所有与日期和时间相关的使用场景。
核心组件
java.time 包提供了几个核心组件:
- LocalDate:表示不含时间和时区的日期。
- LocalTime:表示不含日期和时区的时间。
- LocalDateTime:结合日期和时间,不含时区。
- ZonedDateTime:结合日期、时间和时区。
- Instant:表示时间线上的一个点(时间戳),以 UTC 为基准。
- Duration 与 Period:分别表示基于时间和日期的时间段。
LocalDate
表示不含时间和时区的日期,如 2025-02-12,对象包含几个关键方法
now():获取当前日期。of(int year, int month, int dayOfMonth):通过年、月、日创建日期。plusDays(long days):日期加天数。minusMonths(long months):日期减月份。format(DateTimeFormatter formatter):格式化日期。
import java.time.LocalDate;
public class LocalDateExample {
public static void main(String[] args) {
LocalDate today = LocalDate.now();
LocalDate birthday = LocalDate.of(1990, 5, 20);
System.out.println("今天的日期: " + today);
System.out.println("生日: " + birthday);
}
}
LocalTime
表示不含日期和时区的时间,如 15:30:45,对象包含几个关键方法
now():获取当前时间。of(int hour, int minute, int second):通过时、分、秒创建时间。plusHours(long hours):时间加小时。minusMinutes(long minutes):时间减分钟。format(DateTimeFormatter formatter):格式化时间。
import java.time.LocalTime;
public class LocalTimeExample {
public static void main(String[] args) {
LocalTime now = LocalTime.now();
LocalTime meetingTime = LocalTime.of(14, 30, 0);
System.out.println("当前时间: " + now);
System.out.println("会议时间: " + meetingTime);
}
}
LocalDateTime
结合日期和时间,不含时区,如 2025-02-12T15:30:45,对象包含几个关键方法
now():获取当前日期和时间。of(int year, int month, int day, int hour, int minute):创建日期时间plusDays(long days):日期时间加天数。minusHours(long hours):日期时间减小时。format(DateTimeFormatter formatter):格式化日期时间。
import java.time.LocalDateTime;
public class LocalDateTimeExample {
public static void main(String[] args) {
LocalDateTime now = LocalDateTime.now();
LocalDateTime future = LocalDateTime.of(2026, 1, 1, 0, 0);
System.out.println("当前日期和时间: " + now);
System.out.println("未来日期和时间: " + future);
}
}
ZonedDateTime
ZonedDateTime 结合日期、时间和时区,如 2025-02-12T15:30:45+08:00[Asia/Shanghai]对象包含几个关键方法
now(ZoneId zone):获取指定时区的当前日期和时间。of(int year, int month, int day, int hour, int minute, int second, int nanoOfSecond, ZoneId zone):创建带时区的日期时间实例。withZoneSameInstant(ZoneId zone):更改时区同时保持瞬间不变。format(DateTimeFormatter formatter):格式化带时区的日期时间。
ZoneOffset:表示相对于 UTC 的固定偏移量,如 +02:00。
// 创建北京时区的 ZoneId 对象,时区标识符,如 Asia/Shanghai 或 Europe/London。
ZoneId bjZone = ZoneId.of("Asia/Beijing");
// 创建北京当前时间的 ZonedDateTime 对象
ZonedDateTime bjTime = ZonedDateTime.now(bjZone);
ZonedDateTime utcTime = bjTime.atZone(ZoneId.of("UTC"));
import java.time.ZonedDateTime;
import java.time.ZoneId;
public class ZonedDateTimeExample {
public static void main(String[] args) {
ZonedDateTime nowInUTC = ZonedDateTime.now(ZoneId.of("UTC"));
ZonedDateTime nowInShanghai = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));
System.out.println("当前 UTC 时间: " + nowInUTC);
System.out.println("当前上海时间: " + nowInShanghai);
}
}
Instant
在 Java 里纪元秒是从 UTC 1970 年 1 月 1 日 00:00:00 开始到指定时刻所经过的秒数
表示时间线上的一个点(时间戳),以 UTC 为基准,对象包含的关键方法
now():获取当前时间戳。ofEpochSecond(long epochSecond):根据纪元秒创建时间戳。getEpochSecond():获取时间戳的纪元秒。format(DateTimeFormatter formatter):格式化时间戳。
import java.time.Instant;
public class InstantExample {
public static void main(String[] args) {
Instant now = Instant.now();
Instant epoch = Instant.ofEpochSecond(0);
// 当前时间戳: 2025-02-12T05:43:18.959453Z
System.out.println("当前时间戳: " + now);
// 纪元时间戳: 1970-01-01T00:00:00Z
System.out.println("纪元时间戳: " + epoch);
}
}
Duration 与 Period
Period:表示基于日期的时间段,如“2 天”或“3 个月”。Duration:表示基于时间的时间段,如“5 小时”或“30 分钟”。
Period 对象可以通过静态方法 Period.of() 创建
// 3年10个月20天的 Period 对象
Period period = Period.of(3, 10, 20);
Duration 类表示时间上的一个持续时间,可以用来表示小时、分钟、秒、毫秒这样的时间段
// 3小时20分钟的 Duration 对象
Duration duration = Duration.ofHours(3).plusMinutes(20);
可以利用 Period 和 Duration 对象计算日期和时间之间的差距
LocalDate date1 = LocalDate.of(2023, 1, 1);
LocalDate date2 = LocalDate.of(2023, 11, 22);
// 计算两个 LocalDate 对象的日期差
Period period = Period.between(date1, date2);
System.out.println(
period.getYears() + " years, " +
period.getMonths() + " months, " +
period.getDays() + " days");
LocalDateTime time1 = LocalDateTime.of(2023, 01, 01, 10, 0);
LocalDateTime time2 = LocalDateTime.of(2023, 05, 01, 10, 0);
// 计算两个 LocalDateTime 对象之间的时间差
Duration duration = Duration.between(time1, time2);
System.out.println(
duration.toHours() + " hours, " +
duration.toMinutes() % 60 + " minutes, " +
duration.getSeconds() % 60 + " seconds");
DataTimeFormatter
java.time.format.DateTimeFormatter 类提供了灵活且线程安全的方式来格式化和解析日期时间对象。
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class FormattingExample {
public static void main(String[] args) {
LocalDateTime now = LocalDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("EEEE, MMMM d, yyyy HH:mm:ss");
String formattedDate = now.format(formatter);
// 格式化后的日期: Wednesday, February 12, 2025 13:55:49
System.out.println("格式化后的日期: " + formattedDate);
}
}
解析字符串为日期时间对象
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class ParsingExample {
public static void main(String[] args) {
String dateStr = "2025-02-12 13:30:45";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime dateTime = LocalDateTime.parse(dateStr, formatter);
// 解析后的日期时间: 2025-02-12T13:30:45
System.out.println("解析后的日期时间: " + dateTime);
}
}
常见的日期时间格式模式
- yyyy:年份(如 2025)
- MM:月份(01-12)
- dd:月份中的日(01-31)
- HH:24 小时制的小时(00-23)
- hh:12 小时制的小时(01-12)
- mm:分钟(00-59)
- ss:秒(00-59)
- a:上午/下午标记(AM/PM)
- EEEE:星期几的全称(如 Wednesday)
- MMM:月份的缩写(如 Oct)
- MMMM:月份的全称(如 October)
- z:时区缩写(如 EST)
- Z:相对于 UTC 的偏移量(如 -05:00)
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
public class MultiplePatternsExample {
public static void main(String[] args) {
LocalDate date = LocalDate.now();
LocalTime time = LocalTime.now();
DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");
DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("hh:mm a");
String formattedDate = date.format(dateFormatter);
String formattedTime = time.format(timeFormatter);
// 格式化后的日期: 12/02/2025
System.out.println("格式化后的日期: " + formattedDate);
// 格式化后的时间: 03:30 PM
System.out.println("格式化后的时间: " + formattedTime);
}
}
夏令时
Java 在使用正确的 ZoneId 时会自动调整夏令时,java.time 包中的类与 IANA(Internet Assigned Numbers Authority)时区数据库紧密集成,该数据库包含了全球各个时区的详细信息,其中就包括夏令时和冬令时的规则。这些规则会根据不同国家和地区的法律规定进行更新。
import java.time.ZonedDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
public class DSTExample {
public static void main(String[] args) {
ZonedDateTime beforeDST = ZonedDateTime.of(2023, 3, 12, 1, 30, 0, 0, ZoneId.of("America/New_York"));
ZonedDateTime afterDST = beforeDST.plusHours(1);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm z");
// 夏令时前: 2023-03-12 01:30 EST
System.out.println("夏令时前: " + beforeDST.format(formatter));
// 夏令时后: 2023-03-12 03:30 EDT
System.out.println("夏令时后: " + afterDST.format(formatter));
}
}