最近遇到處理陰曆日期的問題(生日),就查資料瞭解了一下關於陰曆陽曆的知識。參照百度文庫
下面簡略介紹一下陰曆陽曆轉換的算法原理:
陽曆,有很強的規律性。每年12個月,1、3、5、7、8、10、12月都爲31天;2月份平年28天,能被4除盡的年份裏爲29天,但1900年爲28天;其餘月份爲31天。陰曆卻沒有什麼特定的規律,是根據天文觀測得到的某月是29天還是30天。下面是經過整理的150年內的陰曆數據:
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進制數,共20bit。最後四位,即8,代表該年閏月的月份,爲0則沒有閏月。前四位,即0,在該年有閏月纔有意義,爲0表示閏月29天,爲1表示閏月30天。中間12位,即4bd代表該年12個月每個月的天數,0表示29天,1表示30天。
然後就是根據陽曆的1900年1月31日是陰曆的1900年的正月初一,然後查表得出陰曆和陽曆的關係。
下面上代碼:
package com.wekri.calendar;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
/**
* <b>描述</b>: 日曆轉換工具類:陰曆和陽曆日期互換(陰曆日期範圍19000101~20491229)<br>
* @author liu 2015-1-5
*/
public class CalendarUtil {
// private static final Logger logger = LoggerFactory.getLogger(CalendarUtil.class);
// 計算陰曆日期參照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 final static int MIN_YEAR = 1900;
// 允許輸入的最大年份
private final static int MAX_YEAR = 2049;
// 當年是否有閏月
private static boolean isLeapYear;
// 陽曆日期計算起點
private final static String START_DATE = "19000130";
/**
* 計算陰曆 {@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 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 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;
}
}
/**
* 計算陰曆{@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);
}
/**
* 計算兩個陽曆日期相差的天數。計算不準確,已經廢棄
* @param startDate 開始時間
* @param endDate 截至時間
* @return (int)天數
* @author liu 2015-1-5
*/
@Deprecated
private static int daysBetween2(Date startDate, Date endDate) {
long between_days=(endDate.getTime()-startDate.getTime())/(1000*3600*24);
return Integer.parseInt(String.valueOf(between_days));
}
/**
* 計算兩個陽曆日期相差的天數。
* @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;
}
/**
* 檢查陰曆日期是否合法
* @param lunarYear 陰曆年
* @param lunarMonth 陰曆月
* @param lunarDay 陰曆日
* @param leapMonthFlag 閏月標誌
* @throws Exception
*/
private static void checkLunarDate(int lunarYear, int lunarMonth, int lunarDay, boolean leapMonthFlag) throws Exception {
if ((lunarYear < MIN_YEAR) || (lunarYear > MAX_YEAR)) {
throw(new Exception("非法農曆年份!"));
}
if ((lunarMonth < 1) || (lunarMonth > 12)) {
throw(new Exception("非法農曆月份!"));
}
if ((lunarDay < 1) || (lunarDay > 30)) { // 中國的月最多30天
throw(new Exception("非法農曆天數!"));
}
int leap = getLeapMonth(lunarYear);// 計算該年應該閏哪個月
if ((leapMonthFlag == true) && (lunarMonth != leap)) {
throw(new Exception("非法閏月!"));
}
}
/**
* 陰曆轉換爲陽曆
* @param lunarDate 陰曆日期,格式YYYYMMDD
* @param leapMonthFlag 是否爲閏月
* @return 陽曆日期,格式:YYYYMMDD
* @throws Exception
* @author liu 2015-1-5
*/
public static String lunarToSolar(String lunarDate, boolean leapMonthFlag) throws Exception{
int lunarYear = Integer.parseInt(lunarDate.substring(0, 4));
int lunarMonth = Integer.parseInt(lunarDate.substring(4, 6));
int lunarDay = Integer.parseInt(lunarDate.substring(6, 8));
checkLunarDate(lunarYear, lunarMonth, lunarDay, leapMonthFlag);
int offset = 0;
for (int i = MIN_YEAR; i < lunarYear; i++) {
int yearDaysCount = getYearDays(i); // 求陰曆某年天數
offset += yearDaysCount;
}
//計算該年閏幾月
int leapMonth = getLeapMonth(lunarYear);
if(leapMonthFlag & leapMonth != lunarMonth){
throw(new Exception("您輸入的閏月標誌有誤!"));
}
//當年沒有閏月或月份早於閏月或和閏月同名的月份
if(leapMonth==0|| (lunarMonth < leapMonth) || (lunarMonth==leapMonth && !leapMonthFlag)){
for (int i = 1; i < lunarMonth; i++) {
int tempMonthDaysCount = getMonthDays(lunarYear, i);
offset += tempMonthDaysCount;
}
// 檢查日期是否大於最大天
if (lunarDay > getMonthDays(lunarYear, lunarMonth)) {
throw(new Exception("不合法的農曆日期!"));
}
offset += lunarDay; // 加上當月的天數
}else{//當年有閏月,且月份晚於或等於閏月
for (int i = 1; i < lunarMonth; i++) {
int tempMonthDaysCount = getMonthDays(lunarYear, i);
offset += tempMonthDaysCount;
}
if (lunarMonth>leapMonth) {
int temp = getLeapMonthDays(lunarYear); // 計算閏月天數
offset += temp; // 加上閏月天數
if (lunarDay > getMonthDays(lunarYear, lunarMonth)) {
throw(new Exception("不合法的農曆日期!"));
}
offset += lunarDay;
}else { // 如果需要計算的是閏月,則應首先加上與閏月對應的普通月的天數
// 計算月爲閏月
int temp = getMonthDays(lunarYear, lunarMonth); // 計算非閏月天數
offset += temp;
if (lunarDay > getLeapMonthDays(lunarYear)) {
throw(new Exception("不合法的農曆日期!"));
}
offset += lunarDay;
}
}
SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMdd");
Date myDate = null;
myDate = formatter.parse(START_DATE);
Calendar c = Calendar.getInstance();
c.setTime(myDate);
c.add(Calendar.DATE, offset);
myDate = c.getTime();
return formatter.format(myDate);
}
/**
* 陽曆日期轉換爲陰曆日期
* @param solarDate 陽曆日期,格式YYYYMMDD
* @return 陰曆日期
* @throws Exception
* @author liu 2015-1-5
*/
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 "陰曆:"+lunarYear+"年"+(leapMonthFlag&(lunarMonth==leapMonth)?"閏":"")+lunarMonth+"月"+lunarDay+"日";
}
}
下面測試一下
package calendar;
public class Test {
public static void main(String[] args) throws Exception {
System.out.println(CalendarUtil.lunarToSolar("19901204", false));
System.out.println(CalendarUtil.lunarToSolar("19841021", true));
System.out.println("************");
System.out.println(CalendarUtil.solarToLunar("19000923"));
System.out.println(CalendarUtil.solarToLunar("19000924"));
System.out.println(CalendarUtil.solarToLunar("19001022"));
System.out.println(CalendarUtil.solarToLunar("19001023"));
System.out.println(CalendarUtil.solarToLunar("19900630"));
System.out.println(CalendarUtil.solarToLunar("19841213"));
System.out.println(CalendarUtil.solarToLunar("19910119"));
}
}
結果:
19910119
19841213
************
陰曆:1900年8月30日
陰曆:1900年閏8月1日
陰曆:1900年閏8月29日
陰曆:1900年9月1日
陰曆:1990年閏5月7日
陰曆:1984年閏10月21日
陰曆:1990年12月4日
希望對大家有幫助,歡迎大家吐槽。