Java 日期時間 API 歷經三代演進:Date/Calendar (Java 1.1)、LocalDateTime (Java 8)、Temporal API (Java 8+)。本文聚焦傳統 Calendar 與 SimpleDateFormat 實用技巧,適用於維護舊系統、Android 開發、或尚未遷移至 Java 8 的專案。
| 類別 | 套件 | 用途 | 線程安全 |
|---|---|---|---|
| Calendar | java.util | 日期運算、欄位存取 | 否 |
| Date | java.util | 時間戳記、傳統 API 相容 | 否 |
| SimpleDateFormat | java.text | 格式化/解析字串 | 否 (每執行緒建立實例) |
| DateFormat | java.text | 抽象基類、預設格式取得 | 否 |
// 方式一:Calendar
Calendar cal = Calendar.getInstance();
Date now = cal.getTime(); // Fri Jan 06 21:03:36 CST 2017
// 方式二:Date 直接取得
Date now2 = new Date();
DateFormat fmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(fmt.format(new Date())); // 2017-01-06 21:19:02
System.out.println(fmt.format(cal.getTime())); // 2017-01-06 23:41:00
// 常用格式模式
// yyyy-MM-dd -> 2017-01-06
// HH:mm:ss -> 21:19:02 (24小時制)
// hh:mm:ss a -> 09:19:02 PM (12小時制)
// E -> 週五 (星期)
// z -> CST (時區)
Calendar cal = Calendar.getInstance();
// 年
int year = cal.get(Calendar.YEAR); // 2017
// 月 (0-11,需 +1)
int month = cal.get(Calendar.MONTH) + 1; // 1
// 日
int day = cal.get(Calendar.DAY_OF_MONTH); // 6
// 時 (24小時制)
int hour = cal.get(Calendar.HOUR_OF_DAY); // 23
// 分
int minute = cal.get(Calendar.MINUTE); // 41
// 秒
int second = cal.get(Calendar.SECOND); // 0
// 星期 (1=星期日, 2=星期一, ...)
int week = cal.get(Calendar.DAY_OF_WEEK); // 6 (星期五)
Calendar cal = Calendar.getInstance();
// 1 小時後
cal.add(Calendar.HOUR_OF_DAY, 1); // 修改 cal 物件本身
// 或不修改原物件
Calendar later = (Calendar) cal.clone();
later.add(Calendar.HOUR_OF_DAY, 1);
// 6 分鐘前
cal.add(Calendar.MINUTE, -6);
// 取得運算後的格式化字串
String timeStr = fmt.format(cal.getTime()); // 2017-01-06 23:26:39
Calendar cal1 = Calendar.getInstance();
Calendar cal2 = Calendar.getInstance();
cal2.add(Calendar.HOUR, 7); // cal2 比 cal1 晚 7 小時
// compareTo: cal1 < cal2 回傳 -1; 相等 0; 大於 1
int cmp = cal1.compareTo(cal2); // -1
Calendar cal1 = Calendar.getInstance();
Calendar cal2 = Calendar.getInstance();
cal2.add(Calendar.HOUR, 7);
// 毫秒差
long diffMs = cal2.getTimeInMillis() - cal1.getTimeInMillis(); // 25200000
// 換算小時
long diffHours = diffMs / (1000 * 60 * 60); // 7
// 更精確:使用 TimeUnit (Java 5+)
long diffHours2 = TimeUnit.MILLISECONDS.toHours(diffMs); // 7
long diffMinutes = TimeUnit.MILLISECONDS.toMinutes(diffMs); // 420
// 方式一:set 方法
Calendar cal = Calendar.getInstance();
cal.set(2024, Calendar.DECEMBER, 25, 12, 30, 0); // 2024-12-25 12:30:00
cal.set(Calendar.MILLISECOND, 0);
// 方式二:SimpleDateFormat 解析
DateFormat df = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
Date specific = df.parse("2024/12/25 12:30:00");
cal.setTime(specific);
DateTimeFormatterCalendar.JANUARY = 0,顯示時必須 +1df.setLenient(false)cal.setTimeZone(TimeZone.getTimeZone("UTC"))cal.add() 會修改原物件,需注意副作用
// 當前時間
LocalDateTime now = LocalDateTime.now();
// 格式化
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String str = now.format(fmt);
// 解析
LocalDateTime parsed = LocalDateTime.parse("2024-12-25 12:30:00", fmt);
// 時間運算 (不可變,鏈式呼叫)
LocalDateTime later = now.plusHours(1).minusMinutes(6);
// 時間差
Duration duration = Duration.between(start, end);
long hours = duration.toHours();
// 時區
ZonedDateTime zdt = now.atZone(ZoneId.of("Asia/Taipei"));
| 傳統 API | Java 8+ API | 優勢 |
|---|---|---|
| Calendar.getInstance() | LocalDateTime.now() | 不可變、線程安全 |
| cal.get(Calendar.YEAR) | ldt.getYear() | 直接回傳 int、無 0-based |
| cal.add(HOUR, 1) | ldt.plusHours(1) | 不可變、鏈式、易讀 |
| SimpleDateFormat | DateTimeFormatter | 線程安全、效能更好 |
| cal.getTimeInMillis() | Instant.now().toEpochMilli() | 統一時間戳模型 |
| TimeZone | ZoneId / ZoneOffset | 命名清晰、支援規則變更 |
Calendar/SimpleDateFormat 是 Java 歷史包袱,但在大量存量程式碼中仍佔主流。掌握其核心用法、陷阱與替代方案,能在維舊專案時游刃有餘。新專案強制使用 Java 8+ java.time 套件,從根源避免線程安全、可變性、時區等經典坑點。