日期時間處理是 Java 開發中高頻且易踩坑的領域。從傳統的 java.util.Date/Calendar 到現代的 java.time (JSR-310),掌握核心 API 的正確用法至關重要。本文系統梳理常見場景:月份天數計算、Date 與 Calendar 互轉、格式化輸出、週期計算、兩日期間隔天數、字符串/長整型/日期互轉,以及數據庫日期類型映射,附帶可直接運行的代碼示例。
關鍵點:Calendar 月份從 0 開始;設置字段前需 clear() 避免殘留值干擾。
Calendar cal = Calendar.getInstance();
cal.clear();
cal.set(Calendar.YEAR, year);
cal.set(Calendar.MONTH, month - 1); // 月份 0-based
int maxDay = cal.getActualMaximum(Calendar.DAY_OF_MONTH);
// Calendar -> Date
Date date = cal.getTime();
// Date -> Calendar
Calendar cal = Calendar.getInstance();
cal.setTime(date);
使用 SimpleDateFormat,注意線程不安全,建議局部變量或使用 ThreadLocal。
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String formatted = sdf.format(new Date()); // 2024-05-20 14:30:00
// 某天是第幾週
Calendar cal = Calendar.getInstance();
cal.set(2006, 8, 3); // 2006-09-03
int weekOfYear = cal.get(Calendar.WEEK_OF_YEAR);
// 第 1 週的周一日期
cal.set(Calendar.YEAR, 2006);
cal.set(Calendar.WEEK_OF_YEAR, 1);
cal.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY);
System.out.println(sdf.format(cal.getTime())); // 2006-01-02
add(field, amount):修改指定字段,並自動調整更大字段(如月份進位)。roll(field, amount):僅在當前字段範圍內循環,不影響更大字段。cal.set(2006, 8, 3); // 9月3日
cal.add(Calendar.DATE, -4); // 2006-08-30 (跨月)
cal.roll(Calendar.DATE, -4); // 2006-09-29 (同月循環)
推薦:使用毫秒差值法(簡單)或 Calendar 字段法(精確跨年)。
// 方法一:毫秒差 (忽略夏令時邊界情況)
public int getIntervalDays(Date start, Date end) {
if (start.after(end)) { Date tmp = start; start = end; end = tmp; }
long diff = end.getTime() - start.getTime();
return (int) (diff / (1000 * 60 * 60 * 24));
}
// 方法二:Calendar 字段法 (精確處理跨年)
public int getDaysBetween(Calendar d1, Calendar d2) {
if (d1.after(d2)) { Calendar tmp = d1; d1 = d2; d2 = tmp; }
int days = d2.get(Calendar.DAY_OF_YEAR) - d1.get(Calendar.DAY_OF_YEAR);
int y2 = d2.get(Calendar.YEAR);
if (d1.get(Calendar.YEAR) != y2) {
d1 = (Calendar) d1.clone();
do {
days += d1.getActualMaximum(Calendar.DAY_OF_YEAR);
d1.add(Calendar.YEAR, 1);
} while (d1.get(Calendar.YEAR) != y2);
}
return days;
}
// String -> Date -> Long
SimpleDateFormat sdf = new SimpleDateFormat("M/dd/yyyy hh:mm:ss a", Locale.US);
Date d = sdf.parse("5/13/2003 10:31:37 AM");
long timestamp = d.getTime();
// Long -> Date -> String (指定格式)
SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String str = fmt.format(new Date(timestamp));
// 年週 -> 日期
SimpleDateFormat f1 = new SimpleDateFormat("yyyy-MM F E");
Date d = f1.parse("2003-05 5 星期五"); // 2003年第5週的周五
// 日期 -> 星期幾
SimpleDateFormat f2 = new SimpleDateFormat("E");
String weekDay = f2.format(d); // 星期五
| 數據庫類型 | Java 類型 | 說明 |
|---|---|---|
| Oracle DATE | java.sql.Date | 僅年月日 |
| MySQL DATETIME / SQL Server DATETIME | java.sql.Timestamp | 含時分秒毫秒 |
// 字符串 -> java.sql.Timestamp
public static Timestamp string2Time(String str) throws ParseException {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.ENGLISH);
sdf.setLenient(false);
return new Timestamp(sdf.parse(str).getTime());
}
// 字符串 -> java.sql.Date
public static java.sql.Date string2Date(String str) throws ParseException {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH);
sdf.setLenient(false);
return new java.sql.Date(sdf.parse(str).getTime());
}
// java.util.Date <-> Timestamp 互轉
Timestamp ts = new Timestamp(new Date().getTime()); // Date -> Timestamp
Date dt = new Date(ts.getTime()); // Timestamp -> Date
java.time 包 (LocalDateTime, ZonedDateTime, DateTimeFormatter):不可變、線程安全、API 更豐富。SimpleDateFormat,改用 ThreadLocal 或每次新建。java.sql.Date 與 Timestamp。Java 日期時間處理看似繁雜,實則脈絡清晰。掌握 Calendar 字段操作、SimpleDateFormat 格式化模式、毫秒與天數換算邏輯,即可應對 90% 的業務場景。隨著 JDK 8+ 普及,建議逐步遷移至 java.time 體系,從根源上消除線程安全與 API 設計缺陷帶來的隱患。