文章目录
1. 日期与时间概述
1.1 日期时间在编程中的重要性
在Java应用程序开发中,日期和时间处理是一项基础且常见的需求。从记录用户注册时间、计算订单期限、设置活动倒计时,到生成报表、处理定时任务等,几乎所有的应用程序都需要进行日期时间处理。正确高效地使用Java中的日期类,对于开发高质量的应用程序至关重要。
1.2 Java中日期API的发展历程
Java中的日期API经历了显著的演变:
- 早期版本(JDK 1.0):
java.util.Date
类是Java最早提供的日期处理类,设计上存在许多缺陷。 - JDK 1.1增强: 引入了
java.util.Calendar
和java.text.SimpleDateFormat
,增强了日期处理能力。 - Java 8革新: 引入了全新的
java.time
包,提供了更加完善的日期时间API,解决了早期API的许多问题。
1.3 日期类选择指南
- 新项目开发: 强烈建议使用Java 8的
java.time
包中的类。 - 维护遗留系统: 可能需要继续使用
Date
和Calendar
,但可以考虑逐步迁移到新API。 - 需要与第三方库集成: 了解库所使用的日期API,并根据需要进行转换。
下面将详细介绍Java中各种日期时间类的使用方法、特点以及最佳实践。
2. java.util.Date类
2.1 Date类的基本概念
java.util.Date
是Java最早提供的日期类,从JDK 1.0就已存在。它实质上表示的是一个时间点,精确到毫秒级,内部实现是一个长整型值,表示自1970年1月1日00:00:00 GMT(格林尼治标准时间)以来的毫秒数,也称为"Unix时间戳"(但单位是毫秒而非秒)。
2.2 Date类的缺点
虽然Date
类是Java中最古老的日期处理类,但它有很多设计上的缺陷:
- 可变性(非线程安全):
Date
对象是可变的,多线程环境下可能导致问题。 - API设计混乱: 许多方法在JDK 1.1后已被废弃,如
getYear()
,getMonth()
等。 - 时区处理不当: 没有明确的时区概念,依赖于默认时区。
- 无法表示日期而不包含时间: 总是包含精确到毫秒的时间信息。
- 格式化能力有限: 需要配合
SimpleDateFormat
使用。 - 月份从0开始计数: 1月用0表示,12月用11表示,容易混淆。
2.3 创建Date对象
import java.util.Date;
public class DateExample {
public static void main(String[] args) {
// 1. 创建表示当前时间的Date对象
Date currentDate = new Date();
System.out.println("当前时间: " + currentDate);
// 2. 创建表示特定时间点的Date对象 (不推荐,仅用于演示)
long timeInMillis = 1609459200000L; // 2021-01-01 00:00:00 GMT
Date specificDate = new Date(timeInMillis);
System.out.println("特定时间: " + specificDate);
// 3. 获取Date对象的时间戳
long timestamp = currentDate.getTime();
System.out.println("当前时间的时间戳(毫秒): " + timestamp);
// 4. 设置Date对象的时间
currentDate.setTime(timeInMillis);
System.out.println("修改后的时间: " + currentDate);
}
}
2.4 Date对象的常用方法
尽管大多数Date
类的方法在Java 1.1后已被废弃,但了解一些核心方法仍然很有必要:
import java.util.Date;
public class DateMethodsExample {
public static void main(String[] args) {
Date date1 = new Date();
Date date2 = new Date(date1.getTime() - 86400000); // 一天前
// 1. getTime() - 获取时间戳
System.out.println("时间戳: " + date1.getTime());
// 2. after() - 判断是否在另一个日期之后
System.out.println("date1在date2之后: " + date1.after(date2));
// 3. before() - 判断是否在另一个日期之前
System.out.println("date1在date2之前: " + date1.before(date2));
// 4. equals() - 判断两个日期是否相等
System.out.println("date1等于date2: " + date1.equals(date2));
// 5. compareTo() - 比较两个日期
System.out.println("date1与date2比较: " + date1.compareTo(date2));
// 返回值: 正数表示date1晚于date2,负数表示date1早于date2,0表示相等
// 6. clone() - 克隆日期对象
Date date3 = (Date) date1.clone();
System.out.println("克隆的日期: " + date3);
// 7. toString() - 转换为字符串
System.out.println("日期字符串: " + date1.toString());
}
}
2.5 Date对象的格式化与解析
Date
类本身并不提供格式化方法,需要配合SimpleDateFormat
类使用:
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class DateFormattingExample {
public static void main(String[] args) {
Date currentDate = new Date();
// 1. 使用SimpleDateFormat格式化日期
SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String formattedDate = sdf1.format(currentDate);
System.out.println("格式化后的日期: " + formattedDate);
// 2. 不同的格式化模式
SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒");
System.out.println("中文格式: " + sdf2.format(currentDate));
SimpleDateFormat sdf3 = new SimpleDateFormat("MM/dd/yyyy");
System.out.println("美国格式: " + sdf3.format(currentDate));
// 3. 解析字符串为Date对象
try {
String dateStr = "2021-12-31 23:59:59";
Date parsedDate = sdf1.parse(dateStr);
System.out.println("解析的日期: " + parsedDate);
// 解析不同格式
String anotherDateStr = "12/31/2021";
Date anotherParsedDate = sdf3.parse(anotherDateStr);
System.out.println("另一个解析的日期: " + anotherParsedDate);
} catch (ParseException e) {
System.out.println("日期解析错误: " + e.getMessage());
}
// 4. 注意:SimpleDateFormat不是线程安全的
// 在多线程环境中,每个线程应该创建自己的SimpleDateFormat实例
}
}
2.6 Date类的线程安全问题
Date
类是可变的,这在多线程环境中可能导致问题:
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class DateThreadSafetyIssue {
// 共享的Date对象
private static Date sharedDate = new Date();
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(5);
// 多个线程同时修改和读取同一个Date对象可能导致数据不一致
for (int i = 0; i < 10; i++) {
final int index = i;
executor.submit(() -> {
if (index % 2 == 0) {
// 修改Date对象
sharedDate.setTime(System.currentTimeMillis() + index * 1000);
System.out.println("线程" + index + "修改日期: " + sharedDate);
} else {
// 读取Date对象
System.out.println("线程" + index + "读取日期: " + sharedDate);
}
});
}
executor.shutdown();
// 解决方案:
// 1. 使用同步块保护共享Date对象
// 2. 使用线程局部变量(ThreadLocal)
// 3. 每次需要时创建新的Date对象
// 4. 使用Java 8中的不可变日期时间类
}
}
2.7 与Calendar和时间戳的转换
Date
类经常需要与Calendar
类和时间戳进行转换:
import java.util.Calendar;
import java.util.Date;
public class DateConversionExample {
public static void main(String[] args) {
// 1. Date与时间戳互转
Date currentDate = new Date();
long timestamp = currentDate.getTime();
System.out.println("Date转时间戳: " + timestamp);
Date dateFromTimestamp = new Date(timestamp);
System.out.println("时间戳转Date: " + dateFromTimestamp);
// 2. Date与Calendar互转
// Date转Calendar
Calendar calendar = Calendar.getInstance();
calendar.setTime(currentDate);
System.out.println("Date转Calendar: " + calendar.getTime());
// Calendar转Date
Date dateFromCalendar = calendar.getTime();
System.out.println("Calendar转Date: " + dateFromCalendar);
}
}
3. java.util.Calendar类
3.1 Calendar类的基本概念
java.util.Calendar
类是在JDK 1.1中引入的,作为对Date
类的改进和补充。Calendar
是一个抽象类,提供了对日期各个部分(年、月、日、时、分、秒等)的访问和操作方法,并增加了对时区的支持。在实际使用中,通常通过调用其静态方法getInstance()
获取实例,默认返回的是GregorianCalendar
(格里高利日历,即公历)的实例。
3.2 Calendar类的优势
相比于Date
类,Calendar
类提供了以下优势:
- 日期计算能力: 提供了丰富的日期计算方法。
- 日期各部分的访问: 可以单独访问和修改日期的年、月、日、时、分、秒。
- 时区支持: 提供了对不同时区的支持。
- 日历系统扩展: 可以支持不同的日历系统(如公历、佛历等)。
3.3 Calendar类的缺点
尽管Calendar
比Date
有很多改进,但仍然存在一些问题:
- 可变性: 与
Date
一样,Calendar
对象也是可变的,非线程安全。 - API设计不直观: 某些API设计不直观,使用起来较为繁琐。
- 月份仍从0开始: 1月仍用0表示,保持了
Date
类的混淆特性。 - 日期时间状态不一致: 在某些操作后,内部状态可能不一致,需要调用
complete()
方法。
3.4 创建Calendar对象
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
public class CalendarCreationExample {
public static void main(String[] args) {
// 1. 获取默认Calendar实例(使用当前时间、默认时区和Locale)
Calendar calendar1 = Calendar.getInstance();
System.out.println("默认Calendar: " + calendar1.getTime());
// 2. 指定时区创建Calendar
Calendar calendar2 = Calendar.getInstance(TimeZone.getTimeZone("America/New_York"));
System.out.println("纽约时区Calendar: " + calendar2.getTime());
// 3. 指定Locale创建Calendar
Calendar calendar3 = Calendar.getInstance(Locale.FRANCE);
System.out.println("法国Locale的Calendar: " + calendar3.getTime());
// 4. 指定时区和Locale创建Calendar
Calendar calendar4 = Calendar.getInstance(TimeZone.getTimeZone("Europe/Paris"), Locale.FRANCE);
System.out.println("巴黎时区和法国Locale的Calendar: " + calendar4.getTime());
// 5. 从Date创建Calendar
Date date = new Date();
Calendar calendar5 = Calendar.getInstance();
calendar5.setTime(date);
System.out.println("从Date创建的Calendar: " + calendar5.getTime());
}
}
3.5 访问Calendar的字段
import java.util.Calendar;
public class CalendarFieldsExample {
public static void main(String[] args) {
Calendar calendar = Calendar.getInstance();
// 1. 获取年、月、日、时、分、秒
int year = calendar.get(Calendar.YEAR);
// 注意:月份从0开始,即0表示1月,11表示12月
int month = calendar.get(Calendar.MONTH) + 1; // 转换为人类可读的月份
int day = calendar.get(Calendar.DAY_OF_MONTH);
int hour = calendar.get(Calendar.HOUR_OF_DAY); // 24小时制
int minute = calendar.get(Calendar.MINUTE);
int second = calendar.get(Calendar.SECOND);
int millisecond = calendar.get(Calendar.MILLISECOND);
System.out.println(String.format("当前日期时间: %d-%02d-%02d %02d:%02d:%02d.%03d",
year, month, day, hour, minute, second, millisecond));
// 2. 获取星期
int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK);
// 注意:DAY_OF_WEEK中,1代表星期日,2代表星期一,依此类推
String[] weekdays = {"", "星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"};
System.out.println("今天是: " + weekdays[dayOfWeek]);
// 3. 获取一年中的第几天
int dayOfYear = calendar.get(Calendar.DAY_OF_YEAR);
System.out.println("一年中的第几天: " + dayOfYear);
// 4. 获取一月中的第几周
int weekOfMonth = calendar.get(Calendar.WEEK_OF_MONTH);
System.out.println("本月第几周: " + weekOfMonth);
// 5. 获取一年中的第几周
int weekOfYear = calendar.get(Calendar.WEEK_OF_YEAR);
System.out.println("一年中的第几周: " + weekOfYear);
// 6. 是否为闰年
boolean isLeapYear = calendar.getActualMaximum(Calendar.DAY_OF_YEAR) > 365;
System.out.println("是否为闰年: " + isLeapYear);
// 7. 获取当月最大天数
int maxDaysInMonth = calendar.getActualMaximum(Calendar.DAY_OF_MONTH);
System.out.println("当月天数: " + maxDaysInMonth);
}
}
3.6 设置Calendar的字段
import java.util.Calendar;
public class CalendarSetFieldsExample {
public static void main(String[] args) {
Calendar calendar = Calendar.getInstance();
// 打印当前日期
System.out.println("当前日期: " + calendar.getTime());
// 1. 使用set方法设置年、月、日
calendar.set(Calendar.YEAR, 2023);
calendar.set(Calendar.MONTH, Calendar.DECEMBER); // 12月
calendar.set(Calendar.DAY_OF_MONTH, 31);
// 2. 设置时、分、秒
calendar.set(Calendar.HOUR_OF_DAY, 23);
calendar.set(Calendar.MINUTE, 59);
calendar.set(Calendar.SECOND, 59);
calendar.set(Calendar.MILLISECOND, 999);
System.out.println("设置后的日期: " + calendar.getTime());
// 3. 一次性设置年、月、日
calendar.set(2024, Calendar.JANUARY, 1); // 2024年1月1日
System.out.println("再次设置后的日期: " + calendar.getTime());
// 4. 一次性设置年、月、日、时、分、秒
calendar.set(2024, Calendar.JANUARY, 1, 0, 0, 0);
System.out.println("完整设置后的日期: " + calendar.getTime());
// 5. 设置时区
calendar.setTimeZone(TimeZone.getTimeZone("GMT+8:00"));
System.out.println("设置东八区后的日期: " + calendar.getTime());
// 6. 清除所有字段
calendar.clear();
System.out.println("清除后的日期: " + calendar.getTime()); // 1970-01-01 00:00:00
// 7. 只清除部分字段
calendar = Calendar.getInstance(); // 重新获取当前日期时间
calendar.clear(Calendar.HOUR_OF_DAY);
calendar.clear(Calendar.MINUTE);
calendar.clear(Calendar.SECOND);
calendar.clear(Calendar.MILLISECOND);
System.out.println("只保留年月日的日期: " + calendar.getTime());
}
}
3.7 日期计算与操作
import java.util.Calendar;
import java.text.SimpleDateFormat;
public class CalendarOperationsExample {
public static void main(String[] args) {
Calendar calendar = Calendar.getInstance();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("当前日期: " + sdf.format(calendar.getTime()));
// 1. 增加/减少年
calendar.add(Calendar.YEAR, 1); // 增加1年
System.out.println("增加1年后: " + sdf.format(calendar.getTime()));
calendar.add(Calendar.YEAR, -2); // 减少2年
System.out.println("再减少2年后: " + sdf.format(calendar.getTime()));
// 2. 增加/减少月
calendar.add(Calendar.MONTH, 3); // 增加3个月
System.out.println("增加3个月后: " + sdf.format(calendar.getTime()));
// 3. 增加/减少天
calendar.add(Calendar.DAY_OF_MONTH, -10); // 减少10天
System.out.println("减少10天后: " + sdf.format(calendar.getTime()));
// 4. 增加/减少小时、分钟、秒
calendar.add(Calendar.HOUR, 5); // 增加5小时
System.out.println("增加5小时后: " + sdf.format(calendar.getTime()));
calendar.add(Calendar.MINUTE, 30); // 增加30分钟
System.out.println("增加30分钟后: " + sdf.format(calendar.getTime()));
calendar.add(Calendar.SECOND, -60); // 减少60秒
System.out.println("减少60秒后: " + sdf.format(calendar.getTime()));
// 5. 使用roll方法(只改变指定字段,不影响其他字段)
calendar = Calendar.getInstance(); // 重置日期
System.out.println("\n重置后的日期: " + sdf.format(calendar.getTime()));
calendar.roll(Calendar.MONTH, 1); // 月份加1,但年份不变
System.out.println("roll月份加1: " + sdf.format(calendar.getTime()));
// 比较roll和add的区别(当月份从12月变为1月时)
calendar.set(Calendar.MONTH, Calendar.DECEMBER); // 设置为12月
System.out.println("设置为12月: " + sdf.format(calendar.getTime()));
Calendar rollCal = (Calendar) calendar.clone();
Calendar addCal = (Calendar) calendar.clone();
rollCal.roll(Calendar.MONTH, 1); // 使用roll,月份变为1月,但年份不变
addCal.add(Calendar.MONTH, 1); // 使用add,月份变为1月,年份加1
System.out.println("使用roll增加1个月: " + sdf.format(rollCal.getTime()));
System.out.println("使用add增加1个月: " + sdf.format(addCal.getTime()));
// 6. 日期比较
Calendar date1 = Calendar.getInstance();
Calendar date2 = Calendar.getInstance();
date2.add(Calendar.DAY_OF_MONTH, 10); // date2比date1晚10天
System.out.println("\ndate1: " + sdf.format(date1.getTime()));
System.out.println("date2: " + sdf.format(date2.getTime()));
// 比较两个日期
System.out.println("date1在date2之前: " + date1.before(date2));
System.out.println("date1在date2之后: " + date1.after(date2));
System.out.println("date1与date2比较结果: " + date1.compareTo(date2));
}
}
3.8 处理不同时区
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;
import java.text.SimpleDateFormat;
public class CalendarTimeZoneExample {
public static void main(String[] args) {
// 1. 查看所有可用的时区ID
String[] availableIDs = TimeZone.getAvailableIDs();
System.out.println("部分可用时区ID:");
for (int i = 0; i < 10; i++) { // 只打印前10个作为示例
System.out.println(availableIDs[i]);
}
System.out.println("共有 " + availableIDs.length + " 个时区ID");
// 2. 获取默认时区
TimeZone defaultTZ = TimeZone.getDefault();
System.out.println("\n默认时区: " + defaultTZ.getID());
System.out.println("默认时区显示名: " + defaultTZ.getDisplayName());
System.out.println("默认时区偏移(毫秒): " + defaultTZ.getRawOffset());
// 3. 创建特定时区的Calendar
Calendar beijingCal = Calendar.getInstance(TimeZone.getTimeZone("Asia/Shanghai"));
Calendar londonCal = Calendar.getInstance(TimeZone.getTimeZone("Europe/London"));
Calendar newYorkCal = Calendar.getInstance(TimeZone.getTimeZone("America/New_York"));
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// 4. 比较不同时区的同一时刻
Date now = new Date();
sdf.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
System.out.println("\n北京时间: " + sdf.format(now));
sdf.setTimeZone(TimeZone.getTimeZone("Europe/London"));
System.out.println("伦敦时间: " + sdf.format(now));
sdf.setTimeZone(TimeZone.getTimeZone("America/New_York"));
System.out.println("纽约时间: " + sdf.format(now));
// 5. 时区转换 - 将北京时间转换为纽约时间
Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("Asia/Shanghai"));
calendar.set(2023, Calendar.JANUARY, 1, 12, 0, 0); // 2023-01-01 12:00:00 北京时间
Date beijingDate = calendar.getTime();
sdf.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
System.out.println("\n北京时间: " + sdf.format(beijingDate));
sdf.setTimeZone(TimeZone.getTimeZone("America/New_York"));
System.out.println("转换为纽约时间: " + sdf.format(beijingDate));
// 6. 处理夏令时
TimeZone newYorkTZ = TimeZone.getTimeZone("America/New_York");
Calendar summerCal = Calendar.getInstance();
summerCal.set(2023, Calendar.JULY, 1); // 夏季日期
Calendar winterCal = Calendar.getInstance();
winterCal.set(2023, Calendar.JANUARY, 1); // 冬季日期
System.out.println("\n纽约7月1日是否使用夏令时: " + newYorkTZ.inDaylightTime(summerCal.getTime()));
System.out.println("纽约1月1日是否使用夏令时: " + newYorkTZ.inDaylightTime(winterCal.getTime()));
}
}
3.9 Calendar的线程安全问题
与Date
类似,Calendar
也不是线程安全的:
import java.util.Calendar;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.text.SimpleDateFormat;
public class CalendarThreadSafetyIssue {
// 共享的Calendar对象
private static Calendar sharedCalendar = Calendar.getInstance();
private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(5);
// 多个线程同时修改和读取同一个Calendar对象
for (int i = 0; i < 10; i++) {
final int index = i;
executor.submit(() -> {
if (index % 2 == 0) {
// 修改Calendar对象
synchronized (sharedCalendar) {
sharedCalendar.add(Calendar.DAY_OF_MONTH, index);
System.out.println("线程" + index + "修改日期: "
+ sdf.format(sharedCalendar.getTime()));
}
} else {
// 读取Calendar对象
synchronized (sharedCalendar) {
System.out.println("线程" + index + "读取日期: "
+ sdf.format(sharedCalendar.getTime()));
}
}
});
}
executor.shutdown();
// 解决方案:
// 1. 使用同步块保护共享Calendar对象(如上所示)
// 2. 使用ThreadLocal
// 3. 每次需要时创建新的Calendar对象
// 4. 使用Java 8中的线程安全日期时间类
}
}
4. java.text.SimpleDateFormat类
4.1 SimpleDateFormat的基本概念
java.text.SimpleDateFormat
是Java提供的一个用于格式化和解析日期的类,它继承自DateFormat
。它允许我们以特定的格式将Date
对象转换为字符串,也可以将符合特定格式的字符串解析为Date
对象。
4.2 日期和时间模式
SimpleDateFormat
使用模式字符串来指定日期和时间的格式。以下是常用的模式字母:
字母 | 描述 | 示例 |
---|---|---|
y | 年 | 2023 |
M | 月份 | 07 或 Jul |
d | 月中的天数 | 10 |
H | 24小时制小时 | 23 |
h | 12小时制小时 | 11 |
m | 分钟 | 30 |
s | 秒 | 55 |
S | 毫秒 | 978 |
E | 星期 | 星期二 或 Tue |
D | 一年中的天数 | 189 |
w | 一年中的周数 | 27 |
W | 一月中的周数 | 2 |
a | AM/PM 标记 | AM 或 PM |
z | 时区 | GMT+8:00 |
Z | 时区偏移量 | +0800 |
4.3 创建SimpleDateFormat对象
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
public class SimpleDateFormatCreationExample {
public static void main(String[] args) {
Date currentDate = new Date();
// 1. 使用默认构造函数
SimpleDateFormat defaultFormat = new SimpleDateFormat();
System.out.println("默认格式: " + defaultFormat.format(currentDate));
// 2. 指定格式模式
SimpleDateFormat customFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("自定义格式: " + customFormat.format(currentDate));
// 3. 指定格式模式和Locale
SimpleDateFormat frenchFormat = new SimpleDateFormat("EEEE, d MMMM yyyy", Locale.FRENCH);
System.out.println("法语格式: " + frenchFormat.format(currentDate));
// 4. 设置时区
SimpleDateFormat tokyoFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
tokyoFormat.setTimeZone(TimeZone.getTimeZone("Asia/Tokyo"));
System.out.println("东京时区: " + tokyoFormat.format(currentDate));
}
}
4.4 日期格式化示例
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
public class DateFormattingPatternExample {
public static void main(String[] args) {
Date currentDate = new Date();
// 各种日期格式模式示例
formatAndPrint(currentDate, "yyyy-MM-dd", "标准日期格式");
formatAndPrint(currentDate, "yyyy年MM月dd日", "中文日期格式");
formatAndPrint(currentDate, "MM/dd/yyyy", "美国日期格式");
formatAndPrint(currentDate, "dd-MM-yyyy", "欧洲日期格式");
formatAndPrint(currentDate, "yyyyMMdd", "紧凑日期格式");
// 各种时间格式模式示例
formatAndPrint(currentDate, "HH:mm:ss", "24小时制时间格式");
formatAndPrint(currentDate, "hh:mm:ss a", "12小时制时间格式");
formatAndPrint(currentDate, "HH:mm", "小时和分钟");
formatAndPrint(currentDate, "HH:mm:ss.SSS", "带毫秒的时间格式");
// 日期和时间组合格式
formatAndPrint(currentDate, "yyyy-MM-dd HH:mm:ss", "标准日期时间格式");
formatAndPrint(currentDate, "yyyy-MM-dd'T'HH:mm:ss.SSSZ", "ISO 8601格式");
formatAndPrint(currentDate, "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", "UTC的ISO格式");
// 包含星期和月份名称的格式
formatAndPrint(currentDate, "E, dd MMM yyyy", "带星期的格式");
formatAndPrint(currentDate, "EEEE, dd MMMM yyyy", "完整星期和月份名称");
// 在不同的Locale下格式化
formatAndPrintWithLocale(currentDate, "EEEE, dd MMMM yyyy", Locale.ENGLISH, "英语");
formatAndPrintWithLocale(currentDate, "EEEE, dd MMMM yyyy", Locale.FRENCH, "法语");
formatAndPrintWithLocale(currentDate, "EEEE, dd MMMM yyyy", Locale.GERMAN, "德语");
formatAndPrintWithLocale(currentDate, "EEEE, dd MMMM yyyy", Locale.CHINESE, "中文");
}
private static void formatAndPrint(Date date, String pattern, String description) {
SimpleDateFormat sdf = new SimpleDateFormat(pattern);
System.out.println(description + ": " + sdf.format(date));
}
private static void formatAndPrintWithLocale(Date date, String pattern, Locale locale, String language) {
SimpleDateFormat sdf = new SimpleDateFormat(pattern, locale);
System.out.println(language + ": " + sdf.format(date));
}
}
4.5 日期解析示例
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class DateParsingExample {
public static void main(String[] args) {
// 1. 基本日期解析
parseAndPrint("2023-07-15", "yyyy-MM-dd", "基本日期");
// 2. 带时间的日期解析
parseAndPrint("2023-07-15 14:30:45", "yyyy-MM-dd HH:mm:ss", "带时间的日期");
// 3. 不同格式的日期解析
parseAndPrint("07/15/2023", "MM/dd/yyyy", "美式日期");
parseAndPrint("15-Jul-2023", "dd-MMM-yyyy", "带月份缩写的日期");
parseAndPrint("星期六, 15 七月 2023", "E, dd MMMM yyyy", "带星期的中文日期");
// 4. 宽松解析(设置lenient为false,要求严格匹配)
try {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
sdf.setLenient(false);
// 下面的日期实际上是不存在的(2月没有31日)
Date invalidDate = sdf.parse("2023-02-31");
System.out.println("宽松模式下解析的结果: " + invalidDate);
} catch (ParseException e) {
System.out.println("严格模式下解析错误: " + e.getMessage());
}
// 5. 解析带时区的日期
try {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");
Date dateWithTZ = sdf.parse("2023-07-15T14:30:45.123+08:00");
System.out.println("带时区的日期: " + dateWithTZ);
// 输出为标准格式
SimpleDateFormat outputSdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("格式化后: " + outputSdf.format(dateWithTZ));
} catch (ParseException e) {
System.out.println("解析错误: " + e.getMessage());
}
}
private static void parseAndPrint(String dateStr, String pattern, String description) {
try {
SimpleDateFormat sdf = new SimpleDateFormat(pattern);
Date date = sdf.parse(dateStr);
System.out.println(description + " [" + dateStr + "]: " + date);
// 转换为标准格式输出
SimpleDateFormat outputSdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(" 标准格式: " + outputSdf.format(date));
} catch (ParseException e) {
System.out.println(description + " 解析错误: " + e.getMessage());
}
}
}
4.6 SimpleDateFormat的线程安全问题
SimpleDateFormat
不是线程安全的,这在多线程环境中可能导致意外行为:
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.CountDownLatch;
public class SimpleDateFormatThreadIssue {
// 共享的SimpleDateFormat对象(不安全)
private static final SimpleDateFormat SHARED_FORMATTER = new SimpleDateFormat("yyyy-MM-dd");
public static void main(String[] args) throws InterruptedException {
int threadCount = 20;
ExecutorService executor = Executors.newFixedThreadPool(threadCount);
CountDownLatch latch = new CountDownLatch(threadCount);
// 测试的日期字符串数组
String[] dates = {"2023-01-15", "2023-02-20", "2023-03-25", "2023-04-30"};
// 演示线程安全问题
System.out.println("===使用共享SimpleDateFormat(不安全)===");
for (int i = 0; i < threadCount; i++) {
final int index = i % dates.length;
executor.submit(() -> {
try {
// 使用共享的SimpleDateFormat解析日期
String dateStr = dates[index];
Date date = parseWithSharedFormatter(dateStr);
String formattedDate = SHARED_FORMATTER.format(date);
// 如果解析和格式化结果不一致,说明出现了线程安全问题
if (!dateStr.equals(formattedDate)) {
System.out.println("线程安全问题! 原始: " + dateStr
+ ", 解析后再格式化: " + formattedDate);
}
} catch (Exception e) {
System.out.println("解析错误: " + e.getMessage());
} finally {
latch.countDown();
}
});
}
latch.await();
// 解决方案1:每次使用时创建新的SimpleDateFormat对象
System.out.println("\n===每次创建新的SimpleDateFormat(安全但低效)===");
testSafeApproach1(executor, threadCount, dates);
// 解决方案2:使用ThreadLocal
System.out.println("\n===使用ThreadLocal(安全且高效)===");
testSafeApproach2(executor, threadCount, dates);
executor.shutdown();
}
// 使用共享的SimpleDateFormat(不安全)
private static Date parseWithSharedFormatter(String dateStr) throws ParseException {
return SHARED_FORMATTER.parse(dateStr);
}
// 解决方案1:每次使用时创建新的SimpleDateFormat
private static void testSafeApproach1(ExecutorService executor,
int threadCount,
String[] dates) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(threadCount);
for (int i = 0; i < threadCount; i++) {
final int index = i % dates.length;
executor.submit(() -> {
try {
// 每次创建新的SimpleDateFormat
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
String dateStr = dates[index];
Date date = sdf.parse(dateStr);
String formattedDate = sdf.format(date);
// 应该不会有问题
if (!dateStr.equals(formattedDate)) {
System.out.println("意外错误! 原始: " + dateStr
+ ", 解析后再格式化: " + formattedDate);
}
} catch (Exception e) {
System.out.println("解析错误: " + e.getMessage());
} finally {
latch.countDown();
}
});
}
latch.await();
}
// 解决方案2:使用ThreadLocal
private static final ThreadLocal<SimpleDateFormat> THREAD_LOCAL_FORMATTER =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
private static void testSafeApproach2(ExecutorService executor,
int threadCount,
String[] dates) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(threadCount);
for (int i = 0; i < threadCount; i++) {
final int index = i % dates.length;
executor.submit(() -> {
try {
// 使用ThreadLocal获取SimpleDateFormat
SimpleDateFormat sdf = THREAD_LOCAL_FORMATTER.get();
String dateStr = dates[index];
Date date = sdf.parse(dateStr);
String formattedDate = sdf.format(date);
// 应该不会有问题
if (!dateStr.equals(formattedDate)) {
System.out.println("意外错误! 原始: " + dateStr
+ ", 解析后再格式化: " + formattedDate);
}
} catch (Exception e) {
System.out.println("解析错误: " + e.getMessage());
} finally {
latch.countDown();
}
});
}
latch.await();
}
}
5. Java 8日期时间API
5.1 Java 8日期时间API概述
Java 8引入了全新的日期时间API,位于java.time
包中,它解决了旧API的诸多问题。新API的主要特点包括:
- 不可变性: 所有类都是不可变的,因此线程安全。
- 分离关注点: 明确区分日期、时间、日期时间、持续时间等概念。
- 清晰的API: 方法名直观,使用更方便。
- 国际化支持: 更好地支持不同的日历系统和时区。
- 功能丰富: 提供了丰富的操作和计算方法。
5.2 核心类简介
新API的核心类包括:
- LocalDate: 表示日期(年月日),不含时间和时区
- LocalTime: 表示时间(时分秒纳秒),不含日期和时区
- LocalDateTime: 表示日期和时间,不含时区
- ZonedDateTime: 表示带时区的日期和时间
- Instant: 表示时间线上的一个点(时间戳)
- Duration: 表示两个时间之间的差(基于时间,秒和纳秒)
- Period: 表示两个日期之间的差(基于日期,年月日)
- DateTimeFormatter: 用于日期时间的格式化和解析
5.3 LocalDate类
LocalDate
表示不带时区的日期(年月日):
import java.time.LocalDate;
import java.time.Month;
import java.time.DayOfWeek;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalAdjusters;
public class LocalDateExample {
public static void main(String[] args) {
// 1. 创建LocalDate实例
LocalDate today = LocalDate.now(); // 当前日期
System.out.println("今天: " + today);
LocalDate specificDate = LocalDate.of(2023, Month.JULY, 15);
System.out.println("指定日期: " + specificDate);
LocalDate dateFromString = LocalDate.parse("2023-12-31");
System.out.println("从字符串解析: " + dateFromString);
// 2. 获取日期的组成部分
int year = today.getYear();
Month month = today.getMonth();
int monthValue = today.getMonthValue();
int dayOfMonth = today.getDayOfMonth();
DayOfWeek dayOfWeek = today.getDayOfWeek();
int dayOfYear = today.getDayOfYear();
System.out.println("年: " + year);
System.out.println("月(英文): " + month);
System.out.println("月(数字): " + monthValue);
System.out.println("日: " + dayOfMonth);
System.out.println("星期: " + dayOfWeek);
System.out.println("一年中的第几天: " + dayOfYear);
// 3. 日期操作
LocalDate tomorrow = today.plusDays(1);
System.out.println("明天: " + tomorrow);
LocalDate nextWeek = today.plusWeeks(1);
System.out.println("下周的今天: " + nextWeek);
LocalDate nextMonth = today.plusMonths(1);
System.out.println("下个月的今天: " + nextMonth);
LocalDate nextYear = today.plusYears(1);
System.out.println("明年的今天: " + nextYear);
LocalDate yesterday = today.minusDays(1);
System.out.println("昨天: " + yesterday);
// 4. 使用TemporalAdjusters进行高级调整
LocalDate firstDayOfMonth = today.with(TemporalAdjusters.firstDayOfMonth());
System.out.println("本月第一天: " + firstDayOfMonth);
LocalDate lastDayOfMonth = today.with(TemporalAdjusters.lastDayOfMonth());
System.out.println("本月最后一天: " + lastDayOfMonth);
LocalDate nextSunday = today.with(TemporalAdjusters.next(DayOfWeek.SUNDAY));
System.out.println("下一个星期日: " + nextSunday);
// 5. 日期比较
boolean isBefore = today.isBefore(tomorrow);
System.out.println("今天在明天之前: " + isBefore);
boolean isAfter = today.isAfter(yesterday);
System.out.println("今天在昨天之后: " + isAfter);
boolean isEqual = today.isEqual(today);
System.out.println("两个今天是否相等: " + isEqual);
// 6. 计算两个日期之间的差距
long daysBetween = ChronoUnit.DAYS.between(specificDate, today);
System.out.println("从指定日期到今天的天数: " + daysBetween);
// 7. 检查特殊日期
boolean isLeapYear = today.isLeapYear();
System.out.println("今年是闰年吗: " + isLeapYear);
// 8. 格式化LocalDate
String formattedDate = today.format(DateTimeFormatter.ISO_DATE);
System.out.println("ISO格式化: " + formattedDate);
DateTimeFormatter customFormatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日");
String customFormatted = today.format(customFormatter);
System.out.println("自定义格式化: " + customFormatted);
}
}
5.4 LocalTime类
LocalTime
表示不带时区的时间(时分秒纳秒):
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
public class LocalTimeExample {
public static void main(String[] args) {
// 1. 创建LocalTime实例
LocalTime now = LocalTime.now(); // 当前时间
System.out.println("现在时间: " + now);
LocalTime specificTime = LocalTime.of(13, 30, 45);
System.out.println("指定时间: " + specificTime);
LocalTime timeWithNanos = LocalTime.of(13, 30, 45, 500000000);
System.out.println("带纳秒的时间: " + timeWithNanos);
LocalTime timeFromString = LocalTime.parse("14:45:30");
System.out.println("从字符串解析: " + timeFromString);
// 2. 获取时间的组成部分
int hour = now.getHour();
int minute = now.getMinute();
int second = now.getSecond();
int nano = now.getNano();
System.out.println("时: " + hour);
System.out.println("分: " + minute);
System.out.println("秒: " + second);
System.out.println("纳秒: " + nano);
// 3. 时间操作
LocalTime oneHourLater = now.plusHours(1);
System.out.println("一小时后: " + oneHourLater);
LocalTime tenMinutesBefore = now.minusMinutes(10);
System.out.println("十分钟前: " + tenMinutesBefore);
LocalTime withChangedHour = now.withHour(10);
System.out.println("修改小时为10: " + withChangedHour);
// 4. 特殊时间
LocalTime midnight = LocalTime.MIDNIGHT;
System.out.println("午夜: " + midnight);
LocalTime noon = LocalTime.NOON;
System.out.println("正午: " + noon);
// 5. 时间比较
boolean isBefore = now.isBefore(oneHourLater);
System.out.println("现在在一小时后之前: " + isBefore);
boolean isAfter = now.isAfter(tenMinutesBefore);
System.out.println("现在在十分钟前之后: " + isAfter);
// 6. 计算两个时间之间的差距
long minutesBetween = ChronoUnit.MINUTES.between(tenMinutesBefore, now);
System.out.println("十分钟前到现在的分钟数: " + minutesBetween);
// 7. 格式化LocalTime
String formattedTime = now.format(DateTimeFormatter.ISO_TIME);
System.out.println("ISO格式化: " + formattedTime);
DateTimeFormatter customFormatter = DateTimeFormatter.ofPattern("HH时mm分ss秒");
String customFormatted = now.format(customFormatter);
System.out.println("自定义格式化: " + customFormatted);
// 8. 截断时间
LocalTime truncatedToHours = now.truncatedTo(ChronoUnit.HOURS);
System.out.println("截断到小时: " + truncatedToHours);
LocalTime truncatedToMinutes = now.truncatedTo(ChronoUnit.MINUTES);
System.out.println("截断到分钟: " + truncatedToMinutes);
}
}
5.5 LocalDateTime类
LocalDateTime
表示不带时区的日期和时间(年月日时分秒):
import java.time.LocalDateTime;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.Month;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.time.temporal.ChronoField;
public class LocalDateTimeExample {
public static void main(String[] args) {
// 1. 创建LocalDateTime实例
LocalDateTime now = LocalDateTime.now(); // 当前日期时间
System.out.println("当前日期时间: " + now);
LocalDateTime specificDateTime = LocalDateTime.of(2023, Month.JULY, 15, 14, 30, 45);
System.out.println("指定日期时间: " + specificDateTime);
LocalDateTime fromDateAndTime = LocalDateTime.of(LocalDate.now(), LocalTime.of(14, 30));
System.out.println("从LocalDate和LocalTime创建: " + fromDateAndTime);
LocalDateTime fromString = LocalDateTime.parse("2023-12-31T23:59:59");
System.out.println("从字符串解析: " + fromString);
// 2. 获取组成部分
int year = now.getYear();
Month month = now.getMonth();
int day = now.getDayOfMonth();
int hour = now.getHour();
int minute = now.getMinute();
int second = now.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);
// 3. 修改日期时间
LocalDateTime tomorrow = now.plusDays(1);
System.out.println("明天同一时间: " + tomorrow);
LocalDateTime nextHour = now.plusHours(1);
System.out.println("一小时后: " + nextHour);
LocalDateTime previousWeek = now.minusWeeks(1);
System.out.println("一周前: " + previousWeek);
// 使用with方法修改特定字段
LocalDateTime firstDayOfMonth = now.withDayOfMonth(1);
System.out.println("本月第一天(同一时间): " + firstDayOfMonth);
LocalDateTime christmasEve = now.withMonth(12).withDayOfMonth(24);
System.out.println("平安夜(同一时间): " + christmasEve);
// 4. 提取LocalDate和LocalTime
LocalDate dateComponent = now.toLocalDate();
System.out.println("日期部分: " + dateComponent);
LocalTime timeComponent = now.toLocalTime();
System.out.println("时间部分: " + timeComponent);
// 5. 计算两个日期时间之间的差距
long hoursBetween = ChronoUnit.HOURS.between(specificDateTime, now);
System.out.println("指定时间到现在的小时数: " + hoursBetween);
long minutesBetween = ChronoUnit.MINUTES.between(specificDateTime, now);
System.out.println("指定时间到现在的分钟数: " + minutesBetween);
// 6. 比较日期时间
boolean isBefore = now.isBefore(tomorrow);
System.out.println("现在在明天之前: " + isBefore);
boolean isAfter = now.isAfter(previousWeek);
System.out.println("现在在一周前之后: " + isAfter);
boolean isEqual = now.isEqual(now);
System.out.println("是否相等: " + isEqual);
// 7. 格式化LocalDateTime
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String formattedDateTime = now.format(formatter);
System.out.println("格式化后的日期时间: " + formattedDateTime);
DateTimeFormatter chineseFormatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH时mm分ss秒");
String chineseDateTime = now.format(chineseFormatter);
System.out.println("中文格式的日期时间: " + chineseDateTime);
// 8. 查询方法
boolean isLeapYear = now.toLocalDate().isLeapYear();
System.out.println("今年是闰年吗: " + isLeapYear);
boolean isSupported = now.isSupported(ChronoField.DAY_OF_WEEK);
System.out.println("是否支持星期几字段: " + isSupported);
}
}
5.6 ZonedDateTime类
ZonedDateTime
表示带时区的日期和时间:
import java.time.ZonedDateTime;
import java.time.ZoneId;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.Set;
public class ZonedDateTimeExample {
public static void main(String[] args) {
// 1. 获取所有可用时区ID
Set<String> availableZoneIds = ZoneId.getAvailableZoneIds();
System.out.println("可用时区数量: " + availableZoneIds.size());
System.out.println("部分时区ID示例: ");
availableZoneIds.stream().limit(10).forEach(System.out::println);
// 2. 创建ZonedDateTime实例
ZonedDateTime nowInSystemDefault = ZonedDateTime.now(); // 系统默认时区
System.out.println("当前系统默认时区的日期时间: " + nowInSystemDefault);
ZonedDateTime nowInTokyo = ZonedDateTime.now(ZoneId.of("Asia/Tokyo"));
System.out.println("东京现在的日期时间: " + nowInTokyo);
ZonedDateTime nowInParis = ZonedDateTime.now(ZoneId.of("Europe/Paris"));
System.out.println("巴黎现在的日期时间: " + nowInParis);
// 3. 从LocalDateTime创建ZonedDateTime
LocalDateTime localDateTime = LocalDateTime.now();
ZonedDateTime zonedFromLocal = localDateTime.atZone(ZoneId.of("America/New_York"));
System.out.println("纽约时区的当前日期时间: " + zonedFromLocal);
// 4. 指定特定的日期时间和时区
ZonedDateTime specificInTokyo = ZonedDateTime.of(
2023, 7, 15, 14, 30, 0, 0, ZoneId.of("Asia/Tokyo"));
System.out.println("东京的特定日期时间: " + specificInTokyo);
// 5. 格式化ZonedDateTime
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss z");
String formattedZoned = nowInSystemDefault.format(formatter);
System.out.println("格式化的带时区日期时间: " + formattedZoned);
// 6. 时区转换
ZonedDateTime tokyoToNewYork = nowInTokyo.withZoneSameInstant(ZoneId.of("America/New_York"));
System.out.println("东京时间转换为纽约时间: " + tokyoToNewYork);
ZonedDateTime parisToSydney = nowInParis.withZoneSameInstant(ZoneId.of("Australia/Sydney"));
System.out.println("巴黎时间转换为悉尼时间: " + parisToSydney);
// 7. 时区转换保持本地时间
ZonedDateTime tokyoToNewYorkLocal = nowInTokyo.withZoneSameLocal(ZoneId.of("America/New_York"));
System.out.println("东京时间转换为纽约时间(保持本地时间): " + tokyoToNewYorkLocal);
// 8. 日期时间操作
ZonedDateTime nextDay = nowInSystemDefault.plusDays(1);
System.out.println("明天同一时间(系统时区): " + nextDay);
ZonedDateTime previousHour = nowInSystemDefault.minusHours(1);
System.out.println("一小时前(系统时区): " + previousHour);
// 9. 计算两个ZonedDateTime之间的差距
long hoursBetween = ChronoUnit.HOURS.between(specificInTokyo, nowInTokyo);
System.out.println("特定时间到现在的小时数(东京): " + hoursBetween);
// 10. 处理夏令时
ZoneId newYorkZone = ZoneId.of("America/New_York");
ZonedDateTime beforeDST = ZonedDateTime.of(
2023, 3, 12, 1, 30, 0, 0, newYorkZone);
ZonedDateTime afterDST = beforeDST.plusHours(1);
System.out.println("夏令时前: " + beforeDST);
System.out.println("夏令时后: " + afterDST);
System.out.println("时区偏移量变化: " +
beforeDST.getOffset() + " -> " + afterDST.getOffset());
}
}
5.7 Instant类
Instant
表示时间线上的一个瞬时点,可以理解为Unix时间戳:
import java.time.Instant;
import java.time.Duration;
import java.time.ZonedDateTime;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;
import java.util.Date;
public class InstantExample {
public static void main(String[] args) {
// 1. 创建Instant实例
Instant now = Instant.now();
System.out.println("当前时间戳: " + now);
// 2. 从纪元(1970-01-01T00:00:00Z)经过的秒数创建Instant
Instant fromEpochSecond = Instant.ofEpochSecond(1500000000);
System.out.println("从纪元经过指定秒数的时间: " + fromEpochSecond);
// 带纳秒偏移量
Instant fromEpochWithNanos = Instant.ofEpochSecond(1500000000, 500000000);
System.out.println("带纳秒偏移量的时间: " + fromEpochWithNanos);
// 3. 从毫秒创建Instant
Instant fromEpochMilli = Instant.ofEpochMilli(System.currentTimeMillis());
System.out.println("从当前毫秒数创建的时间: " + fromEpochMilli);
// 4. 获取秒数和纳秒部分
long epochSecond = now.getEpochSecond();
int nano = now.getNano();
System.out.println("纪元秒数: " + epochSecond);
System.out.println("纳秒部分: " + nano);
// 5. Instant的计算
Instant later = now.plusSeconds(3600); // 一小时后
System.out.println("一小时后: " + later);
Instant earlier = now.minusMillis(60000); // 一分钟前
System.out.println("一分钟前: " + earlier);
// 6. 计算两个Instant之间的差距
Duration duration = Duration.between(earlier, later);
System.out.println("earlier到later的持续时间: " + duration);
long secondsBetween = ChronoUnit.SECONDS.between(earlier, later);
System.out.println("earlier到later的秒数: " + secondsBetween);
// 7. 比较Instant
boolean isBefore = earlier.isBefore(now);
System.out.println("earlier在now之前: " + isBefore);
boolean isAfter = later.isAfter(now);
System.out.println("later在now之后: " + isAfter);
// 8. 转换为Date
Date dateFromInstant = Date.from(now);
System.out.println("从Instant转换为Date: " + dateFromInstant);
// 9. 从Date转换为Instant
Instant instantFromDate = new Date().toInstant();
System.out.println("从Date转换为Instant: " + instantFromDate);
// 10. 转换为ZonedDateTime
ZonedDateTime zonedDateTime = now.atZone(ZoneId.systemDefault());
System.out.println("转换为系统默认时区的ZonedDateTime: " + zonedDateTime);
ZonedDateTime tokyoTime = now.atZone(ZoneId.of("Asia/Tokyo"));
System.out.println("转换为东京时区的ZonedDateTime: " + tokyoTime);
// 11. 截断到秒
Instant truncatedToSeconds = now.truncatedTo(ChronoUnit.SECONDS);
System.out.println("截断到秒: " + truncatedToSeconds);
}
}
5.8 Duration和Period类
Duration
表示时间量,Period
表示日期间隔:
import java.time.Duration;
import java.time.Period;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
public class DurationAndPeriodExample {
public static void main(String[] args) {
// ===== Duration示例 - 基于时间的间隔(时、分、秒、纳秒) =====
System.out.println("===== Duration示例 =====");
// 1. 创建Duration实例
Duration fiveHours = Duration.ofHours(5);
System.out.println("5小时: " + fiveHours);
Duration tenMinutes = Duration.ofMinutes(10);
System.out.println("10分钟: " + tenMinutes);
Duration thirtySeconds = Duration.ofSeconds(30);
System.out.println("30秒: " + thirtySeconds);
Duration oneDay = Duration.ofDays(1);
System.out.println("1天: " + oneDay);
// 2. 从秒和纳秒创建
Duration complex = Duration.ofSeconds(3600, 500000000);
System.out.println("3600秒+5亿纳秒: " + complex);
// 3. 在两个时间点之间创建Duration
LocalTime time1 = LocalTime.of(10, 0);
LocalTime time2 = LocalTime.of(11, 30);
Duration betweenTimes = Duration.between(time1, time2);
System.out.println("两个时间之间: " + betweenTimes);
LocalDateTime dateTime1 = LocalDateTime.of(2023, 7, 15, 10, 0);
LocalDateTime dateTime2 = LocalDateTime.of(2023, 7, 16, 11, 30);
Duration betweenDateTimes = Duration.between(dateTime1, dateTime2);
System.out.println("两个日期时间之间: " + betweenDateTimes);
// 4. 获取Duration的组成部分
long days = betweenDateTimes.toDays();
long hours = betweenDateTimes.toHours();
long minutes = betweenDateTimes.toMinutes();
long seconds = betweenDateTimes.getSeconds();
long nanos = betweenDateTimes.getNano();
System.out.println("天数: " + days);
System.out.println("小时数: " + hours);
System.out.println("分钟数: " + minutes);
System.out.println("秒数: " + seconds);
System.out.println("纳秒数: " + nanos);
// 5. Duration的运算
Duration sum = fiveHours.plus(tenMinutes);
System.out.println("5小时+10分钟: " + sum);
Duration difference = fiveHours.minus(thirtySeconds);
System.out.println("5小时-30秒: " + difference);
Duration doubled = fiveHours.multipliedBy(2);
System.out.println("5小时×2: " + doubled);
Duration divided = fiveHours.dividedBy(2);
System.out.println("5小时÷2: " + divided);
Duration negated = fiveHours.negated();
System.out.println("5小时的负值: " + negated);
// 6. Duration的比较
boolean isPositive = fiveHours.isPositive();
System.out.println("5小时是正值吗? " + isPositive);
boolean isZero = fiveHours.isZero();
System.out.println("5小时是零吗? " + isZero);
boolean isNegative = negated.isNegative();
System.out.println("5小时的负值是负值吗? " + isNegative);
// ===== Period示例 - 基于日期的间隔(年、月、日) =====
System.out.println("\n===== Period示例 =====");
// 1. 创建Period实例
Period twoYears = Period.ofYears(2);
System.out.println("2年: " + twoYears);
Period sixMonths = Period.ofMonths(6);
System.out.println("6个月: " + sixMonths);
Period threeWeeks = Period.ofWeeks(3);
System.out.println("3周: " + threeWeeks);
Period tenDays = Period.ofDays(10);
System.out.println("10天: " + tenDays);
// 2. 创建复合Period
Period complex2 = Period.of(1, 6, 15); // 1年6个月15天
System.out.println("1年6个月15天: " + complex2);
// 3. 在两个日期之间创建Period
LocalDate date1 = LocalDate.of(2022, 1, 1);
LocalDate date2 = LocalDate.of(2023, 7, 15);
Period betweenDates = Period.between(date1, date2);
System.out.println("两个日期之间: " + betweenDates);
// 4. 获取Period的组成部分
int years = betweenDates.getYears();
int months = betweenDates.getMonths();
int daysPeriod = betweenDates.getDays();
System.out.println("年数: " + years);
System.out.println("月数: " + months);
System.out.println("天数: " + daysPeriod);
// 5. 计算总月数
long totalMonths = betweenDates.toTotalMonths();
System.out.println("总月数: " + totalMonths);
// 6. Period的运算
Period sumPeriod = twoYears.plus(sixMonths);
System.out.println("2年+6个月: " + sumPeriod);
Period diffPeriod = twoYears.minus(tenDays);
System.out.println("2年-10天: " + diffPeriod);
Period multipliedPeriod = sixMonths.multipliedBy(2);
System.out.println("6个月×2: " + multipliedPeriod);
Period negatedPeriod = twoYears.negated();
System.out.println("2年的负值: " + negatedPeriod);
// 7. 将Period应用于日期
LocalDate dateWithPeriodAdded = date1.plus(betweenDates);
System.out.println("日期1加上period: " + dateWithPeriodAdded);
// 8. 规范化Period
Period nonNormalized = Period.of(1, 15, 0); // 1年15个月0天
System.out.println("非规范化的Period: " + nonNormalized);
Period normalized = nonNormalized.normalized();
System.out.println("规范化后的Period: " + normalized);
}
}
5.9 DateTimeFormatter类
DateTimeFormatter
用于格式化和解析日期时间:
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.LocalDateTime;
import java.time.ZonedDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.util.Locale;
public class DateTimeFormatterExample {
public static void main(String[] args) {
LocalDate date = LocalDate.of(2023, 7, 15);
LocalTime time = LocalTime.of(14, 30, 15);
LocalDateTime dateTime = LocalDateTime.of(date, time);
ZonedDateTime zonedDateTime = ZonedDateTime.of(dateTime, ZoneId.of("Asia/Shanghai"));
// 1. 使用预定义的格式
System.out.println("===== 预定义格式 =====");
// 基本ISO格式
System.out.println("ISO_LOCAL_DATE: " + DateTimeFormatter.ISO_LOCAL_DATE.format(date));
System.out.println("ISO_LOCAL_TIME: " + DateTimeFormatter.ISO_LOCAL_TIME.format(time));
System.out.println("ISO_LOCAL_DATE_TIME: " + DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(dateTime));
System.out.println("ISO_ZONED_DATE_TIME: " + DateTimeFormatter.ISO_ZONED_DATE_TIME.format(zonedDateTime));
System.out.println("ISO_INSTANT: " + DateTimeFormatter.ISO_INSTANT.format(zonedDateTime.toInstant()));
// 2. 使用本地化的格式样式
System.out.println("\n===== 本地化格式样式 =====");
// 短、中、长、完整 风格的格式
System.out.println("SHORT date: " + DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT).format(date));
System.out.println("MEDIUM date: " + DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM).format(date));
System.out.println("LONG date: " + DateTimeFormatter.ofLocalizedDate(FormatStyle.LONG).format(date));
System.out.println("FULL date: " + DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL).format(date));
System.out.println("\nSHORT time: " + DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT).format(time));
System.out.println("MEDIUM time: " + DateTimeFormatter.ofLocalizedTime(FormatStyle.MEDIUM).format(time));
System.out.println("LONG time: " + DateTimeFormatter.ofLocalizedTime(FormatStyle.LONG).format(time));
System.out.println("FULL time: " + DateTimeFormatter.ofLocalizedTime(FormatStyle.FULL).format(time));
System.out.println("\nSHORT dateTime: " + DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT).format(dateTime));
System.out.println("MEDIUM dateTime: " + DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM).format(dateTime));
// 3. 自定义格式模式
System.out.println("\n===== 自定义格式模式 =====");
DateTimeFormatter customFormatter1 = DateTimeFormatter.ofPattern("yyyy/MM/dd");
System.out.println("自定义日期格式: " + customFormatter1.format(date));
DateTimeFormatter customFormatter2 = DateTimeFormatter.ofPattern("HH:mm:ss.SSS");
System.out.println("自定义时间格式: " + customFormatter2.format(time));
DateTimeFormatter customFormatter3 = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH时mm分ss秒");
System.out.println("自定义日期时间格式: " + customFormatter3.format(dateTime));
DateTimeFormatter customFormatter4 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss [VV]");
System.out.println("带时区的自定义格式: " + customFormatter4.format(zonedDateTime));
// 4. 使用不同的Locale
System.out.println("\n===== 不同Locale的格式 =====");
DateTimeFormatter usFormatter = DateTimeFormatter.ofPattern("MMMM d, yyyy", Locale.US);
System.out.println("美国格式: " + usFormatter.format(date));
DateTimeFormatter frFormatter = DateTimeFormatter.ofPattern("d MMMM yyyy", Locale.FRANCE);
System.out.println("法国格式: " + frFormatter.format(date));
DateTimeFormatter cnFormatter = DateTimeFormatter.ofPattern("yyyy年M月d日", Locale.CHINA);
System.out.println("中国格式: " + cnFormatter.format(date));
DateTimeFormatter jpFormatter = DateTimeFormatter.ofPattern("yyyy年M月d日", Locale.JAPAN);
System.out.println("日本格式: " + jpFormatter.format(date));
// 5. 解析字符串为日期时间
System.out.println("\n===== 字符串解析 =====");
String dateStr = "2023-08-20";
LocalDate parsedDate = LocalDate.parse(dateStr);
System.out.println("解析的日期: " + parsedDate);
String timeStr = "15:45:30";
LocalTime parsedTime = LocalTime.parse(timeStr);
System.out.println("解析的时间: " + parsedTime);
String dateTimeStr = "2023-08-20T15:45:30";
LocalDateTime parsedDateTime = LocalDateTime.parse(dateTimeStr);
System.out.println("解析的日期时间: " + parsedDateTime);
// 使用自定义格式解析
String customDateStr = "2023/08/20";
LocalDate parsedCustomDate = LocalDate.parse(customDateStr, DateTimeFormatter.ofPattern("yyyy/MM/dd"));
System.out.println("使用自定义格式解析的日期: " + parsedCustomDate);
String customDateTimeStr = "2023年08月20日 15时45分30秒";
LocalDateTime parsedCustomDateTime = LocalDateTime.parse(customDateTimeStr,
DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH时mm分ss秒"));
System.out.println("使用自定义格式解析的日期时间: " + parsedCustomDateTime);
}
}
5.10 与Date和Calendar互转
Java 8日期时间API与旧API的互相转换:
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.TimeZone;
public class DateTimeConversionExample {
public static void main(String[] args) {
// ===== Date与新API的互转 =====
System.out.println("===== Date与新API的互转 =====");
// 1. Date转Instant
Date date = new Date();
Instant instant = date.toInstant();
System.out.println("Date转Instant: " + instant);
// 2. Instant转Date
Date dateFromInstant = Date.from(instant);
System.out.println("Instant转Date: " + dateFromInstant);
// 3. Date转LocalDateTime
LocalDateTime localDateTime = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
System.out.println("Date转LocalDateTime: " + localDateTime);
// 4. Date转LocalDate
LocalDate localDate = localDateTime.toLocalDate();
System.out.println("Date转LocalDate: " + localDate);
// 5. Date转LocalTime
LocalTime localTime = localDateTime.toLocalTime();
System.out.println("Date转LocalTime: " + localTime);
// 6. Date转ZonedDateTime
ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
System.out.println("Date转ZonedDateTime: " + zonedDateTime);
// 7. LocalDate转Date
Date dateFromLocalDate = Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant());
System.out.println("LocalDate转Date: " + dateFromLocalDate);
// 8. LocalDateTime转Date
Date dateFromLocalDateTime = Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
System.out.println("LocalDateTime转Date: " + dateFromLocalDateTime);
// 9. ZonedDateTime转Date
Date dateFromZonedDateTime = Date.from(zonedDateTime.toInstant());
System.out.println("ZonedDateTime转Date: " + dateFromZonedDateTime);
// ===== Calendar与新API的互转 =====
System.out.println("\n===== Calendar与新API的互转 =====");
// 1. Calendar转Instant
Calendar calendar = Calendar.getInstance();
Instant instantFromCalendar = calendar.toInstant();
System.out.println("Calendar转Instant: " + instantFromCalendar);
// 2. Calendar转LocalDateTime
LocalDateTime localDateTimeFromCalendar = LocalDateTime.ofInstant(
calendar.toInstant(), ZoneId.systemDefault());
System.out.println("Calendar转LocalDateTime: " + localDateTimeFromCalendar);
// 3. Calendar转ZonedDateTime
ZonedDateTime zonedDateTimeFromCalendar = ZonedDateTime.ofInstant(
calendar.toInstant(), ZoneId.systemDefault());
System.out.println("Calendar转ZonedDateTime: " + zonedDateTimeFromCalendar);
// 4. Calendar转Date
Date dateFromCalendar = calendar.getTime();
System.out.println("Calendar转Date: " + dateFromCalendar);
// 5. Instant转Calendar
Calendar calendarFromInstant = Calendar.getInstance();
calendarFromInstant.setTimeInMillis(instant.toEpochMilli());
System.out.println("Instant转Calendar: " + calendarFromInstant.getTime());
// 6. LocalDateTime转Calendar
Calendar calendarFromLocalDateTime = Calendar.getInstance();
calendarFromLocalDateTime.setTimeInMillis(
localDateTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
System.out.println("LocalDateTime转Calendar: " + calendarFromLocalDateTime.getTime());
// 7. ZonedDateTime转Calendar
Calendar calendarFromZonedDateTime = Calendar.getInstance();
calendarFromZonedDateTime.setTimeInMillis(zonedDateTime.toInstant().toEpochMilli());
System.out.println("ZonedDateTime转Calendar: " + calendarFromZonedDateTime.getTime());
// 8. GregorianCalendar与ZonedDateTime互转
GregorianCalendar gregorianCalendar = new GregorianCalendar();
ZonedDateTime zonedFromGregorian = gregorianCalendar.toZonedDateTime();
System.out.println("GregorianCalendar转ZonedDateTime: " + zonedFromGregorian);
GregorianCalendar gregorianFromZoned = GregorianCalendar.from(zonedDateTime);
System.out.println("ZonedDateTime转GregorianCalendar: " + gregorianFromZoned.getTime());
// 9. TimeZone与ZoneId互转
ZoneId zoneId = TimeZone.getDefault().toZoneId();
System.out.println("TimeZone转ZoneId: " + zoneId);
TimeZone timeZone = TimeZone.getTimeZone(zoneId);
System.out.println("ZoneId转TimeZone: " + timeZone.getID());
}
}
6. 常见日期操作实例
6.1 计算年龄
import java.time.LocalDate;
import java.time.Period;
import java.time.temporal.ChronoUnit;
import java.util.Calendar;
import java.util.Date;
public class AgeCalculationExample {
public static void main(String[] args) {
// 假设的出生日期
LocalDate birthDate = LocalDate.of(1990, 5, 15);
LocalDate currentDate = LocalDate.now();
// 方法1:使用Period (Java 8)
Period period = Period.between(birthDate, currentDate);
System.out.println("使用Period计算年龄: " +
period.getYears() + "年 " +
period.getMonths() + "个月 " +
period.getDays() + "天");
// 方法2:使用ChronoUnit (Java 8)
long years = ChronoUnit.YEARS.between(birthDate, currentDate);
System.out.println("使用ChronoUnit计算年龄(年): " + years);
// 方法3:使用Calendar (旧API)
Calendar birthCal = Calendar.getInstance();
birthCal.set(1990, Calendar.MAY, 15);
Calendar currentCal = Calendar.getInstance();
int calYears = currentCal.get(Calendar.YEAR) - birthCal.get(Calendar.YEAR);
// 检查是否已经过了生日
if (currentCal.get(Calendar.MONTH) < birthCal.get(Calendar.MONTH) ||
(currentCal.get(Calendar.MONTH) == birthCal.get(Calendar.MONTH) &&
currentCal.get(Calendar.DAY_OF_MONTH) < birthCal.get(Calendar.DAY_OF_MONTH))) {
calYears--;
}
System.out.println("使用Calendar计算年龄: " + calYears);
}
}
6.2 计算两个日期之间的工作日
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;
public class WorkingDaysCalculationExample {
public static void main(String[] args) {
// 定义开始和结束日期
LocalDate startDate = LocalDate.of(2023, 7, 1);
LocalDate endDate = LocalDate.of(2023, 7, 31);
// 方法1:遍历每一天检查
int workDays = 0;
LocalDate date = startDate;
while (!date.isAfter(endDate)) {
DayOfWeek dayOfWeek = date.getDayOfWeek();
if (dayOfWeek != DayOfWeek.SATURDAY && dayOfWeek != DayOfWeek.SUNDAY) {
workDays++;
}
date = date.plusDays(1);
}
System.out.println("工作日天数(不包括周末): " + workDays);
// 方法2:考虑节假日
List<LocalDate> holidays = new ArrayList<>();
holidays.add(LocalDate.of(2023, 7, 4)); // 美国独立日
holidays.add(LocalDate.of(2023, 7, 14)); // 法国国庆日
int workDaysExcludingHolidays = 0;
date = startDate;
while (!date.isAfter(endDate)) {
DayOfWeek dayOfWeek = date.getDayOfWeek();
if (dayOfWeek != DayOfWeek.SATURDAY && dayOfWeek != DayOfWeek.SUNDAY
&& !holidays.contains(date)) {
workDaysExcludingHolidays++;
}
date = date.plusDays(1);
}
System.out.println("工作日天数(不包括周末和节假日): " + workDaysExcludingHolidays);
// 方法3:计算总天数后减去周末天数(适用于较长时间段)
long totalDays = ChronoUnit.DAYS.between(startDate, endDate) + 1;
long totalWeeks = totalDays / 7;
long remainingDays = totalDays % 7;
long totalWeekendDays = totalWeeks * 2; // 每周有2天周末
// 处理剩余的不足一周的天数
LocalDate lastDayOfWeek = startDate.plusDays(remainingDays - 1);
for (int i = 0; i < remainingDays; i++) {
LocalDate currentDay = startDate.plusDays(i);
if (currentDay.getDayOfWeek() == DayOfWeek.SATURDAY ||
currentDay.getDayOfWeek() == DayOfWeek.SUNDAY) {
totalWeekendDays++;
}
}
long calculatedWorkDays = totalDays - totalWeekendDays;
System.out.println("方法3计算的工作日天数: " + calculatedWorkDays);
}
}
6.3 日期格式化和解析
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;
public class DateFormatParseExample {
public static void main(String[] args) {
// 旧API: SimpleDateFormat
try {
// 将字符串解析为Date
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = sdf.parse("2023-07-15 14:30:00");
System.out.println("解析的Date: " + date);
// 将Date格式化为字符串
String formattedDate = sdf.format(date);
System.out.println("格式化的日期字符串: " + formattedDate);
// 使用不同的格式
SimpleDateFormat sdf2 = new SimpleDateFormat("MM/dd/yyyy");
String americanFormat = sdf2.format(date);
System.out.println("美式日期格式: " + americanFormat);
} catch (ParseException e) {
System.out.println("日期解析错误: " + e.getMessage());
}
// 新API: DateTimeFormatter
// 将字符串解析为LocalDate
DateTimeFormatter formatter1 = DateTimeFormatter.ofPattern("yyyy-MM-dd");
LocalDate localDate = LocalDate.parse("2023-07-15", formatter1);
System.out.println("解析的LocalDate: " + localDate);
// 将字符串解析为LocalDateTime
DateTimeFormatter formatter2 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime localDateTime = LocalDateTime.parse("2023-07-15 14:30:00", formatter2);
System.out.println("解析的LocalDateTime: " + localDateTime);
// 将LocalDateTime格式化为字符串
String formattedDateTime = localDateTime.format(formatter2);
System.out.println("格式化的日期时间字符串: " + formattedDateTime);
// 使用不同的格式
DateTimeFormatter formatter3 = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH时mm分ss秒");
String chineseFormat = localDateTime.format(formatter3);
System.out.println("中文日期格式: " + chineseFormat);
}
}
6.4 日期加减和调整
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.temporal.TemporalAdjusters;
import java.util.Calendar;
import java.util.Date;
public class DateAdditionExample {
public static void main(String[] args) {
// 旧API: Calendar
Calendar calendar = Calendar.getInstance();
System.out.println("当前日期: " + calendar.getTime());
// 增加天数
calendar.add(Calendar.DAY_OF_MONTH, 10);
System.out.println("10天后: " + calendar.getTime());
// 增加月份
calendar.add(Calendar.MONTH, 3);
System.out.println("再增加3个月: " + calendar.getTime());
// 减少年份
calendar.add(Calendar.YEAR, -1);
System.out.println("减少1年: " + calendar.getTime());
// 设置日期
calendar.set(Calendar.DAY_OF_MONTH, 1);
System.out.println("设置为当月第一天: " + calendar.getTime());
// 新API: LocalDate
LocalDate today = LocalDate.now();
System.out.println("\n当前日期: " + today);
// 增加天数、月份、年份
LocalDate futureDate = today.plusDays(10).plusMonths(3).minusYears(1);
System.out.println("10天后再加3个月再减1年: " + futureDate);
// 使用with方法调整日期
LocalDate firstDayOfMonth = today.with(TemporalAdjusters.firstDayOfMonth());
System.out.println("本月第一天: " + firstDayOfMonth);
LocalDate lastDayOfMonth = today.with(TemporalAdjusters.lastDayOfMonth());
System.out.println("本月最后一天: " + lastDayOfMonth);
LocalDate nextMonday = today.with(TemporalAdjusters.next(DayOfWeek.MONDAY));
System.out.println("下一个星期一: " + nextMonday);
LocalDate previousSunday = today.with(TemporalAdjusters.previous(DayOfWeek.SUNDAY));
System.out.println("上一个星期日: " + previousSunday);
// 计算当月第二个星期二
LocalDate secondTuesday = today
.with(TemporalAdjusters.firstDayOfMonth())
.with(TemporalAdjusters.nextOrSame(DayOfWeek.TUESDAY))
.with(TemporalAdjusters.next(DayOfWeek.TUESDAY));
System.out.println("本月第二个星期二: " + secondTuesday);
}
}
6.5 日期比较
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Calendar;
import java.util.Date;
public class DateComparisonExample {
public static void main(String[] args) {
// 旧API: Date比较
Date date1 = new Date();
try {
Thread.sleep(1000); // 等待1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
Date date2 = new Date();
// 比较两个Date
boolean isDate1Before = date1.before(date2);
boolean isDate1After = date1.after(date2);
boolean isDate1Equals = date1.equals(date2);
int compareResult = date1.compareTo(date2);
System.out.println("date1在date2之前: " + isDate1Before);
System.out.println("date1在date2之后: " + isDate1After);
System.out.println("date1等于date2: " + isDate1Equals);
System.out.println("date1比较date2结果: " + compareResult);
// 旧API: Calendar比较
Calendar cal1 = Calendar.getInstance();
cal1.set(2023, Calendar.JANUARY, 15);
Calendar cal2 = Calendar.getInstance();
cal2.set(2023, Calendar.JULY, 15);
boolean isCal1Before = cal1.before(cal2);
boolean isCal1After = cal1.after(cal2);
boolean isCal1Equals = cal1.equals(cal2);
int compareTime = cal1.compareTo(cal2);
System.out.println("\ncal1在cal2之前: " + isCal1Before);
System.out.println("cal1在cal2之后: " + isCal1After);
System.out.println("cal1等于cal2: " + isCal1Equals);
System.out.println("cal1比较cal2结果: " + compareTime);
// 新API: LocalDate比较
LocalDate localDate1 = LocalDate.of(2023, 1, 15);
LocalDate localDate2 = LocalDate.of(2023, 7, 15);
boolean isLocalDate1Before = localDate1.isBefore(localDate2);
boolean isLocalDate1After = localDate1.isAfter(localDate2);
boolean isLocalDate1Equals = localDate1.isEqual(localDate2);
int localDateCompare = localDate1.compareTo(localDate2);
System.out.println("\nlocalDate1在localDate2之前: " + isLocalDate1Before);
System.out.println("localDate1在localDate2之后: " + isLocalDate1After);
System.out.println("localDate1等于localDate2: " + isLocalDate1Equals);
System.out.println("localDate1比较localDate2结果: " + localDateCompare);
// 新API: LocalDateTime比较
LocalDateTime localDateTime1 = LocalDateTime.of(2023, 1, 15, 12, 0);
LocalDateTime localDateTime2 = LocalDateTime.of(2023, 1, 15, 14, 30);
boolean isLocalDateTime1Before = localDateTime1.isBefore(localDateTime2);
System.out.println("\nlocalDateTime1在localDateTime2之前: " + isLocalDateTime1Before);
// 新API: 跨时区比较(借助Instant)
ZonedDateTime zdt1 = ZonedDateTime.of(localDateTime1, ZoneId.of("Asia/Tokyo"));
ZonedDateTime zdt2 = ZonedDateTime.of(localDateTime2, ZoneId.of("America/New_York"));
boolean isZdt1Before = zdt1.isBefore(zdt2);
System.out.println("东京时间在纽约时间之前: " + isZdt1Before);
// 使用toInstant()进行精确比较
boolean isInstant1Before = zdt1.toInstant().isBefore(zdt2.toInstant());
System.out.println("使用Instant比较,东京时间在纽约时间之前: " + isInstant1Before);
}
}
6.6 处理夏令时
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.TimeZone;
public class DaylightSavingTimeExample {
public static void main(String[] args) {
// 美国纽约2023年夏令时开始时间: 3月12日凌晨2点
// 展示时钟从2点直接跳到3点的过程
// 创建表示凌晨1:30的时间
LocalDateTime beforeDST = LocalDateTime.of(2023, 3, 12, 1, 30, 0);
ZoneId nyZone = ZoneId.of("America/New_York");
ZonedDateTime zonedBeforeDST = ZonedDateTime.of(beforeDST, nyZone);
System.out.println("夏令时前 纽约时间 1:30 AM: " + formatZonedDateTime(zonedBeforeDST));
// 往后推1小时
ZonedDateTime oneHourLater = zonedBeforeDST.plusHours(1);
System.out.println("推后一小时 纽约时间: " + formatZonedDateTime(oneHourLater));
// 再往后推1小时 - 注意这里会跳过2:00-3:00
ZonedDateTime twoHoursLater = zonedBeforeDST.plusHours(2);
System.out.println("推后两小时 纽约时间: " + formatZonedDateTime(twoHoursLater));
// 创建表示3月12日凌晨2:30的时间 - 这个时间在美国东部实际上不存在
LocalDateTime nonExistentTime = LocalDateTime.of(2023, 3, 12, 2, 30, 0);
ZonedDateTime zonedNonExistent = ZonedDateTime.of(nonExistentTime, nyZone);
System.out.println("\n创建不存在的时间 2:30 AM: " + formatZonedDateTime(zonedNonExistent));
// Java会自动调整到3:30,因为2:30在夏令时转换期间不存在
// 创建表示11月5日凌晨1:30的时间(夏令时结束前)
LocalDateTime beforeDSTEnd = LocalDateTime.of(2023, 11, 5, 1, 30, 0);
ZonedDateTime zonedBeforeDSTEnd = ZonedDateTime.of(beforeDSTEnd, nyZone);
System.out.println("\n夏令时结束前 纽约时间 1:30 AM: " + formatZonedDateTime(zonedBeforeDSTEnd));
// 往后推1小时 - 依然是1:30,但已经不是夏令时了
ZonedDateTime oneHourLaterDSTEnd = zonedBeforeDSTEnd.plusHours(1);
System.out.println("推后一小时 纽约时间: " + formatZonedDateTime(oneHourLaterDSTEnd));
// 查看一天内的小时数
LocalDateTime dstTransitionDay = LocalDateTime.of(2023, 3, 12, 0, 0);
int hourCount = 0;
System.out.println("\n夏令时转换日的小时:");
for (int i = 0; i < 24; i++) {
ZonedDateTime zdt = ZonedDateTime.of(dstTransitionDay.plusHours(i), nyZone);
System.out.println(formatZonedDateTime(zdt));
hourCount++;
}
System.out.println("夏令时开始那一天的小时数: " + hourCount); // 实际上只有23小时
// 查看夏令时结束那天的小时数
LocalDateTime dstEndDay = LocalDateTime.of(2023, 11, 5, 0, 0);
hourCount = 0;
System.out.println("\n夏令时结束日的部分小时:");
for (int i = 0; i < 5; i++) {
ZonedDateTime zdt = ZonedDateTime.of(dstEndDay.plusHours(i), nyZone);
System.out.println(formatZonedDateTime(zdt));
}
System.out.println("..."); // 省略中间部分
for (int i = 23; i < 26; i++) {
ZonedDateTime zdt = ZonedDateTime.of(dstEndDay.plusHours(i), nyZone);
System.out.println(formatZonedDateTime(zdt));
}
System.out.println("夏令时结束那一天的小时数: 25"); // 实际上有25小时
}
private static String formatZonedDateTime(ZonedDateTime zdt) {
DateTimeFormatter formatter =
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss z (O)");
return zdt.format(formatter);
}
}
6.7 处理生日和周年纪念
import java.time.LocalDate;
import java.time.Month;
import java.time.Period;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalAdjusters;
public class BirthdayAnniversaryExample {
public static void main(String[] args) {
// 定义生日
LocalDate birthdate = LocalDate.of(1990, Month.MAY, 15);
LocalDate today = LocalDate.now();
// 计算年龄
Period age = Period.between(birthdate, today);
System.out.println("年龄: " + age.getYears() + "年 " +
age.getMonths() + "个月 " +
age.getDays() + "天");
// 计算天数
long totalDays = ChronoUnit.DAYS.between(birthdate, today);
System.out.println("出生至今的总天数: " + totalDays);
// 计算今年的生日日期
LocalDate birthdayThisYear = birthdate.withYear(today.getYear());
// 如果今年的生日已经过了,计算明年的生日
if (birthdayThisYear.isBefore(today) || birthdayThisYear.isEqual(today)) {
birthdayThisYear = birthdayThisYear.plusYears(1);
}
// 计算距离下次生日的天数
long daysUntilNextBirthday = ChronoUnit.DAYS.between(today, birthdayThisYear);
System.out.println("距离下次生日的天数: " + daysUntilNextBirthday);
// 检查是否是闰年生日(2月29日)
LocalDate leapBirthday = LocalDate.of(2000, Month.FEBRUARY, 29);
// 计算2023年的生日(非闰年)
LocalDate leapBirthdayIn2023 = null;
try {
leapBirthdayIn2023 = leapBirthday.withYear(2023);
} catch (Exception e) {
// 2023年2月没有29日
leapBirthdayIn2023 = LocalDate.of(2023, Month.FEBRUARY, 28);
System.out.println("2023年不是闰年,生日将在2月28日庆祝: " + leapBirthdayIn2023);
}
// 计算2024年的生日(闰年)
LocalDate leapBirthdayIn2024 = leapBirthday.withYear(2024);
System.out.println("2024年是闰年,生日将在2月29日庆祝: " + leapBirthdayIn2024);
// 计算特定周年纪念日
LocalDate anniversary10Years = birthdate.plusYears(10);
System.out.println("10周年纪念日: " + anniversary10Years);
// 计算100天、1000天等纪念日
LocalDate days100 = birthdate.plusDays(100);
LocalDate days1000 = birthdate.plusDays(1000);
System.out.println("出生后100天: " + days100);
System.out.println("出生后1000天: " + days1000);
// 计算下一个整年周年(如30岁、40岁)
int nextMilestone = ((age.getYears() / 10) + 1) * 10;
LocalDate nextMilestoneBirthday = birthdate.plusYears(nextMilestone);
System.out.println("下一个" + nextMilestone + "岁生日: " + nextMilestoneBirthday);
long daysUntilMilestone = ChronoUnit.DAYS.between(today, nextMilestoneBirthday);
System.out.println("距离" + nextMilestone + "岁生日还有: " + daysUntilMilestone + "天");
}
}
7. 日期API的对比与最佳实践
7.1 日期API比较
下表比较了Java中三代日期API的主要特性:
特性 | java.util.Date | java.util.Calendar | java.time |
---|---|---|---|
线程安全 | 否(可变) | 否(可变) | 是(不可变) |
可读性 | 较差 | 一般 | 良好 |
时区支持 | 有限 | 良好 | 优秀 |
日期计算 | 有限 | 良好 | 优秀 |
格式化 | 需要SimpleDateFormat | 需要SimpleDateFormat | 内置DateTimeFormatter |
API设计 | 混乱,很多方法已废弃 | 繁琐,易用性差 | 清晰直观 |
月份表示 | 0-11(容易混淆) | 0-11(容易混淆) | 1-12(直观) |
日期与时间分离 | 否 | 否 | 是(LocalDate/LocalTime) |
性能 | 较好 | 较差 | 良好 |
7.2 各API的适用场景
-
java.util.Date和java.util.Calendar
- 维护遗留代码
- 与仅支持旧API的库集成
- 需要与老系统兼容
-
java.time包(Java 8+)
- 新项目开发
- 需要处理复杂的日期时间计算
- 需要更好的国际化支持
- 多线程环境下操作日期时间
- 处理涉及时区的应用
7.3 迁移到Java 8日期时间API的建议
如果你正在考虑将现有代码从旧的日期API迁移到Java 8的新API,以下是一些建议:
- 渐进式迁移:不需要一次性替换所有代码,可以从新增功能开始使用新API。
- 使用适配器模式:创建转换工具类,封装新旧API之间的转换逻辑。
- 优先迁移问题代码:首先替换那些有线程安全问题或逻辑复杂的日期处理代码。
- 添加测试:在迁移过程中添加足够的单元测试,确保逻辑一致性。
- 考虑库支持:使用ThreeTen-Extra或其他库来扩展Java 8日期时间API功能。
7.4 日期处理的最佳实践
7.4.1 通用最佳实践
- 使用不可变对象:优先使用Java 8的日期时间类,它们是不可变的,线程安全的。
- 正确处理时区:明确指定时区,特别是在分布式系统中。
- 使用ISO 8601格式:在系统间传输日期时使用标准格式(如:
2023-07-15T14:30:00Z
)。 - 注意夏令时:在涉及夏令时变化的日期计算中要特别小心。
- 避免硬编码:不要硬编码日期格式,使用常量或配置。
7.4.2 使用旧API时的注意事项
- SimpleDateFormat线程安全:SimpleDateFormat不是线程安全的,使用ThreadLocal或每次创建新实例。
- Calendar性能:Calendar创建和操作的开销较大,尽量复用实例。
- 月份偏移:始终记住Calendar和Date中月份是从0开始的(0=1月,11=12月)。
- 时区处理:显式设置时区,不要依赖默认时区。
7.4.3 使用Java 8 API的最佳实践
- 选择合适的类:根据需求选择合适的类(LocalDate表示日期,LocalTime表示时间等)。
- 利用方法链:Java 8 API支持流畅的方法链,使代码更简洁。
- 使用预定义常量:使用如DayOfWeek.MONDAY而不是数字表示星期。
- 使用TemporalAdjusters:处理复杂的日期调整,如"下个工作日"、"本月最后一个星期日"等。
- 掌握Period和Duration:Period用于日期间隔(年月日),Duration用于时间间隔(时分秒纳秒)。
8. 总结
通过本文,我们详细介绍了Java中的日期时间API,从早期的Date
和Calendar
类到Java 8引入的java.time
包。每种API都有其特点和适用场景,尤其是Java 8的日期时间API解决了早期API的许多问题,提供了更加清晰、直观、安全的日期时间处理能力。
对于Java开发者来说,掌握这些日期时间API对于处理各种业务需求非常重要,例如:
- 处理用户注册时间和生日
- 计算业务周期和账单周期
- 预订系统中的日期时间处理
- 生成定时报表
- 处理跨时区的通信
- 实现日历和提醒功能
随着Java的不断发展,日期时间API也在不断完善。从Java 9开始,java.time
包得到了一些小的增强,如新的日期格式化模式字母。未来版本可能会有更多改进,但Java 8的日期时间API已经足够强大,能够满足大多数应用场景的需求。
最后,建议新项目尽量使用Java 8的日期时间API,利用其不可变性、清晰的API设计以及丰富的功能,来构建更加健壮和可维护的日期时间处理逻辑。