C语言根据日期判断星期几(使用基姆拉尔森计算公式)

算法如下: 
基姆拉尔森计算公式
W= (d+2*m+3*(m+1)/5+y+y/4-y/100+y/400) mod 7

在公式中d表示日期中的日数,m表示月份数,y表示年数。

注意:在公式中有个与其他公式不同的地方:

把一月和二月看成是上一年的十三月和十四月,例:如果是2004-1-10则换算成:2003-13-10来代入公式计算。
以公元元年为参考,公元元年1月1日为星期一

程序如下:

/*利用基姆拉尔森计算日期公式  w=(d+2*m+3*(m+1)/5+y+y/4-y/100+y/400)*/

#include <stdio.h>

const char * getWeekdayByYearday(int iY, int iM, int iD)
{
int iWeekDay = -;
if ( == iM || == iM)
{
iM += ;
iY--;
}
iWeekDay = (iD + + * iM + * (iM + ) / + iY + iY / - iY / + iY / ) % ;
switch(iWeekDay)
{
case : return "Sunday"; break;
case : return "Monday"; break;
case : return "Tuesday"; break;
case : return "Wednesday"; break;
case : return "Thursday"; break;
case : return "Friday"; break;
case : return "Saturday"; break;
default : return NULL; break;
} return NULL;
} int main()
{
int year,month,day;
char ch='';
while(ch != '\033')
{
printf("\n请输入日期:\n格式为:1900,1,1\n");
scanf("%d,%d,%d",&year,&month,&day);
const char * p = getWeekdayByYearday(year, month, day);
printf("WeekDay : %s\n", p);
ch = getchar();
printf("\n");
}
}

运行效果:

$ ./getweekdaybyday 

请输入日期:
格式为:,,
,,
WeekDay : Tuesday 请输入日期:
格式为:,,
,,
WeekDay : Wednesday 请输入日期:
格式为:,,

编者注:用来算现在真实日期的星期是没有问题的。原理是根据已知公元1年1月1日的星期数来推算。如果在你的题目中约定了某天是星期几,你要注意那天的星期是否跟真实的星期相同,如果不同,需要考虑相差几天!

如果大家觉得不够过瘾,可以看看以下该公式的推导过程,让大家对历法有个更深刻的认识

下面我们完全按自己的思路由简单到复杂一步步进行推导……

推导之前,先作两项规定: 
①用 y, m, d, w 分别表示 年 月 日 星期(w=0-6 代表星期日-星期六 
②我们从 公元0年1月1日星期日 开始

一、只考虑最开始的 7 天,即 d = 1---7 变换到 w = 0---6 
很直观的得到: 
w = d-1

二、扩展到整个1月份 
模7的概念大家都知道了,也没什么好多说的。不过也可以从我们平常用的日历中看出来,在周历里边每列都是一个按7增长的等差数列,如1、8、15、22的星期都是相同的。所以得到整个1月的公式如下: 
w = (d-1) % 7 --------- 公式⑴

三、按年扩展 
由于按月扩展比较麻烦,所以将年扩展放在前面说

① 我们不考虑闰年,假设每一年都是 365 天。由于365是7的52倍多1天,所以每一年的第一天和最后一天星期是相同的。 
也就是说下一年的第一天与上一年的第一天星期滞后一天。这是个重要的结论,每过一年,公式⑴会有一天的误差,由于我们是从0年开始的,所以只须要简单的加上年就可以修正扩展年引起的误差,得到公式如下: 
w = (d-1 + y) % 7

② 将闰年考虑进去 
每个闰年会多出一天,会使后面的年份产生一天的误差。如我们要计算2005年1月1日星期几,就要考虑前面的已经过的2004年中有多少个闰年,将这个误差加上就可以正确的计算了。 
根据闰年的定义(能被4整但不能被100整除或能被400整),得到计算闰年的个数的算式:y/4 - y/100 + y/400。 
由于我们要计算的是当前要计算的年之前的闰年数,所以要将年减1,得到了如下的公式: 
w = [d-1+y + (y-1)/4-(y-1)/100+(y-1)/400] % 7 -----公式⑵

现在,我们得到了按年扩展的公式⑵,用这个公式可以计算任一年的1月份的星期

四、扩展到其它月 
考虑这个问题颇费了一翻脑筋,后来还是按前面的方法大胆假才找到突破口。

①现在我们假设每个月都是28天,且不考虑闰年 
有了这个假设,计算星期就太简单了,因为28正好是7的整数倍,每个月的星期都是一样的,公式⑵对任一个月都适用 :)

②但假设终究是假设,首先1月就不是28天,这将会造成2月份的计算误差。1月份比28天要多出3天,就是说公式⑵的基础上,2月份的星期应该推后3天。 
而对3月份来说,推后也是3天(2月正好28天,对3月的计算没有影响)。 
依此类推,每个月的计算要将前面几个月的累计误差加上。 
要注意的是误差只影响后面月的计算,因为12月已是最后一个月,所以不用考虑12月的误差天数,同理,1月份的误差天数是0,因为前面没有月份影响它。

