来自 http://www.ilikewhite.com/?p=186

中国阴历与阳历的转换实现【java实现】

最近因为项目需求,需要进行阴历和阳历转换。在网上搜了下相关代码,只找到了阳历转为阴历日期的。而还有一份相互转换的,因为闰月考虑不足,有bug存在,且实现过于臃肿,不够通用。
这两天仔细研读了一下阴历和阳历转换原理(发现真的很复杂,都是推算的,没有公式),同时参考了网上阳历转换阴历的代码,抽象出了一个阴历工具jar(zuibon-calendar-1.0.20140116.jar),支持1900年1月31日~2050年1月22日的阴阳历互转。其中阳历转阴历,感谢热心网友(真没找到原作者是谁)的源代码,我只是进行了重新整理和注解。

本jar包括两个类,LunarUtil.java和Lunar.java。LunarUtil.java为阴阳历转换工具类,Lunar.java为阴历数据模型(简单说下,阴历是按照天干地支表示年,存在闰月。所以其中的yyyy年并非我们理解的公历年,而是想对于1900年己亥年的相对年数。老祖宗的计数法换到现在来看,其实真的有一点复杂)。

以下为源代码:

Lunar.java

/**
* Zuibon.com Inc.
* Copyright (c) 2012-2014 All Rights Reserved.
*/
package com.zuibon.bestman.common.util.calendar;

/**
* 阴历日期模型
* @author wendong.wu
* @version $Id: Lunar.java, v 0.1 2014-1-15 下午03:06:31 wendong.wu Exp $
*/
public class Lunar {
/** 年 */
private int year;
/** 月 */
private int month;
/** 日 */
private int day;
/** 该月是否为闰月 */
private boolean leap;

/**
* Getter method for property year.
*
* @return property value of year
*/
public int getYear() {
return year;
}

/**
* Setter method for property year.
*
* @param year value to be assigned to property year
*/
public void setYear(int year) {
this.year = year;
}

/**
* Getter method for property month.
*
* @return property value of month
*/
public int getMonth() {
return month;
}

/**
* Setter method for property month.
*
* @param month value to be assigned to property month
*/
public void setMonth(int month) {
this.month = month;
}

/**
* Getter method for property day.
*
* @return property value of day
*/
public int getDay() {
return day;
}

/**
* Setter method for property day.
*
* @param day value to be assigned to property day
*/
public void setDay(int day) {
this.day = day;
}

/**
* Getter method for property leap.
*
* @return property value of leap
*/
public boolean isLeap() {
return leap;
}

/**
* Setter method for property leap.
*
* @param leap value to be assigned to property leap
*/
public void setLeap(boolean leap) {
this.leap = leap;
}

@Override
public String toString() {
return LunarUtil.cyclical(getYear()) + “年” + (isLeap() ? “闰” : “”)
+ LunarUtil.getChnMonth(getMonth()) + “月” + LunarUtil.getChnDay(getDay());
}
}

LunarUtil.java

