最近项目需要展示当天的农历日期,我刚开始以为农历和阳历类似,不过是60年一轮呢。经过翻阅资料才发现,农历是经过观察天体看出来的(其实我也是一知半解,有兴趣的可以自行百度)。不过,也是因为这样,我们无法凭空把阳历转换为阴历。所以我们需要有个表作为参照。
先贴上出处以示尊敬:http://blog.csdn.net/Eacter/article/details/42495291
这个是从1900年到2049年的表:
// 计算阴历日期参照1900年到2049年
private final static int[] LUNAR_INFO = {
0x04bd8, 0x04ae0, 0x0a570, 0x054d5, 0x0d260, 0x0d950, 0x16554, 0x056a0, 0x09ad0, 0x055d2,
0x04ae0, 0x0a5b6, 0x0a4d0, 0x0d250, 0x1d255, 0x0b540, 0x0d6a0, 0x0ada2, 0x095b0, 0x14977,
0x04970, 0x0a4b0, 0x0b4b5, 0x06a50, 0x06d40, 0x1ab54, 0x02b60, 0x09570, 0x052f2, 0x04970,
0x06566, 0x0d4a0, 0x0ea50, 0x06e95, 0x05ad0, 0x02b60, 0x186e3, 0x092e0, 0x1c8d7, 0x0c950,
0x0d4a0, 0x1d8a6, 0x0b550, 0x056a0, 0x1a5b4, 0x025d0, 0x092d0, 0x0d2b2, 0x0a950, 0x0b557,
0x06ca0, 0x0b550, 0x15355, 0x04da0, 0x0a5d0, 0x14573, 0x052d0, 0x0a9a8, 0x0e950, 0x06aa0,
0x0aea6, 0x0ab50, 0x04b60, 0x0aae4, 0x0a570, 0x05260, 0x0f263, 0x0d950, 0x05b57, 0x056a0,
0x096d0, 0x04dd5, 0x04ad0, 0x0a4d0, 0x0d4d4, 0x0d250, 0x0d558, 0x0b540, 0x0b5a0, 0x195a6,
0x095b0, 0x049b0, 0x0a974, 0x0a4b0, 0x0b27a, 0x06a50, 0x06d40, 0x0af46, 0x0ab60, 0x09570,
0x04af5, 0x04970, 0x064b0, 0x074a3, 0x0ea50, 0x06b58, 0x055c0, 0x0ab60, 0x096d5, 0x092e0,
0x0c960, 0x0d954, 0x0d4a0, 0x0da50, 0x07552, 0x056a0, 0x0abb7, 0x025d0, 0x092d0, 0x0cab5,
0x0a950, 0x0b4a0, 0x0baa4, 0x0ad50, 0x055d9, 0x04ba0, 0x0a5b0, 0x15176, 0x052b0, 0x0a930,
0x07954, 0x06aa0, 0x0ad50, 0x05b52, 0x04b60, 0x0a6e6, 0x0a4e0, 0x0d260, 0x0ea65, 0x0d530,
0x05aa0, 0x076a3, 0x096d0, 0x04bd7, 0x04ad0, 0x0a4d0, 0x1d0b6, 0x0d250, 0x0d520, 0x0dd45,
0x0b5a0, 0x056d0, 0x055b2, 0x049b0, 0x0a577, 0x0a4b0, 0x0aa50, 0x1b255, 0x06d20, 0x0ada0
};
这里我觉得有必要解释一下这些东西的意思,防止某天我忘了再去百度。以0x04bd8为例:
首先我们看的出这是5位16进制的数。我们看下面转成二进制的图
我们分别解释一下,我们从最后一位开始,这里的8表示该年有润8月,如果是0的话表示没有闰月。
然后是中间的三位4bd,代表了每个月的天数,0表示29天,1表示30天。
所以按上图中的显示我们可以认为该年1-12月的月份分别是29、30、29、29;30、29、30、30;30、30、29、30;
当然,你也可能会疑惑,这不是还有个润8月的吗,那这个月的天数呢。
别急,这里不是还有一位的吗,最开始的哪一位0表示该年的润月天数0位29天,1为30天。当然,这要是在确定今年有闰月的前提下,这一位才有意义。
那么,这样我们就可以将阳历转为阴历了。这里我要说一下,农历记年是按天干地支算的,年份的话是和阳历同步的。所以呢,经过我的查阅我们认为1900年1月31日表示的是农历1900年正月初一为基准的。
因此我们就可以进行月份查询了。首先,我们需要知道我们要查询的阳历日期。这里以20171025为例:
首先我们判断查询年份是否是在我们的查询区间内以及我们所设置阳历最小的日期:
// 允许输入的最小年份
private final static int MIN_YEAR = 1900;
// 允许输入的最大年份
private final static int MAX_YEAR = 2049;
// 阳历日期计算起点
private final static String START_DATE = "19000130";
这里我们要先计算所查询的日期与START_DATE之间相差的天数(我们用offset表示):
private static int daysBetween(Date startDate, Date endDate) {
int days = 0;
//将转换的两个时间对象转换成Calendar对象
Calendar can1 = Calendar.getInstance();
can1.setTime(startDate);
Calendar can2 = Calendar.getInstance();
can2.setTime(endDate);
//拿出两个年份
int year1 = can1.get(Calendar.YEAR);
int year2 = can2.get(Calendar.YEAR);
//天数
Calendar can = null;
//如果can1 < can2
//减去小的时间在这一年已经过了的天数
//加上大的时间已过的天数
if (can1.before(can2)) {
days -= can1.get(Calendar.DAY_OF_YEAR);
days += can2.get(Calendar.DAY_OF_YEAR);
can = can1;
} else {
days -= can2.get(Calendar.DAY_OF_YEAR);
days += can1.get(Calendar.DAY_OF_YEAR);
can = can2;
}
for (int i = 0; i < Math.abs(year2 - year1); i++) {
//获取小的时间当前年的总天数
days += can.getActualMaximum(Calendar.DAY_OF_YEAR);
//再计算下一年。
can.add(Calendar.YEAR, 1);
}
return days;
}
在这之后,我们需要计算这一年是LUNAR_INFO 中的那一年。
这里是通过天数从1900年递减,最终找到这一年,这里有一个获取某一年天数的方法(我们用temp表示):
private static int getYearDays(int year) {
int sum = 29 * 12;
for (int i = 0x8000; i >= 0x8; i >>= 1) {
if ((LUNAR_INFO[year - 1900] & 0xfff0 & i) != 0) {
sum++;
}
}
return sum + getLeapMonthDays(year);
}
这个方法是通过传入年份,获取该年的天数。然后我们比较offset与该年天数的大小,如果offset比temp大,我们就令offset-=temp,然后计算下一年。直到offset小于等于temp。这里就表示我们找到了对应的年。然后我们在按上述方式获取当前是第几个月和日。最终获取到农历日期,下面是完整代码:
public static String solarToLunar(String solarDate) throws Exception {
int i;
int temp = 0;
int lunarYear;
int lunarMonth; //农历月份
int lunarDay; //农历当月第几天
boolean leapMonthFlag = false;
SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMdd");
Date myDate = null;
Date startDate = null;
try {
myDate = formatter.parse(solarDate);
startDate = formatter.parse(START_DATE);
} catch (ParseException e) {
e.printStackTrace();
}
int offset = daysBetween(startDate, myDate);
for (i = MIN_YEAR; i <= MAX_YEAR; i++) {
temp = getYearDays(i); //求当年农历年天数
if (offset - temp < 1) {
break;
} else {
offset -= temp;
}
}
lunarYear = i;
int leapMonth = getLeapMonth(lunarYear);//计算该年闰哪个月
//设定当年是否有闰月
if (leapMonth > 0) {
isLeapYear = true;
} else {
isLeapYear = false;
}
for (i = 1; i <= 12; i++) {
if (i == leapMonth + 1 && isLeapYear) {
temp = getLeapMonthDays(lunarYear);
isLeapYear = false;
leapMonthFlag = true;
i--;
} else {
temp = getMonthDays(lunarYear, i);
}
offset -= temp;
if (offset <= 0) {
break;
}
}
offset += temp;
lunarMonth = i;
lunarDay = offset;
return "农历:"+(leapMonthFlag & (lunarMonth == leapMonth) ? "闰" : "") + chineseMonthNames[lunarMonth-1] + "月" + chineseDayNames[lunarDay-1];
}
// 允许输入的最小年份
private final static int MIN_YEAR = 1900;
// 允许输入的最大年份
private final static int MAX_YEAR = 2049;
// 当年是否有闰月
private static boolean isLeapYear;
// 阳历日期计算起点
private final static String START_DATE = "19000130";
/**
* 计算两个阳历日期相差的天数。
*
* @param startDate 开始时间
* @param endDate 截至时间
* @return (int)天数
* @author liu 2017-3-2
*/
private static int daysBetween(Date startDate, Date endDate) {
int days = 0;
//将转换的两个时间对象转换成Calendar对象
Calendar can1 = Calendar.getInstance();
can1.setTime(startDate);
Calendar can2 = Calendar.getInstance();
can2.setTime(endDate);
//拿出两个年份
int year1 = can1.get(Calendar.YEAR);
int year2 = can2.get(Calendar.YEAR);
//天数
Calendar can = null;
//如果can1 < can2
//减去小的时间在这一年已经过了的天数
//加上大的时间已过的天数
if (can1.before(can2)) {
days -= can1.get(Calendar.DAY_OF_YEAR);
days += can2.get(Calendar.DAY_OF_YEAR);
can = can1;
} else {
days -= can2.get(Calendar.DAY_OF_YEAR);
days += can1.get(Calendar.DAY_OF_YEAR);
can = can2;
}
for (int i = 0; i < Math.abs(year2 - year1); i++) {
//获取小的时间当前年的总天数
days += can.getActualMaximum(Calendar.DAY_OF_YEAR);
//再计算下一年。
can.add(Calendar.YEAR, 1);
}
return days;
}
/**
* 计算阴历{@code year}年的总天数
*
* @param year 阴历年
* @return (int)总天数
* @author liu 2015-1-5
*/
private static int getYearDays(int year) {
int sum = 29 * 12;
for (int i = 0x8000; i >= 0x8; i >>= 1) {
if ((LUNAR_INFO[year - 1900] & 0xfff0 & i) != 0) {
sum++;
}
}
return sum + getLeapMonthDays(year);
}
/**
* 计算阴历{@code year}年闰月多少天
*
* @param year 阴历年
* @return (int)天数
* @author liu 2015-1-5
*/
private static int getLeapMonthDays(int year) {
if (getLeapMonth(year) != 0) {
if ((LUNAR_INFO[year - 1900] & 0xf0000) == 0) {
return 29;
} else {
return 30;
}
} else {
return 0;
}
}
/**
* 计算阴历 {@code year}年闰哪个月 1-12 , 没闰传回 0
*
* @param year 阴历年
* @return (int)月份
* @author liu 2015-1-5
*/
private static int getLeapMonth(int year) {
return (int) (LUNAR_INFO[year - 1900] & 0xf);
}
/**
* 计算阴历{@code lunarYeay}年{@code month}月的天数
*
* @param lunarYeay 阴历年
* @param month 阴历月
* @return (int)该月天数
* @throws Exception
* @author liu 2015-1-5
*/
private static int getMonthDays(int lunarYeay, int month) throws Exception {
if ((month > 31) || (month < 0)) {
throw (new Exception("月份有错!"));
}
// 0X0FFFF[0000 {1111 1111 1111} 1111]中间12位代表12个月,1为大月,0为小月
int bit = 1 << (16 - month);
if (((LUNAR_INFO[lunarYeay - 1900] & 0x0FFFF) & bit) == 0) {
return 29;
} else {
return 30;
}
}
// 计算阴历日期参照1900年到2049年
private final static int[] LUNAR_INFO = {
0x04bd8, 0x04ae0, 0x0a570, 0x054d5, 0x0d260, 0x0d950, 0x16554, 0x056a0, 0x09ad0, 0x055d2,
0x04ae0, 0x0a5b6, 0x0a4d0, 0x0d250, 0x1d255, 0x0b540, 0x0d6a0, 0x0ada2, 0x095b0, 0x14977,
0x04970, 0x0a4b0, 0x0b4b5, 0x06a50, 0x06d40, 0x1ab54, 0x02b60, 0x09570, 0x052f2, 0x04970,
0x06566, 0x0d4a0, 0x0ea50, 0x06e95, 0x05ad0, 0x02b60, 0x186e3, 0x092e0, 0x1c8d7, 0x0c950,
0x0d4a0, 0x1d8a6, 0x0b550, 0x056a0, 0x1a5b4, 0x025d0, 0x092d0, 0x0d2b2, 0x0a950, 0x0b557,
0x06ca0, 0x0b550, 0x15355, 0x04da0, 0x0a5d0, 0x14573, 0x052d0, 0x0a9a8, 0x0e950, 0x06aa0,
0x0aea6, 0x0ab50, 0x04b60, 0x0aae4, 0x0a570, 0x05260, 0x0f263, 0x0d950, 0x05b57, 0x056a0,
0x096d0, 0x04dd5, 0x04ad0, 0x0a4d0, 0x0d4d4, 0x0d250, 0x0d558, 0x0b540, 0x0b5a0, 0x195a6,
0x095b0, 0x049b0, 0x0a974, 0x0a4b0, 0x0b27a, 0x06a50, 0x06d40, 0x0af46, 0x0ab60, 0x09570,
0x04af5, 0x04970, 0x064b0, 0x074a3, 0x0ea50, 0x06b58, 0x055c0, 0x0ab60, 0x096d5, 0x092e0,
0x0c960, 0x0d954, 0x0d4a0, 0x0da50, 0x07552, 0x056a0, 0x0abb7, 0x025d0, 0x092d0, 0x0cab5,
0x0a950, 0x0b4a0, 0x0baa4, 0x0ad50, 0x055d9, 0x04ba0, 0x0a5b0, 0x15176, 0x052b0, 0x0a930,
0x07954, 0x06aa0, 0x0ad50, 0x05b52, 0x04b60, 0x0a6e6, 0x0a4e0, 0x0d260, 0x0ea65, 0x0d530,
0x05aa0, 0x076a3, 0x096d0, 0x04bd7, 0x04ad0, 0x0a4d0, 0x1d0b6, 0x0d250, 0x0d520, 0x0dd45,
0x0b5a0, 0x056d0, 0x055b2, 0x049b0, 0x0a577, 0x0a4b0, 0x0aa50, 0x1b255, 0x06d20, 0x0ada0
};
private static String[] chineseMonthNames = {"正", "二", "三", "四", "五", "六", "七", "八", "九", "十", "冬", "腊"};
private static String[] chineseDayNames={
"初一","初二","初三","初四","初五","初六","初七","初八","初九","初十",
"十一","十二","十三","十四","十五","十六","十七","十八","十九","二十",
"廿一","廿二","廿三","廿四","廿五","廿六","廿七","廿八","廿九","三十",
};
这里是调用:
solarToLunar("20171025");
最后,因为我这里没有让显示天干地支以及节日节气什么的,所以我就没写。要是在之后闲下来的话,就加上去。