java.util.Calendar 是 Java 中用于处理日期和时间的抽象类,旨在弥补 Date 类在日期字段(年、月、日等)操作上的不足。它提供了对日期字段的精细化控制,支持日期计算、时区处理等功能。
Calendar 类通过字段(如年、月、日、时、分、秒)来表示日期时间,允许对这些字段进行单独操作,并支持时区转换和日期计算(如加减天数、月份等)。
Calendar.getInstance():最常用,返回当前系统时间的实例,默认使用系统时区和本地语言环境(Locale)。
Calendar.getInstance(TimeZone zone):指定时区(如纽约、伦敦),适用于跨时区时间处理。
示例:获取当前日期
import java.util.Calendar; public class CalendarExample { public static void main(String[] args) { // 获取当前时间的 Calendar 实例 Calendar calendar = Calendar.getInstance(); System.out.println("当前时间:" + calendar.getTime()); } }
注意:Calendar 是抽象类,不能直接通过 new 创建,必须通过工厂方法(如 getInstance())获取子类实例(实际是 GregorianCalendar,即公历)。
通过 get() 方法获取指定字段的值,核心字段常量如下:
字段常量 | 含义 | 取值范围 | 注意事项 |
Calendar.YEAR | 年份 | 4 位整数(如 2024) | 无 |
Calendar.MONTH | 月份 | 0~11 | 0=1 月,11=12 月(需加 1 使用) |
Calendar.DATE | 日期(月内天数) | 1~31(根据月份变化) | 同 DAY_OF_MONTH |
Calendar.HOUR_OF_DAY | 小时(24 小时制) | 0~23 | 推荐使用(避免 12 小时制混淆) |
Calendar.HOUR | 小时(12 小时制) | 0~11 | 需配合 AM_PM 判断上 / 下午 |
Calendar.MINUTE | 分钟 | 0~59 | 无 |
Calendar.SECOND | 秒 | 0~59 | 无 |
Calendar.DAY_OF_WEEK | 星期 | 1~7 | 1 = 周日,2 = 周一,...7 = 周六 |
示例:
Calendar calendar = Calendar.getInstance(); int year = calendar.get(Calendar.YEAR); // 年份(4位,如2023) int month = calendar.get(Calendar.MONTH) + 1; // 月份(0~11,需+1) int day = calendar.get(Calendar.DAY_OF_MONTH); // 日 int hour = calendar.get(Calendar.HOUR_OF_DAY); // 24小时制小时 int minute = calendar.get(Calendar.MINUTE); // 分钟 System.out.printf("当前日期:%d年%d月%d日 %d:%d%n", year, month, day, hour, minute);
单个字段设置:set(int field, int value),如 cal.set(Calendar.YEAR, 2024)。
批量设置(年月日):set(int year, int month, int date),如 cal.set(2024, 6, 15)(6=7 月)。
批量设置(年月日时分秒):set(int year, int month, int date, int hourOfDay, int minute, int second),如 cal.set(2024, 8, 25, 14, 30, 0)。
示例:
Calendar calendar = Calendar.getInstance(); // 设置为 2024年3月15日(月份需-1,因为0代表1月) calendar.set(Calendar.YEAR, 2024); calendar.set(Calendar.MONTH, Calendar.MARCH); // 等价于 set(Calendar.MONTH, 2) calendar.set(Calendar.DAY_OF_MONTH, 15); System.out.println("设置后的日期:" + calendar.getTime());
注意:设置字段时,Calendar 不会立即计算时间,直到调用 get()、add() 等方法时才会“延迟计算”,避免重复计算开销。
add(int field, int amount) 方法用于对指定字段进行 “增减” 操作,amount 为正数表示 “加”,负数表示 “减”。若增减后字段超出范围,会自动向高位字段进位。例如:2024-12-31 加 1 天,会自动变为 2025-01-01。
示例:
Calendar calendar = Calendar.getInstance(); System.out.println("当前日期:" + calendar.getTime()); // 加 3 天 calendar.add(Calendar.DAY_OF_MONTH, 3); System.out.println("3天后:" + calendar.getTime()); // 减 2 个月 calendar.add(Calendar.MONTH, -2); System.out.println("再减2个月后:" + calendar.getTime());
roll(int field, int amount) 方法与 add() 类似,但只修改指定字段,不影响更高层级的字段(如月份滚动不影响年份)。示例:
import java.util.Calendar; public class CalendarExample { public static void main(String[] args) { Calendar calendar = Calendar.getInstance(); calendar.set(2023, Calendar.DECEMBER, 31); // 2023-12-31 System.out.println("初始日期:" + calendar.getTime()); // roll 加 1 天(只修改日期,月份不变) calendar.roll(Calendar.DAY_OF_MONTH, 1); System.out.println("roll加1天后:" + calendar.getTime()); // 2023-12-01(而非2024-01-01) } }
运行结果:
初始日期:Sun Dec 31 22:35:50 CST 2023 roll加1天后:Fri Dec 01 22:35:50 CST 2023
compareTo(Calendar anotherCalendar) 方法用于比较两个 Calendar 的时间先后,返回 int 结果:
若当前 Calendar 时间 早于 目标时间:返回 负数(如 - 1)。
若当前 Calendar 时间 晚于 目标时间:返回 正数(如 1)。
若时间 相等:返回 0。
示例:
import java.util.Calendar; public class CalendarCompareExample { public static void main(String[] args) { // 创建第一个Calendar对象(2023年1月1日) Calendar cal1 = Calendar.getInstance(); cal1.set(2023, Calendar.JANUARY, 1); // 创建第二个Calendar对象(2024年1月1日) Calendar cal2 = Calendar.getInstance(); cal2.set(2024, Calendar.JANUARY, 1); // 比较两个日期 int result1 = cal1.compareTo(cal2); int result2 = cal2.compareTo(cal1); int result3 = cal1.compareTo(cal1); // 输出结果 System.out.println("cal1 与 cal2 比较: " + result1); // 输出负数,cal1早于cal2 System.out.println("cal2 与 cal1 比较: " + result2); // 输出正数,cal2晚于cal1 System.out.println("cal1 与自身比较: " + result3); // 输出0,时间相同 } }
运行结果:
cal1 与 cal2 比较: -1 cal2 与 cal1 比较: 1 cal1 与自身比较: 0
注意:比较前需确保两个 Calendar 的时区一致,否则可能出现逻辑错误。
Calendar -> Date:calendar.getTime(),返回对应的 java.util.Date 对象(Date 类是更基础的时间类,常用于接口交互)。
Date -> Calendar:calendar.setTime(Date date),将 Date 对象的时间设置到 Calendar 中。
示例:
import java.util.Calendar; import java.util.Date; public class CalendarDateConversion { public static void main(String[] args) { // 1. 创建Calendar对象并设置时间(2023年10月1日) Calendar calendar = Calendar.getInstance(); calendar.set(2023, Calendar.OCTOBER, 1); System.out.println("Calendar时间: " + calendar.getTime()); // 直接打印会调用toString() // 2. Calendar -> Date:使用getTime()方法 Date dateFromCalendar = calendar.getTime(); System.out.println("从Calendar转换得到的Date: " + dateFromCalendar); // 3. 创建一个新的Calendar对象,用于演示Date -> Calendar Calendar newCalendar = Calendar.getInstance(); // 4. Date -> Calendar:使用setTime()方法 newCalendar.setTime(dateFromCalendar); System.out.println("从Date转换得到的Calendar时间: " + newCalendar.get(Calendar.YEAR) + "年" + (newCalendar.get(Calendar.MONTH) + 1) + "月" + // 月份从0开始,需+1 newCalendar.get(Calendar.DATE) + "日"); } }
运行结果:
Calendar时间: Sun Oct 01 01:29:17 UTC 2023 从Calendar转换得到的Date: Sun Oct 01 01:29:17 UTC 2023 从Date转换得到的Calendar时间: 2023年10月1日
注意:Date 类本身不包含时区信息,互转时需依赖 Calendar 的时区。
getActualMaximum(int field):获取指定字段的 “实际最大值”(根据当前时间动态计算)。例如:2024 年 2 月的最大天数是 29(闰年),3 月是 31(大月)。
clear(int field):清除指定字段的值(设为默认值,如 clear(Calendar.HOUR_OF_DAY) 会将小时设为 0)。
getTimeInMillis():返回当前 Calendar 对应的 “时间戳”(从 1970-01-01 00:00:00 UTC 到当前时间的毫秒数),便于时间戳计算。
示例:
import java.util.Calendar; public class CalendarMethodsExample { public static void main(String[] args) { // 创建Calendar对象并设置为2024年2月 Calendar calendar = Calendar.getInstance(); calendar.set(2024, Calendar.FEBRUARY, 1); // 2月(月份参数为1,因为从0开始) // 1. getActualMaximum():获取指定字段的实际最大值 int maxDaysInFeb2024 = calendar.getActualMaximum(Calendar.DAY_OF_MONTH); System.out.println("2024年2月的最大天数: " + maxDaysInFeb2024); // 2024是闰年,输出29 // 切换到3月 calendar.set(2024, Calendar.MARCH, 1); int maxDaysInMar2024 = calendar.getActualMaximum(Calendar.DAY_OF_MONTH); System.out.println("2024年3月的最大天数: " + maxDaysInMar2024); // 大月,输出31 // 2. clear():清除指定字段的值 System.out.println("清除前的月份: " + calendar.get(Calendar.MONTH)); calendar.clear(Calendar.MONTH); // 清除小时字段(设为0) System.out.println("清除后的月份: " + calendar.get(Calendar.MONTH)); // 输出0 // 3. getTimeInMillis():获取时间戳(毫秒数) long timestamp = calendar.getTimeInMillis(); System.out.println("当前Calendar的时间戳: " + timestamp + " 毫秒"); } }
运行结果:
2024年2月的最大天数: 29 2024年3月的最大天数: 31 清除前的月份: 2 清除后的月份: 0 当前Calendar的时间戳: 1704073557727 毫秒
注意:clear() 方法对 HOUR_OF_DAY(一天中的小时,24 小时制)、HOUR(12 小时制的小时)和 AM_PM(上午 / 下午标识)这三个字段的处理是相互独立的,且会应用一天中时间的解析规则。清除这三个字段中的任意一个,都不会重置此 Calendar 的“一天中的小时”值。若需重置小时值,请使用 set (Calendar.HOUR_OF_DAY, 0) 方法。
以下示例将通过具体代码演示 Calendar 类中常用方法的用法:
import java.util.Calendar; import java.util.Date; import java.util.TimeZone; public class CalendarDemo { public static void main(String[] args) { // 1. 获取 Calendar 实例(核心:不能直接 new,需通过工厂方法) // 方法1:获取当前系统时间的 Calendar 实例(默认时区、默认Locale) Calendar cal1 = Calendar.getInstance(); System.out.println("1. 当前系统时间的 Calendar 实例:" + cal1); // 方法2:指定时区的 Calendar 实例(如:纽约时区) TimeZone nyTimeZone = TimeZone.getTimeZone("America/New_York"); Calendar cal2 = Calendar.getInstance(nyTimeZone); System.out.println("2. 纽约时区的 Calendar 实例:" + cal2); // 2. 常用字段常量(理解字段含义,避免硬编码) // Calendar 用常量定义了所有时间字段,例如: // YEAR(年)、MONTH(月,注意:0=1月,11=12月)、DATE(日)、HOUR(12小时制)、HOUR_OF_DAY(24小时制) // MINUTE(分)、SECOND(秒)、MILLISECOND(毫秒)、DAY_OF_WEEK(星期,1=周日,2=周一,...,7=周六) System.out.println("\n3. 常用字段说明(以 cal1 为例):"); System.out.println(" - 当前年份:" + cal1.get(Calendar.YEAR)); System.out.println(" - 当前月份(0=1月):" + (cal1.get(Calendar.MONTH) + 1)); // 加1后符合日常习惯 System.out.println(" - 当前日期:" + cal1.get(Calendar.DATE)); System.out.println(" - 当前小时(24小时制):" + cal1.get(Calendar.HOUR_OF_DAY)); System.out.println(" - 当前分钟:" + cal1.get(Calendar.MINUTE)); System.out.println(" - 当前秒:" + cal1.get(Calendar.SECOND)); System.out.println(" - 当前星期(1=周日):" + convertWeekToChinese(cal1.get(Calendar.DAY_OF_WEEK))); // 转换为中文显示 // 3. 设置时间字段(set() 方法的3种用法) Calendar cal3 = Calendar.getInstance(); System.out.println("\n4. 设置时间字段演示:"); // 方法1:单个字段设置(参数:字段常量,值) cal3.set(Calendar.YEAR, 2024); // 设置年份为2024 cal3.set(Calendar.MONTH, Calendar.JULY); // 设置月份为7月(Calendar.JULY=6,对应实际7月) cal3.set(Calendar.DATE, 15); // 设置日期为15日 System.out.println(" - 单个字段设置后:" + formatCalendar(cal3)); // 格式化显示,便于阅读 // 方法2:批量设置(年、月、日) cal3.set(2024, Calendar.AUGUST, 20); // 直接设置 年=2024,月=8月,日=20日 System.out.println(" - 批量设置(年/月/日)后:" + formatCalendar(cal3)); // 方法3:批量设置(年、月、日、时、分、秒) cal3.set(2024, Calendar.SEPTEMBER, 25, 14, 30, 0); // 年=2024,月=9月,日=25日,时=14,分=30,秒=0 System.out.println(" - 批量设置(年/月/日/时/分/秒)后:" + formatCalendar(cal3)); //4. 时间计算(add() 方法:基于当前字段增减) Calendar cal4 = Calendar.getInstance(); cal4.set(2024, Calendar.OCTOBER, 5, 10, 0, 0); // 初始时间:2024-10-05 10:00:00 System.out.println("\n5. 时间计算演示(初始时间:" + formatCalendar(cal4) + "):"); // 增加 3 天 cal4.add(Calendar.DATE, 3); System.out.println(" - 增加3天后:" + formatCalendar(cal4)); // 减少 2 个月 cal4.add(Calendar.MONTH, -2); System.out.println(" - 减少2个月后:" + formatCalendar(cal4)); // 增加 1 小时 15 分钟 cal4.add(Calendar.HOUR_OF_DAY, 1); cal4.add(Calendar.MINUTE, 15); System.out.println(" - 增加1小时15分钟后:" + formatCalendar(cal4)); // 5. 时间比较(compareTo() 方法) // 初始化两个 Calendar 对象 Calendar calA = Calendar.getInstance(); calA.set(2024, Calendar.NOVEMBER, 10); // 时间A:2024-11-10 Calendar calB = Calendar.getInstance(); calB.set(2024, Calendar.DECEMBER, 10); // 时间B:2024-12-10 System.out.println("\n6. 时间比较演示:"); System.out.println(" - 时间A:" + formatCalendar(calA) + ",时间B:" + formatCalendar(calB)); int compareResult = calA.compareTo(calB); if (compareResult < 0) { System.out.println(" - 比较结果:时间A < 时间B"); } else if (compareResult > 0) { System.out.println(" - 比较结果:时间A > 时间B"); } else { System.out.println(" - 比较结果:时间A = 时间B"); } // 6. 转换方法(与 Date 类互转) Calendar cal5 = Calendar.getInstance(); System.out.println("\n7. 与 Date 类互转演示:"); // Calendar -> Date:通过 getTime() 方法 Date dateFromCal = cal5.getTime(); System.out.println(" - Calendar 转 Date:" + dateFromCal); // Date -> Calendar:通过 setTime() 方法 Date targetDate = new Date(); // 假设一个目标 Date 对象 cal5.setTime(targetDate); System.out.println(" - Date 转 Calendar:" + formatCalendar(cal5)); // 7. 其他实用方法 Calendar cal6 = Calendar.getInstance(); cal6.set(2024, Calendar.FEBRUARY, 28); // 2024年2月28日(2024是闰年) System.out.println("\n8. 其他实用方法演示:"); // 获取当月最大天数(判断闰年/大月/小月) int maxDaysInMonth = cal6.getActualMaximum(Calendar.DATE); System.out.println(" - 2024年2月最大天数:" + maxDaysInMonth); // 输出29(闰年) // 获取当年最大天数 int maxDaysInYear = cal6.getActualMaximum(Calendar.DAY_OF_YEAR); System.out.println(" - 2024年最大天数:" + maxDaysInYear); // 输出366(闰年) // 重置时间字段(将时分秒毫秒设为0) cal6.clear(Calendar.HOUR_OF_DAY); cal6.clear(Calendar.MINUTE); cal6.clear(Calendar.SECOND); cal6.clear(Calendar.MILLISECOND); System.out.println(" - 重置时分秒后:" + formatCalendar(cal6)); } // 将 Calendar 格式化为 "yyyy-MM-dd HH:mm:ss" 字符串 private static String formatCalendar(Calendar calendar) { if (calendar == null) { return null; } // 获取各字段值 int year = calendar.get(Calendar.YEAR); int month = calendar.get(Calendar.MONTH) + 1; // 月份0~11,加1后为1~12 int day = calendar.get(Calendar.DATE); int hour = calendar.get(Calendar.HOUR_OF_DAY); int minute = calendar.get(Calendar.MINUTE); int second = calendar.get(Calendar.SECOND); // 格式化(补0,确保两位数) return String.format("%d-%02d-%02d %02d:%02d:%02d", year, month, day, hour, minute, second); } // 将星期字段(1~7)转换为中文(1=周日,2=周一...7=周六) private static String convertWeekToChinese(int week) { String[] weekNames = {"", "周日", "周一", "周二", "周三", "周四", "周五", "周六"}; return weekNames[week]; // 索引对应:1→周日,2→周一...7→周六 } }
运行结果:
1. 当前系统时间的 Calendar 实例:java.util.GregorianCalendar[time=1756461588689,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="Etc/UTC",offset=0,dstSavings=0,useDaylight=false,transitions=0,lastRule=null],firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=1,YEAR=2025,MONTH=7,WEEK_OF_YEAR=35,WEEK_OF_MONTH=5,DAY_OF_MONTH=29,DAY_OF_YEAR=241,DAY_OF_WEEK=6,DAY_OF_WEEK_IN_MONTH=5,AM_PM=0,HOUR=9,HOUR_OF_DAY=9,MINUTE=59,SECOND=48,MILLISECOND=689,ZONE_OFFSET=0,DST_OFFSET=0] 2. 纽约时区的 Calendar 实例:java.util.GregorianCalendar[time=1756461588739,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="America/New_York",offset=-18000000,dstSavings=3600000,useDaylight=true,transitions=235,lastRule=java.util.SimpleTimeZone[id=America/New_York,offset=-18000000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=3,startMonth=2,startDay=8,startDayOfWeek=1,startTime=7200000,startTimeMode=0,endMode=3,endMonth=10,endDay=1,endDayOfWeek=1,endTime=7200000,endTimeMode=0]],firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=1,YEAR=2025,MONTH=7,WEEK_OF_YEAR=35,WEEK_OF_MONTH=5,DAY_OF_MONTH=29,DAY_OF_YEAR=241,DAY_OF_WEEK=6,DAY_OF_WEEK_IN_MONTH=5,AM_PM=0,HOUR=5,HOUR_OF_DAY=5,MINUTE=59,SECOND=48,MILLISECOND=739,ZONE_OFFSET=-18000000,DST_OFFSET=3600000] 3. 常用字段说明(以 cal1 为例): - 当前年份:2025 - 当前月份(0=1月):8 - 当前日期:29 - 当前小时(24小时制):9 - 当前分钟:59 - 当前秒:48 - 当前星期(1=周日):周五 4. 设置时间字段演示: - 单个字段设置后:2024-07-15 09:59:48 - 批量设置(年/月/日)后:2024-08-20 09:59:48 - 批量设置(年/月/日/时/分/秒)后:2024-09-25 14:30:00 5. 时间计算演示(初始时间:2024-10-05 10:00:00): - 增加3天后:2024-10-08 10:00:00 - 减少2个月后:2024-08-08 10:00:00 - 增加1小时15分钟后:2024-08-08 11:15:00 6. 时间比较演示: - 时间A:2024-11-10 09:59:48,时间B:2024-12-10 09:59:48 - 比较结果:时间A < 时间B 7. 与 Date 类互转演示: - Calendar 转 Date:Fri Aug 29 09:59:48 UTC 2025 - Date 转 Calendar:2025-08-29 09:59:48 8. 其他实用方法演示: - 2024年2月最大天数:29 - 2024年最大天数:366 - 重置时分秒后:2024-02-28 09:00:00
月份取值问题:Calendar.MONTH 是从 0 开始(即 0=1 月,11=12 月),使用时需加 1 才能符合日常习惯,避免踩坑。
星期取值问题:Calendar.DAY_OF_WEEK 是从 1 开始(1 = 周日,2 = 周一,...7 = 周六),需注意与实际星期的对应关系。
时区问题:默认 Calendar 使用系统时区,跨时区场景需显式指定时区(如 TimeZone.getTimeZone("Asia/Shanghai"))。
替代方案:Java 8 及以后推荐使用 java.time 包(如 LocalDateTime、ZonedDateTime),API 更简洁、线程安全,但 Calendar 在老项目中仍广泛使用,需掌握其用法。
Calendar 虽然比 Date 提供了更丰富的日期字段操作,但仍存在设计缺陷。JDK 1.8 之后推荐使用 java.time 包(如 LocalDateTime、ZonedDateTime),这些类线程安全、API 简洁,且避免了 Calendar 的诸多问题。