由此,想到建立一个误差表来修正每个月的计算。 
================================================== 
月 误差 累计 模7 
1 3 0 0 
2 0 3 3 
3 3 3 3 
4 2 6 6 
5 3 8 1 
6 2 11 4 
7 3 13 6 
8 3 16 2 
9 2 19 5 
10 3 21 0 
11 2 24 3 
12 - 26 5 
(闰年时2月会有一天的误差,但我们现在不考虑) 
==================================================

我们将最后的误差表用一个数组存放 
在公式⑵的基础上可以得到扩展到其它月的公式

e[] = {0,3,3,6,1,4,6,2,5,0,3,5} 
w = [d-1+y + e[m-1] + (y-1)/4-(y-1)/100+(y-1)/400] % 7 --公式⑶

③上面的误差表我们没有考虑闰年,如果是闰年,2月会一天的误差,会对后面的3-12月的计算产生影响,对此,我们暂时在编程时来修正这种情况,增加的限定条件是如果当年是闰年,且计算的月在2月以后,需要加上一天的误差。大概代码是这样的:

w = (d-1 + y + e[m-1] + (y-1)/4 - (y-1)/100 + (y-1)/400); 
if(m>2 && (y%4==0 && y%100!=0 || y%400==0) && y!=0) 
++w; 
w %= 7;

现在,已经可以正确的计算任一天的星期了。 
注意:0年不是闰年,虽然现在大都不用这个条件,但我们因从公元0年开始计算,所以这个条件是不能少的。

④ 改进 
公式⑶中,计算闰年数的子项 (y-1)/4-(y-1)/100+(y-1)/400 没有包含当年,如果将当年包含进去,则实现了如果当年是闰年,w 自动加1。 
由此带来的影响是如果当年是闰年,1,2月份的计算会多一天误差,我们同样在编程时修正。则代码如下

w = (d-1 + y + e[m-1] + y/4 - y/100 + y/400); ---- 公式⑷ 
if(m<3 && (y%4==0 && y%100!=0 || y%400==0) && y!=0) 
--w; 
w %= 7;

与前一段代码相比,我们简化了 w 的计算部分。 
实际上还可以进一步将常数 -1 合并到误差表中,但我们暂时先不这样做。

至此,我们得到了一个阶段性的算法,可以计算任一天的星期了。

