使用c++ 写一个2024年的日历

#include <iostream>
#include <iomanip>
#include <vector>
#include <string>
#include <cassert>

class Date {
public:
    Date(int year, int month, int day) : year_(year), month_(month), day_(day) {}

    static long getDaysSince1970(int year, int month) {
        int a = (14 - month) / 12;
        int y = year + 4800 - a;
        int m = month + 12 * a - 3;
        return (153 * m + 2) / 5 + 365 * y + y / 4 - y / 100 + y / 400 - 32045;
    }

private:
    int year_, month_, day_;
};

class Month {
public:
    Month(int year, int month) : year_(year), month_(month) {
        assert(month >= 1 && month <= 12);
        assert(year >= 1970 && year <= 65535);

        long this_month_beg = Date::getDaysSince1970(year, month);
        long next_month_beg = Date::getDaysSince1970(year + (month == 12), month % 12 + 1);

        days_in_month_ = next_month_beg - this_month_beg;
        first_day_of_week_ = (this_month_beg + 1) % 7;
    }

    int getDaysInMonth() const { return days_in_month_; }
    int getFirstDayOfWeek() const { return first_day_of_week_; }
    int getYear() const { return year_; }
    int getMonth() const { return month_; }

private:
    int year_, month_, days_in_month_, first_day_of_week_;
};

class Calendar {
public:
    Calendar(int year) : year_(year) {
        for (int i = 1; i <= 12; ++i) {
            months_.emplace_back(year, i);
        }
    }

    void print() const {
        static const std::vector<std::string> month_names = {
                "一月", "二月", "三月", "四月", "五月", "六月",
                "七月", "八月", "九月", "十月", "十一月", "十二月"
        };
        static const std::vector<std::string> weekdays = {
                "日", "一", "二", "三", "四", "五", "六"
        };

        for (int i = 0; i < 12; i += 3) {
            // 打印月份名称
            for (int j = 0; j < 3; ++j) {
                std::cout << std::setw(20) << std::left << month_names[i + j];
                if (j < 2) std::cout << " ";
            }
            std::cout << "\n";

            // 打印星期
            for (int j = 0; j < 3; ++j) {
                for (const auto& day : weekdays) {
                    std::cout << std::setw(3) << day;
                }
                if (j < 2) std::cout << "  ";
            }
            std::cout << "\n";

            // 打印日期
            std::vector<std::vector<std::string>> month_grids(3, std::vector<std::string>(6, ""));
            for (int j = 0; j < 3; ++j) {
                const Month& month = months_[i + j];
                int day = 1;
                for (int week = 0; week < 6; ++week) {
                    for (int weekday = 0; weekday < 7; ++weekday) {
                        if (week == 0 && weekday < month.getFirstDayOfWeek()) {
                            month_grids[j][week] += "   ";
                        } else if (day <= month.getDaysInMonth()) {
                            month_grids[j][week] += std::to_string(day).length() < 2 ? "  " + std::to_string(day) : " " + std::to_string(day);
                            ++day;
                        } else {
                            month_grids[j][week] += "   ";
                        }
                    }
                }
            }

            for (int week = 0; week < 6; ++week) {
                for (int j = 0; j < 3; ++j) {
                    std::cout << month_grids[j][week];
                    if (j < 2) std::cout << "  ";
                }
                std::cout << "\n";
            }
            std::cout << "\n";
        }
    }

private:
    int year_;
    std::vector<Month> months_;
};

int main() {
    Calendar calendar(2024);
    calendar.print();
    return 0;
}
  • Date::getDaysSince1970 方法是算法的核心,它使用了一个改进的 Zeller's congruence 算法来计算从 1970 年 1 月 1 日到指定日期的天数。这个算法允许我们:
    • 确定每个月的第一天是星期几
    • 计算每个月有多少天
  • 月份表示: Month 类封装了每个月的关键信息:
    • 年份和月份
    • 该月的天数
    • 该月第一天是星期几
  • 日历生成: Calendar 类的 print 方法实现了日历的生成和打印:
    • 按季度(每三个月一组)生成日历
    • 使用二维网格表示每个月的日期
  • 输出格式化: 算法通过以下步骤格式化输出:
    • 打印月份名称
    • 打印星期头
    • 使用嵌套循环填充日期网格
    • 对齐和格式化输出以创建美观的布局
  • 闰年处理: 算法自动处理闰年,因为 getDaysSince1970 方法在计算天数时考虑了闰年。
  • 模块化设计: 通过将功能分散到不同的类中,算法实现了关注点分离,使得代码更易于理解和维护。
  • 使用数学公式精确计算日期,避免硬编码或查表。
  • 将日历看作是由多个月组成的二维网格。
  • 先生成每个月的数据,然后按照预定的布局格式化输出。

上面的代码中最重要的是Zeller's congruence 算法

Zeller's congruence 是由德国数学家 Christian Zeller 在 19 世纪发明的一种计算任何公历日期对应星期几的算法。这个算法非常有用,因为它可以快速计算出任何日期是星期几,而不需要查表或进行复杂的迭代。

算法的基本公式如下:

h = (q + 13(m+1)/5 + K + K/4 + J/4 + 5J) mod 7

其中:

  • h 是星期几(0 = 星期六,1 = 星期日,2 = 星期一,...,6 = 星期五)
  • q 是日期
  • m 是月份(3 = 三月,4 = 四月,...,14 = 二月)
  • K 是年份的后两位数字
  • J 是年份的前两位数字

注意:在这个公式中,一月和二月被当作前一年的 13 月和 14 月。

让我们详细解释一下这个算法的思想和步骤:

  1. 月份调整:
    • 将一月和二月视为上一年的 13 月和 14 月。这是为了处理跨年的情况,特别是在处理闰年时。
  2. 年份分割:
    • 将年份分为世纪(J)和年(K)两部分。例如,1995 年中 J = 19,K = 95。
  3. 数学计算:
    • 13(m+1)/5:这部分与月份有关,用于调整不同月份的起始日偏移。
    • K + K/4:考虑了普通闰年(每 4 年一次)。
    • J/4:考虑了世纪闰年(每 400 年一次,而不是每 100 年)。
    • 5J:这是一个调整因子,用于使公式在不同世纪都能正确工作。
  4. 模运算:
    • 最后对 7 取模,得到 0-6 的结果,对应星期六到星期五。
相关推荐
追Star仙10 分钟前
基于Qt中的QAxObject实现指定表格合并数据进行word表格的合并
开发语言·笔记·qt·word
DaphneOdera1742 分钟前
Git Bash 配置 zsh
开发语言·git·bash
Code侠客行1 小时前
Scala语言的编程范式
开发语言·后端·golang
lozhyf1 小时前
Go语言-学习一
开发语言·学习·golang
一只码代码的章鱼1 小时前
粒子群算法 笔记 数学建模
笔记·算法·数学建模·逻辑回归
小小小小关同学1 小时前
【JVM】垃圾收集器详解
java·jvm·算法
dujunqiu1 小时前
bash: ./xxx: No such file or directory
开发语言·bash
圆圆滚滚小企鹅。1 小时前
刷题笔记 贪心算法-1 贪心算法理论基础
笔记·算法·leetcode·贪心算法
爱偷懒的程序源1 小时前
解决go.mod文件中replace不生效的问题
开发语言·golang
日月星宿~1 小时前
【JVM】调优
java·开发语言·jvm