1. 程式人生 > >java 農曆處理轉換和顯示輸出

java 農曆處理轉換和顯示輸出

雖然Java 8之後的日期函式庫能夠支援多種曆法,但是中國的農曆還是尚未被納入支援範圍。如果要在Java使用農曆曆法,可以使用「JavaChineseCalendar」這個Java函式庫。JavaChineseCalendar支援農曆和西曆日期的轉換,時間可從西曆的西元1901年2月19日支援到2050年12月31日。另外,它還能計算生辰八字的重量。【以上資訊轉載於官網】

程式碼演示:

農曆 date = 農曆.建立(2015, 12, 12);

String gregorianDate = date.取得西曆();
String chineseDate = date.取得農曆();

西曆:2015-12-16
農曆:2015(乙未、羊年) 冬月 初六


/*
 *
 * Copyright 2015 magiclen.org
 * 
 * Licensed under the Apache License, 版本 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
package org.magiclen.農曆;

import java.time.LocalDate;
import java.util.ArrayList;

/**
 * <p>
 * 在Java上實現出西曆農曆轉換的類別。可支援的西曆範圍為1901/2/19~2050/12/31。
 * </p>
 * <p>
 * 提供有關於西曆、農曆的類別方法。可藉由產生實體達成西曆農曆轉換的功能,而產生實體需使用類別提供的建立方法。
 * </p>
 *
 * @author Magic Len
 */
public class 農曆 {

    // -----類別列舉-----
    /**
     * 列舉中國十天干,甲、乙、丙、丁、戊、己、更、辛、壬、葵。
     */
    public static enum 天干 {

	甲, 乙, 丙, 丁, 戊, 己, 庚, 辛, 壬, 癸;
    }

    /**
     * 列舉中國十二地支,子、醜、寅、卯、辰、巳、午、未、申、酉、戌、亥。
     */
    public static enum 地支 {

	子, 醜, 寅, 卯, 辰, 巳, 午, 未, 申, 酉, 戌, 亥;
    }

    /**
     * 列舉中國十二生肖,鼠、牛、虎、兔、龍、蛇、馬、羊、猴、雞、狗、豬。
     */
    public static enum 生肖 {

	鼠, 牛, 虎, 兔, 龍, 蛇, 馬, 羊, 猴, 雞, 狗, 豬;
    }

    /**
     * 列舉農曆十二個月份名稱,正月、二月、三月、四月、五月、六月、七月、八月、九月、十月、冬月、臘月。
     */
    public static enum 月 {

	正月, 二月, 三月, 四月, 五月, 六月, 七月, 八月, 九月, 十月, 冬月, 臘月;
    }

    /**
     * 列舉農曆三十個天數名稱,初一、初二、...、十一、十二、...、廿一、廿二、...、三十。
     */
    public static enum 日 {

	初一, 初二, 初三, 初四, 初五, 初六, 初七, 初八, 初九, 初十, 十一, 十二, 十三, 十四, 十五, 十六, 十七, 十八, 十九, 二十, 廿一, 廿二, 廿三, 廿四, 廿五, 廿六, 廿七, 廿八, 廿九, 三十;
    }

    // -----類別常數-----
    /**
     * 本類別可從西元1901開始計算到的最大西曆年。
     */
    private static final int 最大西曆年 = 2050;

    /**
     * int型別有32位元(31...0),將第15~3個位元用來分別表示1~13月(含閏年)是否為大月(1為大月有30天;0為小月有29天)。此處有西元1901~2050年的資料。
     */
    private static final int[] 大月 = {
	0x4ae0, 0xa570, 0x5268, 0xd260, 0xd950, 0x6aa8, 0x56a0, 0x9ad0, 0x4ae8, 0x4ae0, //1910
	0xa4d8, 0xa4d0, 0xd250, 0xd528, 0xb540, 0xd6a0, 0x96d0, 0x95b0, 0x49b8, 0x4970, //1920
	0xa4b0, 0xb258, 0x6a50, 0x6d40, 0xada8, 0x2b60, 0x9570, 0x4978, 0x4970, 0x64b0, //1930
	0xd4a0, 0xea50, 0x6d48, 0x5ad0, 0x2b60, 0x9370, 0x92e0, 0xc968, 0xc950, 0xd4a0, //1940
	0xda50, 0xb550, 0x56a0, 0xaad8, 0x25d0, 0x92d0, 0xc958, 0xa950, 0xb4a8, 0x6ca0, //1950
	0xb550, 0x55a8, 0x4da0, 0xa5b0, 0x52b8, 0x52b0, 0xa950, 0xe950, 0x6aa0, 0xad50, //1960
	0xab50, 0x4b60, 0xa570, 0xa570, 0x5260, 0xe930, 0xd950, 0x5aa8, 0x56a0, 0x96d0, //1970
	0x4ae8, 0x4ad0, 0xa4d0, 0xd268, 0xd250, 0xd528, 0xb540, 0xb6a0, 0x96d0, 0x95b0, //1980
	0x49b0, 0xa4b8, 0xa4b0, 0xb258, 0x6a50, 0x6d40, 0xada0, 0xab60, 0x9570, 0x4978, //1990
	0x4970, 0x64b0, 0x6a50, 0xea50, 0x6b28, 0x5ac0, 0xab60, 0x9368, 0x92e0, 0xc960, //2000
	0xd4a8, 0xd4a0, 0xda50, 0x5aa8, 0x56a0, 0xaad8, 0x25d0, 0x92d0, 0xc958, 0xa950, //2010
	0xb4a0, 0xb550, 0xad50, 0x55a8, 0x4ba0, 0xa5b0, 0x52b8, 0x52b0, 0xa930, 0x74a8, //2020
	0x6aa0, 0xad50, 0x4da8, 0x4b60, 0xa570, 0xa4e0, 0xd260, 0xe930, 0xd530, 0x5aa0, //2030
	0x6b50, 0x96d0, 0x4ae8, 0x4ad0, 0xa4d0, 0xd258, 0xd250, 0xd520, 0xdaa0, 0xb5a0, //2040
	0x56d0, 0x4ad8, 0x49b0, 0xa4b8, 0xa4b0, 0xaa50, 0xb528, 0x6d20, 0xada0, 0x55b0 //2050
    };

