技术宅的结界

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

QQ登录

只需一步,快速开始

搜索
热搜: 下载 VB C 实现 编写
查看: 515|回复: 4
收起左侧

【C】几月几号星期几

[复制链接]

85

主题

263

帖子

3427

积分

用户组: 管理员

No. 418

UID
418
精华
13
威望
52 点
宅币
1969 个
贡献
1026 次
宅之契约
0 份
在线时间
252 小时
注册时间
2014-8-9
发表于 2017-4-14 18:39:00 | 显示全部楼层 |阅读模式

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

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

x
在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月。
用这个公式我们马上可以写出程序:
[C] 纯文本查看 复制代码
// Zeller's congruence
// m: month
// q:
// a:
// return 0: Saturday, 1: Sunday, 2: Monday ... 6: Friday.
//
int ZellerWeek(int m, int q, int a)
{
    int K = 0, J = 0;
    if (1 == m || 2 == m)
    {
        m += 12;
        --a;
    }
    K = a % 100;
    J = a / 100;
    return (q + (13 * (m + 1)) / 5 + K + (K / 4) + (J / 4) - 2 * J) % 7;
}

我们用以下代码测试之:
[C] 纯文本查看 复制代码
#include <stdio.h>

const char * szweek[7] = { "Saturday",
                           "Sunday",
                           "Monday",
                           "Tuesday",
                           "Wednesday",
                           "Thursday",
                           "Friday"
                         };

int ZellerWeek(int m, int q, int a)
{
    int K = 0, J = 0;
    if (1 == m || 2 == m)
    {
        m += 12;
        --a;
    }
    K = a % 100;
    J = a / 100;
    return (q + (13 * (m + 1)) / 5 + K + (K / 4) + (J / 4) - 2 * J) % 7;
}

int main()
{
    int m, q, a;
    printf("Input date format in MM-DD-YYYY: ");
    scanf("%d-%d-%d", &m, &q, &a);
    printf(" = %s\n", szweek[a = ZellerWeek(m, q, a)]);
    return 0;
}

3.png
测试结果表明输入的合法公历年份均能被转换为星期。
关于蔡勒公式原理的相关说明,维基百科上有较为详细的解释:Zeller's congruence
接下来我们通过蔡勒公式打印出给定年份月份的月历:
[C] 纯文本查看 复制代码
#include <stdio.h>

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

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

int main()
{
    int i;
    int f, e;
    int a, m;
    
    printf("MM-YYYY :"); // 输入格式: 月份-年
    scanf("%d-%d", &m, &a);
    f = ZellerWeek(m, 1, a); // 利用蔡勒公式得出该月1号是星期几。
    switch (m) // 判断输入的月份
    {
    case 1: case  3: case  5: case 7:
    case 8: case 10: case 12: e = 31; // 有口诀如下: 一三五七八十腊,三十一天总不差。
        break;
    case 2: // 二月就要判断是否是闰年闰月。
        if (0 == a % 4 || 0 == a % 400 || (0 == a % 3200 && 0 == a % 172800))
            e = 29; // 闰月29天。
        else
            e = 28; // 平月28天。
        break;
    default: // 剩下的月份除了小月就是非法输入,一并判断之。
        if (m < 1 || m > 12)
        {
            printf("Invalid month.\n");
            return 0;
        }
        e = 30;
    }
    printf("%s\n", szwtitle); // 先打印星期抬头。
    for (i = 0, f = f % 7 - 1; i < f; ++i)
    {
        printf("   "); // 补充周日到本月1号之间的空格。
    }
    for (i = 1; i <= e; ++i) // 从1号打印到月末。
    {
        printf("%2d ", i); 
        if (0 == (i + f) % 7) // 以7的倍数为单位换行打印。
            printf("\n");
    }
    printf("\n"); // 打印最后的换行符。
    return 0;
}

Screen Shot 2017-04-14 at 18.28.15.png
In the beginning I was not the best.
And the world was also not the best.
But I still know that I am who I am.
Because I think that it is good.
I have been working hard.
I have been keeping growth with the world.
And it was so.

25

主题

74

帖子

1005

积分

用户组: 版主

UID
1821
精华
6
威望
57 点
宅币
756 个
贡献
31 次
宅之契约
0 份
在线时间
186 小时
注册时间
2016-7-12
发表于 2017-4-14 22:57:29 | 显示全部楼层
[C] 纯文本查看 复制代码
    case 2: // 二月就要判断是否是闰年闰月。
        if (0 == a % 4 || 0 == a % 400 || 0 == a % 3200 || 0 == a % 172800)
            e = 29; // 闰月29天。
        else
            e = 28; // 平月28天。
        break;

这个有问题

85

主题

263

帖子

3427

积分

用户组: 管理员

No. 418

UID
418
精华
13
威望
52 点
宅币
1969 个
贡献
1026 次
宅之契约
0 份
在线时间
252 小时
注册时间
2014-8-9
 楼主| 发表于 2017-4-14 23:00:39 | 显示全部楼层
Ayala 发表于 2017-4-14 22:57
[mw_shl_code=c,true]    case 2: // 二月就要判断是否是闰年闰月。
        if (0 == a % 4 || 0 == a % 4 ...


感谢提醒,果然有问题,逻辑处理错了。应该是同时满足0 == a % 3200 与 0 == a % 172800。
而且172800是3200的倍数,能被172800整除肯定能被3200整除。
In the beginning I was not the best.
And the world was also not the best.
But I still know that I am who I am.
Because I think that it is good.
I have been working hard.
I have been keeping growth with the world.
And it was so.

25

主题

74

帖子

1005

积分

用户组: 版主

UID
1821
精华
6
威望
57 点
宅币
756 个
贡献
31 次
宅之契约
0 份
在线时间
186 小时
注册时间
2016-7-12
发表于 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)

25

主题

74

帖子

1005

积分

用户组: 版主

UID
1821
精华
6
威望
57 点
宅币
756 个
贡献
31 次
宅之契约
0 份
在线时间
186 小时
注册时间
2016-7-12
发表于 2017-4-14 23:34:32 | 显示全部楼层
本帖最后由 Ayala 于 2017-4-14 23:38 编辑


注意原算法结果可能为负数 这里+77 在公元4000年之前有效
[C] 纯文本查看 复制代码
int ZellerWeek(int m, int d, int y)
{
    int K = 0, J = 0;
    if (1 == m || 2 == m)
    {
        m += 12;
        --y;
    }
    K = y % 100;
    J = y / 100;
    return (d + (13 * (m + 1)) / 5 + K + (K / 4) + (J / 4) - 2 * J+77) % 7;
}


[C] 纯文本查看 复制代码
if (ZellerWeek(2,29,a)==ZellerWeek(3,1,a)
...

本版积分规则

QQ|申请友链|Archiver|手机版|小黑屋|技术宅的结界 ( 滇ICP备16008837号|网站地图

GMT+8, 2018-5-24 04:09 , Processed in 0.100621 second(s), 18 queries , Gzip On, Memcache On.

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

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