核心類對比 (Java 8 前 vs 後)
| 場景 | 舊 API (java.util/Date) | 新 API (java.time) | 線程安全 |
| 當前時間點 | new Date() | Instant.now() | 新 ✓ / 舊 ✗ |
| 日期時間 (帶時區) | Calendar.getInstance() | ZonedDateTime.now() | 新 ✓ / 舊 ✗ |
| 本地日期 (無時區) | 無直接對應 | LocalDate.now() | 新 ✓ |
| 本地時間 | 無直接對應 | LocalTime.now() | 新 ✓ |
| 格式化/解析 | SimpleDateFormat | DateTimeFormatter | 新 ✓ / 舊 ✗ |
| 數據庫交互 | java.sql.Date/Time/Timestamp | LocalDateTime → Timestamp | - |
Java 8+ 新 API 推薦用法 (主流標準)
1. 獲取當前日期時間
// 本地日期時間(無時區,適合業務邏輯)
LocalDateTime now = LocalDateTime.now(); // 2024-01-15T14:30:45.123
LocalDate today = LocalDate.now(); // 2024-01-15
LocalTime time = LocalTime.now(); // 14:30:45.123
// 帶時區(準確時間點)
ZonedDateTime zdt = ZonedDateTime.now(); // 2024-01-15T14:30:45.123+08:00[Asia/Shanghai]
Instant instant = Instant.now(); // 2024-01-15T06:30:45.123Z (UTC)
// 指定時區
LocalDateTime shanghai = LocalDateTime.now(ZoneId.of("Asia/Shanghai"));
2. 格式化輸出
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String formatted = now.format(fmt); // "2024-01-15 14:30:45"
// 內建格式器
String iso = now.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME); // "2024-01-15T14:30:45.123"
String chinese = now.format(DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH時mm分ss秒", Locale.CHINESE));
3. 解析字符串
String str = "2024-01-15 14:30:45";
LocalDateTime parsed = LocalDateTime.parse(str, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
// 容錯解析:多格式嘗試
public LocalDateTime parseFlexible(String str) {
DateTimeFormatter[] formatters = {
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"),
DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss"),
DateTimeFormatter.ISO_LOCAL_DATE_TIME
};
for (DateTimeFormatter f : formatters) {
try { return LocalDateTime.parse(str, f); } catch (DateTimeParseException ignored) {}
}
throw new IllegalArgumentException("無法解析: " + str);
}
4. 日期計算與調整
LocalDate date = LocalDate.now();
// 加減操作(不可變,返回新對象)
LocalDate tomorrow = date.plusDays(1);
LocalDate nextMonth = date.plusMonths(1);
LocalDate lastYear = date.minusYears(1);
// TemporalAdjusters 複雜調整
LocalDate firstDayOfMonth = date.with(TemporalAdjusters.firstDayOfMonth());
LocalDate lastDayOfMonth = date.with(TemporalAdjusters.lastDayOfMonth());
LocalDate nextMonday = date.with(TemporalAdjusters.next(DayOfWeek.MONDAY));
LocalDate firstDayOfYear = date.withDayOfYear(1);
// 兩日期間隔
long daysBetween = ChronoUnit.DAYS.between(startDate, endDate);
long monthsBetween = ChronoUnit.MONTHS.between(start, end);
5. 舊 API 互操作 (數據庫/JDBC)
// LocalDateTime → Timestamp
Timestamp ts = Timestamp.valueOf(localDateTime);
// Timestamp → LocalDateTime
LocalDateTime ldt = timestamp.toLocalDateTime();
// Instant → Date
Date date = Date.from(instant);
// Date → Instant
Instant ins = date.toInstant();
舊 API (java.util.Date/Calendar) 維護代碼指南
若項目仍使用舊 API,請遵循以下規範:
1. 線程安全:SimpleDateFormat 非線程安全
// 錯誤:共享實例導致並發錯誤
static final SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd");
// 正確:ThreadLocal 隔離
static final ThreadLocal SDF_THREAD_LOCAL =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
public String format(Date date) {
return SDF_THREAD_LOCAL.get().format(date);
}
2. 常用操作模板
// 獲取當前時間毫秒數 (三種等價方式)
long ms1 = System.currentTimeMillis();
long ms2 = new Date().getTime();
long ms3 = Calendar.getInstance().getTimeInMillis();
// 格式化輸出
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String str = sdf.format(new Date());
// 解析
Date date = sdf.parse("2024-01-15 14:30:45");
// Calendar 字段操作
Calendar cal = Calendar.getInstance();
cal.setTime(new Date());
int year = cal.get(Calendar.YEAR);
int month = cal.get(Calendar.MONTH) + 1; // 0-based!
int day = cal.get(Calendar.DAY_OF_MONTH);
cal.add(Calendar.DAY_OF_MONTH, -7); // 一周前
Date weekAgo = cal.getTime();
實用工具類封裝 (生產級)
public final class DateUtils {
private DateUtils() {}
// 線程安全格式器緩存
private static final Map> FORMATTER_CACHE = new ConcurrentHashMap<>();
public static DateTimeFormatter getFormatter(String pattern) {
return FORMATTER_CACHE.computeIfAbsent(pattern,
p -> ThreadLocal.withInitial(() -> DateTimeFormatter.ofPattern(p)))
.get();
}
// 格式化
public static String format(LocalDateTime ldt, String pattern) {
return ldt.format(getFormatter(pattern));
}
public static String format(Date date, String pattern) {
return format(date.toInstant()
.atZone(ZoneId.systemDefault())
.toLocalDateTime(), pattern);
}
// 解析
public static LocalDateTime parse(String str, String pattern) {
return LocalDateTime.parse(str, getFormatter(pattern));
}
// 相對時間描述
public static String relativeTime(LocalDateTime target) {
Duration dur = Duration.between(target, LocalDateTime.now());
long seconds = dur.getSeconds();
if (seconds < 60) return "剛剛";
if (seconds < 3600) return seconds / 60 + "分鐘前";
if (seconds < 86400) return seconds / 3600 + "小時前";
if (seconds < 604800) return seconds / 86400 + "天前";
return format(target, "yyyy-MM-dd");
}
}
常見陷阱總結
| 陷阱 | 錯誤代碼 | 正確做法 |
| 月份 0-based | cal.get(Calendar.MONTH) // 0=1月 | +1 顯示,或用 Month enum |
| SimpleDateFormat 共享 | static final SDF = new ... | ThreadLocal 或新 API |
| 時區假設 | new Date() 認為是本地時間 | Date 無時區,用 ZonedDateTime |
| 夏令時跳躍 | plusDays(1) 跨夏令時 | 用 LocalDate 而非 Instant 計算天 |
| 數據庫時區不一致 | 存 UTC 讀本地 | 統一用 UTC 存儲,顯示時轉換 |