    /**
     * int型別有32位元(31...0),兩年一組,將第15~8個位元用來表示第一年是閏幾月,第7~0個位元用來表示第二年是閏幾月。若該年數值為0,表示該年沒有閏月。此處有西元1901~2050年的資料。
     */
    private static final int[] 閏月 = {
	0x00, 0x50, 0x04, 0x00, 0x20, //1910
	0x60, 0x05, 0x00, 0x20, 0x70, //1920
	0x05, 0x00, 0x40, 0x02, 0x06, //1930
	0x00, 0x50, 0x03, 0x07, 0x00, //1940
	0x60, 0x04, 0x00, 0x20, 0x70, //1950
	0x05, 0x00, 0x30, 0x80, 0x06, //1960
	0x00, 0x40, 0x03, 0x07, 0x00, //1970
	0x50, 0x04, 0x08, 0x00, 0x60, //1980
	0x04, 0x0a, 0x00, 0x60, 0x05, //1990
	0x00, 0x30, 0x80, 0x05, 0x00, //2000
	0x40, 0x02, 0x07, 0x00, 0x50, //2010
	0x04, 0x09, 0x00, 0x60, 0x04, //2020
	0x00, 0x20, 0x60, 0x05, 0x00, //2030
	0x30, 0xb0, 0x06, 0x00, 0x50, //2040
	0x02, 0x07, 0x00, 0x50, 0x03 //2050
    };

    /**
     * 儲存西曆年和農曆年開始(該年第一天)的偏差量(天數)。此處有西元1901~2050年的資料。
     */
    private static final int[] 新年偏差 = {
	49, 38, 28, 46, 34, 24, 43, 32, 21, 40, //1910
	29, 48, 36, 25, 44, 33, 22, 41, 31, 50, //1920
	38, 27, 46, 35, 23, 43, 32, 22, 40, 29, //1930
	47, 36, 25, 44, 34, 23, 41, 30, 49, 38, //1940
	26, 45, 35, 24, 43, 32, 21, 40, 28, 47, //1950
	36, 26, 44, 33, 23, 42, 30, 48, 38, 27, //1960
	45, 35, 24, 43, 32, 20, 39, 29, 47, 36, //1970
	26, 45, 33, 22, 41, 30, 48, 37, 27, 46, //1980
	35, 24, 43, 32, 50, 39, 28, 47, 36, 26, //1990
	45, 34, 22, 40, 30, 49, 37, 27, 46, 35, //2000
	23, 42, 31, 21, 39, 28, 48, 37, 25, 44, //2010
	33, 22, 40, 30, 49, 38, 27, 46, 35, 24, //2020
	42, 31, 21, 40, 28, 47, 36, 25, 43, 33, //2030
	22, 41, 30, 49, 38, 27, 45, 34, 23, 42, //2040
	31, 21, 40, 29, 47, 36, 25, 44, 32, 22 //2050
    };

    /**
     * 從甲子年到癸亥年共六十年的八字重量。
     */
    private static final int[] 年八字重量 = {
	12, 9, 6, 7, 12, 5, 9, 8, 7, 8, 15, 9,
	16, 8, 8, 12, 12, 6, 8, 7, 5, 15, 6, 16,
	15, 8, 9, 12, 1, 7, 15, 6, 5, 14, 14, 9,
	7, 7, 9, 12, 8, 7, 13, 5, 14, 5, 9, 17,
	5, 7, 12, 8, 8, 6, 19, 6, 8, 16, 1, 7
    };

