Java 时间日期类笔记

1. 核心时间日期API发展历程

1.1 各版本主要API

  • Java 8之前: DateCalendarSimpleDateFormat
  • Java 8+: java.time包 (JSR-310)
  • 推荐: 新项目统一使用java.time

2. Java 8+ 时间日期API (java.time)

2.1 核心类概览

类名 描述 示例
LocalDate 日期 (年月日) 2023-10-25
LocalTime 时间 (时分秒纳秒) 14:30:15.123
LocalDateTime 日期时间 2023-10-25T14:30:15
ZonedDateTime 带时区的日期时间 2023-10-25T14:30:15+08:00[Asia/Shanghai]
Instant 时间戳 (Unix时间) 1698215415
Duration 时间间隔 (秒、纳秒) PT1H30M
Period 日期间隔 (年、月、日) P1Y2M3D

2.2 基础使用示例

import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;

public class DateTimeExamples {
    
    public static void main(String[] args) {
        // 1. 获取当前时间
        LocalDate currentDate = LocalDate.now();
        LocalTime currentTime = LocalTime.now();
        LocalDateTime currentDateTime = LocalDateTime.now();
        
        System.out.println("当前日期: " + currentDate);
        System.out.println("当前时间: " + currentTime);
        System.out.println("当前日期时间: " + currentDateTime);
        
        // 2. 创建特定时间
        LocalDate specificDate = LocalDate.of(2023, 10, 25);
        LocalTime specificTime = LocalTime.of(14, 30, 15);
        LocalDateTime specificDateTime = LocalDateTime.of(2023, 10, 25, 14, 30, 15);
        
        // 3. 日期计算
        LocalDate tomorrow = currentDate.plusDays(1);
        LocalDate lastWeek = currentDate.minusWeeks(1);
        LocalDate nextMonth = currentDate.plusMonths(1);
        
        // 4. 日期比较
        boolean isAfter = specificDate.isAfter(currentDate);
        boolean isBefore = specificDate.isBefore(currentDate);
        boolean isEqual = specificDate.isEqual(currentDate);
        
        // 5. 获取日期组成部分
        int year = currentDate.getYear();
        Month month = currentDate.getMonth();
        int dayOfMonth = currentDate.getDayOfMonth();
        DayOfWeek dayOfWeek = currentDate.getDayOfWeek();
    }
}

2.3 时区处理

public class TimeZoneExamples {
    
    public static void main(String[] args) {
        // 1. 时区日期时间
        ZonedDateTime beijingTime = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));
        ZonedDateTime newYorkTime = ZonedDateTime.now(ZoneId.of("America/New_York"));
        
        System.out.println("北京时间: " + beijingTime);
        System.out.println("纽约时间: " + newYorkTime);
        
        // 2. 时区转换
        ZonedDateTime convertedTime = beijingTime.withZoneSameInstant(ZoneId.of("UTC"));
        System.out.println("UTC时间: " + convertedTime);
        
        // 3. 获取所有可用时区
        // ZoneId.getAvailableZoneIds().forEach(System.out::println);
        
        // 4. Instant (时间戳)
        Instant instant = Instant.now();
        System.out.println("当前时间戳: " + instant.toEpochMilli());
        
        // 从时间戳创建
        Instant fromTimestamp = Instant.ofEpochMilli(1698215415000L);
    }
}

2.4 时间间隔计算

public class DurationPeriodExamples {
    
    public static void main(String[] args) {
        // 1. Duration - 时间间隔 (精确到纳秒)
        LocalTime startTime = LocalTime.of(9, 0, 0);
        LocalTime endTime = LocalTime.of(17, 30, 0);
        Duration duration = Duration.between(startTime, endTime);
        
        System.out.println("工作时间: " + duration.toHours() + "小时");
        System.out.println("总分钟数: " + duration.toMinutes());
        
        // 2. Period - 日期间隔 (年、月、日)
        LocalDate birthDate = LocalDate.of(1990, 5, 15);
        LocalDate currentDate = LocalDate.now();
        Period age = Period.between(birthDate, currentDate);
        
        System.out.println("年龄: " + age.getYears() + "年" + 
                          age.getMonths() + "月" + age.getDays() + "天");
        
        // 3. 使用ChronoUnit计算差值
        long daysBetween = ChronoUnit.DAYS.between(birthDate, currentDate);
        long monthsBetween = ChronoUnit.MONTHS.between(birthDate, currentDate);
        
        System.out.println("总天数: " + daysBetween);
        System.out.println("总月数: " + monthsBetween);
    }
}

2.5 格式化与解析

public class FormattingExamples {
    
    public static void main(String[] args) {
        // 1. 预定义格式器
        LocalDateTime now = LocalDateTime.now();
        
        String isoFormat = now.format(DateTimeFormatter.ISO_DATE_TIME);
        String basicFormat = now.format(DateTimeFormatter.BASIC_ISO_DATE);
        
        System.out.println("ISO格式: " + isoFormat);
        System.out.println("基本ISO格式: " + basicFormat);
        
        // 2. 自定义格式
        DateTimeFormatter customFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        String customFormat = now.format(customFormatter);
        System.out.println("自定义格式: " + customFormat);
        
        // 3. 解析字符串为日期
        String dateString = "2023-10-25 14:30:15";
        LocalDateTime parsedDateTime = LocalDateTime.parse(dateString, customFormatter);
        System.out.println("解析结果: " + parsedDateTime);
        
        // 4. 本地化格式
        DateTimeFormatter germanFormatter = 
            DateTimeFormatter.ofPattern("d. MMMM yyyy", Locale.GERMAN);
        String germanFormat = now.format(germanFormatter);
        System.out.println("德语格式: " + germanFormat);
    }
}