/**
* Zuibon.com Inc.
* Copyright (c) 2012-2014 All Rights Reserved.
*/
package com.zuibon.bestman.common.util.calendar;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
* 阴历工具类
* @author wendong.wu
* @version $Id: LunarUtil.java, v 0.1 2014-1-16 下午08:33:58 wendong.wu Exp $
*/
public class LunarUtil {
/** 大写月数 */
private final static String CHN_NUMBER[] = { “一”, “二”, “三”, “四”, “五”, “六”, “七”, “八”, “九”,
“十”, “冬”, “腊” };
/** 阴历月数大小 */
private final static String BIG_OR_SMALL[] = { “大”, “小”, “大”, “小”, “大”, “小”, “大”, “大”, “小”,
“大”, “小”, “大” };
/** 二十四节气 */
/*
private String[] LunarHolDayName = { “小寒”, “大寒”, “立春”, “雨水”, “惊蛰”, “春分”, “清明”, “谷雨”,
“立夏”, “小满”, “芒种”, “夏至”, “小暑”, “大暑”, “立秋”, “处暑”, “白露”, “秋分”, “寒露”, “霜降”, “立冬”, “小雪”,
“大雪”, “冬至” };
*/

/** 中文日期格式 */
static SimpleDateFormat CHN_DATE_FORMAT = new SimpleDateFormat(“yyyy年MM月dd日”);
/** 简要日期格式 */
static SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat(“yyyyMMdd”);
/** 从1900到2050年的阴历信息 */
final static long[] LUNAR_INFO = new long[] { 0x04bd8, 0x04ae0, 0x0a570,
0x054d5, 0x0d260, 0x0d950, 0×16554, 0x056a0, 0x09ad0, 0x055d2, 0x04ae0, 0x0a5b6,
0x0a4d0, 0x0d250, 0x1d255, 0x0b540, 0x0d6a0, 0x0ada2, 0x095b0, 0×14977, 0×04970,
0x0a4b0, 0x0b4b5, 0x06a50, 0x06d40, 0x1ab54, 0x02b60, 0×09570, 0x052f2, 0×04970,
0×06566, 0x0d4a0, 0x0ea50, 0x06e95, 0x05ad0, 0x02b60, 0x186e3, 0x092e0, 0x1c8d7,
0x0c950, 0x0d4a0, 0x1d8a6, 0x0b550, 0x056a0, 0x1a5b4, 0x025d0, 0x092d0, 0x0d2b2,
0x0a950, 0x0b557, 0x06ca0, 0x0b550, 0×15355, 0x04da0, 0x0a5d0, 0×14573, 0x052d0,
0x0a9a8, 0x0e950, 0x06aa0, 0x0aea6, 0x0ab50, 0x04b60, 0x0aae4, 0x0a570, 0×05260,
0x0f263, 0x0d950, 0x05b57, 0x056a0, 0x096d0, 0x04dd5, 0x04ad0, 0x0a4d0, 0x0d4d4,
0x0d250, 0x0d558, 0x0b540, 0x0b5a0, 0x195a6, 0x095b0, 0x049b0, 0x0a974, 0x0a4b0,
0x0b27a, 0x06a50, 0x06d40, 0x0af46, 0x0ab60, 0×09570, 0x04af5, 0×04970, 0x064b0,
0x074a3, 0x0ea50, 0x06b58, 0x055c0, 0x0ab60, 0x096d5, 0x092e0, 0x0c960, 0x0d954,
0x0d4a0, 0x0da50, 0×07552, 0x056a0, 0x0abb7, 0x025d0, 0x092d0, 0x0cab5, 0x0a950,
0x0b4a0, 0x0baa4, 0x0ad50, 0x055d9, 0x04ba0, 0x0a5b0, 0×15176, 0x052b0, 0x0a930,
0×07954, 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 };

/**
* 阴历转阳历
*
*

* 输入的年-1后,循环遍历获取该年天数,一直递减到1900年的己亥年,求出年的总天数。
*

* 再求出该年的阴历月日到春节一月一日的天数(这里就要注意闰月了)。
*

  • 如果传入的月数大于闰月,则要加上闰月的天数。

 

*

  • 如果传入的是闰月,则没关系,直接加上前面几个月的天数和自己的日子。

 

*

* 最后得到该阴历日期到1900年1月31日的天数,再换算出公历Date
*

* @param yyyyMMdd 阴历年月日。特别说明,年不是公历年,是以1900年1月31日己亥年相对的年数。如2014年1月16日为癸巳年 【蛇年】,则传入的年为2013
* @param isLeapMonth 传入的月是否为闰月
* @return
* @throws ParseException
*/
public static Date lunar2Solar(String yyyyMMdd, boolean isLeapMonth) throws ParseException {
//【1】数据准备
Date solar = new Date();//阳历结果日期
int year = Integer.parseInt(yyyyMMdd.substring(0, 4));//取出阴历年
int month = Integer.parseInt(yyyyMMdd.substring(4, 6));//取出阴历月
int day = Integer.parseInt(yyyyMMdd.substring(6, 8));//取出阴历日
int totalDays = 0;//该阴历到公历1900年1月31日的总日子数

//【2】求出该年到春节一月一日的总天数
//2.1.获取该年的闰月
int leapMonth = leapMonth(year);
//2.2.如果该年存在闰月,且传入的月份在闰月后,先加上一个闰月日子数
if (leapMonth > 0 && leapMonth < month) { totalDays += leapDays(year); } //2.3.如果传入的是闰月,则先加上一个该月非闰月的天数,因为下面直接就跳过该月了。 else if (leapMonth == month && isLeapMonth) { totalDays += monthDays(year, month); } //2.4.求出到除本月后,其他先前月份的总日子数 for (–month; month > 0; month–) {
totalDays += monthDays(year, month);
}
//2.5.加上日子的天数
totalDays += day;

//【3】求出到1900年的己亥年,年的总天数。
for (–year; year >= 1900; year–) {
totalDays += yearDays(year);
}

//【4】计算从1900年1月31日到入参阴历的相对时间,以此得出该阴历的阳历日期
Date baseDate = CHN_DATE_FORMAT.parse(“1900年1月31日”);
long totalMs = baseDate.getTime() + totalDays * 24l * 60l * 60l * 1000l;
solar = new Date(totalMs);

return solar;
}

