java基础-万年历

前言

万年历作为日常生活中不可或缺的时间管理工具,广泛应用于电脑系统、移动设备及各类软件中。本文将通过Java编程实现一个功能完整的命令行万年历程序,它能根据用户输入的年份(1900年及之后)和月份,生成对应月份的日历视图。通过这个项目,您将深入理解日期计算的核心逻辑与程序设计的模块化思想。

核心设计思路

1. 月份天数计算:日历的基石

/**
 * 计算指定月份的天数
 * @param month 目标月份 (1-12)
 * @param year 目标年份 (用于判断二月天数)
 * @return 该月的天数,输入错误返回-1
 */
public static int getMonthDays(int month, int year) {
    // 31天的月份:1月(January), 3月(March), 5月(May), 7月(July), 
    // 8月(August), 10月(October), 12月(December)
    if (month == 1 || month == 3 || month == 5 || month == 7 || 
        month == 8 || month == 10 || month == 12) {
        return 31;
    } 
    // 30天的月份:4月(April), 6月(June), 9月(September), 11月(November)
    else if (month == 4 || month == 6 || month == 9 || month == 11) {
        return 30;
    } 
    // 特殊处理二月:平年28天,闰年29天
    else if (month == 2) {
        return isLeapYear(year) ? 29 : 28;
    } 
    // 无效的月份输入
    else {
        return -1;
    }
}

关键点解析

  • 大月(31天)和小月(30天)的规则基于公历设置

  • 二月天数需要年份参数进行平闰年判断

  • 月份天数决定了日历视图的行数布局(4-6行)

  • 返回-1作为错误代码便于上层处理异常输入

2. 平闰年判断:时间计算的精髓

/**
 * 判断指定年份是否为闰年
 * @param year 待判断的年份
 * @return 闰年返回true,平年返回false
 */
public static boolean isLeapYear(int year) {
    // 闰年规则:
    // 1. 能被4整除但不能被100整除 或
    // 2. 能被400整除
    return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
}

历史背景:现行公历闰年规则由1582年教皇格里高利十三世引入,解决了儒略历每128年误差1天的问题。

3. 基准日计算:日历定位的核心

// 计算从1900年1月1日到目标年月前一个月的总天数
int totalDays = 0;

// 计算整年天数(1900年到目标年份的前一年)
for (int i = 1900; i < year; i++) {
    totalDays += isLeapYear(i) ? 366 : 365;
}

// 计算目标年份1月到目标月份前一个月的天数
for (int i = 1; i < month; i++) {
    totalDays += getMonthDays(i, year);
}

// 计算目标月份1日是星期几(0=星期一,1=星期二...6=星期日)
int firstDayOfWeek = (totalDays + 1) % 7; // +1因为1900.1.1是星期一

数学原理

  • 选择1900年1月1日(星期一)作为基准点

  • 总天数模7的结果直接对应星期位置

  • 这种计算方法称为"蔡勒公式"的简化版

4. 日历排版:视觉呈现的艺术

// 打印星期标题
System.out.println("一\t二\t三\t四\t五\t六\t日");

// 根据首日位置打印前置空格
for (int i = 0; i < firstDayOfWeek; i++) {
    System.out.print("\t");
}

// 打印日期并控制换行
int currentDayOfWeek = firstDayOfWeek;
for (int day = 1; day <= monthDays; day++) {
    System.out.print(day + "\t");
    currentDayOfWeek = (currentDayOfWeek + 1) % 7;
    
    // 每打印7个日期换行
    if (currentDayOfWeek == 0) {
        System.out.println();
    }
}

排版技巧

  • 使用制表符\t保持列对齐

  • 动态计算换行时机,适应不同月份

  • 当前日期可添加特殊标记(如*号)

完整代码实现

import java.util.Scanner;

public class PerpetualCalendar {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        
        // 用户输入年份和月份
        System.out.print("请输入年份(≥1900):");
        int year = sc.nextInt();
        System.out.print("请输入月份(1-12):");
        int month = sc.nextInt();
        
