1. 核心概念

  • java.util.Date:表示特定时间点(毫秒级精度),内部存储自 1970-01-01 00:00:00 UTC 的毫秒数。
  • String:日期的可读文本表示形式,如 "2025-08-15 10:47:00"
  • 转换目标:将机器可读的时间戳转换为人类可读的字符串格式。

2. 操作步骤(非常详细)

方法一:使用 SimpleDateFormat(传统方式)

步骤 1:创建 Date 对象

import java.util.Date;

Date date = new Date(); // 当前时间
// 或 Date date = new Date(1672531200000L); // 指定时间戳

步骤 2:定义日期格式模式

import java.text.SimpleDateFormat;

// 常见格式示例
String pattern = "yyyy-MM-dd HH:mm:ss";        // 2025-08-15 10:47:00
// String pattern = "dd/MM/yyyy";              // 15/08/2025
// String pattern = "yyyy年MM月dd日 HH时mm分";   // 2025年08月15日 10时47分
// String pattern = "EEEE, MMMM dd, yyyy";     // Friday, August 15, 2025

步骤 3:创建 SimpleDateFormat 实例

SimpleDateFormat sdf = new SimpleDateFormat(pattern);

步骤 4:设置时区(可选但推荐)

import java.util.TimeZone;

sdf.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
// 或 sdf.setTimeZone(TimeZone.getDefault());

步骤 5:执行转换

String dateString = sdf.format(date);
System.out.println(dateString); // 输出: 2025-08-15 10:47:00

完整示例

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;

public class DateToStringExample {
    public static void main(String[] args) {
        Date date = new Date();
        
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        sdf.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
        
        String result = sdf.format(date);
        System.out.println("格式化结果: " + result);
    }
}

方法二:使用 Java 8+ DateTimeFormatter(推荐)

步骤 1:将 Date 转为 LocalDateTime

import java.time.LocalDateTime;
import java.time.ZoneId;

LocalDateTime localDateTime = date.toInstant()
                                 .atZone(ZoneId.of("Asia/Shanghai"))
                                 .toLocalDateTime();

步骤 2:创建 DateTimeFormatter

import java.time.format.DateTimeFormatter;

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

步骤 3:格式化为字符串

String dateString = localDateTime.format(formatter);

完整示例(推荐)

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Date;

public class ModernDateToString {
    public static void main(String[] args) {
        Date date = new Date();
        
        // 1. Date → LocalDateTime (指定时区)
        LocalDateTime ldt = date.toInstant()
                               .atZone(ZoneId.of("Asia/Shanghai"))
                               .toLocalDateTime();
        
        // 2. 创建格式化器
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        
        // 3. 转换
        String result = ldt.format(formatter);
        System.out.println("现代方式结果: " + result);
    }
}

3. 常用日期格式模式

模式 含义 示例
yyyy 四位年份 2025
MM 两位月份 08
dd 两位日期 15
HH 24小时制小时 10
hh 12小时制小时 10
mm 分钟 47
ss 00
SSS 毫秒 123
EEEE 星期几(短) Fri
EEEE 星期几(长) Friday
a 上午/下午 AM
z 时区名称 CST
Z RFC 822 时区 +0800

常用组合

"yyyy-MM-dd"                    // 2025-08-15
"yyyy-MM-dd HH:mm:ss"           // 2025-08-15 10:47:00
"yyyy-MM-dd HH:mm:ss.SSS"       // 2025-08-15 10:47:00.123
"dd/MM/yyyy"                    // 15/08/2025
"MMM dd, yyyy"                  // Aug 15, 2025
"EEEE, MMMM dd, yyyy"           // Friday, August 15, 2025

4. 常见错误

4.1 大小写错误

// ❌ 错误:mm 是分钟,不是月份
new SimpleDateFormat("yyyy-mm-dd"); // 输出: 2025-47-15 (错误!)

// ✅ 正确:MM 表示月份
new SimpleDateFormat("yyyy-MM-dd");

4.2 时区忽略

// ❌ 可能错误:未设置时区,使用系统默认
SimpleDateFormat sdf = new SimpleDateFormat("...");
// 结果依赖服务器时区设置

// ✅ 显式设置时区
sdf.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));

4.3 线程安全问题