    /**
     * 從正月到臘月共十二個月的八字重量。
     */
    private static final int[] 月八字重量 = {
	6, 7, 18, 9, 5, 16, 9, 15, 18, 8, 9, 5
    };

    /**
     * 從初一到三十共三十天的八字重量。
     */
    private static final int[] 日八字重量 = {
	5, 1, 8, 15, 16, 15, 8, 16, 8, 16,
	9, 17, 8, 17, 1, 8, 9, 18, 5, 15,
	1, 9, 8, 9, 15, 18, 7, 8, 16, 6
    };

    /**
     * 從子時到亥時共十二地支的八字重量。
     */
    private static final int[] 時辰八字重量 = {
	16, 6, 7, 1, 9, 16, 1, 8, 8, 9, 6, 6, 16
    };

    //-------------------------------類別方法-------------------------------
    /**
     * <p>
     * 程式進入點。若以農曆類別作為程式進入點,則預設顯示今天的西曆與農曆日期。如果有傳入YYYY MM
     * DD三項西曆日期引數,則顯示傳入的該天之西曆與農曆日期。</p>
     *
     * @param 引數 傳入西曆年、月、日
     */
    public static void main(final String[] 引數) {
	final 農曆 農曆一;
	if (引數.length != 3) {
	    final LocalDate currentLocalDate = LocalDate.now();
	    農曆一 = 農曆.建立(currentLocalDate.getYear(), currentLocalDate.getMonthValue(), currentLocalDate.getDayOfMonth());
	} else {
	    try {
		農曆一 = 農曆.建立(Integer.parseInt(引數[0]), Integer.parseInt(引數[1]), Integer.parseInt(引數[2]));
	    } catch (final Exception e) {
		System.out.println("不支援的日期");
		return;
	    }
	}
	System.out.println(農曆一);
    }

    /**
     * <p>
     * 自我測試類別。用以驗證使用本程式轉換的西曆與農曆之正確性。若檢查到錯誤會顯示出來。
     * </p>
     */
    private static void 自動化自我測試() {
	農曆 農曆一, 農曆二; //A用西曆建立農曆,B用農曆建立西曆
	for (int 年 = 1901; 年 <= 2050; ++年) {
	    for (int 月 = 1; 月 <= 12; ++月) {
		for (int 日 = 1; 日 <= 31; ++日) {
		    //西曆建立農曆
		    農曆一 = 農曆.建立(年, 月, 日);
		    if (農曆一 == null) {
			//沒有這個西曆日期
			continue;
		    }
		    //用A轉換出的農曆套給B
		    農曆二 = 農曆.建立(農曆一.農曆年, 農曆一.農曆月, 農曆一.是否為閏月(), 農曆一.農曆日); //B用農曆建立西曆
		    if (農曆二 == null) {
			System.err.printf("-----%n此筆資料無法建立出農曆%n%s%n-----%n", 農曆一);
			continue;
		    }
		    if (!農曆一.equals(農曆二)) { //西曆和農曆互轉必須為相同的結果,所以若A、B不同,表示計算結果有誤。
			System.err.printf("-----%n資料不相等農曆%nA%n%s%n-%nB%n%s%n-----%n", 農曆一, 農曆二);
		    }
		}
	    }
	}
	System.out.println("自動化測試結束");
    }

    /**
     * <p>
     * 判斷傳入的西曆年範圍是否正確。同樣適用於農曆年。
     * </p>
     *
     * @param 西曆年 傳入西曆年。
     * @return 傳回西曆年範圍是否正確。
     */
    public static boolean 西曆年範圍正確(final int 西曆年) {
	return 西曆年 > 1900 && 西曆年 <= 最大西曆年;
    }

    /**
     * 判斷傳入的西曆月範圍是否正確。
     *
     * @param 西曆月 傳入西曆月。
     * @return 傳回西曆月範圍是否正確。
     */
    public static boolean 西曆月範圍正確(final int 西曆月) {
	return 西曆月 >= 1 && 西曆月 <= 12;
    }

    /**
     * <p>
     * 判斷傳入的西曆日範圍是否正確。若正確,表示傳入的西曆日和西曆月也正確。
     * </p>
     *
     * @param 西曆年 傳入西曆年。
     * @param 西曆月 傳入西曆月。
     * @param 西曆日 傳入西曆日。
     * @return 傳回西曆年、月、日範圍是否正確。
     */
    public static boolean 西曆日範圍正確且西曆年月也正確(final int 西曆年, final int 西曆月, final int 西曆日) {
	return 西曆日 >= 1 && 西曆日 <= 西曆的一個月有幾天(西曆年, 西曆月);
    }

    /**
     * <p>
     * 判斷傳入的西曆年是否為閏年。(四年閏、四百年閏、百年不閏)</p>
     *
     * @param 西曆年 傳入西曆年。
     * @return 傳回傳入的西曆年是否為閏年。
     */
    public static boolean 西曆是閏年(final int 西曆年) {
	return ((西曆年 % 4 == 0) && (西曆年 % 100 != 0) || 西曆年 % 400 == 0); //西曆閏年可被4整除但不可被100整除,或是可被400整除
    }