/**
* 阳历日期转换为阴历日期
*
* @param yyyyMMdd
* @return
* @throws ParseException
*/
public static Lunar solar2Lunar(String yyyyMMdd) throws ParseException {
//【1】数据初始化和准备
Lunar lunar = new Lunar();
int monCyl;//从1900年1月31日以来,闰月数
int leapMonth = 0;
Date baseDate = CHN_DATE_FORMAT.parse(“1900年1月31日”);
Date solarDate = SIMPLE_DATE_FORMAT.parse(yyyyMMdd);

//【2】求出和1900年1月31日相差的天数
int offset = (int) ((solarDate.getTime() – baseDate.getTime()) / 86400000L);

//【3】通过相差天数,求出农历年月日
monCyl = 14;
//3.1.求出农历年份
int iYear, daysOfYear = 0;
for (iYear = 1900; iYear < 2050 && offset > 0; iYear++) {
//一直减少从1900年1月31日的年天数,直到减到今年
daysOfYear = yearDays(iYear);
offset -= daysOfYear;
monCyl += 12;
}
if (offset < 0) {
offset += daysOfYear;
iYear–;
monCyl -= 12;
}
/*
* 求出了农历年份。
* 农历年份是以干支标识的,以1900年1月31日己亥年开始。
* 默认1900为己亥年,1901则为辛丑。
* 但这个1901与公历的1901不同,如公历1901年1月还为己亥年,则其阴历年为1900。
*/
lunar.setYear(iYear);

//3.2.计算农历月份。包括是否为闰月
leapMonth = leapMonth(iYear); // 闰哪个月,1-12
boolean leap = false;

//用当年的天数offset,逐个减去每月(农历)的天数,求出当天是本月的第几天
int iMonth, daysOfMonth = 0;
for (iMonth = 1; iMonth < 13 && offset > 0; iMonth++) {
//判断是否为闰月
if (leapMonth > 0 && iMonth == (leapMonth + 1) && !leap) {
–iMonth;
leap = true;
daysOfMonth = leapDays(lunar.getYear());
} else {
daysOfMonth = monthDays(lunar.getYear(), iMonth);
}

offset -= daysOfMonth;
//解除闰月
if (leap && iMonth == (leapMonth + 1))
leap = false;
if (!leap)
monCyl++;
}
//offset为0时,并且刚才计算的月份是闰月,要校正
if (offset == 0 && leapMonth > 0 && iMonth == leapMonth + 1) {
if (leap) {
leap = false;
} else {
leap = true;
–iMonth;
–monCyl;
}
}
//offset小于0时,也要校正
if (offset < 0) { offset += daysOfMonth; –iMonth; –monCyl; } //求出了月日及是否为闰月 lunar.setMonth(iMonth); lunar.setDay(offset + 1); lunar.setLeap(leap); return lunar; } /** * 传回农历 y年的生肖 * * @return */ public static String animalsYear(int year) { final String[] Animals = new String[] { “鼠”, “牛”, “虎”, “兔”, “龙”, “蛇”, “马”, “羊”, “猴”, “鸡”, “狗”, “猪” }; return Animals[(year – 4) % 12]; } /** * 传回干支, 0=甲子 * * @param year * @return */ public static String cyclical(int year) { int num = year – 1900 + 36; return (cyclicalm(num)); } /** * 返回阴历的月份 * * @param month * @return */ public static String getChnMonth(int month) { return CHN_NUMBER[month – 1]; } /** * 返回阴历的天 * * @param day * @return */ public static String getChnDay(int day) { return getChinaDayString(day); } /** * 返回的月份的大或小 * * @param month * @return */ public static String getBigOrSmall(int month) { return BIG_OR_SMALL[month – 1]; } /** * 传回农历 y年的总天数 * * @param yyyy 四位年数 * @return */ private static int yearDays(int yyyy) { int i, sum = 348; for (i = 0×8000; i > 0×8; i >>= 1) {
if ((LUNAR_INFO[yyyy – 1900] & i) != 0)
sum += 1;
}
return (sum + leapDays(yyyy));
}

/**
* 传回农历 y年闰月的天数
*
* @param yyyy 四位年数
* @return
*/
private static int leapDays(int yyyy) {
if (leapMonth(yyyy) != 0) {
if ((LUNAR_INFO[yyyy – 1900] & 0×10000) != 0)
return 30;
else
return 29;
} else
return 0;
}

/**
* 传回农历 y年闰哪个月 1-12 , 没闰传回 0
*
* @param yyyy 四位年数
* @return
*/
private static int leapMonth(int yyyy) {
return (int) (LUNAR_INFO[yyyy – 1900] & 0xf);
}

/**
* 传回农历 y年m月的总天数
*
* @param y
* @param m
* @return
*/
private static int monthDays(int y, int m) {
if ((LUNAR_INFO[y – 1900] & (0×10000 >> m)) == 0)
return 29;
else
return 30;
}

/**
* 传回干支, 0=甲子
*
* @param num
* @return
*/
private static String cyclicalm(int num) {
final String[] Gan = new String[] { “甲”, “乙”, “丙”, “丁”, “戊”, “己”, “庚”, “辛”, “壬”, “癸” };
final String[] Zhi = new String[] { “子”, “丑”, “寅”, “卯”, “辰”, “巳”, “午”, “未”, “申”, “酉”, “戌”,
“亥” };
return (Gan[num % 10] + Zhi[num % 12]);
}

/**
* 将数字日转换为中国日。如21转换为廿一
*
* @param day
* @return
*/
private static String getChinaDayString(int day) {
String chineseTen[] = { “初”, “十”, “廿”, “三” };
int n = day % 10 == 0 ? 9 : day % 10 – 1;
if (day > 30)
return “”;
if (day == 10)
return “初十”;
if (day == 20)
return “二十”;
else
return chineseTen[day / 10] + CHN_NUMBER[n];
}
}

 

参考:http://netfork.iteye.com/blog/277221

Share