// ❌ 错误:SimpleDateFormat 不是线程安全的
private static SimpleDateFormat sdf = new SimpleDateFormat("...");

// 多线程环境下可能导致数据混乱

4.4 null 值处理

Date date = null;
// sdf.format(date); // 抛出 NullPointerException

5. 注意事项

  1. SimpleDateFormat 线程不安全:不要在多线程环境中共享同一个实例。
  2. 时区重要性Date 是 UTC 时间点,显示时需要转换为本地时间。
  3. 模式字符大小写敏感MM(月) vs mm(分),HH(24小时) vs hh(12小时)。
  4. 性能考虑:频繁创建 SimpleDateFormat 有开销。
  5. 过时方法:避免使用 DatetoString() 方法进行格式化。

6. 使用技巧

6.1 封装工具类

public class DateUtils {
    private static final DateTimeFormatter DEFAULT_FORMATTER = 
        DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    
    private static final ZoneId SHANGHAI = ZoneId.of("Asia/Shanghai");
    
    public static String format(Date date) {
        if (date == null) return null;
        LocalDateTime ldt = date.toInstant().atZone(SHANGHAI).toLocalDateTime();
        return ldt.format(DEFAULT_FORMATTER);
    }
    
    public static String format(Date date, String pattern) {
        if (date == null) return null;
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
        LocalDateTime ldt = date.toInstant().atZone(SHANGHAI).toLocalDateTime();
        return ldt.format(formatter);
    }
}

// 使用
String result = DateUtils.format(new Date());
String custom = DateUtils.format(new Date(), "yyyy年MM月dd日");

6.2 使用 ThreadLocal(传统方式)

private static final ThreadLocal<SimpleDateFormat> DATE_FORMATTER = 
    ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));

public static String formatSafely(Date date) {
    return DATE_FORMATTER.get().format(date);
}

6.3 处理异常

public static String formatDateSafely(Date date, String pattern) {
    try {
        if (date == null) return "N/A";
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
        LocalDateTime ldt = date.toInstant()
                               .atZone(ZoneId.systemDefault())
                               .toLocalDateTime();
        return ldt.format(formatter);
    } catch (Exception e) {
        return "格式化失败: " + e.getMessage();
    }
}

7. 最佳实践与性能优化

7.1 最佳实践

  1. 优先使用 java.time APIDateTimeFormatter 是线程安全的,API 更清晰。
  2. 显式指定时区:避免依赖系统默认时区。
  3. 预定义格式常量
    public static final String DATETIME_PATTERN = "yyyy-MM-dd HH:mm:ss";
    public static final DateTimeFormatter DATETIME_FORMATTER = 
        DateTimeFormatter.ofPattern(DATETIME_PATTERN);
    
  4. 处理 null:在工具方法中进行空值检查。

7.2 性能优化

  1. 缓存 DateTimeFormatter:它是不可变且线程安全的,可全局缓存。
  2. 避免循环中创建格式化器
    // ✅ 好:循环外创建
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("...");
    for (Date date : dates) {
        String str = date.toInstant().atZone(zone).toLocalDateTime().format(formatter);
    }
    
  3. 使用 Apache Commons Lang(可选):
    import org.apache.commons.lang3.time.FastDateFormat;
    
    FastDateFormat fdf = FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss");
    String formatted = fdf.format(date); // 线程安全且性能好
    

8. 总结

方法 优点 缺点 推荐程度
SimpleDateFormat 传统,广泛支持 线程不安全,API 混乱 ⚠️ 仅用于维护旧代码
DateTimeFormatter 线程安全,API 清晰,性能好 需要 Java 8+ 强烈推荐
FastDateFormat 线程安全,性能优异 需要第三方库 ✅ 推荐(尤其在 Java 7 环境)

核心要点

  1. 推荐使用 java.time 方案Date → Instant → LocalDateTime → String
  2. 必须处理时区:明确指定目标时区(如 "Asia/Shanghai"
  3. 避免 SimpleDateFormat 共享:多线程环境下会出错
  4. 封装常用逻辑:创建工具类提高代码复用性

一行代码转换(推荐)

String result = date.toInstant()
                   .atZone(ZoneId.of("Asia/Shanghai"))
                   .toLocalDateTime()
                   .format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));