    /**
     * 計算傳入的西曆年共有幾天。
     *
     * @param 西曆年 傳入西曆年。
     * @return 傳回西曆年含有的天數。傳入數值範圍若有誤,則回傳0。
     */
    public static int 西曆一年有幾天(final int 西曆年) {
	if (!西曆年範圍正確(西曆年)) {
	    return 0;
	}
	return 西曆是閏年(西曆年) ? 366 : 365; //閏年366天,非閏年365天。
    }

    /**
     * 計算傳入的西曆年中的西曆月共有幾天。
     *
     * @param 西曆年 傳入西曆年。
     * @param 西曆月 傳入西曆月。
     * @return 傳回西曆年中的西曆月共有幾天。傳入數值範圍若有誤,則回傳0。
     */
    public static int 西曆的一個月有幾天(final int 西曆年, final int 西曆月) {
	if (!西曆年範圍正確(西曆年) || !西曆月範圍正確(西曆月)) {
	    return 0;
	}
	if (西曆月 % 2 == (西曆月 < 8 ? 1 : 0)) {
	    return 31; //大月31天
	} else if (西曆月 == 2) {
	    return 西曆是閏年(西曆年) ? 29 : 28; //閏年2月29天,非閏年2月28天
	} else {
	    return 30; //小月30天
	}
    }

    /**
     * <p>
     * 計算傳入的西曆年月日是該西曆年的第幾天。舉例:2013/1/4,就是第4天。</p>
     *
     * @param 西曆年 傳入西曆年。
     * @param 西曆月 傳入西曆月。
     * @param 西曆日 傳入西曆日。
     * @return 傳回傳入的西曆年月日是該西曆年的第幾天。傳入數值範圍若有誤,則回傳0。
     */
    public static int 西曆日期是該年的第幾天(final int 西曆年, final int 西曆月, final int 西曆日) {
	if (!西曆日範圍正確且西曆年月也正確(西曆年, 西曆月, 西曆日)) {
	    return 0;
	}
	int 總和天數 = 0;
	for (int 月 = 1; 月 < 西曆月; 月++) {
	    總和天數 += 西曆的一個月有幾天(西曆年, 月);
	}
	總和天數 += 西曆日;
	return 總和天數;
    }

    /**
     * <p>
     * 計算傳入的農曆年月日是該農曆年的第幾天。舉例:2013/正月/初五,就是第5天。</p>
     *
     * @param 農曆年 傳入農曆年。
     * @param 農曆月 傳入農曆月。
     * @param 是閏月 傳入的農曆月是否為閏月。
     * @param 農曆日 傳入農曆日。
     * @return 傳回傳入的農曆年月日是該農曆年的第幾天。傳入數值範圍若有誤,則回傳0。
     */
    public static int 農曆日期是該年的第幾天(final int 農曆年, final 農曆.月 農曆月, final boolean 是閏月, final 農曆.日 農曆日) {
	if (農曆日.ordinal() + 1 > 農曆的一個月有幾天(農曆年, 農曆月, 是閏月)) { //防呆
	    return 0;
	}
	int 總和天數 = 農曆日.ordinal() + 1;
	for (int 月 = 1; 月 < 農曆月.ordinal() + 1 + (是閏月 ? 1 : 0); 月++) {
	    總和天數 += 農曆的一個月有幾天(農曆年, 農曆.月.values()[月 - 1], false);
	}
	final 農曆.月 農曆閏月 = 農曆該年的閏月月份(農曆年);
	if (農曆閏月 != null) {
	    if (農曆月.ordinal() > 農曆閏月.ordinal()) {
		總和天數 += 農曆該年的閏月有幾天(農曆年);
	    }
	} else {
	    if (是閏月) { //防呆
		return 0;
	    }
	}
	return 總和天數;
    }

    /**
     * 將傳入的西曆年轉成農曆對應的天干。
     *
     * @param 西曆年 傳入西曆年。
     * @return 傳回傳入的西曆年所對應的天干。傳入數值範圍若有誤,則回傳null。
     */
    public static 農曆.天干 西曆年轉農曆天干(final int 西曆年) {
	if (!西曆年範圍正確(西曆年)) {
	    return null;
	}
	return 農曆.天干.values()[(6 + (西曆年 - 1900)) % 農曆.天干.values().length];
    }

    /**
     * 將傳入的西曆年轉成農曆對應的地支。
     *
     * @param 西曆年 傳入西曆年。
     * @return 傳回傳入的西曆年所對應的地支。傳入數值範圍若有誤,則回傳null。
     */
    public static 農曆.地支 西曆年轉農曆地支(final int 西曆年) {
	if (!西曆年範圍正確(西曆年)) {
	    return null;
	}
	return 農曆.地支.values()[(西曆年 - 1900) % 農曆.地支.values().length];
    }

