| 方案 | API | 特點 | 推薦指數 |
| DateFormat | java.text.DateFormat | 舊 API、Locale 敏感、格式化/解析、非線程安全 | ⭐⭐ (維護舊代碼) |
| Timestamp | java.sql.Timestamp | JDBC 交互、納秒精度、繼承 Date、toString 固定格式 | ⭐⭐⭐ (數據庫層) |
| LocalDateTime | java.time.LocalDateTime | 現代 API、不可變、線程安全、豐富計算、ISO 格式 | ⭐⭐⭐⭐⭐ (新代碼首選) |
方案一:DateFormat(舊 API,僅維護用)
// 獲取日期時間實例(風格常數)
DateFormat df1 = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, Locale.CHINESE);
String s1 = df1.format(new Date()); // 2024/1/15 上午10:30
DateFormat df2 = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, Locale.CHINESE);
String s2 = df2.format(new Date()); // 2024年1月15日 上午10時30分45秒
// 自定義格式
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String s3 = sdf.format(new Date()); // 2024-01-15 10:30:45
// ⚠️ 線程不安全!多線程環境必須 ThreadLocal 或每次 new
ThreadLocal tl = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
方案二:Timestamp(JDBC/數據庫層)
// 當前時間戳(納秒精度)
Timestamp ts = new Timestamp(System.currentTimeMillis());
// 或
Timestamp ts2 = Timestamp.from(Instant.now());
// 固定格式輸出:yyyy-MM-dd HH:mm:ss.fffffffff
String tsStr = ts.toString(); // 2024-01-15 10:30:45.123456789
// 與數據庫交互
PreparedStatement ps = conn.prepareStatement("INSERT INTO t(time_col) VALUES (?)");
ps.setTimestamp(1, ts); // 自動處理時區轉換
// 從結果集獲取
ResultSet rs = ps.executeQuery();
Timestamp dbTs = rs.getTimestamp("time_col"); // 使用連接時區
Timestamp dbTsUtc = rs.getTimestamp("time_col", Calendar.getInstance(TimeZone.getTimeZone("UTC")));
方案三:LocalDateTime(現代首選)
// 當前本地時間
LocalDateTime now = LocalDateTime.now(); // 系統默認時區
// 指定時區
ZonedDateTime zdt = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));
// 格式化
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String formatted = now.format(fmt); // 2024-01-15 10:30:45
// 解析
LocalDateTime parsed = LocalDateTime.parse("2024-01-15 10:30:45", fmt);
// 計算
LocalDateTime yesterday = now.minusDays(1);
LocalDateTime nextMonth = now.plusMonths(1);
Duration dur = Duration.between(yesterday, now); // PT24H
// 轉 Timestamp(JDBC 4.2+)
Timestamp ts = Timestamp.valueOf(now); // 無時區轉換,視為本地時間
Timestamp ts2 = Timestamp.from(zdt.toInstant()); // 正確時區轉換
完整工具類封裝
public final class TimeUtils {
private TimeUtils() {}
public static final DateTimeFormatter STANDARD = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
public static final DateTimeFormatter DATE_ONLY = DateTimeFormatter.ofPattern("yyyy-MM-dd");
public static final DateTimeFormatter TIME_ONLY = DateTimeFormatter.ofPattern("HH:mm:ss");
public static final DateTimeFormatter ISO_MILLIS = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS");
// === 獲取當前 ===
public static String now() { return LocalDateTime.now().format(STANDARD); }
public static String now(DateTimeFormatter fmt) { return LocalDateTime.now().format(fmt); }
public static LocalDateTime nowLocal() { return LocalDateTime.now(); }
public static Instant nowInstant() { return Instant.now(); }
public static ZonedDateTime nowZone(String zoneId) { return ZonedDateTime.now(ZoneId.of(zoneId)); }
// === 格式化 ===
public static String format(LocalDateTime ldt) { return ldt.format(STANDARD); }
public static String format(LocalDateTime ldt, DateTimeFormatter fmt) { return ldt.format(fmt); }
public static String format(Instant instant, String zoneId) {
return instant.atZone(ZoneId.of(zoneId)).format(STANDARD);
}
public static String format(Timestamp ts) { return ts.toLocalDateTime().format(STANDARD); }
// === 解析 ===
public static LocalDateTime parse(String str) { return LocalDateTime.parse(str, STANDARD); }
public static LocalDateTime parse(String str, DateTimeFormatter fmt) { return LocalDateTime.parse(str, fmt); }
// === 相對時間 ===
public static LocalDateTime yesterday() { return LocalDateTime.now().minusDays(1); }
public static LocalDateTime tomorrow() { return LocalDateTime.now().plusDays(1); }
public static LocalDateTime daysAgo(int days) { return LocalDateTime.now().minusDays(days); }
public static LocalDateTime daysLater(int days) { return LocalDateTime.now().plusDays(days); }
// === 互轉 ===
public static Timestamp toTimestamp(LocalDateTime ldt) { return Timestamp.valueOf(ldt); }
public static LocalDateTime toLocalDateTime(Timestamp ts) { return ts.toLocalDateTime(); }
public static Date toDate(LocalDateTime ldt) { return Date.from(ldt.atZone(ZoneId.systemDefault()).toInstant()); }
public static LocalDateTime toLocalDateTime(Date date) { return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime(); }
// === 友好顯示 ===
public static String friendly(LocalDateTime ldt) {
Duration d = Duration.between(ldt, LocalDateTime.now());
long sec = d.getSeconds();
if (sec < 60) return "剛剛";
if (sec < 3600) return sec/60 + "分鐘前";
if (sec < 86400) return sec/3600 + "小時前";
if (sec < 172800) return "昨天 " + ldt.format(TIME_ONLY);
return ldt.format(DATE_ONLY);
}
}
避坑指南
- SimpleDateFormat 非線程安全:併發環境用 ThreadLocal 或改用 DateTimeFormatter
- Timestamp.toString() 格式固定:不可自定義,需配合 LocalDateTime 格式化
- 時區陷阱:Date/Calendar/Timestamp 依賴 JVM 時區,LocalDateTime 無時區,ZonedDateTime 明確時區
- 數據庫存儲建議:存 UTC (TIMESTAMP WITH TIME ZONE 或 BIGINT 毫秒數),應用層轉換
- 序列化格式統一:API 輸出 ISO-8601 (yyyy-MM-dd'T'HH:mm:ss.SSSZ)
總結
新代碼無腦用 LocalDateTime/Instant/ZonedDateTime + DateTimeFormatter。DateFormat 僅用於維護舊系統,Timestamp 僅用於 JDBC 交互邊界。封裝 TimeUtils 統一格式常數,代碼審查禁止出現 new SimpleDateFormat、new Date()、Calendar.getInstance()。
- 上一篇文章: MyBatis 在 CS 架構應用實戰:XML 加密、動態 SqlSessionFactory、多環境隔離
- 下一篇文章: Java 自定義 JSP 標籤實戰:權限控制標籤完整開發流程
Copyright ©
程式碼之家 All Rights Reserved