前言
这篇文章有什么实用价值?没有什么价值!因为查询农历的工具途径设备等等太多,而且excel是得到结果效率最低速度最慢的一种。
但是写这些代码的过程中,对农历历法的算法兴趣渐浓,另外代码里面也涉及一些以前未曾留意的vba方面的知识点,有必要存留下来,做个备忘。
这篇文章的内容并不是解释如何推算农历的,而是在excel中利用农历数据来查询阳历日期对应的农历日期。目前我发现现有的网络有关于农历的数据只有两个版本,其中网页版的万年历网站基本上都是采用四位的16进制数据来存储每年的农历数据,另外唯一看到百度文库里面有一篇C语言编写的农历代码中使用了六位的16进制来存储每年的农历数据。两个版本的数据中都有极少的数据偏差,这个是通过后期excel数据对比的时候发现的,可能是最早版本的数据编制时遗留的历史错误。
应该说,所有说通过公式对任意一天阳历进行公式计算,可以得到对应的农历日期的说法都是骗人的。
目前的万年历基本上都是采用数据存储,匹配查找来得到结果的。
目前excel内置的日期默认是从1900年1月1日开始,对应的数字格式为1。但是excel的日期数据有一个bug(这个bug据说是当年office为了抢市场兼容lotus-123的时候遗留下来的),就是在excel里面存在1900/02/29这个日期,而实际上这个日期是不存在的。阳历中对于2月为28天还是29是有计算公式的,1)非整百年能被4整除的,当年2月就是29日。2)整百年的能被400整除的,当年的2月是29日。而1900年属于正百年,虽然能被4整除,但是不能被400整除,所以1900年的2月应该是28天,而不是29天。但是你用excel来计算的话,它的结果是29天,这个bug到现在还有。
篇幅以及记忆力的原因,这篇可能要分为几部分。相对来说,阳历部分的内容比较简单,设计的数据处理也比较少;农历的数据处理过程相对繁琐,而且两种数据格式获得农历数据的方法不一样。
关于农历(摘一段司马3G微博的介绍,这段介绍的较为全面了):
历法,是人类文明发展和进步的高度体现,涉及到天文学、气象学、时间等等概念和学科。
对于生活在地球上的人类而言,太阳和月亮是影响人类生活最大的因素,人类在总结太阳和月亮的规律的过程中,分别发展出以太阳运行规律为主的“阳历”,以及以月亮运行规律为主的“月历”(也称“阴历”)
地球围绕太阳公转,同时地球还在自转,
地球沿着黄道围绕太阳公转一圈,形成一年,周而复始;
地球自转一圈,太阳再次升起为标志,形成一天,周而复始;
这二个概念,无论是阳历还是阴历,都是完全一致的。
由于地球的自转轴,相对于太阳,具有一个偏角,所以我们人类能够观察到太阳在一年的自转过程中,分别在北回归线和南回归线之间循环往复。
我们中国处于地球的北半球,所以当太阳照射到地球的北回归线时,白天时间最长,北半球的气温最高,这就是我们北半球的夏天。同理,此时对于南半球的人类而言,日照最短,气温最低,就是冬天。
地球自转轴的二个极点,分别是北极和南极,太阳照射角度永远最小,所以永远是低温环境。
这就是日历的基础。
问题开始出现了:
人类很自然的将地球自转一圈,作为一天的时间长度,但问题在于,地球围绕太阳公转一圈,自转365.24圈,也就是说,一年的准确天数,应该是365.24,所以阳历需要每逢四年,增加一天,成为闰年,当年的二月份就有了29天。
以古罗马人、希腊人为代表的欧洲人在总结阳历的规律时,人为的将一年分为12个月,(最初是10个月,后来增添了2个月),由于受到宗教的影响,将最不吉利的2月份,减去二天,所以阳历的七月和八月,都有31天。
阳历其实和月亮的运行规律,没有任何关系。
在北半球以中国人为代表的的农历,其实综合考虑了太阳和月亮的规律,月份的基础,就是月亮的运行规律。
众所周知,地球在围绕太阳公转的同时,地球还在自转。同时,月亮还在围绕地球公转,月亮同时也在自转。
由于月亮的自转和公转同步,所以我们永远只能看到月亮的一个表面。
月亮围绕地球公转一周,时间是29.53天,农历分别设置为大月30天,小月29天。
所以我们会看到有些个别的年份,农历腊月只有29天,这年的腊月,没有了年三十。
问题又出现了:农历如果还是以十二个月为一年,那么一年只有364.36天,和地球围绕太阳公转一圈的一年,相差了约11天,因此,农历在每个19年的周期内,分别加入了七个闰月,以保证农历的月份,和阳历的年份基本一致。
俗称“19加7置闰法”。农历是按照3年1闰,5年闰2,11年4闰,19年7闰。
所以我们才能看到,每过十九年,就会发生阳历日期和农历日期的重叠。
但也有例外,如果当年恰好有农历的“置闰月”,就会相差一天。
由于农历的“置闰法”,所以我们才能惊奇的发现,每过几年,就会有农历的大年初一特别早,早到一月上旬;而有的大年初一,又特别晚,晚到二月上旬。
农历以月球运行周期为标准,完全和太阳的运行周期无关,具有重要的意义。
月球对地球的最大影响,就是“潮汐”。
由于地球和月球之间的引力作用,地球面对月球的地表海水,受到月球引力的作用,会明显隆起,形成潮水;
同理,地球背对月球的表面海水,由于受到月球引力最小,但同时受到地球公转的离心力最大,也会隆起,形成潮水;
地球上的某一个区域,每天形成二次潮水,白天的潮水称为“潮”,晚上的潮水称为“汐”,并称“潮汐”。
月球公转的轨道,是椭圆轨道,每年的三月和九月,分别是距离地球最近的轨道点,由于中秋节就是秋分前后,所以八月半看潮水,其实三月底和九月底,都是潮水最大的时刻,因为这二个时刻,月球距离地球的距离,最近。
农历对于历法最重要的贡献和发明,就是“二十四节气”。
农历中根据太阳运行对地球温度、气候的规律,分别设置了“十二节令”以及“十二中气”,合称“二十四节气”,
农历根据“二十四节气”,用于指导农业生产耕种。
严格意义上,农历的历法,具有以下的特点和科学性:
以太阳自转周期为一天;
以月球公转周期为一月;
以太阳公转周期为一年;
农历完美的结合了太阳和月球对地球的影响。
严格意义上,农历并非纯粹的“阴历”,而是“阴阳合历”。
在人类文明发展历史上,中国是最早有皇权或者政府的形式,设置官方机构观察天文,并推算历法,这就是我们熟悉的明代朝廷机构“司天监”。由于历法涉及到的天文学、气象学、自然科学种类极多,普通人难以理解和掌握,所以历朝历代掌管历法的专职人员,往往也是星相学家、神学家。最为著名的人物,就是隋末唐初玄学家、天文学家、道士袁天罡。
这种现象,形成了中国历史上独特的文化。
在我们中国,历史上长期以农历为主,上世纪初,中华民国成立,采用西历,并欲废除中国传统历。中华人民共和国在1949年成立时,继续使用西历,以公元纪年;但保留中国传统农历的使用,二者并存。
中国现行农历由中国科学院紫金山天文台负责计算。
阳历的自动查询代码
阳历的查询用excel就非常简单,其内置的函数功能基本上就可以搞定,配上vba进行一些数据判断和处理就能完成万年历。阳历中主要使用到的一些函数:
date函数,将三个数据构建成一个日期格式的数据,如date(2020,09,27),结果就是2020/09/27的日期格式数据。注:在vba中date函数不能使用,而是用dateserial函数来代替。
day函数,这个在查询当年2月是28天还是29天很有用。如day(date(2020,3,0)),返回值为29,表示2020年2月是29天。
now函数,返回当前日期时间。
weekday函数,返回当前日期的星期,这个函数第二个参数属于星期日还是星期一的前置习惯,一般11表示第一天是星期一。
代码思路:
首先采用三个存储单元格存放年,月,日的数据,单元格都设置数据验证,采用序列的形式。其中
年可以采用单元格范围来是指,一般设置为1900→2099。
月采用固定的序列,1→12
日采用day函数来判断当年的2月是28还是29,然后生成对应字符串,采用vba填充到单元格内,作为数据验证的序列内容,例如:
Str1 = "" a = Day(DateSerial([i2], [j2] + 1, 0)) '查询[j2]月的天数 For i = 1 To a Str1 = Str1 & i & "," Next Str1 = Left(Str1, Len(Str1) - 1) '获得当月的日期列表 If [k2] > a Then [k2] = "" '防止前面的数据超过指定月的最大数字。 With [k2].Validation .Delete .Add Type:=xlValidateList, AlertStyle:=xlValidAlertStop, Operator:= _ xlBetween, Formula1:=Str1 .IgnoreBlank = True .InCellDropdown = True .IMEMode = xlIMEModeNoControl .ShowInput = True .ShowError = True End With
这样就可以生成一个万年历的年月日下拉菜单数据,可以通过此选择后,采用date(year,month,day)来获得选择的日期。
下一步就是编写日历界面,设置一个指定区域:A1:G13,具体单元格功能为:
A1:G1为标题列,对应星期一到星期天的描述
A2:G2,A4:G4,A6:G6,A8:G8,A10:G10,A12:G12:存放阳历的日期,阳历的每个月最多31天,如第一天为星期天,则总周数最多就会达到6周。所以采用6个行来存放当前阳历的日期。
A3:G3,A5:G5,……A11:G11,A13:G13,这5行用来存放农历数据(后面分析),它与阳历的日期一一对应,列数相同,行数+1。
编写下拉框切换日期时在星期界面(A2:G13)自动生成月历牌,并且当前选择的日期用醒目的颜色标注出来。代码如下:
Sub showyangli() '首先对现有的月历牌区域进行清空复位。 With Range("a2:g13") .ClearContents .Interior.ColorIndex = 37 .Font.ColorIndex = xlAutomatic End With Range("b2:b13,d2:d13,f2:f13").Interior.ColorIndex = 20 '本例中采用了月历牌竖向颜色穿插的外观,所以增加一行偶数列的背景色设置。 n = Weekday(DateSerial([i2], [j2], 1), vbMonday) '获得当前月1号的星期数,vbmonday的参数为第一天为周一,返回的结果是整数 allday = Day(DateSerial([i2], [j2] + 1, 0)) '获得当月的总天数 Cells(2, n) = 1 '这里采用返回的星期值就是当月1号的星期,所以如果返回6,则n=6,就是第6列写入日期1(前提是月历牌按照周一起始的) '继续把第一行顺序日期填充到第7天(周日) If n < 7 Then For j = n To 6 Cells(2, j + 1) = Cells(2, j) + 1 Next End If '临时记录第二行开始的日期,等于首行末尾+1 end_l1 = Cells(2, 7).Value + 1 '开始写剩下的日期,从第4行写入,这里采用从2起循环,主要是加入如果当前日期在第一行内,那么需要对日期进行颜色标注。 For i = 2 To 12 Step 2 '每两行写入,空出的奇数行作为后面的农历数据写入。 For j = 1 To 7 If i >= 4 Then '因为第2行的数据已经写完,所以这里到第4行开始写入数据 If end_l1 > allday Then Exit For ‘如果行起始日期已经超出当月最大天数,则表示写入结束。 Cells(i, j) = end_l1 end_l1 = end_l1 + 1 End If '对当前日期的位置进行颜色标注(同时将对应农历的区域也一起标注) If Cells(i, j) = [k2] Then Cells(i, j).Interior.ColorIndex = 6 Cells(i + 1, j).Interior.ColorIndex = 6 Cells(i, j).Font.ColorIndex = 3 Cells(i + 1, j).Font.ColorIndex = 3 End If Next Next '代码结束 End Sub
至此就可以顺利实现选择(或者输入)日期进行月历查询,并在月历上用颜色标注出当前日期。