public class Week { 
public static void main(String[] args){ 
int y = 2005; 
int m = 4; 
int d = 25;

int e[] = new int[]{0,3,3,6,1,4,6,2,5,0,3,5}; 
int w = (d-1+e[m-1]+y+(y>>2)-y/100+y/400); 
if(m<3 && ((y&3)==0 && y%100!=0 || y%400==0) && y!=0){ 
--w; 

w %= 7;

System.out.println(w); 

}

五、简化 
现在我们推导出了自己的计算星期的算法了,但还不能称之为公式。 
所谓公式,应该给定年月日后可以手工算出星期几的,但我们现在的算法需要记住一个误差表才能进行计算,所以只能称为一种算法,还不是公式。 
下面,我们试图消掉这个误差表……

============================= 
消除闰年判断的条件表达式 
=============================

由于闰年在2月份产生的误差,影响的是后面的月份计算。如果2月是排在一年的最后的话,它就不能对其它月份的计算产生影响了。可能已经有人联想到了文章开头的公式中为什么1,2月转换为上年的13,14月计算了吧 :)

就是这个思想了,我们也将1,2月当作上一年的13,14月来看待。 
由此会产生两个问题需要解决: 
1>一年的第一天是3月1日了,我们要对 w 的计算公式重新推导 
2>误差表也发生了变化,需要得新计算

①推导 w 计算式 
1> 用前面的算法算出 0年3月1日是星期3 
前7天, d = 1---7 ===> w = 3----2 
得到 w = (d+2) % 7 
此式同样适用于整个三月份 
2> 扩展到每一年的三月份 
[d + 2 + y + (y-1)/4 - (y-1)/100 + (y-1)/400] % 7

②误差表 
================================================== 
月 误差 累计 模7 
3 3 0 0 
4 2 3 3 
5 3 5 5 
6 2 8 1 
7 3 10 3 
8 3 13 6 
9 2 16 2 
10 3 18 4 
11 2 21 0 
12 3 23 2 
13 3 26 5 
14 - 29 1 
==================================================

③得到扩展到其它月的公式 
e[] = {0,3,5,1,3,6,2,4,0,2,5,1} 
w = [d+2 + e[m-3] +y+(y-1)/4-(y-1)/100+(y-1)/400] % 7 
(3 <= m <= 14)

我们还是将 y-1 的式子进行简化 
w = [d+2 + e[m-3] +y+y/4-y/100+y/400] % 7 
(3 <= m <= 14)

这个式子如果当年是闰年,会告成多1的误差 
但我们将1,2月变换到上一年的13,14月,年份要减1,所以这个误差会自动消除,所以得到下面的算法:

int e[] = new int[]{0,3,5,1,3,6,2,4,0,2,5,1}; 
if(m < 3) { 
m += 12; 
--y; 

int w = (d+2 + e[m-3] +y+(y/4)-y/100+y/400) % 7; -----公式⑸

我们可以看到公式⑸与公式⑷几乎是一样的,仅仅是误差天和一个常数的差别 
常数的区别是由起始日期的星期不同引起的,0年1月1日星期日,0年3日1日星期三,有三天的差别,所以常数也从 -1 变成了 2。

现在,我们成功的消除了繁琐的闰年条件判断。

============================= 
消除误差表 
============================= 
假如存在一种m到e的函数映射关系,使得 
e[m-3] = f(m) 
则我们就可以用 f(m) 取代公式⑸中的子项 e[m-3],也就消除了误差表。

由于误差表只有12个项,且每一项都可以加减 7n 进行调整,这个函数关系是可以拼凑出来的。但是这个过程可能是极其枯燥无味的,我现在不想自己去推导它,我要利用前人的成果。所谓前人栽树,后人乘凉嘛 :)

文章开头开出的公式中的 2*m+3*(m+1)/5 这个子项引起了我的兴趣

经过多次试试验,我运行下面的代码

for(m=1; m<=14; ++m) 
System.out.print((-1+2*m+3*(m+1)/5)%7 + " "); 
System.out.println();

天哪,输出结果与我的误差表不谋而合,成功了,哈哈

2 4 0 3 5 1 3 6 2 4 0 2 5 1 
Press any key to continue...

上面就是输出结果,看它后面的12项,与我的误差表完全吻合!!!

现在就简单的,将 f(m) = -1 + 2*m + 3*(m+1)/5 代入公式⑸,得到

w = (d+1+2*m+3*(m+1)/5+y+(y/4)-y/100+y/400) % 7 ----公式6 
约束条件: m=1,m=2 时 m=m+12,y=y-1;

现在,我们得到了通用的计算星期的公式,并且“完全”是按自己的思想推导出来的(那个函数映射关系不算),只要理解了这个推导的步骤,即使有一天忘记了这个公式,也可以重新推导出来!

可能有人会注意到公式⑹与文章开头的公式相差一个常数 1,这是因为原公式使用数字0--6表示星期一到星期日,而我用0--6表示星期日到星期六。实际上是一样,你可以改成任意你喜欢的表示方法,只需改变这个常数就可以了。

六、验证公式的正确性。

一个月中的日期是连续的,只要有一天对的,模7的关系就不会错,所以一个月中只须验证一天就可以了,一天需要验12天。由于扩展到年和月只跟是否闰年有关系,就是说至少要验证一个平年和一个闰年,也就是最少得验证24次。 
我选择了 2005 年和 2008 年,验证每个月的1号。 
测试代码如下:

class test {
public int GetWeek(int y, int m, int d) {
if(m<3) {
m += 12;
--y;
}
int w = (d+1+2*m+3*(m+1)/5+y+(y>>2)-y/100+y/400) % 7;
return w;
}
}
public class Week {
public static void main(String[] args){
int y = 2005;
int m = 1;
int d = 1; test t = new test();
String week[] = new String[]{
"星期日","星期一","星期二","星期三","星期四","星期五","星期六"
}; for(y=2005; y<=2008; y+=3) {
for(m=1; m<=12; ++m) {
String str = y + "-" + m + "-" + d + "\t" + week[t.GetWeek(y,m,d)];
System.out.println(str);
}
}
}
}

查万年历,检查程序的输出,完全正确。

七、后话

我们这个公式的推导是以0年3月1日为基础的,对该日以后的日期都是可以计算的。但是否可以扩展到公元前(1,2已属于公元前1年的13,14月了)呢?

虽然我对0年1月和2月、以及公元前1年(令y=-1)的12月作了验证是正确的,但我在推导这个公式时并未想到将其扩展到公元前,所以上面的推导过程没有足够理论依据可以证明其适用于公元前。(负数的取模在不同的编译器如C++中好象处理并不完全正确)。

另外一有点是对于0年是否存在的争议,一种折中的说法是0年存在,但什么也没有发生,其持续时间为0。还有在罗马的格利戈里历法中有10天是不存的(1582年10月5日至14持续时间为0),英国的历法中有11天(1752年9月3日至13日)是不存在的。感兴趣的朋友可以看看这里:

但是我们做的是数字计算,不管那一天是否存在,持续的时间是24小时还是23小时甚至是0小时,只要那个号码存在,就有一个星期与之对应。所以这个公式仍然是适用的。 
如果要计算的是时间段,就必须考虑这个问题了。

C语言根据日期(年,月,日)判断星期几(使用基姆拉尔森计算公式)的更多相关文章