        // 1. 验证输入有效性
        if (year < 1900 || month < 1 || month > 12) {
            System.out.println("输入无效!年份应≥1900,月份应为1-12");
            return;
        }
        
        // 2. 计算总天数基准
        int totalDays = calculateTotalDays(year, month);
        
        // 3. 获取当月信息
        int monthDays = getMonthDays(month, year);
        int firstDayOfWeek = totalDays % 7; // 0=星期一,6=星期日
        
        // 4. 打印日历
        printCalendar(year, month, monthDays, firstDayOfWeek);
        
        sc.close();
    }
    
    // 计算从1900.1.1到目标年月前一个月的总天数
    private static int calculateTotalDays(int year, int month) {
        int totalDays = 0;
        
        // 整年天数累加
        for (int i = 1900; i < year; i++) {
            totalDays += isLeapYear(i) ? 366 : 365;
        }
        
        // 目标年份的月份天数累加
        for (int i = 1; i < month; i++) {
            totalDays += getMonthDays(i, year);
        }
        
        return totalDays;
    }
    
    // 平闰年判断(省略)
    // 月份天数计算(省略)
    
    // 打印日历视图
    private static void printCalendar(int year, int month, int days, int firstDayOfWeek) {
        // 月份名称映射
        String[] monthNames = {"一月", "二月", "三月", "四月", "五月", "六月",
                              "七月", "八月", "九月", "十月", "十一月", "十二月"};
        
        // 打印标题
        System.out.println("\n\t\t" + year + "年 " + monthNames[month-1]);
        System.out.println("==========================================");
        System.out.println("一\t二\t三\t四\t五\t六\t日");
        
        // 打印前置空格
        for (int i = 0; i < firstDayOfWeek; i++) {
            System.out.print("\t");
        }
        
        // 打印日期
        int currentPosition = firstDayOfWeek;
        for (int day = 1; day <= days; day++) {
            // 当前日期特殊标记(可选)
            // if (day == currentDay) System.out.print("*");
            
            System.out.print(day + "\t");
            currentPosition = (currentPosition + 1) % 7;
            
            // 每周日换行
            if (currentPosition == 0) {
                System.out.println();
            }
        }
        
        // 最后一行补全
        if (currentPosition != 0) {
            System.out.println();
        }
        System.out.println("==========================================");
    }
}

项目思考与优化方向

设计思路回顾

  1. 基础数据准备(月份天数)→ 日历内容的基础

  2. 时间基准计算(总天数)→ 日历定位的核心

  3. 星期定位 → 排版的关键

  4. 视图构建 → 用户体验的呈现

  5. 输出格式化 → 可读性的保障

优化方向

  1. 增加节假日标记:在特定日期添加特殊符号

  2. 支持周起始定制:让用户选择周一或周日作为每周第一天

  3. 多语言支持:实现中英文切换

  4. 图形界面:使用Swing或JavaFX创建可视化界面

  5. 日期范围扩展:支持格里高利历法实施前(1582年以前)的日期

算法改进

// 使用Zeller公式直接计算星期几
public static int zeller(int year, int month, int day) {
    if (month < 3) {
        month += 12;
        year -= 1;
    }
    int k = year % 100;
    int j = year / 100;
    int h = (day + 13*(month+1)/5 + k + k/4 + j/4 + 5*j) % 7;
    return (h + 5) % 7; // 0=星期六, 1=星期日, ..., 6=星期五
}

总结

通过实现这个万年历程序,我们不仅掌握了:

  1. 日期计算的核心算法(平闰年判断、月份天数)

  2. 模运算在日历定位中的巧妙应用

  3. 控制台格式化的输出技巧

  4. 程序模块化设计思想

更重要的是理解了时间表示在计算机系统中的重要性。万年历作为基础工具,其实现中蕴含的日期处理逻辑、循环控制思想和排版算法,是后续开发复杂日期相关应用(如日程管理系统、节假日计算等)的重要基础。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值