    /**
     * 將農曆的天干地支,轉換成所有可能的西曆年。
     *
     * @param 農曆年天干 傳入農曆天干。
     * @param 農曆年地支 傳入農曆地支。
     * @return 以整數陣列的形式,傳迴天乾地支可能對應的所有西曆年。
     */
    public static int[] 農曆天干地支換算成西曆年(final 農曆.天干 農曆年天干, final 農曆.地支 農曆年地支) {
	final ArrayList<Integer> 西曆年清單 = new ArrayList<>();
	農曆.天干[] 天干 = 農曆.天干.values();
	農曆.地支[] 地支 = 農曆.地支.values();
	int 天干計數器 = 6;
	int 地支計數器 = 0;
	int 西曆年計數器 = 1900;
	int 增加年數 = 1;
	for (; 西曆年計數器 <= 最大西曆年; 西曆年計數器 += 增加年數) {
	    if (天干[天干計數器 % 天干.length] == 農曆年天干 && 地支[地支計數器 % 地支.length] == 農曆年地支) {
		增加年數 = 60;
		西曆年清單.add(西曆年計數器);
	    } else {
		天干計數器++;
		地支計數器++;
	    }
	}
	final int 西曆年陣列[] = new int[西曆年清單.size()];
	for (int 索引 = 0; 索引 < 西曆年陣列.length; ++索引) {
	    西曆年陣列[索引] = 西曆年清單.get(索引);
	}
	return 西曆年陣列;
    }

    /**
     * 取得農曆該年的閏月月份。
     *
     * @param 農曆年 傳入農曆年。
     * @return 傳回農曆該年對應的閏月月份。傳入數值範圍若有誤,則回傳null。
     */
    public static 農曆.月 農曆該年的閏月月份(final int 農曆年) {
	if (!西曆年範圍正確(農曆年)) {
	    return null;
	}
	int 月 = 閏月[(農曆年 - 1901) / 2];
	int 索引;
	if (農曆年 % 2 == 1) {
	    索引 = ((月 & 0xf0) >> 4) - 1;
	} else {
	    索引 = (月 & 0x0f) - 1;
	}
	if (索引 < 0) {
	    return null;
	} else {
	    return 農曆.月.values()[索引 % 12];
	}
    }

    /**
     * 取得農曆該年的閏月有幾天。
     *
     * @param 農曆年 傳入農曆年。
     * @return 傳回農曆該年對應的閏月有幾天。傳入數值範圍若有誤或是沒有閏月,則回傳0。
     */
    public static int 農曆該年的閏月有幾天(final int 農曆年) {
	if (!西曆年範圍正確(農曆年)) {
	    return 0;
	}
	農曆.月 閏月 = 農曆該年的閏月月份(農曆年);
	if (閏月 == null) {
	    return 0;
	}
	return 農曆的一個月有幾天(農曆年, 閏月, true);
    }

    /**
     * 計算傳入的農曆年中的農曆月共有幾天。
     *
     * @param 農曆年 傳入農曆年。
     * @param 農曆月 傳入農曆月。
     * @param 是閏月 傳入的農曆月是否為閏月。
     * @return 傳回農曆年中的農曆月共有幾天。傳入數值範圍若有誤,則回傳0。
     */
    public static int 農曆的一個月有幾天(final int 農曆年, 農曆.月 農曆月, final boolean 是閏月) {
	if (!西曆年範圍正確(農曆年)) {
	    return 0;
	}
	int 月 = 農曆月.ordinal() + 1;
	農曆.月 閏月 = 農曆該年的閏月月份(農曆年);
	final int 閏月值;
	if (閏月 == null) {
	    閏月值 = 0;
	} else {
	    閏月值 = 閏月.ordinal() + 1;
	}
	if (是閏月) {
	    if (月 != 閏月值) { //防呆
		return 0;
	    } else { //此為閏月需往計算之後一個月的天數
		if ((大月[農曆年 - 1901] & (0x8000 >> 閏月值)) == 0) {
		    return 29;
		} else {
		    return 30;
		}
	    }
	} else {
	    if ((閏月值 > 0) && (月 > 閏月值)) { //若今年有閏月,且該西曆月應在閏月之後再加一個月
		月++;
	    }
	    if ((大月[農曆年 - 1901] & (0x8000 >> (月 - 1))) == 0) {
		return 29;
	    } else {
		return 30;
	    }
	}
    }

    /**
     * 取得傳入的農曆年共有幾天。
     *
     * @param 農曆年 傳入農曆年。
     * @return 傳回農曆年含有的天數。傳入數值範圍若有誤,則回傳0。
     */
    public static int 農曆一年有幾天(final int 農曆年) {
	int 總和天數 = 0;
	for (int 索引 = 0; 索引 < 12; 索引++) {
	    總和天數 += 農曆的一個月有幾天(農曆年, 農曆.月.values()[索引], false);
	}
	總和天數 += 農曆該年的閏月有幾天(農曆年);
	return 總和天數;
    }

