<think>嗯,用户让我用Java编写一个程序,输入年份和该年第一天的星期几,然后打印出每月的月历。我需要先理清楚这个问题的步骤,然后逐步解决。首先,我得考虑如何获取用户输入,然后处理每个月的天数,以及每个月的第一天是星期几,接着格式化输出月历。
首先,用户需要输入年份和第一天的星期几。例如,2021年1月1日是星期5(用数字5表示)。那程序需要从1月开始,依次打印每个月的日历。每个月的日历需要正确显示星期排列,并且日期要对齐正确的星期位置。
接下来,我需要确定每个月的天数。这里需要注意的是闰年的二月有29天,否则是28天。判断闰年的条件是:能被4整除但不能被100整除,或者能被400整除。所以,在程序中需要先写一个判断闰年的方法,根据输入的年份来确定二月的天数。
然后,每个月的第一天的星期几是关键。例如,1月的第一天是用户输入的星期几,那么2月的第一天就是1月的最后一天的星期几加1。例如,1月有31天,假设1月1日是星期五(5),那么31天后,1月31日是(5 + 31 -1)%7 = (35)%7=0,也就是星期日。所以2月1日就是0 +1 = 1,星期一。这样,每个月的第一天需要根据前一个月的天数来计算。
所以,我需要一个数组来存储每个月的天数,然后循环处理每个月,同时跟踪当前月份的第一天的星期几。例如,初始时,currentDayOfWeek是用户输入的值。处理完1月后,计算下个月的起始星期几,即(currentDayOfWeek + daysInMonth) %7。例如,1月有31天,起始星期5,那么31天后是星期(5+31)%7=36%7=1(星期一),所以下个月的起始星期是1。但这里可能需要再仔细计算,因为假设1月1日是星期5,那么1月31日就是(5 + 31-1)%7=(35)%7=0,星期日。所以2月1日应该是0+1=1?或者是否需要调整?
这里可能需要重新考虑。每个月的第一天是前一个月的最后一天的下一天。例如,如果前一个月有n天,起始星期是s,那么最后一天的星期是(s + n -1)%7,那么下一个月的第一天是(s +n )%7。例如,假设1月1日是星期5(s=5),1月有31天,最后一天是(5+31-1)=35,35%7=0,所以下个月的第一天是(5+31)%7=36%7=1,星期一。是的,这样是对的。所以每个月的起始星期是(currentDayOfWeek + daysInPreviousMonth)%7。例如,处理完1月后,currentDayOfWeek更新为(currentDayOfWeek + daysInJanuary)%7,即下个月的第一天的星期几。
因此,在处理每个月的循环中,需要:
1. 获取当前月份的天数,考虑闰年。
2. 打印该月的标题,例如“January 2021”。
3. 打印星期标题,例如日 一 二 三 四 五 六。
4. 确定该月第一天是星期几,然后打印对应的空格。
5. 逐日打印日期,每7天换行。
接下来,如何格式化输出呢?例如,每个日期占4个字符的宽度,用空格分隔。可以使用printf来格式化输出,比如用%4s来对齐。但需要注意,每个星期从星期日开始,还是星期一?根据用户的要求,用户输入的星期用数字表示,0是星期日,所以排列应该是日、一、二、三、四、五、六。
所以,星期标题应该是“日 一 二 三 四 五 六”,每个占3或4个字符宽度。例如,每个数字用%3d或%4d,或者字符串对齐。
对于每个月的第一天是星期几,比如如果第一天是星期三(3),那么前面需要空出3天的位置。例如,星期三对应的位置是第三列(假设星期日是0列),所以在打印日期前需要打印3个空位,每个占4个空格?或者具体要看格式怎么安排。
例如,假设每个日期占4个字符宽度,那么第一行可能需要打印起始空格,例如,如果第一天是星期三(即第三列,对应索引3,因为星期日是0),那么前面有0(日)、1(一)、2(二)三个空格?或者可能需要重新计算。
假设月份的第一天是星期几的数字是s(0表示星期日,1星期一,等等),那么需要在开始打印s*4个空格?或者需要根据位置来安排。例如,对于每一天,确定它在星期中的位置,然后换行。
例如,假设输出如下:
January 2021
日 一 二 三 四 五 六
1 2
3 4 5 6 7 8 9
...
如果第一天是星期三(s=3),那么前三个位置(日、一、二)是空的,然后从星期三开始打印1号。每个日期占3个字符宽度,右对齐,例如用%3d。
这样,每个星期有7天,所以每打印一个日期后,换行到下一周。具体来说,当打印到星期六时,换行。
所以,对于每个月份,首先打印月份名称和年份,然后打印星期标题,然后处理日期。
步骤总结:
1. 输入年份和第一天的星期几。
2. 遍历12个月,对每个月:
a. 确定该月的名称和天数。
b. 确定该月第一天是星期几(初始是输入的,后续根据前一个月的天数计算)。
c. 打印月名和年份。
d. 打印星期标题。
e. 打印该月的日期,根据起始星期调整前面的空格,然后每7天换行。
3. 处理闰年的二月天数。
4. 计算每个月的起始星期。
现在,具体到代码实现:
首先,如何存储月份名称和天数?可以用两个数组,一个String数组存储月份名称,另一个int数组存储天数,其中二月先按28天处理,然后根据闰年判断调整。
例如:
String[] monthNames = {"January", "February", ..., "December"};
int[] daysInMonth = {31,28,31,30,...};
判断闰年:
public static boolean isLeapYear(int year) {
return (year %4 ==0 && year%100 !=0) || year%400 ==0;
}
然后在处理二月时,如果是闰年,天数设为29。
接下来,计算每个月的起始星期:
初始时,currentDayOfWeek是用户输入的值(比如,2021年1月1日是5)。
处理完1月后,currentDayOfWeek = (currentDayOfWeek + daysInMonth[0]) %7;
这样,下一个月的起始星期就是这个结果。
然后循环处理每个月。
对于每个月份,打印月名和年份:
比如,January 2021。
然后打印星期标题:日 一 二 三 四 五 六,每个占3个字符宽度,用空格分隔。
然后,打印该月每一天的位置。例如,假设该月第一天是星期三(s=3),那么前面需要打印3个空格(每个占3字符,所以可能需要打印3个空位,然后开始打印1号)。
具体实现时,可以先打印起始的空格,数量为起始星期几的值乘以每个日期占的宽度。或者更准确地说,每个日期占3个字符,所以每个位置之间有一个空格?或者每个日期用%3d格式,这样每个数字占3位,包括前面的空格。
例如,第一天是星期三(s=3),即该月的第一天是星期三,对应的是星期中的第4位(0是周日,1周一,2周二,3周三)。那么,在打印第一行时,前面的三个位置(日、一、二)需要留空,然后从第三个位置开始打印1号。
这里可能需要确定如何根据起始星期来放置日期。例如,假设起始星期是s,那么需要打印s个空位,每个占3个字符,接着开始打印1号,2号等,每打印7个后换行。
例如,假设起始星期是3(星期三),那么前面有3个空位,每个占3个字符,即总共有3个空位,然后打印1号。
但是,如何实现这个空位?可以用循环打印空格,或者更简单地,在开始打印日期之前,先打印s个空格,然后开始日期。但日期的排列需要考虑每周的位置。
或者,另一种方法是计算每一天在星期中的位置。例如,初始的偏移量是s,然后对于日期d,当前的位置是 (s + d -1) %7。当这个值等于6时,换行。
或者,可以用一个计数器,从0开始,每打印一个日期,计数器加1,当计数器%7 ==0时换行,但需要考虑起始的空格。
更具体地说:
对于该月的第一天是星期s(例如,3),则:
- 打印s个空位(每个占3个空格,比如“ ”)。
- 然后从1开始打印日期,每打印一个日期,计数器增加,当(计数器 + s)%7 ==0时换行。
或者,另一种方式:
初始化一个变量 dayOfWeek = s;
然后,对于该月的每一天:
打印日期,然后dayOfWeek = (dayOfWeek +1) %7;
当dayOfWeek ==0时,换行。
但这样的话,如果某个月的最后一天是星期六(6),则不需要在最后换行,或者是否需要?
例如,假设某个月有30天,起始s=3:
日期1在星期三(3),打印后dayOfWeek变成4(周四)
日期2:4 ->5
日期3:5->6
日期4:6->0,此时换行
依此类推。当打印完所有日期后,如果最后一天的dayOfWeek不是6,则可能需要补全后面的空格,或者直接结束。
不过,这样处理的话,可能需要每次打印日期后检查是否需要换行。比如,每次打印日期后,如果dayOfWeek ==0,则换行。
但这样在换行时,可能会有多余的空行,比如当最后一天刚好是周六,那么最后换行一次。
可能需要测试这种情况。
例如,假设某个月有31天,起始星期是5:
1月1日是星期五(5),那么该月的星期分布如下:
日期1:5 → 打印后dayOfWeek=6
日期2:6 → 打印后dayOfWeek=0 →换行
日期3:1 →...
依此类推,到31号时,dayOfWeek是(5+31-1)=35 →35%7=0 →所以最后一天是星期日,换行。
那在这种情况下,最后换行是正确的。
所以,代码的大致逻辑是:
for 每个月份:
打印月份标题
打印星期标题
dayOfWeek = 该月起始的星期几
// 打印前面的空格
for (int i=0; i < dayOfWeek; i++){
System.out.print(" "); // 每个星期几占3个字符
}
for (int day=1; day <= daysInMonth; day++){
System.out.printf("%3d", day); // 每个日期占3位,右对齐
dayOfWeek++;
if (dayOfWeek %7 ==0){
System.out.println(); // 换行
}
}
// 处理最后可能未换行的情况
if (dayOfWeek %7 !=0){
System.out.println();
}
但这里有问题,比如,当dayOfWeek初始是3(星期三),打印前三个空格,然后开始打印日期。每次打印日期后,dayOfWeek自增,当达到7的倍数时换行。例如,日期1打印后,dayOfWeek是4,不换行;直到日期4,dayOfWeek变成7(即0),换行。这可能对吗?
或者,可能需要使用模7:
dayOfWeek = (dayOfWeek +1) %7;
例如,初始dayOfWeek=3:
日期1打印后,dayOfWeek变为4 →4%7=4 →不换行
日期2 →5 →不换行
日期3 →6 →不换行
日期4 →0 →换行
是的,这样正确。
所以,正确的处理方式应该是:
初始时,打印空格的数量等于起始的星期几的值。例如,起始是星期三(3),则打印3个空格,每个占3字符。然后,每打印一个日期,将dayOfWeek加1,模7。如果dayOfWeek等于0(即星期日),则换行。
所以,代码应该如下:
int dayOfWeek = startingDay; // 该月第一天的星期几
// 打印前面的空格
for (int i=0; i < dayOfWeek; i++){
System.out.print(" ");
}
for (int day=1; day <= daysInMonth; day++){
System.out.printf("%3d", day);
dayOfWeek = (dayOfWeek +1) %7;
if (dayOfWeek ==0){
System.out.println();
}
}
// 如果最后一天打印完后,dayOfWeek不是0,则需要换行
if (dayOfWeek !=0){
System.out.println();
}
这样处理的话,例如:
假设该月有31天,起始是星期三(3):
打印3个空格,然后开始打印1。每打印一个日期,更新dayOfWeek:
day=1 →dayOfWeek=4 →不换行
day=2 →5 →不换行
day=3 →6 →不换行
day=4 →0 →换行
day=5 →1 →不换行
...
到day=31,此时dayOfWeek的初始是3,每打印一个日期,dayOfWeek会增加:
总共有31天,初始空格3,然后31个日期。
每次循环,day从1到31:
计算每次dayOfWeek的变化:
初始dayOfWeek=3 →打印空格3次
然后:
day=1 →dayOfWeek=4 →不换行
day=2 →5 →不换行
day=3 →6 →不换行
day=4 →0 →换行
day=5 →1 →不换行
...
到day=31时,计算:
初始dayOfWeek=3 →循环31次,每次增加1:
总增加次数31次 →dayOfWeek变化为3 +31 =34 →34%7=34-7*4=34-28=6 →最后dayOfWeek=6 →不等于0,所以最后需要换行。
所以,最后一行输出后,是否换行?
例如,假设最后一行只有6号,那么循环结束后的dayOfWeek是6,此时需要换行吗?
假设当循环结束后,无论是否填满一周,都需要换行。例如,在打印完所有日期之后,如果最后的dayOfWeek不是0,说明最后一周没填满,需要换行。
例如,在例子中的31天,最后dayOfWeek是6,循环结束后会执行:
if (dayOfWeek !=0) →是的,所以换行。
这样,整个月份的日历打印完毕,最后有一个换行。
这样看起来是对的。
现在,把这些步骤转换成Java代码。
首先,处理输入:
Scanner输入年份和起始星期几。
然后,遍历12个月:
for (int month=0; month <12; month++){
// 处理每个月的逻辑
}
对于每个月,计算该月的天数:
如果是二月,判断是否闰年。
然后,计算该月的起始星期,初始是用户输入的值,之后每个月更新为(currentDayOfWeek + daysInPreviousMonth)%7.
所以,需要维护一个变量currentDayOfWeek,初始为用户输入的值。
例如:
int currentDayOfWeek = startDay; // 用户输入的第一天星期几
for (int month=0; month <12; month++){
// 获取月份的天数
int days = daysInMonth[month];
if (month ==1 && isLeapYear(year)){ // 二月且闰年
days=29;
}
// 打印月份名称和年份
System.out.println("\n " + monthNames[month] + " " + year);
System.out.println("日 一 二 三 四 五 六");
// 打印该月的日期
int dayOfWeek = currentDayOfWeek;
// 打印前面的空格
for (int i=0; i < dayOfWeek; i++){
System.out.print(" ");
}
for (int day=1; day <= days; day++){
System.out.printf("%3d", day);
dayOfWeek = (dayOfWeek +1) %7;
if (dayOfWeek ==0){
System.out.println();
}
}
if (dayOfWeek !=0){
System.out.println();
}
// 更新currentDayOfWeek为下个月的起始星期
currentDayOfWeek = (currentDayOfWeek + days) %7;
}
这样,currentDayOfWeek在每个循环后更新为下个月的起始星期。
例如,假设1月有31天,起始星期是5:
currentDayOfWeek初始为5 →处理完1月后,currentDayOfWeek = (5+31)%7 =36%7=1 →所以2月的起始星期是1。
这样,每个月的起始星期正确。
现在,测试一下这个逻辑是否正确。
假设输入年份2021,起始星期5(即1月1日是星期五)。
1月的天数是31天,currentDayOfWeek初始是5.
处理完1月后,currentDayOfWeek变为(5+31) mod7=36 mod7=1 →正确。所以2月的第一天是星期一。
二月如果是非闰年,天数是28天。处理完二月后,currentDayOfWeek变为(1+28) mod7=29 mod7=1 →所以3月第一天是星期一。
这样,每个月的起始星期正确。
现在,关于月份名称的数组,应该用中文还是英文?用户的问题描述中提到程序要打印每月的月历,可能希望用英文月份名称,例如“January 2021”。但是用户可能需要中文?需要看用户的具体要求。但用户给的例子是“Java编写程序”,通常输出月份可能用英文。或者需要确认。
在用户的问题描述中,示例是“该年第一天是星期几(如5)”,而输出可能是中文或英文。但用户的问题没有明确要求,所以假设使用英文月份名称。
所以,月份名称数组可以是:
String[] monthNames = {"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"};
这样,打印的标题是“January 2021”等。
接下来,星期标题是“日 一 二 三 四 五 六”,每个字之间用空格分隔,每个占3字符宽度。例如,使用:
System.out.println("日 一 二 三 四 五 六");
这样打印出来的是日、一、二、三、四、五、六,每个之间有一个空格,共占3字符宽度?
例如,"日"后面有一个空格,占两个字符,但可能不够。例如,“日”字后面有两个空格,这样每个标题项占3字符,例如:
日 一 二 三 四 五 六
每个标题项之间有一个空格,所以总共有7*3 -1 =20字符?或者可能使用更紧凑的方式。或者,可能用三个字符的位置,比如每个用%3s,这样每个标题项右对齐,但中文可能占两个字符宽度,所以可能会有问题。但为了简单,直接输出字符串“日 一 二 三 四 五 六”即可。
现在,编写完整的代码:
import java.util.Scanner;
public class PrintCalendar {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.print("请输入年份:");
int year = scanner.nextInt();
System.out.print("请输入该年第一天是星期几(0-6,0代表星期日):");
int startDay = scanner.nextInt();
String[] monthNames = {"January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December"};
int[] daysInMonth = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
if (isLeapYear(year)) {
daysInMonth[1] = 29;
}
int currentDayOfWeek = startDay;
for (int month = 0; month < 12; month++) {
int days = daysInMonth[month];
System.out.printf("\n %s %d\n", monthNames[month], year);
System.out.println("日 一 二 三 四 五 六");
int dayOfWeek = currentDayOfWeek;
// 打印前面的空格
for (int i = 0; i < dayOfWeek; i++) {
System.out.print(" ");
}
for (int day = 1; day <= days; day++) {
System.out.printf("%3d", day);
dayOfWeek = (dayOfWeek + 1) % 7;
if (dayOfWeek == 0) {
System.out.println();
}
}
if (dayOfWeek != 0) {
System.out.println();
}
// 更新下个月的起始星期
currentDayOfWeek = (currentDayOfWeek + days) % 7;
}
}
private static boolean isLeapYear(int year) {
return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
}
}
现在,测试这个代码是否正确。
例如,输入year=2021,startDay=5(2021年1月1日是星期五)。
程序会处理每个月的currentDayOfWeek:
1月:currentDayOfWeek初始是5,处理完后,currentDayOfWeek = (5+31)=36 mod7=1 →2月起始是星期一。
二月2021年不是闰年,所以28天。处理完后,currentDayOfWeek = (1+28)=29 mod7=1 →三月起始是星期一。
三月有31天,currentDayOfWeek更新为1+31=32 mod7=4 →四月起始是星期四。
依此类推。
现在,查看1月的输出是否正确:
January 2021的起始星期是5(星期五),所以1月1日是星期五,对应的是:
日 一 二 三 四 五 六
1 2
3 4 5 6 7 8 9
...
因为起始空格是5,即打印5个空格,每个占3字符。例如,星期五是5,所以起始空格循环i<5,即打印5次“ ”,即15个空格?
但这样会导致第一行的日期1出现在星期六的位置?
或者可能哪里弄反了?
哦,这里可能有个错误。因为currentDayOfWeek初始是startDay,即用户输入的该年第一天的星期几。例如,输入5,代表星期五,即该年的1月1日是星期五。那么在1月的处理中,dayOfWeek初始是5,所以前面会打印5个空格,每个占3字符。这样,日期1会被打印在第5个位置,即星期六的位置?这显然不对。
哦,这里发现了问题:星期排列是日、一、二、三、四、五、六。每个位置对应0到6。例如,星期日是0,星期一是1,依此类推,星期六是6。
所以,如果某个月的1月1日是星期五(对应数字5),那么该天应该位于星期五的位置,即第5个位置(从0开始计算)。所以,当打印前面的空格时,应该打印5个空格,每个占3字符,这样日期1会被放在星期五的位置。
例如,假设起始星期是5,那么前面有5个空格,日期1在星期五的位置,即:
日 一 二 三 四 五 六
1 2
3 ...
这样,对吗?
比如,假设起始是星期五(5),那么:
空格数目是5 →每个空格占3字符,所以前面的空格总共有5*3=15空格,那么日期1出现在星期五的位置。例如:
日 一 二 三 四 五 六
1 2
这样,1号在星期五的位置,对吗?
是的。因为星期排列是日(0)、一(1)、二(2)、三(3)、四(4)、五(5)、六(6)。所以,如果某天是星期五,其对应的位置是5,所以前面有5个空格,每个占3字符,那么日期1会被打印在星期五的位置。
这样是正确的。
那在代码中,当用户输入startDay为5时,1月1日是星期五,打印正确的位置。
但需要验证代码的输出是否符合预期。
例如,假设用户输入2021年,startDay=5:
1月的日历应该如下:
January 2021
日 一 二 三 四 五 六
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31
因为1月1日是星期五(5),所以前面有5个空格,然后打印1,接着2,换行(因为dayOfWeek在打印2后变为(5+2)%7=7%7=0 →换行。然后继续打印3到9,依此类推。
但根据代码的逻辑,初始dayOfWeek是5,所以前面打印5个空格。然后打印1,dayOfWeek变为6 →不换行。打印2,dayOfWeek变为0 →换行。所以第一行是:
1 2 →对吗?
是的,因为5个空格,每个占3字符,所以是15个空格?然后1和2分别占3字符。例如:
空格数:5个,每个3字符 →15空格,接着1(占3字符,右对齐,即“ 1”),然后2(“ 2”),然后换行。
所以第一行显示为“ 1 2”(假设空格用点表示:...............1 2),然后换行。
接着第二天是3,dayOfWeek=1,打印到星期六(6)时换行。
因此,1月的第一周显示为:
1 2
3 4 5 6 7 8 9
这可能与实际2021年1月的日历相符吗?
实际2021年1月1日是星期五,所以该月的日历:
1日是星期五,位于第5个位置(日、一、二、三、四、五的位置是5)。
所以,第一行的星期五位置是1,星期六是2。那么第一行应该只有1和2,对吗?
是的。所以代码的输出是正确的。
综上,代码的逻辑正确。
可能存在的另一个问题是,在打印月份标题时,是否居中?例如,“January 2021”居中显示。当前代码使用System.out.printf("\n %s %d\n", monthNames[month], year);,添加了6个空格,可能不够,导致标题不居中。例如,假设每行的宽度是21字符(7天,每个占3字符),那么月份标题可能需要居中。但这个问题可能不影响功能,用户可能不严格要求。或者可以计算标题的长度,然后添加合适的前导空格,使其居中。例如,月份名称加年份的总长度是例如“January 2021”共11字符,每行显示21字符的话,需要左边添加5个空格,使得总空格为5,使得标题居中。例如:
int totalWidth = 21; // 7*3 characters
String title = monthNames[month] + " " + year;
int leadingSpaces = (totalWidth - title.length())/2;
然后打印leadingSpaces个空格。但这样可能更复杂,但用户问题中没有特别要求,所以可能可以忽略,使用固定的空格。
或者,用户可能希望像例子中的那样,使用“ January 2021”这样的格式,即前面有6个空格,让标题左对齐。这可能已经足够。
综上,代码应该可以正确输出每个月的日历。</think>以下是分步实现的Java程序,用于打印指定年份的月历:
```java
import java.util.Scanner;
public class CalendarPrinter {
public static void main(String[] args) {
// 1. 获取用户输入
Scanner scanner = new Scanner(System.in);
System.out.print("请输入年份:");
int year = scanner.nextInt();
System.out.print("请输入该年第一天的星期(0-6):");
int startDay = scanner.nextInt();
// 2. 初始化月份数据
String[] months = {"January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December"};
int[] days = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
// 3. 处理闰年
if (isLeapYear(year)) {
days[1] = 29;
}
// 4. 遍历12个月
int currentDay = startDay;
for (int i = 0; i < 12; i++) {
// 打印月份标题
System.out.printf("\n %s %d\n", months[i], year);
System.out.println("日 一 二 三 四 五 六");
// 打印首行空格
for (int j = 0; j < currentDay; j++) {
System.out.print(" ");
}
// 打印日期
for (int day = 1; day <= days[i]; day++) {
System.out.printf("%3d", day);
// 换行判断
if ((currentDay + day) % 7 == 0 || day == days[i]) {
System.out.println();
}
}
// 计算下个月起始星期
currentDay = (currentDay + days[i]) % 7;
}
}
// 闰年判断方法
private static boolean isLeapYear(int year) {
return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
}
}
```
### 实现步骤说明
1. **输入处理**
使用`Scanner`获取用户输入的年份和该年第一天的星期数,其中:
- `0` 表示星期日
- `1` 表示星期一
- ...
- `6` 表示星期六
2. **月份数据初始化**
- `months`数组存储12个月的英文名称
- `days`数组存储每个月的天数(二月默认28天)
3. **闰年判断**
根据闰年规则调整二月的天数:
$$ \text{闰年} = (\text{能被4整除且不能被100整除}) \lor (\text{能被400整除}) $$
4. **循环处理每个月**
- **打印标题**:月份名称和年份
- **打印星期头**:固定显示"日 一 二 三 四 五 六"
- **对齐首日**:通过空格调整每月第一天的位置
- **日期打印**:
- 使用`%3d`格式化保证数字对齐
- 每7天换行
- **计算下月起始**:通过取模运算确定下个月第一天的星期
### 示例输出(2021年1月)
当输入`2021`和`5`(2021年1月1日是星期五)时:
```
January 2021
日 一 二 三 四 五 六
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31
```
### 关键逻辑
1. **日期对齐**
通过`currentDay`变量跟踪每月首日的星期位置,确保日期正确对齐。
2. **换行机制**
当累计日期数与星期数的和为7的倍数时换行,保证每周显示7天。
3. **闰年处理**
独立的闰年判断方法使代码更清晰易维护。