Java 日期与时间
Java - 日期与时间
Section titled “Java - 日期与时间”在许多应用程序中,处理日期和时间是一项常见需求。Java 为此提供了强大的 API,但使用 Java 8 中引入的现代 API 非常重要。
现代日期/时间 API (java.time)
Section titled “现代日期/时间 API (java.time)”java.time 包在 Java 8 中引入,提供了一个全面、不可变且大大改进的 API,用于处理日期、时间、时区、持续时间和时间段。这是所有新开发的推荐 API。
为什么选择 java.time?
Section titled “为什么选择 java.time?”- 不可变性 (Immutability):
java.time中的对象(如LocalDate,LocalTime)是不可变的,这意味着它们的状态一旦创建后就无法更改。这使得它们本质上是线程安全的,并且更容易推理。 - 清晰性 (Clarity): 日期、时间、日期-时间、时间戳、持续时间、时间段和时区等概念之间有明确的区分。
- 流畅 API (Fluent API): 方法设计支持清晰易读的链式调用(例如,
dateTime.plusDays(5).minusHours(2))。 - 更好的时区处理: 对时区提供强大的支持(
ZoneId,ZonedDateTime)。 - ISO 8601 标准: 基于 ISO 日期和时间表示标准。
java.time 中的核心类:
Section titled “java.time 中的核心类:”LocalDate:表示一个没有时间或时区的日期(例如,2023-10-27)。LocalTime:表示一个没有日期或时区的时间(例如,14:30:55.123)。LocalDateTime:表示日期和时间,但没有时区(例如,2023-10-27T14:30:55)。ZonedDateTime:表示带有特定时区的日期和时间(例如,2023-10-27T14:30:55+01:00[Europe/London])。Instant:表示时间线上的特定点(纳秒精度),通常相对于 UTC epoch(1970 年 1 月 1 日)。适用于时间戳。Duration:表示基于时间的时间量(秒,纳秒)。适用于测量机器时间(例如,两个Instant之间的差值)。Period:表示基于日期的时间量(年,月,日)。适用于人类可读的时间差(例如,年龄)。DateTimeFormatter:用于将日期/时间对象格式化为字符串以及将字符串解析为日期/时间对象。ZoneId:表示时区标识符(例如,Europe/Paris,Asia/Tokyo)。ZoneOffset:表示与 UTC 的固定偏移量(例如,+02:00)。
获取当前日期和时间
Section titled “获取当前日期和时间”使用静态方法 now():
import java.time.*;
public class CurrentDateTime { public static void main(String[] args) { LocalDate today = LocalDate.now(); System.out.println("Today's Date: " + today); // 例如,2023-10-27
LocalTime currentTime = LocalTime.now(); System.out.println("Current Time: " + currentTime); // 例如,10:15:30.123456789
LocalDateTime currentDateTime = LocalDateTime.now(); System.out.println("Current Date and Time: " + currentDateTime);
// 获取特定时区的当前时间 ZoneId londonZone = ZoneId.of("Europe/London"); ZonedDateTime londonTime = ZonedDateTime.now(londonZone); System.out.println("Current London Time: " + londonTime);
Instant timestamp = Instant.now(); System.out.println("Current Instant (UTC): " + timestamp); }}创建特定日期和时间
Section titled “创建特定日期和时间”使用静态工厂方法 of():
import java.time.*;
public class SpecificDateTime { public static void main(String[] args) { LocalDate specificDate = LocalDate.of(2024, Month.JANUARY, 15); // 年,月,日 System.out.println("Specific Date: " + specificDate);
LocalDate dateFromYMD = LocalDate.of(2024, 1, 15); // 年,月(1-12),日 System.out.println("Date from YMD: " + dateFromYMD);
LocalTime specificTime = LocalTime.of(18, 30, 0); // 时,分,秒 System.out.println("Specific Time: " + specificTime);
LocalDateTime specificDateTime = LocalDateTime.of(2024, 1, 15, 18, 30); System.out.println("Specific DateTime: " + specificDateTime);
ZoneId newYorkZone = ZoneId.of("America/New_York"); ZonedDateTime newYorkMeeting = ZonedDateTime.of(specificDateTime, newYorkZone); System.out.println("New York Meeting: " + newYorkMeeting); }}格式化与解析
Section titled “格式化与解析”DateTimeFormatter 用于将日期/时间对象转换为字符串(格式化)以及将字符串解析回日期/时间对象(解析)。
使用预定义格式化器:
Section titled “使用预定义格式化器:”import java.time.LocalDateTime;import java.time.format.DateTimeFormatter;import java.time.format.FormatStyle;
public class PredefinedFormatting { public static void main(String[] args) { LocalDateTime now = LocalDateTime.now();
// 预定义的 ISO 格式(toString() 的默认格式) System.out.println("ISO DATE TIME: " + now.format(DateTimeFormatter.ISO_DATE_TIME)); System.out.println("ISO DATE: " + now.format(DateTimeFormatter.ISO_DATE));
// 特定于语言环境的格式 DateTimeFormatter shortFormat = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT); DateTimeFormatter mediumFormat = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM);
System.out.println("Short format: " + now.format(shortFormat)); // 例如,10/27/23, 11:30 AM (US 语言环境) System.out.println("Medium format: " + now.format(mediumFormat)); // 例如,Oct 27, 2023, 11:30:00 AM }}使用自定义模式:
Section titled “使用自定义模式:”import java.time.LocalDate;import java.time.LocalDateTime;import java.time.format.DateTimeFormatter;import java.time.format.DateTimeParseException;
public class CustomFormattingParsing { public static void main(String[] args) { LocalDateTime now = LocalDateTime.now();
// 定义自定义模式 DateTimeFormatter customFormatter = DateTimeFormatter.ofPattern("EEEE, MMMM dd, yyyy 'at' hh:mm a");
// 格式化 String formattedString = now.format(customFormatter); System.out.println("Custom format: " + formattedString); // 例如,Friday, October 27, 2023 at 11:35 AM
// 解析 String dateString = "Monday, June 05, 2023 at 02:30 PM"; try { LocalDateTime parsedDateTime = LocalDateTime.parse(dateString, customFormatter); System.out.println("Parsed DateTime: " + parsedDateTime); } catch (DateTimeParseException e) { System.err.println("Error parsing date string: " + e.getMessage()); // 解析日期字符串出错: }
// 解析更简单的格式 String simpleDate = "2023-01-20"; try { // 这里适合使用 ISO_DATE 格式化器 LocalDate parsedDate = LocalDate.parse(simpleDate, DateTimeFormatter.ISO_DATE); System.out.println("Parsed simple date: " + parsedDate); } catch (DateTimeParseException e) { System.err.println("Error parsing simple date: " + e.getMessage()); // 解析简单日期出错: } }}常用模式字母:
Section titled “常用模式字母:”| 字母 | 组件 | 示例 |
|---|---|---|
y | 年份 | yy (03), yyyy (2003) |
M | 月份 | M (7), MM (07), MMM (Jul), MMMM (July) |
d | 月份中的天数 | d (5), dd (05) |
E | 星期中的天数 | E/EE/EEE (Tue), EEEE (Tuesday) |
H | 小时 (0-23) | H (9), HH (09) |
h | 小时 (1-12) | h (9), hh (09) |
m | 分钟 | m (5), mm (05) |
s | 秒 | s (8), ss (08) |
a | AM/PM 标记 | a (AM/PM) |
z | 时区名称 | z (PDT) |
Z / O / X | 时区偏移 | Z (+0100), O (GMT+1), X / XX / XXX (-08 / -0800 / -08:00) |
' | 文本转义 | 'Date:' yyyy |
日期/时间操作
Section titled “日期/时间操作”由于 java.time 对象是不可变的,操作方法会返回一个带有修改值的新对象。
import java.time.*;
public class DateTimeManipulation { public static void main(String[] args) { LocalDateTime meetingTime = LocalDateTime.of(2023, 11, 10, 14, 0); // 2023 年 11 月 10 日下午 2 点 System.out.println("Original Meeting Time: " + meetingTime);
// 添加/减去时间 LocalDateTime rescheduledTime = meetingTime.plusDays(3).minusHours(1); System.out.println("Rescheduled Time: " + rescheduledTime); // 2023 年 11 月 13 日下午 1 点
LocalDate today = LocalDate.now(); LocalDate nextWeek = today.plusWeeks(1); System.out.println("Next Week: " + nextWeek);
// 获取日期/时间的部分 int year = meetingTime.getYear(); // 2023 Month month = meetingTime.getMonth(); // NOVEMBER int day = meetingTime.getDayOfMonth(); // 10 DayOfWeek dayOfWeek = meetingTime.getDayOfWeek(); // FRIDAY int hour = meetingTime.getHour(); // 14
System.out.println("Meeting details: " + dayOfWeek + ", " + month + " " + day + ", " + year);
// 使用 'with' 修改特定字段 LocalDateTime startOfDay = meetingTime.withHour(9).withMinute(0).withSecond(0); System.out.println("Start of Business Day: " + startOfDay); }}时间段 (Periods) 和持续时间 (Durations)
Section titled “时间段 (Periods) 和持续时间 (Durations)”Period 用于基于日期的差异,Duration 用于基于时间的差异。
import java.time.*;
public class PeriodDurationExample { public static void main(String[] args) { // 时间段 (年, 月, 日) LocalDate startDate = LocalDate.of(2023, 5, 15); LocalDate endDate = LocalDate.of(2024, 7, 20); Period periodBetween = Period.between(startDate, endDate); System.out.println("Period: " + periodBetween); // P1Y2M5D (1 年, 2 个月, 5 天) System.out.printf("Difference: %d years, %d months, %d days%n", periodBetween.getYears(), periodBetween.getMonths(), periodBetween.getDays());
Period twoWeeks = Period.ofWeeks(2); LocalDate futureDate = startDate.plus(twoWeeks); System.out.println("Two weeks later: " + futureDate);
// 持续时间 (天, 时, 分, 秒, 纳秒) Instant startInstant = Instant.now(); // 模拟一些工作 try { Thread.sleep(1500); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } Instant endInstant = Instant.now(); Duration timeElapsed = Duration.between(startInstant, endInstant); System.out.println("\nTime Elapsed (Duration): " + timeElapsed); // 例如,PT1.500123456S System.out.println("Time Elapsed (Millis): " + timeElapsed.toMillis()); // 经过的时间 (毫秒):
Duration twoHours = Duration.ofHours(2); LocalTime deadline = LocalTime.of(15, 0).plus(twoHours); System.out.println("Deadline Time: " + deadline); }}测量经过的时间
Section titled “测量经过的时间”使用 Instant 和 Duration 进行精确的时间测量。
import java.time.Duration;import java.time.Instant;
public class MeasureTime { public static void main(String[] args) { Instant start = Instant.now(); System.out.println("Starting operation at: " + start);
// 模拟一个任务 try { Thread.sleep(2000); // 暂停 2 秒 } catch (InterruptedException e) { Thread.currentThread().interrupt(); System.err.println("Sleep interrupted"); // 暂停被中断 }
Instant end = Instant.now(); System.out.println("Finished operation at: " + end);
Duration duration = Duration.between(start, end); System.out.println("Duration: " + duration); // 例如,PT2.001S System.out.println("Duration in milliseconds: " + duration.toMillis()); // 持续时间(毫秒): }}遗留日期/时间 API (java.util.Date, Calendar)
Section titled “遗留日期/时间 API (java.util.Date, Calendar)”在 Java 8 之前,开发者使用 java.util.Date 和 java.util.Calendar (通常是 java.util.GregorianCalendar)。这些类有几个缺点:
- 可变性 (Mutability):
Date对象是可变的,这可能导致在并发环境或对象传递时出现 bug。 - 令人困惑的 API:
Date表示一个时间点,但其toString()方法包含基于默认时区的日期/时间组件。Date和Calendar中的月份索引基于 0(一月=0),这是一个常见的错误源。 - 糟糕的区分:
Date将时间点概念与日期/时间字段混合在一起。 - 有限的时区支持:
Calendar的时区处理很麻烦。
在新代码中避免使用 java.util.Date 和 java.util.Calendar。 如果需要与使用它们的旧 API 交互,请在遗留 API 和现代 API 之间进行转换:
import java.time.*;import java.util.Date;import java.util.Calendar;import java.util.GregorianCalendar;
public class LegacyConversion { public static void main(String[] args) { // 遗留 Date 转 Instant Date legacyDate = new Date(); Instant instantFromDate = legacyDate.toInstant(); ZonedDateTime zdtFromDate = instantFromDate.atZone(ZoneId.systemDefault()); System.out.println("From legacy Date: " + zdtFromDate);
// Instant 转 遗留 Date Instant nowInstant = Instant.now(); Date dateFromInstant = Date.from(nowInstant); System.out.println("To legacy Date: " + dateFromInstant);
// 遗留 Calendar 转 ZonedDateTime Calendar legacyCalendar = new GregorianCalendar(); // 表示当前时间 ZonedDateTime zdtFromCalendar = ((GregorianCalendar) legacyCalendar).toZonedDateTime(); System.out.println("From legacy Calendar: " + zdtFromCalendar);
// ZonedDateTime 转 遗留 Calendar ZonedDateTime currentZdt = ZonedDateTime.now(); GregorianCalendar calendarFromZdt = GregorianCalendar.from(currentZdt); System.out.println("To legacy Calendar (month is 0-based): " + calendarFromZdt.get(Calendar.MONTH)); // 转遗留 Calendar (月份基于 0 开始): }}下一步是什么?
Section titled “下一步是什么?”掌握 java.time API 对于现代 Java 开发至关重要。接下来,我们将探讨另一个常见的数据操作任务:使用 String 类和相关概念(如正则表达式)处理字符序列。