代碼中`c.set(2015, 10, 25)`實際設置為**2015年11月25日**,因`Calendar.MONTH`字段**0-11對應1-12月**。兩個本應相同時間的毫秒值相減得到-31天(-2678400000毫秒)。本文詳解月份陷阱及避坑指南。
錯誤代碼還原
Calendar c = Calendar.getInstance();
c.set(2015, 10, 25); // 意圖:2015-10-25,實際:2015-11-25
long t1 = c.getTimeInMillis();
Calendar today = Calendar.getInstance(); // 假設今天 2015-10-25
long diff = today.getTimeInMillis() - t1; // -31天
Calendar月份常量表
| 常量 | 值 | 月份 |
| `Calendar.JANUARY` | 0 | 1月 |
| `Calendar.FEBRUARY` | 1 | 2月 |
| ... | ... | ... |
| `Calendar.OCTOBER` | 9 | 10月 |
| `Calendar.NOVEMBER` | 10 | 11月 |
| `Calendar.DECEMBER` | 11 | 12月 |
正確用法三種
- 使用常量(推薦,可讀性最強):
c.set(2015, Calendar.OCTOBER, 25); // 明確10月
- 月份-1(若用變量/字面量):
int month = 10; // 10月
c.set(2015, month - 1, 25);
- Java 8+ java.time.LocalDate(徹底替代Calendar):
LocalDate date = LocalDate.of(2015, 10, 25); // 直觀、不可變、線程安全
long diff = ChronoUnit.DAYS.between(LocalDate.now(), date); // 直接得天數差
Calendar其他易錯字段
| 字段 | 範圍 | 陷阱 |
| `DAY_OF_WEEK` | 1-7 | 1=星期日,7=星期六(非ISO標準) |
| `WEEK_OF_YEAR` | 1-53 | 週邊界取決於Locale |
| `HOUR` | 0-11 | 12小時制,需配合`AM_PM` |
| `HOUR_OF_DAY` | 0-23 | 24小時制,推薦用此 |
遷移建議:全面拥抱 java.time (JSR-310)
- `LocalDate`/`LocalTime`/`LocalDateTime`:無時區日期時間。
- `ZonedDateTime`/`OffsetDateTime`:帶時區/偏移。
- `Instant`:時間戳(納秒精度)。
- `Duration`/`Period`:時間間隔/日期間隔。
- `DateTimeFormatter`:線程安全格式化/解析。
鐵律:Calendar月份0-based是歷史包袱,新代碼零成本遷移到java.time,舊代碼遇到月份計算必檢查-1。