    /**
     * 傳入出生年月日和出生時辰(生辰八字),計算此八字有幾兩重。
     *
     * @param 出生年月日 傳入此農曆類別的出生年月日。
     * @param 出生時辰
     * <p>
     * 傳入以地支表示的出生時辰。(子:23~1、醜:1~3、寅:3~5、卯:5~7、辰:7~9、巳:9~11、午:11~13、未:13~15、申:15~17、酉:17~19、戌:19~21、亥:21~23)
     * </p>
     * @return 傳回八字計算之後的重量(單位:兩)。
     */
    public static float 計算八字有幾兩重(final 農曆 出生年月日, final 農曆.地支 出生時辰) {
	int 總和重量 = 0;
	int 年計數器 = 0, 天干計數器 = 0, 地支計數器 = 0;
	while (!(天干.values()[(天干計數器++) % 天干.values().length] == 出生年月日.取得天干() & 地支.values()[(地支計數器++) % 地支.values().length] == 出生年月日.取得地支())) {
	    年計數器++;
	}
	總和重量 += 年八字重量[年計數器];
	總和重量 += 月八字重量[出生年月日.取得農曆月().ordinal()];
	總和重量 += 日八字重量[出生年月日.取得農曆日().ordinal()];
	總和重量 += 時辰八字重量[出生時辰.ordinal()];
	return 總和重量 / 10.0f;
    }

    /**
     * <p>
     * 建立農曆的類別方法。傳入西曆年、月、日。</p>
     *
     * @param 西曆年 傳入西曆年。
     * @param 西曆月 傳入西曆月。
     * @param 西曆日 傳入西曆日。
     * @return 傳回建立出來的農曆實體。
     */
    public static 農曆 建立(final int 西曆年, final int 西曆月, final int 西曆日) {
	if (!西曆日範圍正確且西曆年月也正確(西曆年, 西曆月, 西曆日)) {
	    return null;
	}
	農曆 o = new 農曆();
	return o.讀入西曆(西曆年, 西曆月, 西曆日) ? o : null;
    }

    /**
     * <p>
     * 建立農曆的類別方法。傳入農曆年、月、日。</p>
     *
     * @param 農曆年 傳入農曆年。
     * @param 農曆月 傳入農曆月。
     * @param 是閏月 傳入的農曆月是否為閏月。
     * @param 農曆日 傳入農曆日。
     * @return 傳回建立出來的農曆實體。
     */
    public static 農曆 建立(final int 農曆年, final 農曆.月 農曆月, final boolean 是閏月, final 農曆.日 農曆日) {
	if (!西曆年範圍正確(農曆年)) {
	    return null;
	}
	農曆 o = new 農曆();
	return o.讀入農曆(農曆年, 農曆月, 是閏月, 農曆日) ? o : null;
    }

    // -----物件變數-----
    private 農曆.天干 農曆年天干;
    private 農曆.地支 農曆年地支;
    private 農曆.月 農曆月;
    private 農曆.日 農曆日;
    private boolean 是閏月;
    private int 西曆年, 西曆月, 西曆日, 農曆年;

    // -----建構子-----
    /**
     * 私有建構子,無法被外部類別使用,使這個類別無法被實體化。
     */
    private 農曆() { //禁止外部類別直接使用建構子建構物件
    }

    // -----物件方法-----
    /**
     * 取得西曆年。
     *
     * @return 傳回西曆年。
     */
    public int 取得西曆年() {
	return 西曆年;
    }

    /**
     * 取得西曆月。
     *
     * @return 傳回西曆月。
     */
    public int 取得西曆月() {
	return 西曆月;
    }

    /**
     * 取得西曆日。
     *
     * @return 傳回西曆日。
     */
    public int 取得西曆日() {
	return 西曆日;
    }

    /**
     * 取得農曆年。
     *
     * @return 傳回農曆年。
     */
    public int 取得農曆年() {
	return 農曆年;
    }

    /**
     * 取得農曆年對應的天干。
     *
     * @return 傳迴天幹。
     */
    public 農曆.天干 取得天干() {
	return 農曆年天干;
    }

    /**
     * 取得農曆年對應的地支。
     *
     * @return 傳回地支。
     */
    public 農曆.地支 取得地支() {
	return 農曆年地支;
    }

    /**
     * 取得農曆月。
     *
     * @return 傳回農曆月。
     */
    public 農曆.月 取得農曆月() {
	return 農曆月;
    }

    /**
     * 取得農曆日。
     *
     * @return 傳回農曆日。
     */
    public 農曆.日 取得農曆日() {
	return 農曆日;
    }

