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
E 或 EEE 星期几(短) 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. 过时方法:避免使用 Date 的 toString() 方法进行格式化。

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 API:DateTimeFormatter 是线程安全的,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"));