Java中的日期类详解
文章目录
- 1. 日期与时间概述
- 1.1 日期时间在编程中的重要性
- 1.2 Java中日期API的发展历程
- 1.3 日期类选择指南
- 2. java.util.Date类
- 2.1 Date类的基本概念
- 2.2 Date类的缺点
- 2.3 创建Date对象
- 2.4 Date对象的常用方法
- 2.5 Date对象的格式化与解析
- 2.6 Date类的线程安全问题
- 2.7 与Calendar和时间戳的转换
- 3. java.util.Calendar类
- 3.1 Calendar类的基本概念
- 3.2 Calendar类的优势
- 3.3 Calendar类的缺点
- 3.4 创建Calendar对象
- 3.5 访问Calendar的字段
- 3.6 设置Calendar的字段
- 3.7 日期计算与操作
- 3.8 处理不同时区
- 3.9 Calendar的线程安全问题
- 4. java.text.SimpleDateFormat类
- 4.1 SimpleDateFormat的基本概念
- 4.2 日期和时间模式
- 4.3 创建SimpleDateFormat对象
- 4.4 日期格式化示例
- 4.5 日期解析示例
- 4.6 SimpleDateFormat的线程安全问题
- 5. Java 8日期时间API
- 5.1 Java 8日期时间API概述
- 5.2 核心类简介
- 5.3 LocalDate类
- 5.4 LocalTime类
- 5.5 LocalDateTime类
- 5.6 ZonedDateTime类
- 5.7 Instant类
- 5.8 Duration和Period类
- 5.9 DateTimeFormatter类
- 5.10 与Date和Calendar互转
- 6. 常见日期操作实例
- 6.1 计算年龄
- 6.2 计算两个日期之间的工作日
- 6.3 日期格式化和解析
- 6.4 日期加减和调整
- 6.5 日期比较
- 6.6 处理夏令时
- 6.7 处理生日和周年纪念
- 7. 日期API的对比与最佳实践
- 7.1 日期API比较
- 7.2 各API的适用场景
- 7.3 迁移到Java 8日期时间API的建议
- 7.4 日期处理的最佳实践
- 7.4.1 通用最佳实践
- 7.4.2 使用旧API时的注意事项
- 7.4.3 使用Java 8 API的最佳实践
- 8. 总结
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 GMTDate 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转CalendarCalendar calendar = Calendar.getInstance();calendar.setTime(currentDate);System.out.println("Date转Calendar: " + calendar.getTime());// Calendar转DateDate 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. 指定时区创建CalendarCalendar calendar2 = Calendar.getInstance(TimeZone.getTimeZone("America/New_York"));System.out.println("纽约时区Calendar: " + calendar2.getTime());// 3. 指定Locale创建CalendarCalendar calendar3 = Calendar.getInstance(Locale.FRANCE);System.out.println("法国Locale的Calendar: " + calendar3.getTime());// 4. 指定时区和Locale创建CalendarCalendar calendar4 = Calendar.getInstance(TimeZone.getTimeZone("Europe/Paris"), Locale.FRANCE);System.out.println("巴黎时区和法国Locale的Calendar: " + calendar4.getTime());// 5. 从Date创建CalendarDate 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月,年份加1System.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. 查看所有可用的时区IDString[] 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. 创建特定时区的CalendarCalendar 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. 指定格式模式和LocaleSimpleDateFormat 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:使用ThreadLocalSystem.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:每次使用时创建新的SimpleDateFormatprivate 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 {// 每次创建新的SimpleDateFormatSimpleDateFormat 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:使用ThreadLocalprivate 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获取SimpleDateFormatSimpleDateFormat 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. 格式化LocalDateString 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. 格式化LocalTimeString 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和LocalTimeLocalDate 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. 格式化LocalDateTimeDateTimeFormatter 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. 获取所有可用时区IDSet<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创建ZonedDateTimeLocalDateTime 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. 格式化ZonedDateTimeDateTimeFormatter 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)经过的秒数创建InstantInstant fromEpochSecond = Instant.ofEpochSecond(1500000000);System.out.println("从纪元经过指定秒数的时间: " + fromEpochSecond);// 带纳秒偏移量Instant fromEpochWithNanos = Instant.ofEpochSecond(1500000000, 500000000);System.out.println("带纳秒偏移量的时间: " + fromEpochWithNanos);// 3. 从毫秒创建InstantInstant 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. 比较Instantboolean isBefore = earlier.isBefore(now);System.out.println("earlier在now之前: " + isBefore);boolean isAfter = later.isAfter(now);System.out.println("later在now之后: " + isAfter);// 8. 转换为DateDate dateFromInstant = Date.from(now);System.out.println("从Instant转换为Date: " + dateFromInstant);// 9. 从Date转换为InstantInstant instantFromDate = new Date().toInstant();System.out.println("从Date转换为Instant: " + instantFromDate);// 10. 转换为ZonedDateTimeZonedDateTime 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. 在两个时间点之间创建DurationLocalTime 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. 创建复合PeriodPeriod complex2 = Period.of(1, 6, 15); // 1年6个月15天System.out.println("1年6个月15天: " + complex2);// 3. 在两个日期之间创建PeriodLocalDate 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. 规范化PeriodPeriod 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. 使用不同的LocaleSystem.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转InstantDate date = new Date();Instant instant = date.toInstant();System.out.println("Date转Instant: " + instant);// 2. Instant转DateDate dateFromInstant = Date.from(instant);System.out.println("Instant转Date: " + dateFromInstant);// 3. Date转LocalDateTimeLocalDateTime localDateTime = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());System.out.println("Date转LocalDateTime: " + localDateTime);// 4. Date转LocalDateLocalDate localDate = localDateTime.toLocalDate();System.out.println("Date转LocalDate: " + localDate);// 5. Date转LocalTimeLocalTime localTime = localDateTime.toLocalTime();System.out.println("Date转LocalTime: " + localTime);// 6. Date转ZonedDateTimeZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());System.out.println("Date转ZonedDateTime: " + zonedDateTime);// 7. LocalDate转DateDate dateFromLocalDate = Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant());System.out.println("LocalDate转Date: " + dateFromLocalDate);// 8. LocalDateTime转DateDate dateFromLocalDateTime = Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());System.out.println("LocalDateTime转Date: " + dateFromLocalDateTime);// 9. ZonedDateTime转DateDate dateFromZonedDateTime = Date.from(zonedDateTime.toInstant());System.out.println("ZonedDateTime转Date: " + dateFromZonedDateTime);// ===== Calendar与新API的互转 =====System.out.println("\n===== Calendar与新API的互转 =====");// 1. Calendar转InstantCalendar calendar = Calendar.getInstance();Instant instantFromCalendar = calendar.toInstant();System.out.println("Calendar转Instant: " + instantFromCalendar);// 2. Calendar转LocalDateTimeLocalDateTime localDateTimeFromCalendar = LocalDateTime.ofInstant(calendar.toInstant(), ZoneId.systemDefault());System.out.println("Calendar转LocalDateTime: " + localDateTimeFromCalendar);// 3. Calendar转ZonedDateTimeZonedDateTime zonedDateTimeFromCalendar = ZonedDateTime.ofInstant(calendar.toInstant(), ZoneId.systemDefault());System.out.println("Calendar转ZonedDateTime: " + zonedDateTimeFromCalendar);// 4. Calendar转DateDate dateFromCalendar = calendar.getTime();System.out.println("Calendar转Date: " + dateFromCalendar);// 5. Instant转CalendarCalendar calendarFromInstant = Calendar.getInstance();calendarFromInstant.setTimeInMillis(instant.toEpochMilli());System.out.println("Instant转Calendar: " + calendarFromInstant.getTime());// 6. LocalDateTime转CalendarCalendar calendarFromLocalDateTime = Calendar.getInstance();calendarFromLocalDateTime.setTimeInMillis(localDateTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());System.out.println("LocalDateTime转Calendar: " + calendarFromLocalDateTime.getTime());// 7. ZonedDateTime转CalendarCalendar 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: SimpleDateFormattry {// 将字符串解析为DateSimpleDateFormat 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// 将字符串解析为LocalDateDateTimeFormatter formatter1 = DateTimeFormatter.ofPattern("yyyy-MM-dd");LocalDate localDate = LocalDate.parse("2023-07-15", formatter1);System.out.println("解析的LocalDate: " + localDate);// 将字符串解析为LocalDateTimeDateTimeFormatter 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: CalendarCalendar 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: LocalDateLocalDate 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();// 比较两个Dateboolean 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:00ZonedDateTime 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设计以及丰富的功能,来构建更加健壮和可维护的日期时间处理逻辑。