找回密码
 立即注册→加入我们

QQ登录

只需一步,快速开始

搜索
热搜: 下载 VB C 实现 编写
查看: 11299|回复: 5

【C】几月几号星期几

[复制链接]
发表于 2017-4-14 18:39:00 | 显示全部楼层 |阅读模式

欢迎访问技术宅的结界,请注册或者登录吧。

您需要 登录 才可以下载或查看,没有账号?立即注册→加入我们

×
在Unix系统中有一条有趣的命令:cal,它是用来打印月历的(calendar)如图:
0.png
它还可以用来打印最近几个月的月历,甚至指定特定年份的全年月历。具体用法可以参见 man cal 或者 cal -help 命令执行结果。
那么问题来了,怎样使用程序打印一张月历?我们不妨观察一下月历的规律。
1.png
如上图所示:蓝圈内,2017年4月1号是星期六。2018年4月1号就是星期天。红圈内,2017年1月1号是星期天。2018年1月1号就是星期一。
以此规律类推,2019年1月1日是星期二,2020年1月1日是星期三。但是2016年1月1号是不是星期六呢?答案是否定的。
因为2016年是闰年,所以2016年2月有29天。这样从2016年2月28日后每个月的星期数都要向后推一天。这样导致了2017年1月1日变成星期日。
反过来说,2016年1月1日就是从2017年1月1日的星期日往前推两天。那么2016年1月1日就是星期五,而不是没有涉及闰月的星期六。
按照这个规律我们只要保存任意一个年份的任意一天是星期几,其他年份的日期星期数全部可以由该日期向前或者向后通过特定的迭代公式推得。
且慢,如果真要使用这种办法,假设我保存了2000年1月1日的星期数,我要推算2998年7月25日是星期几,运算量可想而知。
有没有知道“任意一个有效日期是星期几”的办法呢?通过搜索大量资料,这里我引入“蔡勒公式(Zeller's congruence )”
蔡勒公式如下:
Screen Shot 2017-04-14 at 14.13.05.png
图中:
h 是星期数(0=周六、1=周日、2=周一……6=周五)
q 是日。
m 是月份。
K 是当前年份的世纪。(年份 mod 100)
J 是当前年份除以100再向下取整后的值。
还需要注意的是,在蔡勒公式中一月和二月要分别看作上一年的13月和14月。比如:2017年1月等于2016年13月、2017年14月等于2018年2月。
用这个公式我们马上可以写出程序:

  1. // Zeller's congruence
  2. // m: month
  3. // q:
  4. // a:
  5. // return 0: Saturday, 1: Sunday, 2: Monday ... 6: Friday.
  6. //
  7. int ZellerWeek(int m, int q, int a)
  8. {
  9.     int K = 0, J = 0;
  10.     if (1 == m || 2 == m)
  11.     {
  12.         m += 12;
  13.         --a;
  14.     }
  15.     K = a % 100;
  16.     J = a / 100;
  17.     return (q + (13 * (m + 1)) / 5 + K + (K / 4) + (J / 4) - 2 * J) % 7;
  18. }
复制代码

我们用以下代码测试之:

  1. #include <stdio.h>

  2. const char * szweek[7] = { "Saturday",
  3.                            "Sunday",
  4.                            "Monday",
  5.                            "Tuesday",
  6.                            "Wednesday",
  7.                            "Thursday",
  8.                            "Friday"
  9.                          };

  10. int ZellerWeek(int m, int q, int a)
  11. {
  12.     int K = 0, J = 0;
  13.     if (1 == m || 2 == m)
  14.     {
  15.         m += 12;
  16.         --a;
  17.     }
  18.     K = a % 100;
  19.     J = a / 100;
  20.     return (q + (13 * (m + 1)) / 5 + K + (K / 4) + (J / 4) - 2 * J) % 7;
  21. }

  22. int main()
  23. {
  24.     int m, q, a;
  25.     printf("Input date format in MM-DD-YYYY: ");
  26.     scanf("%d-%d-%d", &m, &q, &a);
  27.     printf(" = %s\n", szweek[a = ZellerWeek(m, q, a)]);
  28.     return 0;
  29. }
复制代码

3.png
测试结果表明输入的合法公历年份均能被转换为星期。
关于蔡勒公式原理的相关说明,维基百科上有较为详细的解释:Zeller's congruence
接下来我们通过蔡勒公式打印出给定年份月份的月历:

  1. #include <stdio.h>

  2. const char * szwtitle = "Su Mo Tu We Th Fr Sa";

  3. int ZellerWeek(int m, int q, int a) // 蔡勒公式
  4. {
  5.     int K = 0, J = 0;
  6.     if (1 == m || 2 == m)
  7.     {
  8.         m += 12;
  9.         --a;
  10.     }
  11.     K = a % 100;
  12.     J = a / 100;
  13.     return (q + (13 * (m + 1)) / 5 + K + (K / 4) + (J / 4) - 2 * J) % 7;
  14. }

  15. int main()
  16. {
  17.     int i;
  18.     int f, e;
  19.     int a, m;
  20.    
  21.     printf("MM-YYYY :"); // 输入格式: 月份-年
  22.     scanf("%d-%d", &m, &a);
  23.     f = ZellerWeek(m, 1, a); // 利用蔡勒公式得出该月1号是星期几。
  24.     switch (m) // 判断输入的月份
  25.     {
  26.     case 1: case  3: case  5: case 7:
  27.     case 8: case 10: case 12: e = 31; // 有口诀如下: 一三五七八十腊,三十一天总不差。
  28.         break;
  29.     case 2: // 二月就要判断是否是闰年闰月。
  30.         if (0 == a % 4 || 0 == a % 400 || (0 == a % 3200 && 0 == a % 172800))
  31.             e = 29; // 闰月29天。
  32.         else
  33.             e = 28; // 平月28天。
  34.         break;
  35.     default: // 剩下的月份除了小月就是非法输入,一并判断之。
  36.         if (m < 1 || m > 12)
  37.         {
  38.             printf("Invalid month.\n");
  39.             return 0;
  40.         }
  41.         e = 30;
  42.     }
  43.     printf("%s\n", szwtitle); // 先打印星期抬头。
  44.     for (i = 0, f = f % 7 - 1; i < f; ++i)
  45.     {
  46.         printf("   "); // 补充周日到本月1号之间的空格。
  47.     }
  48.     for (i = 1; i <= e; ++i) // 从1号打印到月末。
  49.     {
  50.         printf("%2d ", i);
  51.         if (0 == (i + f) % 7) // 以7的倍数为单位换行打印。
  52.             printf("\n");
  53.     }
  54.     printf("\n"); // 打印最后的换行符。
  55.     return 0;
  56. }
复制代码

Screen Shot 2017-04-14 at 18.28.15.png
回复

使用道具 举报

发表于 2017-4-14 22:57:29 | 显示全部楼层
  1.     case 2: // 二月就要判断是否是闰年闰月。
  2.         if (0 == a % 4 || 0 == a % 400 || 0 == a % 3200 || 0 == a % 172800)
  3.             e = 29; // 闰月29天。
  4.         else
  5.             e = 28; // 平月28天。
  6.         break;
复制代码

这个有问题
回复 赞! 靠!

使用道具 举报

 楼主| 发表于 2017-4-14 23:00:39 | 显示全部楼层
Ayala 发表于 2017-4-14 22:57
[code]    case 2: // 二月就要判断是否是闰年闰月。
        if (0 == a % 4 || 0 == a % 4 ...


感谢提醒,果然有问题,逻辑处理错了。应该是同时满足0 == a % 3200 与 0 == a % 172800。
而且172800是3200的倍数,能被172800整除肯定能被3200整除。
回复 赞! 靠!

使用道具 举报

发表于 2017-4-14 23:31:36 | 显示全部楼层
cyycoish 发表于 2017-4-14 23:00
感谢提醒,果然有问题,逻辑处理错了。应该是同时满足0 == a % 3200 与 0 == a % 172800。
而且172800是3 ...

不仅如此
闰年历法在4000年之前有效 满足0 == a %400 ||  (0 != a % 100 && 0 == a%4)
回复 赞! 靠!

使用道具 举报

发表于 2017-4-14 23:34:32 | 显示全部楼层
本帖最后由 Ayala 于 2017-4-14 23:38 编辑


注意原算法结果可能为负数 这里+77 在公元4000年之前有效
  1. int ZellerWeek(int m, int d, int y)
  2. {
  3.     int K = 0, J = 0;
  4.     if (1 == m || 2 == m)
  5.     {
  6.         m += 12;
  7.         --y;
  8.     }
  9.     K = y % 100;
  10.     J = y / 100;
  11.     return (d + (13 * (m + 1)) / 5 + K + (K / 4) + (J / 4) - 2 * J+77) % 7;
  12. }
复制代码

  1. if (ZellerWeek(2,29,a)==ZellerWeek(3,1,a)
  2. ...
复制代码

回复 赞! 靠!

使用道具 举报

发表于 2020-5-23 14:52:27 | 显示全部楼层
谢谢分析师潇洒潇洒潇洒造型
回复 赞! 靠!

使用道具 举报

本版积分规则

QQ|Archiver|小黑屋|技术宅的结界 ( 滇ICP备16008837号 )|网站地图

GMT+8, 2024-11-21 17:49 , Processed in 0.038420 second(s), 28 queries , Gzip On.

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表