  1. oracle 日期取 月 日

    今天碰到只要取月份和天数,如果月份前面有0要去掉0.比如说2010-01-08 ,需要的结果是1-8. 引出了一系列的sql语句 第一: 利用to_number的函数转换自动截0 select to_ ...

  2. 计算xx年xx月xx日是星期几

    代码: #include <iostream> #include <string> #include <vector> using namespace std; i ...

  3. Java通过基姆拉尔森公式判断当前日期是不是工作日

    基姆拉尔森公式 算法如下: 基姆拉尔森计算公式 W= (d+2*m+3*(m+1)/5+y+y/4-y/100+y/400+1)%7 在公式中d表示日期中的日数,m表示月份数,y表示年数. 注意:在公 ...

  4. php计算两个日期相差 年 月 日

    在PHP程序中,很多时候都会遇到处理时间的问题,比如:判断用户在线了多长时间,共登录了多少天,两个帖子发布的时间差或者是不同操作之间的日志记录等等.在文章中,简单地举例介绍了PHP中如何计算两个日期相 ...

  5. 【原创】Js:日期处理(日期格式必须【yyyy-mm-dd】才能转成long的毫秒!其他的不是【年-月-日】的格式,结果会是【NaN】)

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  6. ASP.NET C# 日期 时间 年 月 日 时 分 秒 格式及转换(转自happymagic的专栏)

    在平时编码中,经常要把日期转换成各种各样的形式输出或保持,今天专门做了个测试,发现DateTime的ToString()方法居然有这么多的表现形式,和大家一起分享. DateTime time=Dat ...

  7. ASP.NET 日期 时间 年 月 日 时 分 秒 格式及转换

    在平时编码中,经常要把日期转换成各种各样的形式输出或保持,今天专门做了个测试,发现DateTime的ToString()方法居然有这么多的表现形式,和大家一起分享. DateTime time=Dat ...

  8. 通过Calendar简单解析Date日期,获取年、月、日、星期的数值

    有时候项目中需要用到Date的年.月.日.星期的数值.那么解析方法如下: /**解析日期,获取年月日星期*/ private void parseDateToYearMonthDayWeek(Date ...

  9. R语言两种方式求指定日期所在月的天数

                 R语言两种方式求指定日期所在月的天数 days_monthday<-function(date){ m<-format(date,format="%m& ...

随机推荐

  1. squid日志配置与轮询

    squid日志分类及参数 SQUID默认的log文件非常多,其中最重要的LOG日志有三个,分别为access.log.store.log.cache.log.三个日志的记录的内容如下: access. ...

  2. 【Hibernate】Hibernate系列7之二级缓存

    二级缓存 7.1.概述 7.2.配置方法

  3. UEditor去除复制样式实现无格式粘贴

    UEditor内置了无格式粘贴的功能,只需要简单的配置即可. 1.修改ueditor.config.js,开启retainOnlyLabelPasted,并设置为true 2.开启pasteplain ...

  4. DML操作对索引的影响

    一:delete操作 现在我们已经知道,索引都是以B树的形式存在的,既然是B树,我们就要看看他们的叶子节点和分支结点,先准备点测试数据,如下图: 按 Ctrl+C 复制代码 按 Ctrl+C 复制代码 ...

  5. 【JAVA、C++】LeetCode 005 Longest Palindromic Substring

    Given a string S, find the longest palindromic substring in S. You may assume that the maximum lengt ...

  6. Java for LeetCode 069 Sqrt(x)

    Implement int sqrt(int x). Compute and return the square root of x. 解题思路一: public int mySqrt(int x) ...

  7. 昂贵的聘礼(poj 1062)

    Description 年轻的探险家来到了一个印第安部落里.在那里他和酋长的女儿相爱了,于是便向酋长去求亲.酋长要他用10000个金币作为聘礼才答应把女儿嫁给他.探险家拿不出这么多金币,便请求酋长降低 ...

  8. kvm与selinux

    Desktop virsh start 16lnmp error: Failed to start domain 16lnmp error: unsupported configuration: Un ...

  9. Spring中的设计模式学习

    Spring提供了一种Template的设计哲学,包含了很多优秀的软件工程思想. 1. 简单工厂 又叫做静态工厂方法(StaticFactory Method)模式,但不属于23种GOF设计模式之一. ...

  10. Entity Framework 使用

    1.EF中Include方法的使用使用Include方法,告诉EF连接查询哪个外键属性,生成Inner join连接 //必须引用using System.Data.Entity;才能用Include ...