Skip to content

SimpleDateFormat

格式化与解析

SimpleDateFormat 是 Java 早期用于日期格式化的类,可以把 Date 转成字符串,也可以把字符串解析成 Date。

但它有一个致命问题:非线程安全

基本用法

格式化(Date → String)

java
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

Date now = new Date();
String formatted = sdf.format(now);
System.out.println(formatted); // 2026-03-22 14:30:00

解析(String → Date)

java
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");

String dateStr = "2026-03-22";
Date date = sdf.parse(dateStr); // 返回 java.util.Date
System.out.println(date); // Sun Mar 22 00:00:00 CST 2026

常用格式符号

符号含义示例
yyyyy = 2026
MMM = 03
ddd = 22
H24 小时HH = 14
h12 小时hh = 02
mmm = 30
sss = 45
S毫秒SSS = 123
E星期E = 日
a上午/下午a = 下午

常用格式示例

java
SimpleDateFormat f1 = new SimpleDateFormat("yyyy-MM-dd");           // 2026-03-22
SimpleDateFormat f2 = new SimpleDateFormat("yyyy/MM/dd");           // 2026/03/22
SimpleDateFormat f3 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  // 2026-03-22 14:30:00
SimpleDateFormat f4 = new SimpleDateFormat("yyyy年MM月dd日");       // 2026年03月22日
SimpleDateFormat f5 = new SimpleDateFormat("E");                    // 日
SimpleDateFormat f6 = new SimpleDateFormat("EEEE");                // 星期日
SimpleDateFormat f7 = new SimpleDateFormat("a");                   // 下午
SimpleDateFormat f8 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ"); // ISO 8601

解析中的陷阱

时区问题

java
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
sdf.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));

// 默认时区
Date date = sdf.parse("2026-03-22 14:30:00");
System.out.println(date); // 带时区转换

lenient 模式

java
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");

// lenient=false:严格解析
sdf.setLenient(false);
try {
    sdf.parse("2026-02-30"); // 不存在,抛异常
} catch (ParseException e) {
    System.out.println("日期无效");
}

// lenient=true(默认):宽容解析
sdf.setLenient(true);
Date date = sdf.parse("2026-02-30"); // 自动转为 2026-03-02
System.out.println(date);

线程安全问题

这是 SimpleDateFormat 最大的坑。

错误示例:共享实例

java
public class DateUtil {
    private static final SimpleDateFormat SDF =
        new SimpleDateFormat("yyyy-MM-dd");

    public static String format(Date date) {
        return SDF.format(date); // 危险!多线程下出错
    }

    public static Date parse(String str) {
        return SDF.parse(str); // 危险!
    }
}

多线程下同时调用会产生错误结果或异常:

java
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 100; i++) {
    executor.submit(() -> {
        DateUtil.format(new Date()); // 并发错误!
    });
}

解决方案一:每次创建新实例

java
public static String format(Date date) {
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
    return sdf.format(date);
}

缺点:创建对象有开销。

解决方案二:ThreadLocal

java
public class DateUtil {
    private static final ThreadLocal<SimpleDateFormat> SDF =
        ThreadLocal.withInitial(() ->
            new SimpleDateFormat("yyyy-MM-dd"));

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

    public static Date parse(String str) throws ParseException {
        return SDF.get().parse(str);
    }
}

解决方案三:JDK 8+ DateTimeFormatter

java
public static String format(Date date) {
    return DateTimeFormatter.ofPattern("yyyy-MM-dd")
        .withZone(ZoneId.systemDefault())
        .format(date.toInstant());
}

与 DateTimeFormatter 对比

方面SimpleDateFormatDateTimeFormatter
线程安全
API 清晰度混乱清晰
月份格式M
年份格式yyyy
JDK 版本1.18+
时区支持更好

总结

SimpleDateFormat 的问题:

  1. 非线程安全:不能作为静态共享字段
  2. API 设计混乱:Date 和 Calendar 一样别扭
  3. 时区处理麻烦:不如新的 API 直观

建议:新代码直接用 JDK 8+ 的 DateTimeFormatter,旧代码用 ThreadLocal 包装 SimpleDateFormat。

基于 VitePress 构建