Java中的日期类详解

文章目录

1. 日期与时间概述

1.1 日期时间在编程中的重要性

在Java应用程序开发中,日期和时间处理是一项基础且常见的需求。从记录用户注册时间、计算订单期限、设置活动倒计时,到生成报表、处理定时任务等,几乎所有的应用程序都需要进行日期时间处理。正确高效地使用Java中的日期类,对于开发高质量的应用程序至关重要。

1.2 Java中日期API的发展历程

Java中的日期API经历了显著的演变:

  1. 早期版本(JDK 1.0): java.util.Date类是Java最早提供的日期处理类,设计上存在许多缺陷。
  2. JDK 1.1增强: 引入了java.util.Calendarjava.text.SimpleDateFormat,增强了日期处理能力。
  3. Java 8革新: 引入了全新的java.time包,提供了更加完善的日期时间API,解决了早期API的许多问题。

1.3 日期类选择指南

  • 新项目开发: 强烈建议使用Java 8的java.time包中的类。
  • 维护遗留系统: 可能需要继续使用DateCalendar,但可以考虑逐步迁移到新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中最古老的日期处理类,但它有很多设计上的缺陷:

  1. 可变性(非线程安全): Date对象是可变的,多线程环境下可能导致问题。
  2. API设计混乱: 许多方法在JDK 1.1后已被废弃,如getYear(), getMonth()等。
  3. 时区处理不当: 没有明确的时区概念,依赖于默认时区。
  4. 无法表示日期而不包含时间: 总是包含精确到毫秒的时间信息。
  5. 格式化能力有限: 需要配合SimpleDateFormat使用。
  6. 月份从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类提供了以下优势:

  1. 日期计算能力: 提供了丰富的日期计算方法。
  2. 日期各部分的访问: 可以单独访问和修改日期的年、月、日、时、分、秒。
  3. 时区支持: 提供了对不同时区的支持。
  4. 日历系统扩展: 可以支持不同的日历系统(如公历、佛历等)。

3.3 Calendar类的缺点

尽管CalendarDate有很多改进,但仍然存在一些问题:

  1. 可变性: 与Date一样,Calendar对象也是可变的,非线程安全。
  2. API设计不直观: 某些API设计不直观,使用起来较为繁琐。
  3. 月份仍从0开始: 1月仍用0表示,保持了Date类的混淆特性。
  4. 日期时间状态不一致: 在某些操作后,内部状态可能不一致,需要调用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使用模式字符串来指定日期和时间的格式。以下是常用的模式字母:

字母描述示例
y2023
M月份07 或 Jul
d月中的天数10
H24小时制小时23
h12小时制小时11
m分钟30
s55
S毫秒978
E星期星期二 或 Tue
D一年中的天数189
w一年中的周数27
W一月中的周数2
aAM/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的主要特点包括:

  1. 不可变性: 所有类都是不可变的,因此线程安全。
  2. 分离关注点: 明确区分日期、时间、日期时间、持续时间等概念。
  3. 清晰的API: 方法名直观,使用更方便。
  4. 国际化支持: 更好地支持不同的日历系统和时区。
  5. 功能丰富: 提供了丰富的操作和计算方法。

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.Datejava.util.Calendarjava.time
线程安全否(可变)否(可变)是(不可变)
可读性较差一般良好
时区支持有限良好优秀
日期计算有限良好优秀
格式化需要SimpleDateFormat需要SimpleDateFormat内置DateTimeFormatter
API设计混乱,很多方法已废弃繁琐,易用性差清晰直观
月份表示0-11(容易混淆)0-11(容易混淆)1-12(直观)
日期与时间分离是(LocalDate/LocalTime)
性能较好较差良好

7.2 各API的适用场景

  1. java.util.Date和java.util.Calendar

    • 维护遗留代码
    • 与仅支持旧API的库集成
    • 需要与老系统兼容
  2. java.time包(Java 8+)

    • 新项目开发
    • 需要处理复杂的日期时间计算
    • 需要更好的国际化支持
    • 多线程环境下操作日期时间
    • 处理涉及时区的应用

7.3 迁移到Java 8日期时间API的建议

如果你正在考虑将现有代码从旧的日期API迁移到Java 8的新API,以下是一些建议:

  1. 渐进式迁移:不需要一次性替换所有代码,可以从新增功能开始使用新API。
  2. 使用适配器模式:创建转换工具类,封装新旧API之间的转换逻辑。
  3. 优先迁移问题代码:首先替换那些有线程安全问题或逻辑复杂的日期处理代码。
  4. 添加测试:在迁移过程中添加足够的单元测试,确保逻辑一致性。
  5. 考虑库支持:使用ThreeTen-Extra或其他库来扩展Java 8日期时间API功能。

7.4 日期处理的最佳实践

7.4.1 通用最佳实践
  1. 使用不可变对象:优先使用Java 8的日期时间类,它们是不可变的,线程安全的。
  2. 正确处理时区:明确指定时区,特别是在分布式系统中。
  3. 使用ISO 8601格式:在系统间传输日期时使用标准格式(如:2023-07-15T14:30:00Z)。
  4. 注意夏令时:在涉及夏令时变化的日期计算中要特别小心。
  5. 避免硬编码:不要硬编码日期格式,使用常量或配置。
7.4.2 使用旧API时的注意事项
  1. SimpleDateFormat线程安全:SimpleDateFormat不是线程安全的,使用ThreadLocal或每次创建新实例。
  2. Calendar性能:Calendar创建和操作的开销较大,尽量复用实例。
  3. 月份偏移:始终记住Calendar和Date中月份是从0开始的(0=1月,11=12月)。
  4. 时区处理:显式设置时区,不要依赖默认时区。
7.4.3 使用Java 8 API的最佳实践
  1. 选择合适的类:根据需求选择合适的类(LocalDate表示日期,LocalTime表示时间等)。
  2. 利用方法链:Java 8 API支持流畅的方法链,使代码更简洁。
  3. 使用预定义常量:使用如DayOfWeek.MONDAY而不是数字表示星期。
  4. 使用TemporalAdjusters:处理复杂的日期调整,如"下个工作日"、"本月最后一个星期日"等。
  5. 掌握Period和Duration:Period用于日期间隔(年月日),Duration用于时间间隔(时分秒纳秒)。

8. 总结

通过本文,我们详细介绍了Java中的日期时间API,从早期的DateCalendar类到Java 8引入的java.time包。每种API都有其特点和适用场景,尤其是Java 8的日期时间API解决了早期API的许多问题,提供了更加清晰、直观、安全的日期时间处理能力。

对于Java开发者来说,掌握这些日期时间API对于处理各种业务需求非常重要,例如:

  • 处理用户注册时间和生日
  • 计算业务周期和账单周期
  • 预订系统中的日期时间处理
  • 生成定时报表
  • 处理跨时区的通信
  • 实现日历和提醒功能

随着Java的不断发展,日期时间API也在不断完善。从Java 9开始,java.time包得到了一些小的增强,如新的日期格式化模式字母。未来版本可能会有更多改进,但Java 8的日期时间API已经足够强大,能够满足大多数应用场景的需求。

最后,建议新项目尽量使用Java 8的日期时间API,利用其不可变性、清晰的API设计以及丰富的功能,来构建更加健壮和可维护的日期时间处理逻辑。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

全栈凯哥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值