3. 传统日期API (Java 8之前)

3.1 Date 和 Calendar

import java.util.*;
import java.text.SimpleDateFormat;

public class LegacyDateTime {
    
    public static void main(String[] args) throws Exception {
        // 1. Date 类
        Date now = new Date();
        System.out.println("当前时间: " + now);
        
        // 2. Calendar 类
        Calendar calendar = Calendar.getInstance();
        calendar.set(2023, Calendar.OCTOBER, 25, 14, 30, 15);
        Date specificDate = calendar.getTime();
        
        // 日历计算
        calendar.add(Calendar.DAY_OF_MONTH, 7); // 加7天
        calendar.add(Calendar.MONTH, -1); // 减1个月
        
        // 3. SimpleDateFormat
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String formatted = sdf.format(now);
        System.out.println("格式化: " + formatted);
        
        // 解析
        Date parsedDate = sdf.parse("2023-10-25 14:30:15");
        
        // 注意: SimpleDateFormat 非线程安全!
    }
}

3.2 线程安全问题

// 错误的用法 - 多线程环境下会有问题
public class UnsafeDateFormat {
    private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
    
    // 多线程调用时会抛出异常或返回错误结果
}

// 正确的用法 - 使用ThreadLocal
public class SafeDateFormat {
    private static final ThreadLocal<SimpleDateFormat> threadLocal =
        ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
    
    public static String format(Date date) {
        return threadLocal.get().format(date);
    }
}

4. 新旧API转换

4.1 互相转换方法

public class ConversionExamples {
    
    public static void main(String[] args) {
        // 1. Date -> Instant -> LocalDateTime
        Date oldDate = new Date();
        Instant instant = oldDate.toInstant();
        LocalDateTime newDateTime = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
        
        // 2. LocalDateTime -> Instant -> Date
        LocalDateTime localDateTime = LocalDateTime.now();
        Instant instant2 = localDateTime.atZone(ZoneId.systemDefault()).toInstant();
        Date newDate = Date.from(instant2);
        
        // 3. Calendar -> ZonedDateTime
        Calendar calendar = Calendar.getInstance();
        ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(
            calendar.toInstant(), calendar.getTimeZone().toZoneId());
        
        // 4. 时间戳转换
        long timestamp = System.currentTimeMillis();
        Instant instantFromTimestamp = Instant.ofEpochMilli(timestamp);
        LocalDateTime dateTimeFromTimestamp = LocalDateTime.ofInstant(
            instantFromTimestamp, ZoneId.systemDefault());
    }
}

5. 最佳实践和注意事项

5.1 推荐做法

public class BestPractices {
    
    // 1. 使用不可变对象
    public void processOrder(LocalDateTime orderTime) {
        LocalDateTime processedTime = orderTime.plusMinutes(30); // 返回新对象
        // orderTime 保持不变
    }
    
    // 2. 使用常量格式器
    private static final DateTimeFormatter FORMATTER = 
        DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    
    // 3. 明确处理时区
    public ZonedDateTime convertToTimezone(LocalDateTime localDateTime, String zoneId) {
        return localDateTime.atZone(ZoneId.systemDefault())
                           .withZoneSameInstant(ZoneId.of(zoneId));
    }
    
    // 4. 使用合适的类
    public void demonstrateAppropriateUse() {
        // 只需要日期
        LocalDate birthday = LocalDate.of(1990, 5, 15);
        
        // 只需要时间
        LocalTime meetingTime = LocalTime.of(14, 30);
        
        // 需要日期时间但不需要时区
        LocalDateTime createdTime = LocalDateTime.now();
        
        // 需要时区信息
        ZonedDateTime publishTime = ZonedDateTime.now(ZoneId.of("UTC"));
        
        // 时间戳存储
        Instant timestamp = Instant.now();
    }
}

5.2 常见陷阱

public class CommonPitfalls {
    
    public static void main(String[] args) {
        // 1. 不要使用已废弃的构造方法
        // Date date = new Date(123, 9, 25); // 已废弃!
        
        // 2. Calendar 的月份从0开始
        Calendar cal = Calendar.getInstance();
        cal.set(2023, 9, 25); // 10月 (0=1月, 9=10月)
        
        // 3. 时区处理要一致
        LocalDateTime local = LocalDateTime.now();
        // 错误的时区假设
        // ZonedDateTime zoned = local.atZone(ZoneId.of("UTC")); 
        // 正确的做法
        ZonedDateTime zoned = local.atZone(ZoneId.systemDefault())
                                  .withZoneSameInstant(ZoneId.of("UTC"));
        
        // 4. 格式化解析要匹配
        String input = "2023/10/25";
        // DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); // 错误!
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd"); // 正确
    }
}

总结

  1. 新项目优先使用 java.time
  2. 根据需求选择合适的类: LocalDateLocalTimeLocalDateTimeZonedDateTime
  3. 时区处理要明确, 避免隐式时区转换
  4. 格式化器尽量重用, 避免重复创建
  5. 注意线程安全问题, 特别是传统API
  6. 利用不可变特性, 避免意外的状态修改

这套新的日期时间API设计更加合理,解决了传统API的很多问题,是Java日期时间处理的现代解决方案。

posted @ 2025-11-03 12:58  吹吹风喝喝酒  阅读(7)  评论(0)    收藏  举报