算法系列(十七) 日历生成算法-中国公历(格里历)(上)2014-05-24 csdn博客 吹泡泡的小猫日历在我们的生活中扮演着十分重要的角色,上班、上学、约会都离不开日历。每年新年开始,人们 都要更换新的日历,你想知道未来一年的这么多天是怎么被确定下来的吗?为什么去年的国庆节是星期五 而今年的国庆节是星期三?那就来研究一下日历算法吧。本文将介绍日历的编排规则,确定某日是星期几 的计算方法,以及如何在计算机上打印某一年的年历。要研究日 历算法,首先要知道日历的编排规则,也就是历法。所谓历法,指的就是推算年、月、日的时间长度和它 们之间的关系,指定时间序列的法则。我国的官方历法是中国公历,也就是世界通用的格里历 (Gregorian Calendar),中国公历的年分为平常年和闰年,平常年一年是365天,闰年一年是366天。判 定一年是平常年还是闰年的规则如下:1、 如果年份是4的倍数,且不是100的倍数,则是 闰年;2、 如果年份是400的倍数,则是闰年;3、 不满足1、2条件的就是 平常年。总结成一句话就是:四年一闰,百年不闰,四百年再闰。中国公历关于月的规则是这样的,一年分为十二个月,其中一月、三月、五月、七月、八 月、十月和十二月是大月,一个月有31天。四月、六月、九月和十一月是小月,一个月有30天。二月天数 要根据是否是闰年来定,如果是闰年,二月是29天,如果是平常年,二月是28天。除了年月日,人们日常生活中还对日期定义了另一个属性,就是星期几。星期并不 是公历范畴内的东西,但是人们已经习惯用星期来管理和规划时间,比如一个星期工作五天,休息两天等 等,星期的规则彻底改变了人们的生活习惯,因此星期已经成为历法中的一部分了。星期的命名最早起源 于古巴比伦文化。公元前7-6世纪,巴比伦人就使用了星期制,一个星期中的每一天都有一个天神掌管。 这一规则后来传到古罗马,并逐渐演变成现在的星期制度。如何 知道某一天到底是星期几?除了查日历之外,是否有办法推算出来某一天是星期几呢?答案是肯定的,星 期不象年和月那样有固定的历法规则,但是星期的计算也有自己的规律。星期是固定的7天周期,其排列 顺序固定,不随闰年、平常年以及大小月的天数变化影响。因此,只要确切地知道某一天是星期几,就可 以推算出其它日期是星期几。推算的方法很简单,就是计算两个日期之间相差多少天,用相差的天数对7 取余数,这个余数就是两个日期的星期数的差值。举个例子,假设已经知道1977年3月27日是星期日,如 何得知1978年3月27日是星期几?按照前面的方法,计算出1977年3月27日到1978年3月27日之间相差365天 ,365除以7余数是1,所以1978年3月27日就是星期一。上述方法 计算星期几的关键是求出两个日期之间相隔的天数。有两种常用的方法计算两个日期之间相隔的天数,一 种是利用公历的月和年的规则直接计算,另一种是利用儒略日计算。利用公历规则直接计算两个日期之间 相差的天数,简单地讲就是将两个日期之间相隔的天数分成三个部分:前一个日期所在年份还剩下的天数 、两个日期之间相隔的整数年所包含的天数和后一个日期所在的年过去的天数。如果两个日期是相邻两个 年份的日期,则第二部分整年的天数就是0。以1977年3月27日到2005年5月31日为例,1977年还剩下的天 数是279天,中间整数年是从1978年到2005年(不包括2005年),共26年,包括7个闰年和20个平常年,总 计9862天,最后是2005年从1月1日到5月31日经过的天数151天。三者总结10292天。直接利用公历规则计 算日期相差天数的算法实现如下(为了简化算法复杂度,这个实现假设用于定位星期的那个日期总是在需 要计算星期几的那个日期之前):
99 int CalculateDays(int ys, int ms, int ds, int ye, int me, int de)100 {101 int days = CalcYearRestDays(ys, ms, ds);102 103 if(ys != ye) /*不是同一年的日期*/ 104 {105 if((ye - ys) >= 2) /*间隔超过一年,要计算间隔的整年时间*/ 106 {107 days += CalcYearsDays(ys + 1, ye);108 }109 days += CalcYearPassedDays(ye, me, de);110 }111 else 112 {113 days = days - CalcYearRestDays(ye, me, de);114 }115 116 return days;117 }43 /*计算一年中过去的天数,包括指定的这一天*/ 44 int CalcYearPassedDays(int year, int month, int day)45 {46 int passedDays = 0;47 48 int i;49 for(i = 0; i < month - 1; i++)50 {51 passedDays += daysOfMonth[i];52 }53 54 passedDays += day;55 if((month > 2) && IsLeapYear(year))56 passedDays++;57 58 return passedDays;59 }60 61 /*计算一年中还剩下的天数,不包括指定的这一天*/ 62 int CalcYearRestDays(int year, int month, int day)63 {64 int leftDays = daysOfMonth[month - 1] - day;65 66 int i;67 for(i = month; i < MONTHES_FOR_YEAR; i++)68 {69 leftDays += daysOfMonth[i];70 }71 72 if((month <= 2) && IsLeapYear(year))73 leftDays++;74 75 return leftDays;76 }77 78 /*79 计算years年1月1日和yeare年1月1日之间的天数,80 包括years年1月1日,但是不包括yeare年1月1日81 */ 82 int CalcYearsDays(int years, int yeare)83 {84 int days = 0;85 86 int i;87 for(i = years; i < yeare; i++)88 {89 if(IsLeapYear(i))90 days += DAYS_OF_LEAP_YEAR;91 else 92 days += DAYS_OF_NORMAL_YEAR;93 }94 95 return days;96 }