算法系列之十七:日历生成算法-中国公历(格里历)(上)
日历在我们的生活中扮演着十分重要的角色,上班、上学、约会都离不开日历。每年新年开始,人们都要更换新的日历,你想知道未来一年的这么多天是怎么被确定下来的吗?为什么去年的国庆节是星期五而今年的国庆节是星期三?那就来研究一下日历算法吧。本文将介绍日历的编排规则,确定某日是星期几的计算方法,以及如何在计算机上打印某一年的年历。
要研究日历算法,首先要知道日历的编排规则,也就是历法。所谓历法,指的就是推算年、月、日的时间长度和它们之间的关系,指定时间序列的法则。我国的官方历法是中国公历,也就是世界通用的格里历(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 } |
另一种计算两个日期相差天数的方法是利用儒略日(Julian Day,JD)进行计算。首先介绍一下儒略日,儒略日是一种不记年,不记月,只记日的历法,是由法国学者Joseph Justus Scaliger(1540-1609)在1583年提出来的一种以天数为计量单位的流水日历。儒略日和儒略历(Julian Calendar)没有任何关系,命名为儒略日也仅仅他本人为了纪念他的父亲――意大利学者Julius Caesar Scaliger(1484-1558)。简单来讲,儒略日就是指从公元前4713年1月1日UTC 12:00开始所经过的天数,JD0就被指定为公元前4713年1月1日 12:00到公元前4713年1月2日12:00之间的24小时,依次顺推,每一天都被赋予一个唯一的数字。例如从1996年1月1日12:00开始的一天就是儒略日JD2450084。使用儒略日可以把不同历法的年表统一起来,很方便地在各种历法中追溯日期。如果计算两个日期之间的天数,利用儒略日计算也很方便,先计算出两个日期的儒略日数,然后直接相减就可以得到两个日期相隔的天数。
由公历的日期计算出儒略日数是一个很简单的事情,有多个公式可以计算儒略日,本文选择如下公式计算儒略日:
其中y是年份,m是月份,d是日期,如果m小于或等于2,则m修正为m+12,同时年份修正为y-1。c值由以下方法计算:
下面就是由公历日期计算儒略日的算法实现:
119 int CalculateJulianDay(int year, int month, int day) 120 { 121 int B = 0; 122 123 if(month <= 2) 124 { 125 month += 12; 126 year -= 1; 127 } 128 if(IsGregorianDays(year, month, day)) 129 { 130 B = year / 100; 131 B = 2 - B + year / 400; 132 } 133 134 double dd = day + 0.5000115740; /*本日12:00后才是儒略日的开始(过一秒钟)*/ 135 return int(365.25 * (year + 4716) + 0.01) + int(30.60001 * (month + 1)) + dd + B - 1524.5; 136 } |
儒略日的计算通常精确到秒,得到的JD数也是一个浮点数,本文仅仅是为了计算日期相隔的整数天数,因此都采用整数计算。由于儒略日的周期开始与每天中午12:00,而历法中的天数通常是从0:00开始的,因此儒略日计算上对日期的天数进行了修正。1977年3月27日的儒略日是2443230,2005年5月31日的儒略日是2453522,差值是10292,和前一种方法计算的结果一致。
我们用两种方法计算出两个日期之间的天数都是10292,现在用10292除以7得到余数是2,也就是说2005年5月31日与1977年3月27日星期数差两天,所以2005年5月31日就是是星期二。
上述计算星期的方法虽然步骤简单,但是每次都要计算两个日期的时间差,不是非常方便。如果能够有一个公式可以直接根据日期计算出对应的星期岂不是更好?幸运的是,这样的公式是存在的,下篇将继续介绍公式法直接计算某一天星期数的算法。
小知识1:公历的闰年
中国公历(也就是格里历)的置闰规则是四年一闰,百年不闰,四百年再闰,为什么会有这么奇怪的置闰规则呢?这实际上与天体运行周期与人类定义的历法周期之间的误差有关。地球绕太阳运转的周期是365.2422天,即一个回归年(Tropical Year),而公历的一年是365天,这样一年就比回归年短了0.2422日,四年积累下来就多出0.9688天(约1天),于是设置一个闰年,这一年多一天。这样一来,四个公历年又比四个回归年多了0.0312天,平均每年多0.0078天,这样经过四百年就会多出3.12天,也就是说每四百年要减少3个闰年才行,于是就设置了百年不闰,四百年再闰的置闰规则。
实际上公历的置闰还有一条规则,就是对于数值很大的年份,如果能整除3200,同时能整除172800则是闰年。这是因为前面即使四百年一闰,仍然多了0.12天,平均就是每天多0.0003天,于是每3200年就又多出0.96天,也就是说每3200年还要减少一个闰年,于是能被3200整除的年就不是闰年了。然而误差并没有终结,每3200年减少一个闰年(减少一天)实际上多减了0.04天,这个误差还要继续累计计算,这已经超出了本文的范围,有兴趣的读者可以自己计算。
算法系列之十七:日历生成算法-中国公历(格里历)(上)相关推荐
- 算法系列之二十:计算中国农历(二)
(接上篇) 所谓的"天文算法",就是利用经典力学定律推导行星运转轨道,对任意时刻的行星位置进行精确计算,从而获得某种天文现象发生时的时间,比如日月合朔这一天文现象就是太阳和月亮的地 ...
- 精通八大排序算法系列:二、堆排序算法
精通八大排序算法系列:二.堆排序算法 作者:July .二零一一年二月二十日 本文参考:Introduction To Algorithms,second edition. ------------- ...
- 【算法】组合数学——排列数生成算法详解(一)
组合数学中的全排列深成算法历来是组合数学考试的重要考察点,因此在这里我简单的介绍一下6种全排列生成算法的详细过程,并借此比较它们之间的优劣之处. 不论是哪种全排列生成算法,都遵循着"原排列& ...
- 日历生成算法-中国公历
日历在我们的生活中扮演着十分重要的角色,上班.上学.约会都离不开日历.每年新年开始,人们都要更换新的日历,你想知道未来一年的这么多天是怎么被确定下来的吗?为什么去年的国庆节是星期五而今年的国庆节是星期 ...
- [转]日历生成算法-中国公历(格里历)
转自吹泡泡的小猫,原文地址:http://blog.csdn.net/orbit/article/details/7749723 日历在我们的生活中扮演着十分重要的角色,上班.上学.约会都离不开日历. ...
- 算法系列之二十:计算中国农历(一)
世界各国的日历都是以天为最小单位,但是关于年和月的算法却各不相同,大致可以分为三类: 阳历--以天文年作为日历的主要周期,例如:中国公历(格里历) 阴历--以天文月作为日历的主要周期,例如:伊斯兰历 ...
- 非主流自然语言处理——遗忘算法系列(一):算法概述
一.前言 这里"遗忘"不是笔误,这个系列要讲的"遗忘算法",是以牛顿冷却公式模拟遗忘为基础.用于自然语言处理(NLP)的一类方法的统称,而不是大名鼎鼎的&q ...
- c语言 迷宫深度遍历 算法,图的遍历迷宫生成算法浅析
1. 引言 在平常的游戏中,我们常常会碰到随机生成的地图.这里我们就来看看一个简单的随机迷宫是如何生成. 2. 迷宫描述随机生成一个m * n的迷宫,可用一个矩阵maze[m][n]来表示,如图: ...
- java抢红包算法_Java抢红包的红包生成算法
马上过年了.过年微信红包很火,最近有个项目也要做抢红包,于是写了个红包的生成算法. 红包生成算法的需求 预先生成所有的红包还是一个请求随机生成一个红包 简单来说,就是把一个大整数m分解(直接以&quo ...
最新文章
- GameObject 与gameObject的区别
- memcache多语言unix socket访问
- java -cp ***.jar WordCount 无法找到类名解决方案记录
- WARNING: YARN_ROOT_LOGGER has been replaced by HADOOP_ROOT_LOGGER. Using value of YARN_ROOT_LOGGER
- linux下面的chrome总是跳出xdg-open怎么办
- 中科院开源 RISC-V 处理器“香山”流片,已成功运行 Linux
- 屏蔽wget下载网站内容
- [VB]多级目录创建函数,支持很深的目录创建。
- 智慧城市近两年来受到国家高度重视
- 设计模式之(Facade)外观模式
- 为什么100offer坚持私密招聘?
- Jzoj5445【NOIP2017提高A组冲刺11.2】失格
- 原生ajax请求流程
- stm32F205程序移植到stm32F405片子,使用FPU时注意事项
- python读取fits第三方库_Python读取和显示Fits文件
- openresty 与 java RSA加解密
- 各个击破!高效解决游戏开发8大痛点
- android 4k手机屏幕分辨率,安卓手机都4K屏了 真的有必要?
- #内存泄露# linux常用内存相关命令
- C++基础入门(超详细)
热门文章
- 女孩,请你珍惜男孩的眼泪......
- 2022考研政徐涛核心考案pdf电子版及考研政咸鱼翻身笔记本pdf
- 应用matlab计算线性定常系统的矩阵指数
- html的悬停图片圆形,HTML5 圆形悬停放大图片预览网格
- 29日全国铁路预计发送旅客978万人次 同比增长4%
- 对python彻底绝望_对一个人彻底失望绝望的句子
- 自学python在家赚钱-自学python后,可以自己独立做什么事情来挣钱吗?
- 易点天下IPO被中止:财务资料过有效期 申请上市2年仍无进展
- IPsec ××× 工作中感受
- python中一些for循环知识