    /**
     * 判斷農曆月是否為閏月。
     *
     * @return 傳回農曆月是否為閏月。
     */
    public boolean 是否為閏月() {
	return 是閏月;
    }

    /**
     * 取得農曆年對應的生肖。
     *
     * @return 傳回生肖。
     */
    public 農曆.生肖 取得生肖() {
	return 農曆.生肖.values()[取得地支().ordinal()]; //生肖順序就是地支順序。
    }

    /**
     * 傳入出生時辰,和此物件本身的年月日湊成完整的八字,計算八字重量。
     *
     * @param 出生時辰
     * <p>
     * 傳入以地支表示的出生時辰。(子:23~1、醜:1~3、寅:3~5、卯:5~7、辰:7~9、巳:9~11、午:11~13、未:13~15、申:15~17、酉:17~19、戌:19~21、亥:21~23)</p>
     * @return 傳回八字計算之後的重量(單位:兩)。
     */
    public float 計算八字有幾兩重(final 農曆.地支 出生時辰) {
	return 計算八字有幾兩重(this, 出生時辰);
    }

    /**
     * <p>
     * 私有方法。以西曆計算農曆並建立農曆。</p>
     *
     * @param 西曆年 傳入西曆年。
     * @param 西曆月 傳入西曆月。
     * @param 西曆日 傳入西曆日。
     * @return 傳回農曆是否建立成功。
     */
    private boolean 讀入西曆(final int 西曆年, final int 西曆月, final int 西曆日) {
	int 農曆年, 農曆月, 農曆日;
	int 天數差距 = 西曆日期是該年的第幾天(西曆年, 西曆月, 西曆日) - 1;
	農曆.月 暫存農曆月 = 農曆該年的閏月月份(西曆年);
	int 農曆閏月 = ((暫存農曆月 != null) ? 暫存農曆月.ordinal() + 1 : 0);

	//天數差距目前存的是西曆日期與該西曆年第一天(新年)的天數差距
	if (天數差距 < 新年偏差[西曆年 - 1901]) { //若天數差距比「西曆新年與對應農曆年新年」之天數差距小,表示此西曆日期尚未進入下一個農曆年(尚未摸到正月,還在農曆年尾)
	    暫存農曆月 = 農曆該年的閏月月份(西曆年 - 1);
	    農曆閏月 = ((暫存農曆月 != null) ? 暫存農曆月.ordinal() + 1 : 0);

	    農曆年 = 西曆年 - 1; //農曆年必須對應去年西曆年
	    if (農曆年 < 1901) { //若年份超出範圍
		return false; //讀入失敗
	    }
	    天數差距 = 新年偏差[西曆年 - 1901] - 天數差距; //此時天數差距為此西曆日期到該西曆年應該對應的農曆年新年之差距天數(若西曆日期為2/3,農曆新年對應的西曆日期為2/10,則兩天數差距為40-(30+3)=7)
	    農曆月 = 12;
	    while (true) {
		if (農曆閏月 == 農曆月) {
		    int 農曆閏月天數 = 農曆該年的閏月有幾天(農曆年);
		    if (天數差距 > 農曆閏月天數) {
			天數差距 -= 農曆閏月天數;
		    } else {
			是閏月 = true;
		    }
		}
		int 農曆月天數 = 農曆的一個月有幾天(農曆年, 農曆.月.values()[農曆月 - 1], false);
		if (天數差距 > 農曆月天數) {
		    天數差距 -= 農曆月天數;
		    是閏月 = false;
		    農曆月--;
		} else {
		    break;
		}
	    }
	    if (天數差距 == 0) {
		農曆日 = 1;
		農曆月++;
	    } else {
		農曆日 = 農曆的一個月有幾天(農曆年, 農曆.月.values()[農曆月 - 1], 是閏月) - 天數差距 + 1;
	    }
	} else { //若天數差距沒比「西曆新年與對應農曆年新年」之天數差距小,表示此西曆日期已經進入下一個農曆年(已摸到正月,從在農曆年頭開始)
	    農曆年 = 西曆年; //農曆年就是西曆年
	    天數差距 -= 新年偏差[西曆年 - 1901]; //此時天數差距為西曆日期與對應農曆年第一天之距離(若西曆日期為2/23(此時天數差距為53),而對應農曆年第一天是西曆的2/10(新年偏差為40),則這兩個日期的天數差距為53-40=13)
	    農曆月 = 1;
	    while (true) {
		int 農曆月天數 = 農曆的一個月有幾天(農曆年, 農曆.月.values()[農曆月 - 1], false);

		if (天數差距 >= 農曆月天數) {
		    天數差距 -= 農曆月天數;
		    if (農曆閏月 == 農曆月) {
			int 農曆閏月天數 = 農曆該年的閏月有幾天(西曆年);
			if (天數差距 >= 農曆閏月天數) {
			    天數差距 -= 農曆閏月天數;
			} else {
			    是閏月 = true;
			    break;
			}
		    }
		    農曆月++;
		} else {
		    break;
		}
	    }
	    農曆日 = 天數差距 + 1;
	}
	this.西曆年 = 西曆年;
	this.西曆月 = 西曆月;
	this.西曆日 = 西曆日;
	this.農曆年 = 農曆年;
	this.農曆年天干 = 西曆年轉農曆天干(農曆年);
	this.農曆年地支 = 西曆年轉農曆地支(農曆年);
	this.農曆月 = 農曆.月.values()[農曆月 - 1];
	this.農曆日 = 農曆.日.values()[農曆日 - 1];

	return true;
    }

