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常用格式符号
| 符号 | 含义 | 示例 |
|---|---|---|
| y | 年 | yyyy = 2026 |
| M | 月 | MM = 03 |
| d | 日 | dd = 22 |
| H | 24 小时 | HH = 14 |
| h | 12 小时 | hh = 02 |
| m | 分 | mm = 30 |
| s | 秒 | ss = 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 对比
| 方面 | SimpleDateFormat | DateTimeFormatter |
|---|---|---|
| 线程安全 | 否 | 是 |
| API 清晰度 | 混乱 | 清晰 |
| 月份格式 | M | 同 |
| 年份格式 | yyyy | 同 |
| JDK 版本 | 1.1 | 8+ |
| 时区支持 | 有 | 更好 |
总结
SimpleDateFormat 的问题:
- 非线程安全:不能作为静态共享字段
- API 设计混乱:Date 和 Calendar 一样别扭
- 时区处理麻烦:不如新的 API 直观
建议:新代码直接用 JDK 8+ 的 DateTimeFormatter,旧代码用 ThreadLocal 包装 SimpleDateFormat。
