和荣笔记 中国农历二百年算法及年历 - leetcola/nong GitHub Wiki
网址:http://www.herongyang.com/Year_zh/
书名:和荣笔记 - 中国农历二百年算法及年历
作者:杨和荣
分类:中国日历
版次:第4.01版, 二○一四年
页数:258
摘要:本书开始部份简单地介绍了日历的基本原理,同时也介绍了目前中国使用的公历和 农历的基本推算规则。中间部份列出了一个中国农历推算原程式。这个程式采用了 压缩数据,可以用来推算二百年农历日期。本书的其余部份收录了由这个程式制作 的二百年年历。
价格:免费
修改记录:
- 第4.00版,二○一四年,少量修改。
- 第4.00版,二○一一年,改编成Unicode编码。
- 第3.00版,二○○三年,改编成打印版。
- 第2.00版,一九九九年,改编成网页形式。
版权声明:
- 版权所有 (c) 二○一四年,杨和荣。
- 未经许可,任何人不得复制,传播,或以其他任何方式进行使用书中内容。
- 书中算法和代码仅供参考,无任何形式的担保。
本章介绍了中国日历的基本规则。内容包括了各种日历的天文学知识;公历( Gregorian calendar)基本规则介绍;农历基本规则介绍。
本节介绍了各种日历的天文学知识。天文学中有三个天体运动周期常用于日历规则 制定中:地球自转,月球公转,地球公转。
日历是以天文学的三个不同的天体运动周期作为基础的:
- 天:地球自转一周。
- 月:月球公转围绕地球运行一周。
- 年:地球公转围绕太阳运行一周。
如果以天作为单位,天文学的一年的平均时间是 365.2421896698 - 0.00000615359 T - 7.29E-10 T^2 + 2.64E-10 T^3 天,其中 T 的表达式为 (JD - 2451545.0)/36525,JD 是 Julian 天数。
如果以天作为单位,天文学的一月的平均时间是 29.5305888531 + 0.00000021621 T - 3.64E-10 T^2 天,其中 T 的表达式为 (JD - 2451545.0)/36525,JD 是 Julian 天数。 世界各国的日历都是以天作为最小单位,但是年和月的算法却各不相同,一共有 三大种类:
- 阳历:以天文年作为日历的主要周期,例如:中国公历。
- 阴历:以天文月作为日历的主要周期,例如:伊斯兰日历。
- 阴阳历:以天文月和年作为日历的主要周期,例如:中国农历。
本节介绍了公历(Gregorian calendar)基本规则。公历以天文年为基础,把一年 分为十二个月。普通年为 365 天,闰年为 366 天。
中国公历也就是世界通用的 Gregorian 历,它以年作为主要周期。为了和天文年 保持同步,公历使用两种不同天数的年:
- 常年:365 天。
- 闰年:366 天。
公历年一般都是常年,只有少数年是闰年,由下面三条规则确定:
- 一:如果年的数目是 4 的倍数,就是闰年。
- 二:但是,如果年的数目是 100 的倍数,规则一就无效,仍是常年。
- 三:但是,如果年的数目是 400 的倍数,规则二就无效,仍是闰年。
根据规则一,公历每 4 年就比天文年多 0.03124 天:
4 Gregorian years: 3 * 365 + 366 = 1461 days
4 tropical years: 4 * 365.2421896698 = 1460.9687586792 days
根据规则二,公历每 100 年就比天文年少 0.21897 天:
100 Gregorian years: 76 * 365 + 24 * 366 = 36524 days
100 tropical years: 100 * 365.2421896698 = 36524.21896698 days
根据规则三,公历每 400 年就比天文年多 0.12413 天:
400 Gregorian years: 305 * 365 + 95 * 366 = 146097 days
400 tropical years: 400 * 365.2421896698 = 146096.87586792 days
照此计算,公历每 2500 年就比天文年多出 1 天。 公历将一年分为十二个月。常年每月的天数分布如下:
月份 1 2 3 4 5 6 7 8 9 10 11 12
天数 31 28 31 30 31 30 31 31 30 31 30 31
闰年的二月为 29 天。
公历的月跟天文月毫无关系。
公历还有一个跟年和月毫无关系的周期,叫作星期,每星期七天。公历第一年的第 一天是星期一。
公历 400 年共有 146097 天,正好是 7 的倍数,所以星期和年每 400 年循环一 次。
本节介绍了农历基本规则。农历以天文月为基础,十二个月为一年。同时又以天文 年为基准,加入闰月,让农历与地球公转吻合。
中国农历是阴阳历,同时跟天文月和天文年同步。
农历月的天数是一个变数,有时是 29 天,有时是 30 天。
农历每月的第一天是月亮全黑的日子。
农历年由 24 个节气来确定,节气则由太阳的角度来确定。农历的第一个节气叫 雨水,定在太阳的角度为 330 度的日子。其余的 23 个节气分别定在太阳的角度 每变化 15 度的日子。下面的表格列出了 24 个节气的名称和定义:
命称 角度 公历日期 周期
立春 315 2月 4日
雨水 330 2月19日 29.8天
惊蛰 345 3月 6日
春分 0 3月21日 30.2天
清明 15 4月 5日
谷雨 30 4月20日 30.7天
立夏 45 5月 6日
夏满 60 5月21日 31.2天
芒种 75 6月 6日
夏至 90 6月22日 31.4天
小暑 105 7月 7日
大暑 120 7月23日 31.4天
立秋 135 8月 8日
处暑 150 8月23日 31.1天
白露 165 9月 8日
秋分 180 9月23日 30.7天
寒露 195 10月 8日
霜降 210 10月24日 30.1天
立冬 225 11月 8日
小雪 240 11月22日 29.7天
大雪 255 12月 7日
冬至 270 12月22日 29.5天
小寒 285 1月 6日
大寒 300 1月20日 29.5天
24 个节气中有 12 个是主节气:雨水,春分,谷雨,夏满,夏至,大暑,处暑, 秋分,霜降,小雪,冬至,大寒。
农历年跟天文年相差较大。农历常年有十二个农历月,有 353,354,或者 355 天,比天文年少大约 11 天。为了跟天文年同步,每隔三个农历常年左右,必需设 一闰年。闰年有十三个月,添加的这个月叫闰月。
农历十二个月的名称分别为:正月,二月,三月,四月,五月,六月,七月,八 月,九月,十月,冬月,腊月。
农历闰年闰月的确定比较难,规则有两条:
- 一:冬至必须落在农历冬月。如果落不上,腊月之前就要添上一个月,成为闰 年。
- 二:如果是闰年,冬月后边第一个不含主节气的月份定为闰月。闰月使用前一 月份的名称。
农历年以 60 年为一周期,每年的名称由 10 个天干的一个字和 12 个地支的一 个字排列而成。10 天干为:甲,乙,丙,丁,戊,己,庚,辛,壬,癸。12 地支 为:子,丑,寅,卯,辰,巳,午,未,申,酉,戌,亥。12 地支有 12 动物生肖 与其对应:鼠,牛,虎,兔,龙,蛇,马,羊,猴,鸡,狗,猪。
跟据历史记载,农历年已经经过了 78 个周期。今年,公历 1999 年,是第 79 个周期的第 17 年,也就是农历第 4696 年。
总结起来,中国农历有下例六条规则:
- 一:月全黑规则 - 月全黑的日子是农历月的第一天。农历月周期由此而定。
- 二:24 节气规则 - 24 节气把天文年按太阳角度分成 24 等份,15 度一节气。 这个规则确定了农历和天文年的关系。
- 三:冬至规则 - 冬至必须落在农历冬月。如果落不上,腊月之前就要添上一个 月,成为闰年。
- 四:闰月规则 - 如果是闰年,冬月后边第一个不含主节气的月份定为闰月。
- 五:60 年周期 - 农历年以 60 年为一周期。
- 六:规则一和二的计算必须以中国南京紫金山天文台的观察为准。
小结:
天文学中有三个天体运动周期常用于日历规则制定中:地球自转,月球公转, 地球公转。 公历以地球公转的运动周期定为日历一年,再把一年分十二个月。 农历以地球公转的运动周期定为日历一年,再以月球公转的运动周期定为日历 一月。
本章介绍了中国日历算法和源程序。内容包括了公历闰年算法;农历算法天文数据 ;公历和农历二百年(1901年到2010年)计算源程序。
本节介绍了公历(Gregorian calendar)的算法 ,其中包括了星期的计算和闰年 的计算。
中国公历算法不是太难,关键是星期值的确定。这里给出了简单算法:
- 依照闰年规则编写 isGregorianLeapYear() 函数用以计算闰年。
- 依靠 isGregorianLeapYear(),编写 dayOfYear() 函数用以计算日期的天数。
- 依靠 dayOfYear(),编写 dayOfWeek() 函数用以计算日期的星期值。
isGregorianLeapYear() 函数反回值为 True,如果输入值为闰年:
public static boolean isGregorianLeapYear(int year) {
boolean isLeap = false;
if (year%4==0) isLeap = true;
if (year%100==0) isLeap = false;
if (year%400==0) isLeap = true;
return isLeap;
}
dayOfYear() 函数反回值为输入日期在一年之中的天数:
public static int dayOfYear(int y, int m, int d) {
int c = 0;
for (int i=1; i<m; i++) { // Number of months passed
c = c + daysInGregorianMonth(y,i);
}
c = c + d;
return c;
}
public static int daysInGregorianMonth(int y, int m) {
int d = daysInGregorianMonth[m-1];
if (m==2 && isGregorianLeapYear(y)) d++;
return d;
}
dayOfWeek() 函数反回值为输入日期的星期值:
public static int dayOfWeek(int y, int m, int d) {
int w = 1; // 公历一年一月一日是星期一,所以起始值为星期日
y = (y-1)%400 + 1; // 公历星期值分部 400 年循环一次
int ly = (y-1)/4; // 闰年次数
ly = ly - (y-1)/100;
ly = ly + (y-1)/400;
int ry = y - 1 - ly; // 常年次数
w = w + ry; // 常年星期值增一
w = w + 2*ly; // 闰年星期值增二
w = w + dayOfYear(y,m,d);
w = (w-1)%7 + 1;
return w;
}
本节介绍了农历的算法,算法的基础是 eleworld.com 提供的天文数据。
根公历相比,中国农历的算法相当复杂。我在网上找的算法之中,eleworld.com 的算法是最好的一个。这个算法使用了大量的数据来确定农历月份和节气的分部, 它仅实用于公历 1901 年到 2100 年之间的 200 年。
eleworld.com 使用的农历数据有两个部分,第一部分为农历月的份列,第二部分 为农历节气的列表。
月份列表使用了十六位二进值来记录每年月份的编排:前面的四位二进值表达闰月; 后面的十二位二进值表达十二月每月的天数,1 代表 29 天,0 代表 30 天。
节气列表有四个小部分:主要节气年表,主要节气日期表,次要节气年表,次要节 气日期表。
农历数据的具体使用方法,请参阅农历计算的源程式。
本节介绍了中国农历计算 Java 程式,算法和数据取自 eleworld.com。
跟据 eleworld.com 提供的算法和数据,我写了下面这个 Java 程式:
/**
* ChineseCalendarGB.java
* Copyright (c) 1997-2011 by Dr. Herong Yang
* 中国农历算法 - 实用于公历 1901 年至 2100 年之间的 200 年
*/
import java.text.*;
import java.util.*;
class ChineseCalendarGB {
private int gregorianYear;
private int gregorianMonth;
private int gregorianDate;
private boolean isGregorianLeap;
private int dayOfYear;
private int dayOfWeek; // 周日一星期的第一天
private int chineseYear;
private int chineseMonth; // 负数表示闰月
private int chineseDate;
private int sectionalTerm;
private int principleTerm;
private static char[] daysInGregorianMonth =
{31,28,31,30,31,30,31,31,30,31,30,31};
private static String[] stemNames =
{"甲","乙","丙","丁","戊","己","庚","辛","壬","癸"};
private static String[] branchNames =
{"子","丑","寅","卯","辰","巳","午","未","申","酉","戌","亥"};
private static String[] animalNames =
{"鼠","牛","虎","兔","龙","蛇","马","羊","猴","鸡","狗","猪"};
public static void main(String[] arg) {
ChineseCalendarGB c = new ChineseCalendarGB();
String cmd = "day";
int y = 1901;
int m = 1;
int d = 1;
if (arg.length>0) cmd = arg[0];
if (arg.length>1) y = Integer.parseInt(arg[1]);
if (arg.length>2) m = Integer.parseInt(arg[2]);
if (arg.length>3) d = Integer.parseInt(arg[3]);
c.setGregorian(y,m,d);
c.computeChineseFields();
c.computeSolarTerms();
if (cmd.equalsIgnoreCase("year")) {
String[] t = c.getYearTable();
for (int i=0; i<t.length; i++) System.out.println(t[i]);
} else if (cmd.equalsIgnoreCase("month")) {
String[] t = c.getMonthTable();
for (int i=0; i<t.length; i++) System.out.println(t[i]);
} else {
System.out.println(c.toString());
}
}
public ChineseCalendarGB() {
setGregorian(1901,1,1);
}
public void setGregorian(int y, int m, int d) {
gregorianYear = y;
gregorianMonth = m;
gregorianDate = d;
isGregorianLeap = isGregorianLeapYear(y);
dayOfYear = dayOfYear(y,m,d);
dayOfWeek = dayOfWeek(y,m,d);
chineseYear = 0;
chineseMonth = 0;
chineseDate = 0;
sectionalTerm = 0;
principleTerm = 0;
}
public static boolean isGregorianLeapYear(int year) {
boolean isLeap = false;
if (year%4==0) isLeap = true;
if (year%100==0) isLeap = false;
if (year%400==0) isLeap = true;
return isLeap;
}
public static int daysInGregorianMonth(int y, int m) {
int d = daysInGregorianMonth[m-1];
if (m==2 && isGregorianLeapYear(y)) d++; // 公历闰年二月多一天
return d;
}
public static int dayOfYear(int y, int m, int d) {
int c = 0;
for (int i=1; i<m; i++) {
c = c + daysInGregorianMonth(y,i);
}
c = c + d;
return c;
}
public static int dayOfWeek(int y, int m, int d) {
int w = 1; // 公历一年一月一日是星期一,所以起始值为星期日
y = (y-1)%400 + 1; // 公历星期值分部 400 年循环一次
int ly = (y-1)/4; // 闰年次数
ly = ly - (y-1)/100;
ly = ly + (y-1)/400;
int ry = y - 1 - ly; // 常年次数
w = w + ry; // 常年星期值增一
w = w + 2*ly; // 闰年星期值增二
w = w + dayOfYear(y,m,d);
w = (w-1)%7 + 1;
return w;
}
private static char[] chineseMonths = {
// 农历月份大小压缩表,两个字节表示一年。两个字节共十六个二进制位数,
// 前四个位数表示闰月月份,后十二个位数表示十二个农历月份的大小。
0x00,0x04,0xad,0x08,0x5a,0x01,0xd5,0x54,0xb4,0x09,0x64,0x05,0x59,0x45,
0x95,0x0a,0xa6,0x04,0x55,0x24,0xad,0x08,0x5a,0x62,0xda,0x04,0xb4,0x05,
0xb4,0x55,0x52,0x0d,0x94,0x0a,0x4a,0x2a,0x56,0x02,0x6d,0x71,0x6d,0x01,
0xda,0x02,0xd2,0x52,0xa9,0x05,0x49,0x0d,0x2a,0x45,0x2b,0x09,0x56,0x01,
0xb5,0x20,0x6d,0x01,0x59,0x69,0xd4,0x0a,0xa8,0x05,0xa9,0x56,0xa5,0x04,
0x2b,0x09,0x9e,0x38,0xb6,0x08,0xec,0x74,0x6c,0x05,0xd4,0x0a,0xe4,0x6a,
0x52,0x05,0x95,0x0a,0x5a,0x42,0x5b,0x04,0xb6,0x04,0xb4,0x22,0x6a,0x05,
0x52,0x75,0xc9,0x0a,0x52,0x05,0x35,0x55,0x4d,0x0a,0x5a,0x02,0x5d,0x31,
0xb5,0x02,0x6a,0x8a,0x68,0x05,0xa9,0x0a,0x8a,0x6a,0x2a,0x05,0x2d,0x09,
0xaa,0x48,0x5a,0x01,0xb5,0x09,0xb0,0x39,0x64,0x05,0x25,0x75,0x95,0x0a,
0x96,0x04,0x4d,0x54,0xad,0x04,0xda,0x04,0xd4,0x44,0xb4,0x05,0x54,0x85,
0x52,0x0d,0x92,0x0a,0x56,0x6a,0x56,0x02,0x6d,0x02,0x6a,0x41,0xda,0x02,
0xb2,0xa1,0xa9,0x05,0x49,0x0d,0x0a,0x6d,0x2a,0x09,0x56,0x01,0xad,0x50,
0x6d,0x01,0xd9,0x02,0xd1,0x3a,0xa8,0x05,0x29,0x85,0xa5,0x0c,0x2a,0x09,
0x96,0x54,0xb6,0x08,0x6c,0x09,0x64,0x45,0xd4,0x0a,0xa4,0x05,0x51,0x25,
0x95,0x0a,0x2a,0x72,0x5b,0x04,0xb6,0x04,0xac,0x52,0x6a,0x05,0xd2,0x0a,
0xa2,0x4a,0x4a,0x05,0x55,0x94,0x2d,0x0a,0x5a,0x02,0x75,0x61,0xb5,0x02,
0x6a,0x03,0x61,0x45,0xa9,0x0a,0x4a,0x05,0x25,0x25,0x2d,0x09,0x9a,0x68,
0xda,0x08,0xb4,0x09,0xa8,0x59,0x54,0x03,0xa5,0x0a,0x91,0x3a,0x96,0x04,
0xad,0xb0,0xad,0x04,0xda,0x04,0xf4,0x62,0xb4,0x05,0x54,0x0b,0x44,0x5d,
0x52,0x0a,0x95,0x04,0x55,0x22,0x6d,0x02,0x5a,0x71,0xda,0x02,0xaa,0x05,
0xb2,0x55,0x49,0x0b,0x4a,0x0a,0x2d,0x39,0x36,0x01,0x6d,0x80,0x6d,0x01,
0xd9,0x02,0xe9,0x6a,0xa8,0x05,0x29,0x0b,0x9a,0x4c,0xaa,0x08,0xb6,0x08,
0xb4,0x38,0x6c,0x09,0x54,0x75,0xd4,0x0a,0xa4,0x05,0x45,0x55,0x95,0x0a,
0x9a,0x04,0x55,0x44,0xb5,0x04,0x6a,0x82,0x6a,0x05,0xd2,0x0a,0x92,0x6a,
0x4a,0x05,0x55,0x0a,0x2a,0x4a,0x5a,0x02,0xb5,0x02,0xb2,0x31,0x69,0x03,
0x31,0x73,0xa9,0x0a,0x4a,0x05,0x2d,0x55,0x2d,0x09,0x5a,0x01,0xd5,0x48,
0xb4,0x09,0x68,0x89,0x54,0x0b,0xa4,0x0a,0xa5,0x6a,0x95,0x04,0xad,0x08,
0x6a,0x44,0xda,0x04,0x74,0x05,0xb0,0x25,0x54,0x03
};
// 初始日,公历农历对应日期:
// 公历 1901 年 1 月 1 日,对应农历 4598 年 11 月 11 日
private static int baseYear = 1901;
private static int baseMonth = 1;
private static int baseDate = 1;
private static int baseIndex = 0;
private static int baseChineseYear = 4598-1;
private static int baseChineseMonth = 11;
private static int baseChineseDate = 11;
public int computeChineseFields() {
if (gregorianYear<1901 || gregorianYear>2100) return 1;
int startYear = baseYear;
int startMonth = baseMonth;
int startDate = baseDate;
chineseYear = baseChineseYear;
chineseMonth = baseChineseMonth;
chineseDate = baseChineseDate;
// 第二个对应日,用以提高计算效率
// 公历 2000 年 1 月 1 日,对应农历 4697 年 11 月 25 日
if (gregorianYear >= 2000) {
startYear = baseYear + 99;
startMonth = 1;
startDate = 1;
chineseYear = baseChineseYear + 99;
chineseMonth = 11;
chineseDate = 25;
}
int daysDiff = 0;
for (int i=startYear; i<gregorianYear; i++) {
daysDiff += 365;
if (isGregorianLeapYear(i)) daysDiff += 1; // leap year
}
for (int i=startMonth; i<gregorianMonth; i++) {
daysDiff += daysInGregorianMonth(gregorianYear,i);
}
daysDiff += gregorianDate - startDate;
chineseDate += daysDiff;
int lastDate = daysInChineseMonth(chineseYear, chineseMonth);
int nextMonth = nextChineseMonth(chineseYear, chineseMonth);
while (chineseDate>lastDate) {
if (Math.abs(nextMonth)<Math.abs(chineseMonth)) chineseYear++;
chineseMonth = nextMonth;
chineseDate -= lastDate;
lastDate = daysInChineseMonth(chineseYear, chineseMonth);
nextMonth = nextChineseMonth(chineseYear, chineseMonth);
}
return 0;
}
private static int[] bigLeapMonthYears = {
// 大闰月的闰年年份
6, 14, 19, 25, 33, 36, 38, 41, 44, 52,
55, 79,117,136,147,150,155,158,185,193
};
public static int daysInChineseMonth(int y, int m) {
// 注意:闰月 m < 0
int index = y - baseChineseYear + baseIndex;
int v = 0;
int l = 0;
int d = 30;
if (1<=m && m<=8) {
v = chineseMonths[2*index];
l = m - 1;
if ( ((v>>l)&0x01)==1 ) d = 29;
} else if (9<=m && m<=12) {
v = chineseMonths[2*index+1];
l = m - 9;
if ( ((v>>l)&0x01)==1 ) d = 29;
} else {
v = chineseMonths[2*index+1];
v = (v>>4)&0x0F;
if (v!=Math.abs(m)) {
d = 0;
} else {
d = 29;
for (int i=0; i<bigLeapMonthYears.length; i++) {
if (bigLeapMonthYears[i]==index) {
d = 30;
break;
}
}
}
}
return d;
}
public static int nextChineseMonth(int y, int m) {
int n = Math.abs(m) + 1;
if (m>0) {
int index = y - baseChineseYear + baseIndex;
int v = chineseMonths[2*index+1];
v = (v>>4)&0x0F;
if (v==m) n = -m;
}
if (n==13) n = 1;
return n;
}
private static char[][] sectionalTermMap = {
{7,6,6,6,6,6,6,6,6,5,6,6,6,5,5,6,6,5,5,5,5,5,5,5,5,4,5,5},
{5,4,5,5,5,4,4,5,5,4,4,4,4,4,4,4,4,3,4,4,4,3,3,4,4,3,3,3},
{6,6,6,7,6,6,6,6,5,6,6,6,5,5,6,6,5,5,5,6,5,5,5,5,4,5,5,5,5},
{5,5,6,6,5,5,5,6,5,5,5,5,4,5,5,5,4,4,5,5,4,4,4,5,4,4,4,4,5},
{6,6,6,7,6,6,6,6,5,6,6,6,5,5,6,6,5,5,5,6,5,5,5,5,4,5,5,5,5},
{6,6,7,7,6,6,6,7,6,6,6,6,5,6,6,6,5,5,6,6,5,5,5,6,5,5,5,5,4,5,5,5,5},
{7,8,8,8,7,7,8,8,7,7,7,8,7,7,7,7,6,7,7,7,6,6,7,7,6,6,6,7,7},
{8,8,8,9,8,8,8,8,7,8,8,8,7,7,8,8,7,7,7,8,7,7,7,7,6,7,7,7,6,6,7,7,7},
{8,8,8,9,8,8,8,8,7,8,8,8,7,7,8,8,7,7,7,8,7,7,7,7,6,7,7,7,7},
{9,9,9,9,8,9,9,9,8,8,9,9,8,8,8,9,8,8,8,8,7,8,8,8,7,7,8,8,8},
{8,8,8,8,7,8,8,8,7,7,8,8,7,7,7,8,7,7,7,7,6,7,7,7,6,6,7,7,7},
{7,8,8,8,7,7,8,8,7,7,7,8,7,7,7,7,6,7,7,7,6,6,7,7,6,6,6,7,7}
};
private static char[][] sectionalTermYear = {
{13,49,85,117,149,185,201,250,250},
{13,45,81,117,149,185,201,250,250},
{13,48,84,112,148,184,200,201,250},
{13,45,76,108,140,172,200,201,250},
{13,44,72,104,132,168,200,201,250},
{5 ,33,68,96 ,124,152,188,200,201},
{29,57,85,120,148,176,200,201,250},
{13,48,76,104,132,168,196,200,201},
{25,60,88,120,148,184,200,201,250},
{16,44,76,108,144,172,200,201,250},
{28,60,92,124,160,192,200,201,250},
{17,53,85,124,156,188,200,201,250}
};
private static char[][] principleTermMap = {
{21,21,21,21,21,20,21,21,21,20,20,21,21,20,20,20,20,20,20,20,20,19,
20,20,20,19,19,20},
{20,19,19,20,20,19,19,19,19,19,19,19,19,18,19,19,19,18,18,19,19,18,
18,18,18,18,18,18},
{21,21,21,22,21,21,21,21,20,21,21,21,20,20,21,21,20,20,20,21,20,20,
20,20,19,20,20,20,20},
{20,21,21,21,20,20,21,21,20,20,20,21,20,20,20,20,19,20,20,20,19,19,
20,20,19,19,19,20,20},
{21,22,22,22,21,21,22,22,21,21,21,22,21,21,21,21,20,21,21,21,20,20,
21,21,20,20,20,21,21},
{22,22,22,22,21,22,22,22,21,21,22,22,21,21,21,22,21,21,21,21,20,21,
21,21,20,20,21,21,21},
{23,23,24,24,23,23,23,24,23,23,23,23,22,23,23,23,22,22,23,23,22,22,
22,23,22,22,22,22,23},
{23,24,24,24,23,23,24,24,23,23,23,24,23,23,23,23,22,23,23,23,22,22,
23,23,22,22,22,23,23},
{23,24,24,24,23,23,24,24,23,23,23,24,23,23,23,23,22,23,23,23,22,22,
23,23,22,22,22,23,23},
{24,24,24,24,23,24,24,24,23,23,24,24,23,23,23,24,23,23,23,23,22,23,
23,23,22,22,23,23,23},
{23,23,23,23,22,23,23,23,22,22,23,23,22,22,22,23,22,22,22,22,21,22,
22,22,21,21,22,22,22},
{22,22,23,23,22,22,22,23,22,22,22,22,21,22,22,22,21,21,22,22,21,21,
21,22,21,21,21,21,22}
};
private static char[][] principleTermYear = {
{13,45,81,113,149,185,201},
{21,57,93,125,161,193,201},
{21,56,88,120,152,188,200,201},
{21,49,81,116,144,176,200,201},
{17,49,77,112,140,168,200,201},
{28,60,88,116,148,180,200,201},
{25,53,84,112,144,172,200,201},
{29,57,89,120,148,180,200,201},
{17,45,73,108,140,168,200,201},
{28,60,92,124,160,192,200,201},
{16,44,80,112,148,180,200,201},
{17,53,88,120,156,188,200,201}
};
public int computeSolarTerms() {
if (gregorianYear<1901 || gregorianYear>2100) return 1;
sectionalTerm = sectionalTerm(gregorianYear, gregorianMonth);
principleTerm = principleTerm(gregorianYear, gregorianMonth);
return 0;
}
public static int sectionalTerm(int y, int m) {
if (y<1901 || y>2100) return 0;
int index = 0;
int ry = y-baseYear+1;
while (ry>=sectionalTermYear[m-1][index]) index++;
int term = sectionalTermMap[m-1][4*index+ry%4];
if ((ry == 121)&&(m == 4)) term = 5;
if ((ry == 132)&&(m == 4)) term = 5;
if ((ry == 194)&&(m == 6)) term = 6;
return term;
}
public static int principleTerm(int y, int m) {
if (y<1901 || y>2100) return 0;
int index = 0;
int ry = y-baseYear+1;
while (ry>=principleTermYear[m-1][index]) index++;
int term = principleTermMap[m-1][4*index+ry%4];
if ((ry == 171)&&(m == 3)) term = 21;
if ((ry == 181)&&(m == 5)) term = 21;
return term;
}
public String toString() {
StringBuffer buf = new StringBuffer();
buf.append("Gregorian Year: "+gregorianYear+"\n");
buf.append("Gregorian Month: "+gregorianMonth+"\n");
buf.append("Gregorian Date: "+gregorianDate+"\n");
buf.append("Is Leap Year: "+isGregorianLeap+"\n");
buf.append("Day of Year: "+dayOfYear+"\n");
buf.append("Day of Week: "+dayOfWeek+"\n");
buf.append("Chinese Year: "+chineseYear+"\n");
buf.append("Heavenly Stem: "+((chineseYear-1)%10)+"\n");
buf.append("Earthly Branch: "+((chineseYear-1)%12)+"\n");
buf.append("Chinese Month: "+chineseMonth+"\n");
buf.append("Chinese Date: "+chineseDate+"\n");
buf.append("Sectional Term: "+sectionalTerm+"\n");
buf.append("Principle Term: "+principleTerm+"\n");
return buf.toString();
}
public String[] getYearTable() {
setGregorian(gregorianYear,1,1);
computeChineseFields();
computeSolarTerms();
String[] table = new String[58]; // 6*9 + 4
table[0] = getTextLine(27, "公历年历:"+gregorianYear);
table[1] = getTextLine(27, "农历年历:"+(chineseYear+1)
+ " ("+stemNames[(chineseYear+1-1)%10]
+ branchNames[(chineseYear+1-1)%12]
+ " - "+animalNames[(chineseYear+1-1)%12]+"年)");
int ln = 2;
String blank = " "
+" " + " ";
String[] mLeft = null;
String[] mRight = null;
for (int i=1; i<=6; i++) {
table[ln] = blank;
ln++;
mLeft = getMonthTable();
mRight = getMonthTable();
for (int j=0; j<mLeft.length; j++) {
String line = mLeft[j] + " " + mRight[j];
table[ln] = line;
ln++;
}
}
table[ln] = blank;
ln++;
table[ln] = getTextLine(0,
"##/## - 公历日期/农历日期,(*)#月 - (闰)农历月第一天");
ln++;
return table;
}
public static String getTextLine(int s, String t) {
String str = " "
+" " + " ";
if (t!=null && s<str.length() && s+t.length()<str.length())
str = str.substring(0,s) + t + str.substring(s+t.length());
return str;
}
private static String[] monthNames =
{"一","二","三","四","五","六","七","八","九","十","十一","十二"};
public String[] getMonthTable() {
setGregorian(gregorianYear,gregorianMonth,1);
computeChineseFields();
computeSolarTerms();
String[] table = new String[8];
String title = null;
if (gregorianMonth<11) title = " ";
else title = " ";
title = title + monthNames[gregorianMonth-1] + "月"
+ " ";
String header = " 日 一 二 三 四 五 六 ";
String blank = " ";
table[0] = title;
table[1] = header;
int wk = 2;
String line = "";
for (int i=1; i<dayOfWeek; i++) {
line += " " + ' ';
}
int days = daysInGregorianMonth(gregorianYear,gregorianMonth);
for (int i=gregorianDate; i<=days; i++) {
line += getDateString() + ' ';
rollUpOneDay();
if (dayOfWeek==1) {
table[wk] = line;
line = "";
wk++;
}
}
for (int i=dayOfWeek; i<=7; i++) {
line += " " + ' ';
}
table[wk] = line;
for (int i=wk+1; i<table.length; i++) {
table[i] = blank;
}
for (int i=0; i<table.length; i++) {
table[i] = table[i].substring(0,table[i].length()-1);
}
return table;
}
private static String[] chineseMonthNames =
{"正","二","三","四","五","六","七","八","九","十","冬","腊"};
private static String[] principleTermNames =
{"大寒","雨水","春分","谷雨","夏满","夏至","大暑","处暑","秋分","霜降",
"小雪","冬至"};
private static String[] sectionalTermNames =
{"小寒","立春","惊蛰","清明","立夏","芒种","小暑","立秋","白露","寒露",
"立冬","大雪"};
public String getDateString() {
String str = "* / ";
String gm = String.valueOf(gregorianMonth);
if (gm.length()==1) gm = ' ' + gm;
String cm = String.valueOf(Math.abs(chineseMonth));
if (cm.length()==1) cm = ' ' + cm;
String gd = String.valueOf(gregorianDate);
if (gd.length()==1) gd = ' ' + gd;
String cd = String.valueOf(chineseDate);
if (cd.length()==1) cd = ' ' + cd;
if (gregorianDate==sectionalTerm) {
str = " "+sectionalTermNames[gregorianMonth-1];
} else if (gregorianDate==principleTerm) {
str = " "+principleTermNames[gregorianMonth-1];
} else if (chineseDate==1 && chineseMonth>0) {
str = " "+chineseMonthNames[chineseMonth-1]+"月";
} else if (chineseDate==1 && chineseMonth<0) {
str = "*"+chineseMonthNames[-chineseMonth-1]+"月";
} else {
str = gd+'/'+cd;
}
return str;
}
public int rollUpOneDay() {
dayOfWeek = dayOfWeek%7 + 1;
dayOfYear++;
gregorianDate++;
int days = daysInGregorianMonth(gregorianYear,gregorianMonth);
if (gregorianDate>days) {
gregorianDate = 1;
gregorianMonth++;
if (gregorianMonth>12) {
gregorianMonth = 1;
gregorianYear++;
dayOfYear = 1;
isGregorianLeap = isGregorianLeapYear(gregorianYear);
}
sectionalTerm = sectionalTerm(gregorianYear,gregorianMonth);
principleTerm = principleTerm(gregorianYear,gregorianMonth);
}
chineseDate++;
days = daysInChineseMonth(chineseYear,chineseMonth);
if (chineseDate>days) {
chineseDate = 1;
chineseMonth = nextChineseMonth(chineseYear,chineseMonth);
if (chineseMonth==1) chineseYear++;
}
return 0;
}
}
本节介绍了中国农历计算程式的使用方法。农历计算程式可以用来计算年历,月历 和日历。
中国农历计算程式有三种使用方法。
一。计算日历元素。使用指令:"java ChineseCalendarGB day yyyy mm dd"。使用实例:
C:\herong>java ChineseCalendarGB day 2010 2 14
Gregorian Year: 2010
Gregorian Month: 2
Gregorian Date: 14
Is Leap Year: false
Day of Year: 45
Day of Week: 1
Chinese Year: 4707
Heavenly Stem: 6
Earthly Branch: 2
Chinese Month: 1
Chinese Date: 1
Sectional Term: 4
Principle Term: 19
二。计算月历表格。使用指令:"java ChineseCalendarGB month yyyy mm"。使用实例:
C:\herong>java ChineseCalendarGB month 2010 2
二月
一 二 三 四 五 六 日
1/18 2/19 3/20 立春 5/22 6/23
7/24 8/25 9/26 10/27 11/28 12/29 13/30
正月 15/ 2 16/ 3 17/ 4 18/ 5 雨水 20/ 7
21/ 8 22/ 9 23/10 24/11 25/12 26/13 27/14
28/15
三。计算年历表格。使用指令:"java ChineseCalendarGB year yyyy"。使用实例:
C:\herong>java ChineseCalendarGB year 2010
公历年历:2010
农历年历:4707 (庚寅 - 虎年)
一月 二月
日 一 二 三 四 五 六 日 一 二 三 四 五 六
1/17 2/18 1/18 2/19 3/20 立春 5/22 6/23
3/19 4/20 小寒 6/22 7/23 8/24 9/25 7/24 8/25 9/26 10/27 11/28 12/29 13/30
10/26 11/27 12/28 13/29 14/30 腊月 16/ 2 正月 15/ 2 16/ 3 17/ 4 18/ 5 雨水 20/ 7
17/ 3 18/ 4 19/ 5 大寒 21/ 7 22/ 8 23/ 9 21/ 8 22/ 9 23/10 24/11 25/12 26/13 27/14
24/10 25/11 26/12 27/13 28/14 29/15 30/16 28/15
31/17
三月 四月
日 一 二 三 四 五 六 日 一 二 三 四 五 六
1/16 2/17 3/18 4/19 5/20 惊蛰 1/17 2/18 3/19
7/22 8/23 9/24 10/25 11/26 12/27 13/28 4/20 清明 6/22 7/23 8/24 9/25 10/26
14/29 15/30 二月 17/ 2 18/ 3 19/ 4 20/ 5 11/27 12/28 13/29 三月 15/ 2 16/ 3 17/ 4
春分 22/ 7 23/ 8 24/ 9 25/10 26/11 27/12 18/ 5 19/ 6 谷雨 21/ 8 22/ 9 23/10 24/11
28/13 29/14 30/15 31/16 25/12 26/13 27/14 28/15 29/16 30/17
五月 六月
日 一 二 三 四 五 六 日 一 二 三 四 五 六
1/18 1/19 2/20 3/21 4/22 5/23
2/19 3/20 4/21 立夏 6/23 7/24 8/25 芒种 7/25 8/26 9/27 10/28 11/29 五月
9/26 10/27 11/28 12/29 13/30 四月 15/ 2 13/ 2 14/ 3 15/ 4 16/ 5 17/ 6 18/ 7 19/ 8
16/ 3 17/ 4 18/ 5 19/ 6 20/ 7 夏满 22/ 9 20/ 9 夏至 22/11 23/12 24/13 25/14 26/15
23/10 24/11 25/12 26/13 27/14 28/15 29/16 27/16 28/17 29/18 30/19
30/17 31/18
七月 八月
日 一 二 三 四 五 六 日 一 二 三 四 五 六
1/20 2/21 3/22 1/21 2/22 3/23 4/24 5/25 6/26 立秋
4/23 5/24 6/25 小暑 8/27 9/28 10/29 8/28 9/29 七月 11/ 2 12/ 3 13/ 4 14/ 5
11/30 六月 13/ 2 14/ 3 15/ 4 16/ 5 17/ 6 15/ 6 16/ 7 17/ 8 18/ 9 19/10 20/11 21/12
18/ 7 19/ 8 20/ 9 21/10 22/11 大暑 24/13 22/13 处暑 24/15 25/16 26/17 27/18 28/19
25/14 26/15 27/16 28/17 29/18 30/19 31/20 29/20 30/21 31/22
九月 十月
日 一 二 三 四 五 六 日 一 二 三 四 五 六
1/23 2/24 3/25 4/26 1/24 2/25
5/27 6/28 7/29 白露 9/ 2 10/ 3 11/ 4 3/26 4/27 5/28 6/29 7/30 寒露 9/ 2
12/ 5 13/ 6 14/ 7 15/ 8 16/ 9 17/10 18/11 10/ 3 11/ 4 12/ 5 13/ 6 14/ 7 15/ 8 16/ 9
19/12 20/13 21/14 22/15 秋分 24/17 25/18 17/10 18/11 19/12 20/13 21/14 22/15 霜降
26/19 27/20 28/21 29/22 30/23 24/17 25/18 26/19 27/20 28/21 29/22 30/23
31/24
十一月 十二月
日 一 二 三 四 五 六 日 一 二 三 四 五 六
1/25 2/26 3/27 4/28 5/29 十月 1/26 2/27 3/28 4/29
立冬 8/ 3 9/ 4 10/ 5 11/ 6 12/ 7 13/ 8 5/30 冬月 大雪 8/ 3 9/ 4 10/ 5 11/ 6
14/ 9 15/10 16/11 17/12 18/13 19/14 20/15 12/ 7 13/ 8 14/ 9 15/10 16/11 17/12 18/13
21/16 小雪 23/18 24/19 25/20 26/21 27/22 19/14 20/15 21/16 冬至 23/18 24/19 25/20
28/23 29/24 30/25 26/21 27/22 28/23 29/24 30/25 31/26
##/## - 公历日期/农历日期,(*)#月 - (闰)农历月第一天
本节介绍了中国二百年(1901年至2100年)年历的格式。
我用上面这个程式制作了二百年年历,1901 年至 2100 年,全部收录在这本书中。
年历格式说明:
- 农历日期列在公历日期后面。
- 节气用节气名称标明。
- 农历每月第一天用月份名称标明。
例如,2000 年一月的表达格式如下:
一月
日 一 二 三 四 五 六
1/25
2/26 3/27 4/28 5/29 小寒 腊月 8/ 2
9/ 3 10/ 4 11/ 5 12/ 6 13/ 7 14/ 8 15/ 9
16/10 17/11 18/12 19/13 20/14 大寒 22/16
23/17 24/18 25/19 26/20 27/21 28/22 29/23
30/24 31/25
其中:
- "1/25" - 表示公历 1 号和农历 25 号。
- "小寒" - 表示节气。
- "腊月" - 表示农历 12 月第一天。
小结:
- 中国公历算法不是太难,每年有十二个月,每月有固定天数,闰年的二月加一天。
- 中国农历的算法相当复杂,每年的月数不固定,每月的天数也不固定。
- 农历二百年(1901年到2010年)可以依靠现有的天文数据来算法。
- 作者编写了Java 源程序用以计算农历二百年年历。
本章列出了全书所印用的参考文献。