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 的诸多问题。