    /**
     * <p>
     * 私有方法。以農曆計算西曆並建立農曆。</p>
     *
     * @param 農曆年 傳入農曆年。
     * @param 農曆月 傳入農曆月。
     * @param 是閏月 傳入的農曆月是否為閏月。
     * @param 農曆日 取得農曆日。
     * @return 傳回農曆是否建立成功。
     */
    private boolean 讀入農曆(final int 農曆年, final 農曆.月 農曆月, final boolean 是閏月, final 農曆.日 農曆日) {
	int 西曆年, 西曆月, 西曆日;
	int 農曆第幾天 = 農曆日期是該年的第幾天(農曆年, 農曆月, 是閏月, 農曆日);
	if (農曆第幾天 == 0) {
	    return false;
	}
	int 天數差距 = 農曆第幾天 - 1 + 新年偏差[農曆年 - 1901]; //天數差距為該農曆日期與對應西曆年新年的天數差距。其實就是轉換成西曆日期後,西曆日期與新年的距離。(舉例,農曆2012/1/3,為第3天,和農曆新年差了2天。加上西曆農曆偏差52天。因此天數差距為54)
	int 西曆年天數 = 西曆一年有幾天(農曆年);
	if (天數差距 >= 西曆年天數) {
	    天數差距 -= 西曆年天數;
	    西曆年 = 農曆年 + 1;
	} else {
	    西曆年 = 農曆年;
	}
	for (西曆月 = 1; 天數差距 >= 西曆的一個月有幾天(西曆年, 西曆月); 西曆月++) {
	    天數差距 -= 西曆的一個月有幾天(西曆年, 西曆月);
	}
	西曆日 = 天數差距 + 1;

	this.西曆年 = 西曆年;
	this.西曆月 = 西曆月;
	this.西曆日 = 西曆日;
	this.農曆年 = 農曆年;
	this.農曆年天干 = 西曆年轉農曆天干(農曆年);
	this.農曆年地支 = 西曆年轉農曆地支(農曆年);
	this.農曆月 = 農曆月;
	this.農曆日 = 農曆日;
	this.是閏月 = 是閏月;
	return true;
    }

    /**
     * 取得農曆字串。
     *
     * @return 傳回農曆字串。
     */
    public String 取得農曆() {
	return String.format("%d(%s%s、%s年) %s%s %s", 取得農曆年(), 取得天干(), 取得地支(), 取得生肖(), 取得農曆月(), 是否為閏月() ? "(閏)" : "", 取得農曆日());
    }

    /**
     * 取得西曆字串。
     *
     * @return 傳回西曆字串。
     */
    public String 取得西曆() {
	return String.format("%d-%d-%d", 取得西曆年(), 取得西曆月(), 取得西曆日());
    }

    /**
     * <p>
     * 取得物件字串。包含著西曆與農曆的年月日</p>
     *
     * @return 傳回物件字串。
     */
    @Override
    public String toString() {
	return String.format("西曆:%s%n農曆:%s", 取得西曆(), 取得農曆());
    }

    /**
     * 判斷兩農曆物件是否相同。
     *
     * @param 物件 傳入物件。
     * @return 傳回兩農曆物件是否相同(西曆農曆日期相同)。
     */
    @Override
    public boolean equals(final Object 物件) {
	if (物件 == null) {
	    return false;
	}
	if (物件 instanceof 農曆) {
	    return this.hashCode() == 物件.hashCode();
	}
	return false;
    }

    /**
     * 計算並取得農曆的雜湊值。
     *
     * @return 傳回農曆的雜湊值。
     */
    @Override
    public int hashCode() {
	int 雜湊 = 7;
	雜湊 = 61 * 雜湊 + this.農曆月.hashCode();
	雜湊 = 61 * 雜湊 + this.農曆日.hashCode();
	雜湊 = 61 * 雜湊 + (this.是閏月 ? 1 : 0);
	雜湊 = 61 * 雜湊 + this.西曆年;
	雜湊 = 61 * 雜湊 + this.西曆月;
	雜湊 = 61 * 雜湊 + this.西曆日;
	雜湊 = 61 * 雜湊 + this.農曆年;
	return 雜湊;
    }
}

來源資訊

https://magiclen.org/java-chinese-calendar/

程式碼更新網站

https://github.com/magiclen/JavaChineseCalendar