????本文將從編程角度介紹一下日歷,包括閏年、閏月的判斷和計(jì)算,天干地支的表示,陽(yáng)歷、農(nóng)歷之間的相互轉(zhuǎn)換,最后再介紹一種應(yīng)用場(chǎng)景。不包含UI上的繪制,只提供數(shù)據(jù)上的支撐。
(1)日歷基礎(chǔ)知識(shí)
????從陽(yáng)歷來(lái)講,一年有365天或366天。平年2月28天,閏年2月29天。大月31天,小月30天。1、3、5、7、9、10、12月固定為大月,4、6、9、11月固定為小月。地球繞太陽(yáng)轉(zhuǎn)一周,就是一年。
????從農(nóng)歷來(lái)講,平年有354或355天。大月30天,小月29天,閏年多出一個(gè)月。大月、小月并不固定。月亮繞地球一周,就是一月,初一為“朔日”,十五為“望日”。
????陽(yáng)歷閏年判斷:能被400整除或能被4整除但不能被100整除的年。如2000、2012、2020是閏年,2010、2022、2023是平年。每4年一閏。
????農(nóng)歷閏年判斷:含閏月的年份即閏年。比如2020年、今年、2025、2028都是閏年。
????閏月判斷:閏月是農(nóng)歷的概念,并不固定,所以沒(méi)有特定的算法。只能通過(guò)事先準(zhǔn)備好的數(shù)據(jù)來(lái)獲取。
(2)農(nóng)歷閏月信息及處理
????本小節(jié)介紹一下農(nóng)歷的閏月、月份天數(shù)以及如何獲取它們,時(shí)間從1900年到2100年。數(shù)據(jù)采用20位bit的十六進(jìn)制表示,并遵循下面的規(guī)則:
1-4位:如果是閏年,且對(duì)應(yīng)的閏月是大月的話,為1,否則為0;只有這兩個(gè)取值;
5-16位:除了閏月外的正常月份是大月還是小月,1為30天,0為29天;
17-20位:非閏年為0,大于0表示閏月月份,僅當(dāng)存在閏月的情況下有意義。
舉例說(shuō)明:今年(2023)在后面的數(shù)據(jù)中對(duì)應(yīng)的數(shù)據(jù)是0x05b52,轉(zhuǎn)換成二進(jìn)制是:
????0000 0101 1101 0101 0010
后四位不為0,十進(jìn)制是2,表示今年農(nóng)歷會(huì)閏二月;前四位為0,說(shuō)明閏二月只有29天。中間的十二位對(duì)應(yīng)大小月,以5-8位的0101來(lái)說(shuō)明,正月為0,說(shuō)明它是小月,只有29天;二月為1,說(shuō)明是大月,有30天;三月為0,小月,29天;······以此類推。
????具體數(shù)據(jù):
final static long[] lunarInfo = new long[]{
0x04bd8, 0x04ae0, 0x0a570, 0x054d5, 0x0d260, 0x0d950, 0x16554, 0x056a0, 0x09ad0, 0x055d2,//1900-1909
0x04ae0, 0x0a5b6, 0x0a4d0, 0x0d250, 0x1d255, 0x0b540, 0x0d6a0, 0x0ada2, 0x095b0, 0x14977,//1910-1919
0x04970, 0x0a4b0, 0x0b4b5, 0x06a50, 0x06d40, 0x1ab54, 0x02b60, 0x09570, 0x052f2, 0x04970,//1920-1929
0x06566, 0x0d4a0, 0x0ea50, 0x06e95, 0x05ad0, 0x02b60, 0x186e3, 0x092e0, 0x1c8d7, 0x0c950,//1930-1939
0x0d4a0, 0x1d8a6, 0x0b550, 0x056a0, 0x1a5b4, 0x025d0, 0x092d0, 0x0d2b2, 0x0a950, 0x0b557,//1940-1949
0x06ca0, 0x0b550, 0x15355, 0x04da0, 0x0a5b0, 0x14573, 0x052b0, 0x0a9a8, 0x0e950, 0x06aa0,//1950-1959
0x0aea6, 0x0ab50, 0x04b60, 0x0aae4, 0x0a570, 0x05260, 0x0f263, 0x0d950, 0x05b57, 0x056a0,//1960-1969
0x096d0, 0x04dd5, 0x04ad0, 0x0a4d0, 0x0d4d4, 0x0d250, 0x0d558, 0x0b540, 0x0b6a0, 0x195a6,//1970-1979
0x095b0, 0x049b0, 0x0a974, 0x0a4b0, 0x0b27a, 0x06a50, 0x06d40, 0x0af46, 0x0ab60, 0x09570,//1980-1989
0x04af5, 0x04970, 0x064b0, 0x074a3, 0x0ea50, 0x06b58, 0x055c0, 0x0ab60, 0x096d5, 0x092e0,//1990-1999
0x0c960, 0x0d954, 0x0d4a0, 0x0da50, 0x07552, 0x056a0, 0x0abb7, 0x025d0, 0x092d0, 0x0cab5,//2000-2009
0x0a950, 0x0b4a0, 0x0baa4, 0x0ad50, 0x055d9, 0x04ba0, 0x0a5b0, 0x15176, 0x052b0, 0x0a930,//2010-2019
0x07954, 0x06aa0, 0x0ad50, 0x05b52, 0x04b60, 0x0a6e6, 0x0a4e0, 0x0d260, 0x0ea65, 0x0d530,//2020-2029
0x05aa0, 0x076a3, 0x096d0, 0x04afb, 0x04ad0, 0x0a4d0, 0x1d0b6, 0x0d250, 0x0d520, 0x0dd45,//2030-2039
0x0b5a0, 0x056d0, 0x055b2, 0x049b0, 0x0a577, 0x0a4b0, 0x0aa50, 0x1b255, 0x06d20, 0x0ada0,//2040-2049
0x14b63, 0x09370, 0x049f8, 0x04970, 0x064b0, 0x168a6, 0x0ea50, 0x06b20, 0x1a6c4, 0x0aae0,//2050-2059
0x0a2e0, 0x0d2e3, 0x0c960, 0x0d557, 0x0d4a0, 0x0da50, 0x05d55, 0x056a0, 0x0a6d0, 0x055d4,//2060-2069
0x052d0, 0x0a9b8, 0x0a950, 0x0b4a0, 0x0b6a6, 0x0ad50, 0x055a0, 0x0aba4, 0x0a5b0, 0x052b0,//2070-2079
0x0b273, 0x06930, 0x07337, 0x06aa0, 0x0ad50, 0x14b55, 0x04b60, 0x0a570, 0x054e4, 0x0d160,//2080-2089
0x0e968, 0x0d520, 0x0daa0, 0x16aa6, 0x056d0, 0x04ae0, 0x0a9d4, 0x0a2d0, 0x0d150, 0x0f252,//2090-2099
0x0d520};//2100
????下面是基于上述數(shù)據(jù)的方法:
????獲取農(nóng)歷閏月信息,沒(méi)有返回0:
/**
* 獲取閏月,沒(méi)有返回0
* @param year 農(nóng)歷年份
*/
public static int leapMonth(int year) {
return (int) (lunarInfo[year - 1900] & 0xf);
}
????獲取農(nóng)歷某年某月的天數(shù)(不含閏月):
/**
* 農(nóng)歷某年某月的天數(shù)
*
* @param year 農(nóng)歷年
* @param m 月
* @return 天數(shù)
*/
public static int monthDays(int year, int m) {
if ((lunarInfo[year - 1900] & (0x10000 >> m)) == 0)
return 29;
else
return 30;
}
????獲取某年閏月的天數(shù):
/**
* 獲取農(nóng)歷某年閏月的天數(shù)
* @param y 農(nóng)歷年
*/
public static int leapDays(int y) {
if (leapMonth(y) != 0) {
if ((lunarInfo[y - 1900] & 0x10000) != 0)
return 30;
else
return 29;
} else
return 0;
}
????獲取某年的總天數(shù),包括含閏月的情況:
/**
* 傳回農(nóng)歷年的總天數(shù)
*
* @param y 農(nóng)歷年
*/
final private static int lYearDays(int y) {
int i, sum = 348;
for (i = 0x8000; i > 0x8; i >>= 1) {
if ((lunarInfo[y - 1900] & i) != 0)
sum += 1;
}
return (sum + leapDays(y));
}
(3)天干地支、生肖和時(shí)辰
????天干地支是我國(guó)古代的一種歷法表示方式。天干有:
甲(jiǎ)、乙(yǐ)、丙(bǐng)、?。╠īng)、戊(wù)、己(jǐ)、庚(gēng)、辛(xīn)、壬(rén)、癸(guǐ)。
????地支有:
子(zǐ)、丑(chǒu)、寅(yín)、卯(mǎo)、辰(chén)、巳(sì)、午(wǔ)、未(wèi)、申(shēn)、酉(yǒu)、戌(xū)、亥(hài)。
????地支和生肖有對(duì)應(yīng)關(guān)系,如下:
子-鼠,丑-牛,寅-虎,卯-兔,辰-龍,巳-蛇, 午-馬,未-羊,申-猴,酉-雞,戌-狗,亥-豬。
????生肖的叫法現(xiàn)在很普遍,比如今年是兔年。換成地支的叫法是:卯年。加上天干的叫法是:癸卯年。這是怎么來(lái)的呢?先來(lái)了解一下天干地支紀(jì)年與農(nóng)歷紀(jì)年的對(duì)應(yīng)關(guān)系和換算表:
????對(duì)應(yīng)關(guān)系:
天干 = year % 10
地支 = year % 12
????換算表:
| 天干 | 甲 | 乙 | 丙 | 丁 | 戊 | 己 | 庚 | 辛 | 壬 | 癸 |
|---|---|---|---|---|---|---|---|---|---|---|
| “” | 4 | 5 | 6 | 7 | 8 | 9 | 0 | 1 | 2 | 3 |
| 地支 | 子 | 丑 | 寅 | 卯 | 辰 | 巳 | 午 | 未 | 申 | 酉 | 戌 | 亥 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| “” | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 0 | 1 | 2 | 3 |
year = 2023,根據(jù)上面的算式,天干=3,地支=7,再通過(guò)換算表即可得到“癸卯年”。一些歷史事件的叫法都是通過(guò)這種換算得來(lái),比如1911年的辛亥革命,1901年的《辛丑條約》等。
????天干地支的組合共有60種,如下圖所示:

????上圖中,是將甲子年作為第一年來(lái)計(jì)算的。在換算表中,甲和子對(duì)應(yīng)的數(shù)值是4。所以這里存在一定的偏移。如果某一年是甲子年,則 (year - 4) 既能被10整除,也能被12整除。10和12的最小公倍數(shù)是60,因此year -4 = 60n,即year = 60n + 4 , n = 0、1、2 ······;符合這個(gè)等式的年份就是甲子年。以今年為基準(zhǔn),下一個(gè)甲子年是2044年,上一個(gè)甲子年是1984年。
????數(shù)據(jù)準(zhǔn)備:
private final static String[] Gan = new String[]{"甲", "乙", "丙", "丁", "戊",
"己", "庚", "辛", "壬", "癸"};
private final static String[] Zhi = new String[]{"子", "丑", "寅", "卯", "辰",
"巳", "午", "未", "申", "酉", "戌", "亥"};
private final static String[] Animals = new String[]{"鼠", "牛", "虎", "兔",
"龍", "蛇", "馬", "羊", "猴", "雞", "狗", "豬"};
????根據(jù)農(nóng)歷年獲取天干地支紀(jì)年:
/**
* 獲取天干地支紀(jì)年
*
* @param y 農(nóng)歷年份
*/
final public static String cyclical(int y) {
int num = y - 4;
return (Gan[num % 10] + Zhi[num % 12] + "年");
}
????這里為什么要減去4呢?是因?yàn)槠屏康木壒?。?shù)組中以甲和子作為第一項(xiàng),第一個(gè)甲子年是第4年,所以偏移量為4。
????生肖計(jì)算:
/**
* 生肖計(jì)算
* @param y 農(nóng)歷年
*/
final public static String animalsYear(int y) {
return Animals[(y - 4) % 12] + "年";
}
????地支除了用來(lái)表示年以外,還用來(lái)表示時(shí)辰,這里稍做記錄,如下:

????時(shí)辰對(duì)應(yīng)的時(shí)間都是固定的,很容易獲取,這里就不貼代碼了。
????地支還可以用來(lái)表示月份信息,如下:

(4)陽(yáng)歷轉(zhuǎn)農(nóng)歷
????本小節(jié)介紹一下如何將陽(yáng)歷轉(zhuǎn)換為農(nóng)歷。首先來(lái)看二組數(shù)據(jù),第一組是關(guān)于陽(yáng)歷的,遵循下面的格式:
????從1887年到2111年的陽(yáng)歷數(shù)據(jù),每一項(xiàng)由年、月、日按照下面的規(guī)則組成:
????????item = (year << 9) | (month << 5) | day
????其中year是從1887-2111依次遞增,month和day對(duì)應(yīng)的是農(nóng)歷正月初一的陽(yáng)歷月、日,比如今年正月初一是陽(yáng)歷1月22號(hào),那么month = 1,day = 22,對(duì)應(yīng)的數(shù)據(jù)項(xiàng)是(2023 << 9) | (1 << 5) | 22 = 0xfce36 。
????具體數(shù)據(jù):
private static int[] solar_1_1 = {1887, 0xec04c, 0xec23f, 0xec435, 0xec649,
0xec83e, 0xeca51, 0xecc46, 0xece3a, 0xed04d, 0xed242, 0xed436,
0xed64a, 0xed83f, 0xeda53, 0xedc48, 0xede3d, 0xee050, 0xee244,
0xee439, 0xee64d, 0xee842, 0xeea36, 0xeec4a, 0xeee3e, 0xef052,
0xef246, 0xef43a, 0xef64e, 0xef843, 0xefa37, 0xefc4b, 0xefe41,
0xf0054, 0xf0248, 0xf043c, 0xf0650, 0xf0845, 0xf0a38, 0xf0c4d,
0xf0e42, 0xf1037, 0xf124a, 0xf143e, 0xf1651, 0xf1846, 0xf1a3a,
0xf1c4e, 0xf1e44, 0xf2038, 0xf224b, 0xf243f, 0xf2653, 0xf2848,
0xf2a3b, 0xf2c4f, 0xf2e45, 0xf3039, 0xf324d, 0xf3442, 0xf3636,
0xf384a, 0xf3a3d, 0xf3c51, 0xf3e46, 0xf403b, 0xf424e, 0xf4443,
0xf4638, 0xf484c, 0xf4a3f, 0xf4c52, 0xf4e48, 0xf503c, 0xf524f,
0xf5445, 0xf5639, 0xf584d, 0xf5a42, 0xf5c35, 0xf5e49, 0xf603e,
0xf6251, 0xf6446, 0xf663b, 0xf684f, 0xf6a43, 0xf6c37, 0xf6e4b,
0xf703f, 0xf7252, 0xf7447, 0xf763c, 0xf7850, 0xf7a45, 0xf7c39,
0xf7e4d, 0xf8042, 0xf8254, 0xf8449, 0xf863d, 0xf8851, 0xf8a46,
0xf8c3b, 0xf8e4f, 0xf9044, 0xf9237, 0xf944a, 0xf963f, 0xf9853,
0xf9a47, 0xf9c3c, 0xf9e50, 0xfa045, 0xfa238, 0xfa44c, 0xfa641,
0xfa836, 0xfaa49, 0xfac3d, 0xfae52, 0xfb047, 0xfb23a, 0xfb44e,
0xfb643, 0xfb837, 0xfba4a, 0xfbc3f, 0xfbe53, 0xfc048, 0xfc23c,
0xfc450, 0xfc645, 0xfc839, 0xfca4c, 0xfcc41, 0xfce36, 0xfd04a,
0xfd23d, 0xfd451, 0xfd646, 0xfd83a, 0xfda4d, 0xfdc43, 0xfde37,
0xfe04b, 0xfe23f, 0xfe453, 0xfe648, 0xfe83c, 0xfea4f, 0xfec44,
0xfee38, 0xff04c, 0xff241, 0xff436, 0xff64a, 0xff83e, 0xffa51,
0xffc46, 0xffe3a, 0x10004e, 0x100242, 0x100437, 0x10064b, 0x100841,
0x100a53, 0x100c48, 0x100e3c, 0x10104f, 0x101244, 0x101438,
0x10164c, 0x101842, 0x101a35, 0x101c49, 0x101e3d, 0x102051,
0x102245, 0x10243a, 0x10264e, 0x102843, 0x102a37, 0x102c4b,
0x102e3f, 0x103053, 0x103247, 0x10343b, 0x10364f, 0x103845,
0x103a38, 0x103c4c, 0x103e42, 0x104036, 0x104249, 0x10443d,
0x104651, 0x104846, 0x104a3a, 0x104c4e, 0x104e43, 0x105038,
0x10524a, 0x10543e, 0x105652, 0x105847, 0x105a3b, 0x105c4f,
0x105e45, 0x106039, 0x10624c, 0x106441, 0x106635, 0x106849,
0x106a3d, 0x106c51, 0x106e47, 0x10703c, 0x10724f, 0x107444,
0x107638, 0x10784c, 0x107a3f, 0x107c53, 0x107e48};
????第二組數(shù)據(jù)是關(guān)于農(nóng)歷的,遵循下面的規(guī)則:
????從后往前計(jì)數(shù),后13位,表示大小月信息。1表示大月,30天,0表示小月,29天。之所以有13位,是因?yàn)榇嬖陂c月的情況。平年第13位永遠(yuǎn)為0,閏年按照閏月信息依次后移一位。比如今年閏2月,那么在這13位中,第三位表示閏二月的大小月情況,第四位表示三月的大小月情況,依次類推。
????前4位,表示閏哪個(gè)月,不足4位的前面補(bǔ)0。比如今年對(duì)應(yīng)的數(shù)據(jù)項(xiàng)是0x49b5,除去后13位得到前四位是0010,十進(jìn)制數(shù)是2,表示閏2月。
????具體數(shù)據(jù):
private static int[] lunar_month_days = {1887, 0x1694, 0x16aa, 0x4ad5,
0xab6, 0xc4b7, 0x4ae, 0xa56, 0xb52a, 0x1d2a, 0xd54, 0x75aa, 0x156a,
0x1096d, 0x95c, 0x14ae, 0xaa4d, 0x1a4c, 0x1b2a, 0x8d55, 0xad4,
0x135a, 0x495d, 0x95c, 0xd49b, 0x149a, 0x1a4a, 0xbaa5, 0x16a8,
0x1ad4, 0x52da, 0x12b6, 0xe937, 0x92e, 0x1496, 0xb64b, 0xd4a,
0xda8, 0x95b5, 0x56c, 0x12ae, 0x492f, 0x92e, 0xcc96, 0x1a94,
0x1d4a, 0xada9, 0xb5a, 0x56c, 0x726e, 0x125c, 0xf92d, 0x192a,
0x1a94, 0xdb4a, 0x16aa, 0xad4, 0x955b, 0x4ba, 0x125a, 0x592b,
0x152a, 0xf695, 0xd94, 0x16aa, 0xaab5, 0x9b4, 0x14b6, 0x6a57,
0xa56, 0x1152a, 0x1d2a, 0xd54, 0xd5aa, 0x156a, 0x96c, 0x94ae,
0x14ae, 0xa4c, 0x7d26, 0x1b2a, 0xeb55, 0xad4, 0x12da, 0xa95d,
0x95a, 0x149a, 0x9a4d, 0x1a4a, 0x11aa5, 0x16a8, 0x16d4, 0xd2da,
0x12b6, 0x936, 0x9497, 0x1496, 0x1564b, 0xd4a, 0xda8, 0xd5b4,
0x156c, 0x12ae, 0xa92f, 0x92e, 0xc96, 0x6d4a, 0x1d4a, 0x10d65,
0xb58, 0x156c, 0xb26d, 0x125c, 0x192c, 0x9a95, 0x1a94, 0x1b4a,
0x4b55, 0xad4, 0xf55b, 0x4ba, 0x125a, 0xb92b, 0x152a, 0x1694,
0x96aa, 0x15aa, 0x12ab5, 0x974, 0x14b6, 0xca57, 0xa56, 0x1526,
0x8e95, 0xd54, 0x15aa, 0x49b5, 0x96c, 0xd4ae, 0x149c, 0x1a4c, //49b5
0xbd26, 0x1aa6, 0xb54, 0x6d6a, 0x12da, 0x1695d, 0x95a, 0x149a,
0xda4b, 0x1a4a, 0x1aa4, 0xbb54, 0x16b4, 0xada, 0x495b, 0x936,
0xf497, 0x1496, 0x154a, 0xb6a5, 0xda4, 0x15b4, 0x6ab6, 0x126e,
0x1092f, 0x92e, 0xc96, 0xcd4a, 0x1d4a, 0xd64, 0x956c, 0x155c,
0x125c, 0x792e, 0x192c, 0xfa95, 0x1a94, 0x1b4a, 0xab55, 0xad4,
0x14da, 0x8a5d, 0xa5a, 0x1152b, 0x152a, 0x1694, 0xd6aa, 0x15aa,
0xab4, 0x94ba, 0x14b6, 0xa56, 0x7527, 0xd26, 0xee53, 0xd54, 0x15aa,
0xa9b5, 0x96c, 0x14ae, 0x8a4e, 0x1a4c, 0x11d26, 0x1aa4, 0x1b54,
0xcd6a, 0xada, 0x95c, 0x949d, 0x149a, 0x1a2a, 0x5b25, 0x1aa4,
0xfb52, 0x16b4, 0xaba, 0xa95b, 0x936, 0x1496, 0x9a4b, 0x154a,
0x136a5, 0xda4, 0x15ac};
????根據(jù)范圍獲取位數(shù)信息:
static int getBitInt(int data, int length, int shift) {
return (data & (((1 << length) - 1) << shift)) >> shift;
}
????它的含義是:
????從后往前數(shù),獲取數(shù)據(jù)的第(shift + 1)到第(shift + length)位。例如:getBitInt(solar11, 5, 0),表示獲取數(shù)據(jù)solar11的后5位信息;getBitInt(solar11, 4, 5)表示獲取第6-9位的信息。
????如果要計(jì)算兩個(gè)日期之間的間隔天數(shù),第一反應(yīng)是創(chuàng)建兩個(gè)Calendar對(duì)象,然后相減得到間隔的毫秒數(shù),再除以每天的毫秒數(shù),即可獲得。但這里忽略了一個(gè)問(wèn)題,默認(rèn)創(chuàng)建的Calendar其實(shí)是GregorianCalendar,它是以1970年1月1日0點(diǎn)0分0秒開(kāi)始計(jì)時(shí)的,即格林威治時(shí)間(GMT),又叫Unix時(shí)間戳。而這里考慮的時(shí)間跨度超出了這個(gè)范圍,所以不能用它來(lái)計(jì)算。
????下面提供了一種辦法,個(gè)人也沒(méi)有完全弄明白為什么這樣寫,但經(jīng)測(cè)試及實(shí)際檢驗(yàn),并沒(méi)有問(wèn)題:
private static long solarToInt(int y, int m, int d) {
m = (m + 9) % 12;
y = y - m / 10;
return 365 * y + y / 4 - y / 100 + y / 400 + (m * 306 + 5) / 10
+ (d - 1);
}
????拿今天(2023/2/1)來(lái)舉例,距離陽(yáng)歷新年已經(jīng)過(guò)去的天數(shù):
????solarToInt(2023, 2, 1) - solarToInt(2023, 1, 1) 剛好等于31;這個(gè)方法可以獲取任意兩個(gè)陽(yáng)歷日期的間隔天數(shù),比如到今天為止,魯迅先生已經(jīng)離世31516天,solarToInt(2023, 2, 1) - solarToInt(1936, 10, 19) = 31516。一想起魯迅先生,眼睛不禁濕潤(rùn)了。
????言歸正傳,現(xiàn)在“萬(wàn)事俱備,只欠東風(fēng)”了。陽(yáng)歷轉(zhuǎn)農(nóng)歷的計(jì)算如下:
public static int[] solarToLunar(int year, int month, int monthDay) {
int[] lunarDate = new int[4];
int index = year - solar_1_1[0];
int data = (year << 9) | (month << 5)
| (monthDay);
int solar11 = 0;
if (solar_1_1[index] > data) {
index--;
}
solar11 = solar_1_1[index];
int y = getBitInt(solar11, 12, 9);
int m = getBitInt(solar11, 4, 5);
int d = getBitInt(solar11, 5, 0);
long offset = solarToInt(year, month,
monthDay) - solarToInt(y, m, d); //已經(jīng)過(guò)去的天數(shù)
int days = lunar_month_days[index];
int leap = getBitInt(days, 4, 13);
int lunarY = index + solar_1_1[0];
int lunarM = 1;
int lunarD = 1;
offset += 1;
for (int i = 0; i < 13; i++) {
int dm = getBitInt(days, 1, 12 - i) == 1 ? 30 : 29;
if (offset > dm) {
lunarM++;
offset -= dm;
} else {
break;
}
}
lunarD = (int) (offset);
lunarDate[0] = lunarY;
lunarDate[1] = lunarM;
boolean isLeap = false;
if (leap != 0 && lunarM > leap) {
lunarDate[1] = lunarM - 1;
if (lunarM == leap + 1) {
isLeap = true;
}
}
lunarDate[2] = lunarD;
lunarDate[3] = isLeap ? 1 : 0;
return lunarDate;
}
????這個(gè)方法用到了上面提到的二組數(shù)據(jù),并根據(jù)各自的規(guī)則來(lái)獲取需要的信息。下面是進(jìn)一步的說(shuō)明:
????方法的輸入是陽(yáng)歷的年、月、日,返回是包含4條數(shù)據(jù)的數(shù)組lunarDate[],lunarDate[0]表示農(nóng)歷年份,lunarDate[1]表示農(nóng)歷月份,lunarDate[2]表示天數(shù),lunarDate[3]表示是否是閏月,是的話為1,否則為0。
????方法里首先將陽(yáng)歷的年、月、日按照上面第一組數(shù)據(jù)的規(guī)則進(jìn)行處理,然后找到對(duì)應(yīng)的數(shù)組下標(biāo)。它對(duì)應(yīng)的數(shù)據(jù)項(xiàng)包含了當(dāng)年農(nóng)歷年首(正月初一)對(duì)應(yīng)的陽(yáng)歷年、月、日,然后依次獲得它們,并根據(jù)輸入?yún)?shù)計(jì)算已經(jīng)過(guò)去的天數(shù)offset。隨后,根據(jù)第二組數(shù)據(jù)的農(nóng)歷信息,獲取閏月leap,并通過(guò)和offset的對(duì)比找到輸入日期對(duì)應(yīng)的月份lunarM。如果是閏月,那么lunarM應(yīng)剛好等于(leap+1)。
????以今年的3月22號(hào)為例,這一天剛好是閏二月的第一天。因?yàn)殚c二月,所以leap = 2。農(nóng)歷新年過(guò)去的天數(shù)offset = solarToInt(2023, 3, 22) - solarToInt(2023, 1, 22) = 59天,今年正月是29天,二月是30天,剛好59天。因此計(jì)算得到lunarM = 3,滿足lunarM=(leap+1)。
????再以4月20號(hào)為例,這是農(nóng)歷三月的第一天。leap=2依然成立,過(guò)去的天數(shù)offset = solarToInt(2023, 4, 20) - solarToInt(2023, 1, 22) = 88天,閏2月是29天,計(jì)算得到lunarM = 4,不滿足lunarM=(leap+1),因此該月不是閏月,但因?yàn)橛虚c二月的存在,所以用(lunarM - 1)進(jìn)行修正,得到正確的農(nóng)歷月份:三月。后面的農(nóng)歷月份計(jì)算也是據(jù)此修正。
(5)農(nóng)歷轉(zhuǎn)陽(yáng)歷
????本小節(jié)介紹農(nóng)歷轉(zhuǎn)陽(yáng)歷。首先看一組數(shù)據(jù),遵循下面的規(guī)則:
????數(shù)據(jù)包括1900年到2099年間農(nóng)歷年份的相關(guān)信息,共24位bit的16進(jìn)制表示,其中:
????1、前4位表示該年閏哪個(gè)月;
????2、5-17位表示農(nóng)歷年份13個(gè)月的大小月分布,0表示小,1表示大;
????3、18-19位表示農(nóng)歷年首對(duì)應(yīng)的公歷月份。
????4、最后5位表示農(nóng)歷年首對(duì)應(yīng)的公歷天數(shù)。
????以今年的數(shù)據(jù)項(xiàng)0x24DAB6為例來(lái)說(shuō)明,它對(duì)應(yīng)的二進(jìn)制數(shù)如下:
????????0 0 1 0 , 0 1 0 0 1 1 0 1 1 0 1 0 1 , 0 1 , 1 0 1 1 0
????前四位為0010,10進(jìn)制數(shù)是2,表示閏二月;5-17位(13位)表示農(nóng)歷大小月信息;18-19位為01,表示農(nóng)歷年首對(duì)應(yīng)的是陽(yáng)歷1月份,從實(shí)際來(lái)看,一般農(nóng)歷年首都在陽(yáng)歷的1月或2月,這里用2位(4種可能)足夠表示了;后5位是1 0 1 1 0,10進(jìn)制數(shù)是22,結(jié)合來(lái)看,表示農(nóng)歷年首對(duì)應(yīng)的是陽(yáng)歷1月22號(hào)。
????這些規(guī)則和上面第(2)小節(jié)的有類似之處,不過(guò)也有不同,請(qǐng)注意區(qū)分。
????具體數(shù)據(jù)如下:
private static final int LUNAR_INFO[] = {
0x84B6BF,/*1900*/
0x04AE53, 0x0A5748, 0x5526BD, 0x0D2650, 0x0D9544, 0x46AAB9, 0x056A4D, 0x09AD42, 0x24AEB6, 0x04AE4A,/*1901-1910*/
0x6A4DBE, 0x0A4D52, 0x0D2546, 0x5D52BA, 0x0B544E, 0x0D6A43, 0x296D37, 0x095B4B, 0x749BC1, 0x049754,/*1911-1920*/
0x0A4B48, 0x5B25BC, 0x06A550, 0x06D445, 0x4ADAB8, 0x02B64D, 0x095742, 0x2497B7, 0x04974A, 0x664B3E,/*1921-1930*/
0x0D4A51, 0x0EA546, 0x56D4BA, 0x05AD4E, 0x02B644, 0x393738, 0x092E4B, 0x7C96BF, 0x0C9553, 0x0D4A48,/*1931-1940*/
0x6DA53B, 0x0B554F, 0x056A45, 0x4AADB9, 0x025D4D, 0x092D42, 0x2C95B6, 0x0A954A, 0x7B4ABD, 0x06CA51,/*1941-1950*/
0x0B5546, 0x555ABB, 0x04DA4E, 0x0A5B43, 0x352BB8, 0x052B4C, 0x8A953F, 0x0E9552, 0x06AA48, 0x6AD53C,/*1951-1960*/
0x0AB54F, 0x04B645, 0x4A5739, 0x0A574D, 0x052642, 0x3E9335, 0x0D9549, 0x75AABE, 0x056A51, 0x096D46,/*1961-1970*/
0x54AEBB, 0x04AD4F, 0x0A4D43, 0x4D26B7, 0x0D254B, 0x8D52BF, 0x0B5452, 0x0B6A47, 0x696D3C, 0x095B50,/*1971-1980*/
0x049B45, 0x4A4BB9, 0x0A4B4D, 0xAB25C2, 0x06A554, 0x06D449, 0x6ADA3D, 0x0AB651, 0x095746, 0x5497BB,/*1981-1990*/
0x04974F, 0x064B44, 0x36A537, 0x0EA54A, 0x86B2BF, 0x05AC53, 0x0AB647, 0x5936BC, 0x092E50, 0x0C9645,/*1991-2000*/
0x4D4AB8, 0x0D4A4C, 0x0DA541, 0x25AAB6, 0x056A49, 0x7AADBD, 0x025D52, 0x092D47, 0x5C95BA, 0x0A954E,/*2001-2010*/
0x0B4A43, 0x4B5537, 0x0AD54A, 0x955ABF, 0x04BA53, 0x0A5B48, 0x652BBC, 0x052B50, 0x0A9345, 0x474AB9,/*2011-2020*/
0x06AA4C, 0x0AD541, 0x24DAB6, 0x04B64A, 0x6a573D, 0x0A4E51, 0x0D2646, 0x5E933A, 0x0D534D, 0x05AA43,/*2021-2030*/
0x36B537, 0x096D4B, 0xB4AEBF, 0x04AD53, 0x0A4D48, 0x6D25BC, 0x0D254F, 0x0D5244, 0x5DAA38, 0x0B5A4C,/*2031-2040*/
0x056D41, 0x24ADB6, 0x049B4A, 0x7A4BBE, 0x0A4B51, 0x0AA546, 0x5B52BA, 0x06D24E, 0x0ADA42, 0x355B37,/*2041-2050*/
0x09374B, 0x8497C1, 0x049753, 0x064B48, 0x66A53C, 0x0EA54F, 0x06AA44, 0x4AB638, 0x0AAE4C, 0x092E42,/*2051-2060*/
0x3C9735, 0x0C9649, 0x7D4ABD, 0x0D4A51, 0x0DA545, 0x55AABA, 0x056A4E, 0x0A6D43, 0x452EB7, 0x052D4B,/*2061-2070*/
0x8A95BF, 0x0A9553, 0x0B4A47, 0x6B553B, 0x0AD54F, 0x055A45, 0x4A5D38, 0x0A5B4C, 0x052B42, 0x3A93B6,/*2071-2080*/
0x069349, 0x7729BD, 0x06AA51, 0x0AD546, 0x54DABA, 0x04B64E, 0x0A5743, 0x452738, 0x0D264A, 0x8E933E,/*2081-2090*/
0x0D5252, 0x0DAA47, 0x66B53B, 0x056D4F, 0x04AE45, 0x4A4EB9, 0x0A4D4C, 0x0D1541, 0x2D92B5 /*2091-2099*/
};
????這里還要用到一組數(shù)據(jù),用來(lái)表示陽(yáng)歷每月前的天數(shù):
/**
* 公歷每月前的天數(shù)
*/
private static final int DAYS_BEFORE_MONTH[] = {0, 31, 59, 90, 120, 151, 181,
212, 243, 273, 304, 334, 365};
/**
* 支持轉(zhuǎn)換的最小農(nóng)歷年份
*/
public static final int MIN_YEAR = 1900;
/**
* 支持轉(zhuǎn)換的最大農(nóng)歷年份
*/
public static final int MAX_YEAR = 2099;
????現(xiàn)在再來(lái)計(jì)算農(nóng)歷轉(zhuǎn)陽(yáng)歷:
/**
* 將農(nóng)歷日期轉(zhuǎn)換為公歷日期
*
* @param year 農(nóng)歷年份
* @param month 農(nóng)歷月
* @param monthDay 農(nóng)歷日
* @param isLeapMonth 該月是否是閏月
* @return 返回農(nóng)歷日期對(duì)應(yīng)的公歷日期,year0, month1, day2.
*/
public static final int[] lunarToSolar(int year, int month, int monthDay,
boolean isLeapMonth) {
int dayOffset;
int leapMonth;
int i;
if (year < MIN_YEAR || year > MAX_YEAR || month < 1 || month > 12
|| monthDay < 1 || monthDay > 30) {
throw new IllegalArgumentException(
"Illegal lunar date, must be like that:\n\t" +
"year : 1900~2099\n\t" +
"month : 1~12\n\t" +
"day : 1~30");
}
dayOffset = (LUNAR_INFO[year - MIN_YEAR] & 0x001F) - 1;
if (((LUNAR_INFO[year - MIN_YEAR] & 0x0060) >> 5) == 2)
dayOffset += 31;
for (i = 1; i < month; i++) {
if ((LUNAR_INFO[year - MIN_YEAR] & (0x80000 >> (i - 1))) == 0)
dayOffset += 29;
else
dayOffset += 30;
}
dayOffset += monthDay;
leapMonth = (LUNAR_INFO[year - MIN_YEAR] & 0xf00000) >> 20;
// 這一年有閏月
if (leapMonth != 0) {
if (month > leapMonth || (month == leapMonth && isLeapMonth)) {
if ((LUNAR_INFO[year - MIN_YEAR] & (0x80000 >> (month - 1))) == 0)
dayOffset += 29;
else
dayOffset += 30;
}
}
if (dayOffset > 366 || (!isLeapYear(year) && dayOffset > 365)) {
if (isLeapYear(year))
dayOffset -= 366;
else
dayOffset -= 365;
year += 1;
}
int[] solarInfo = new int[3];
for (i = 1; i < 13; i++) {
int iPos = DAYS_BEFORE_MONTH[i];
if (isLeapYear(year) && i > 2) {
iPos += 1;
}
if (isLeapYear(year) && i == 2 && iPos + 1 == dayOffset) {
solarInfo[1] = i;
solarInfo[2] = dayOffset - 31;
break;
}
if (iPos >= dayOffset) {
solarInfo[1] = i;
iPos = DAYS_BEFORE_MONTH[i - 1];
if (isLeapYear(year) && i > 2) {
iPos += 1;
}
if (dayOffset > iPos)
solarInfo[2] = dayOffset - iPos;
else if (dayOffset == iPos) {
if (isLeapYear(year) && i == 2)
solarInfo[2] = DAYS_BEFORE_MONTH[i] - DAYS_BEFORE_MONTH[i - 1] + 1;
else
solarInfo[2] = DAYS_BEFORE_MONTH[i] - DAYS_BEFORE_MONTH[i - 1];
} else
solarInfo[2] = dayOffset;
break;
}
}
solarInfo[0] = year;
return solarInfo;
}
/**
* 是否是閏年,是返回true,否則返回false
*
* @param year 陽(yáng)歷年份
*/
public static boolean isLeapYear(int year) {
if (year % 400 == 0 || (year % 4 == 0 && year % 100 != 0)) {
return true;
}
return false;
}
????主要思想是根據(jù)農(nóng)歷年過(guò)去的天數(shù)dayOffset,來(lái)計(jì)算陽(yáng)歷所在的月份和天數(shù)。下面是詳細(xì)的步驟說(shuō)明:
????第(1)步,dayOffset = 農(nóng)歷年首對(duì)應(yīng)的陽(yáng)歷日期距離陽(yáng)歷年首的天數(shù),如果是1月份,直接等于天數(shù);如果是2月,那么dayOffset += 31;因?yàn)殛?yáng)歷1月是大月,有31天。
????第(2)步,根據(jù)輸入?yún)?shù)month,依次獲取對(duì)應(yīng)的農(nóng)歷月份天數(shù),加到dayOffset上;并加上輸入?yún)?shù)monthDay。
????第(3)步,判斷農(nóng)歷閏月情況,如果有閏月,并且輸入?yún)?shù)month正是閏月或者大于閏月,那么dayOffset再加上閏月的天數(shù)。
????第(4)步,進(jìn)行數(shù)據(jù)修正,如果此時(shí)dayOffset大于366,year加一,dayOffset也進(jìn)行相應(yīng)的修正;此外,如果是平年,而dayOffset=366,也進(jìn)行同樣的修正。到此,陽(yáng)歷的年數(shù)已確定下來(lái),接下來(lái)確定陽(yáng)歷的月和天。
????第(5)步,找到大于等于dayOffset對(duì)應(yīng)的月份 i (對(duì)應(yīng)每月前的天數(shù)),恰好為陽(yáng)歷的月;再根據(jù)月內(nèi)的偏移、閏年和是否為2月的情況,計(jì)算得到天。
(6)更大的范圍
????總的來(lái)說(shuō),上面的各種計(jì)算過(guò)程都是基于已有的多組遵循特定格式的數(shù)據(jù)。如果沒(méi)有這些數(shù)據(jù),一切無(wú)從談起。那么,如果想獲取更大范圍的數(shù)據(jù)呢?比如唐、宋、元、明、清,甚至更早的秦、漢、三國(guó)時(shí)候的數(shù)據(jù)。這里記錄一款叫做“壽星萬(wàn)年歷”的日歷,這上面的時(shí)間跨度范圍非常之廣,可稱得上真正的萬(wàn)年歷。數(shù)據(jù)可以它里面獲取,如果研究一下源碼(js實(shí)現(xiàn)),找出用代碼自動(dòng)獲取數(shù)據(jù)的方式,那就再好不過(guò)了。有需要或者感興趣的朋友可以深入研究一下。
(7)某時(shí)段內(nèi)的循環(huán)提醒事件
????上面的小節(jié)都是討論日歷數(shù)據(jù)及其計(jì)算,本小節(jié)介紹一種在實(shí)際場(chǎng)景中的應(yīng)用。
????親朋好友過(guò)生日時(shí),你想及時(shí)給他們送去祝福和禮物。一旦錯(cuò)過(guò),就容易留下遺憾。你可以在日歷上建立提醒事件,好提前得到通知。
????一般來(lái)說(shuō),這種提醒是循環(huán)的,生日每年都要過(guò)。
????從編程角度,當(dāng)日期臨近時(shí),就需要告訴用戶:“你的好友李四三天后過(guò)生日”。用戶的創(chuàng)建日期可以是任意一天,但只要時(shí)間落在[ 今天,3天后 ]這個(gè)范圍內(nèi),就要提醒他。有的人過(guò)陽(yáng)歷生日,有的人過(guò)農(nóng)歷生日,還有大小月、閏月等情況。年三十是過(guò)年,沒(méi)有三十就二十九??傊?,要用最恰當(dāng)?shù)娜掌凇?br> ????擴(kuò)展一下,不止生日,周報(bào)、月報(bào)等,也有提前提醒的需要。問(wèn)題可以更加地一般化,即:在某個(gè)時(shí)間范圍內(nèi),有哪些需要提醒的事件,以及最恰當(dāng)?shù)娘@示時(shí)間是什么?
????在本小節(jié)中,這個(gè)事件限制為循環(huán)提醒事件。非循環(huán)事件只需比較一下日期即可。
????數(shù)據(jù)準(zhǔn)備,事件類Event :
public class Event {
//標(biāo)題
String title;
//內(nèi)容
String description;
//事件開(kāi)始時(shí)間
Date startTime;
//是否是農(nóng)歷,1表示農(nóng)歷,0表示陽(yáng)歷
int isLunar;
//循環(huán)類型
int repeatType;
public static final int REPEAT_EVERY_WEEK = 1; //每周
public static final int REPEAT_EVERY_MONTH = 2; //每月
public static final int REPEAT_EVERY_YEAR = 3; //每年
}
????問(wèn)題可以歸為兩部分,第一部分是:判斷某個(gè)Event是否在兩日期之間。第二部分是:修正Event顯示的時(shí)間,這個(gè)時(shí)間必須是最合適的。
????先來(lái)看第一部分,判斷某個(gè)Event是否在兩日期之間,根據(jù)循環(huán)類型,分別處理:
//event是否在這兩個(gè)日期之間
static boolean isBetweenTheDate(Calendar beginCalendar, Calendar endCanlendar, Event event) {
//event開(kāi)始日期
Calendar eventStartCalendar = Calendar.getInstance();
eventStartCalendar.setTime(event.startTime);
//event開(kāi)始日期毫秒數(shù)
long eventStartMill = eventStartCalendar.getTimeInMillis();
long beginMill = beginCalendar.getTimeInMillis();
long endMill = endCanlendar.getTimeInMillis();
if (eventStartMill > endMill) { //event開(kāi)始日期,比endCanlendar還大,說(shuō)明不在該區(qū)間,返回false
return false;
}
if (event.repeatType == Event.REPEAT_EVERY_WEEK) {
return isBetweenWeek(beginCalendar, endCanlendar, event);
}
if (event.repeatType == Event.REPEAT_EVERY_MONTH) {
return isBetweenWeek(beginCalendar, endCanlendar, event);
}
if (event.repeatType == Event.REPEAT_EVERY_YEAR) {
return isBetweenWeek(beginCalendar, endCanlendar, event);
}
return false;
}
????每周循環(huán)情況處理,主要思路:
????先計(jì)算日期區(qū)間、Event的startTime是星期幾,然后進(jìn)行對(duì)比判斷即可。需要處理的異常情況:1)日期區(qū)間是否超過(guò)一周?如果是,因?yàn)镋vent是周循環(huán),那Event必然在此區(qū)間;2)如果出現(xiàn)本周四到下周二這種結(jié)束日期對(duì)應(yīng)的星期幾比起始日期的小,需要適當(dāng)?shù)男拚?/p>
// 一天的毫秒數(shù)
public static final int DAY_MILLISECONDS = 24 * 3600000;
//每周循環(huán)事件,是否在這兩個(gè)日期之間
private static boolean isBetweenWeek(Calendar beginCalendar, Calendar endCanlendar, Event event) {
//event開(kāi)始日期
Calendar eventStartCalendar = Calendar.getInstance();
eventStartCalendar.setTime(event.startTime);
long beginMill = beginCalendar.getTimeInMillis();
long endMill = endCanlendar.getTimeInMillis();
//如果間隔區(qū)間大于7天,那么肯定返回true
if (endMill - beginMill > 7 * DAY_MILLISECONDS) {
return true;
}
int beginDayOfWeek = beginCalendar.get(Calendar.DAY_OF_WEEK);
int endDayOfWeek = endCanlendar.get(Calendar.DAY_OF_WEEK);
int eventStartDayOfWeek = eventStartCalendar.get(Calendar.DAY_OF_WEEK);
if (endDayOfWeek < beginDayOfWeek) { // 會(huì)出現(xiàn)這種情況:本周四 —— 下周二
endDayOfWeek += 7;
}
if (eventStartDayOfWeek >= beginDayOfWeek && eventStartDayOfWeek <= endDayOfWeek) {
return true;
}
return false;
}
????每年循環(huán)情況處理,主要思路:
????根據(jù)事件Event的startTime,獲得對(duì)應(yīng)的月和日;然后判斷月和日是否在該日期區(qū)間里。判斷的方法是比較(month *31 + day)的值,這個(gè)31并不代表大月的天數(shù),僅表示權(quán)值,只要使得當(dāng)month加1時(shí),結(jié)果大于任何month不變day取任意值的情況,即
31。不關(guān)心startTime對(duì)應(yīng)的year,因?yàn)槊磕甓家l(fā)生。如果使用的是農(nóng)歷,將日期轉(zhuǎn)換為陽(yáng)歷。
//每年循環(huán)事件,是否在這兩個(gè)日期之間
private static boolean isBetweenYear(Calendar beginCalendar, Calendar endCanlendar, Event event) {
//event開(kāi)始日期
Calendar eventStartCalendar = Calendar.getInstance();
eventStartCalendar.setTime(event.startTime);
int eventStartDay, eventStartMonth;
int beginDay, beginMonth;
int endDay, endMonth;
if (event.isLunar == 1) {
int[] eventStarLunar = solarToLunar(eventStartCalendar.get(Calendar.YEAR), eventStartCalendar.get(Calendar.MONTH) + 1, eventStartCalendar.get(Calendar.DAY_OF_MONTH));
eventStartDay = eventStarLunar[2];
eventStartMonth = eventStarLunar[1];
int[] beginLunar = solarToLunar(beginCalendar.get(Calendar.YEAR), beginCalendar.get(Calendar.MONTH) + 1, beginCalendar.get(Calendar.DAY_OF_MONTH));
beginDay = beginLunar[2];
beginMonth = beginLunar[1];
int[] endLunar = solarToLunar(endCanlendar.get(Calendar.YEAR), endCanlendar.get(Calendar.MONTH) + 1, endCanlendar.get(Calendar.DAY_OF_MONTH));
endDay = endLunar[2];
endMonth = endLunar[1];
} else {
eventStartDay = eventStartCalendar.get(Calendar.DAY_OF_MONTH);
eventStartMonth = eventStartCalendar.get(Calendar.MONTH) + 1;
beginDay = beginCalendar.get(Calendar.DAY_OF_MONTH);
beginMonth = beginCalendar.get(Calendar.MONTH) + 1;
endDay = endCanlendar.get(Calendar.DAY_OF_MONTH);
endMonth = endCanlendar.get(Calendar.MONTH) + 1;
}
int beginKey = beginMonth * 31 + beginDay;
int endKey = endMonth * 31 + endDay;
int eventStartKey = eventStartMonth * 31 + eventStartDay;
if (beginKey > endKey) { //跨年情況
if (eventStartKey >= beginKey || eventStartKey <= endKey) {
return true;
}
} else {
if (eventStartKey >= beginKey && eventStartKey <= endKey) {
return true;
}
}
return false;
}
????每月循環(huán)情況處理,主要思路:
????使用年、月、日根據(jù)(year * 366 + month * 31 + day)來(lái)計(jì)算權(quán)重,看是否落在設(shè)置的日期區(qū)間,這里需要考慮大小月、農(nóng)歷閏月等。如果是閏月,那么month需要額外加1。
????在進(jìn)行下一步之前,介紹一個(gè)方法addLunarMonth():農(nóng)歷month自增,涉及閏月、溢出等情況,要求使用最恰當(dāng)日期,如下:
/**
* 農(nóng)歷月份加1
* <p>
* 先轉(zhuǎn)換成農(nóng)歷,然后把農(nóng)歷月加1,然后再轉(zhuǎn)換成公歷,以及閏月、溢出情況處理
*
* @param startLunarDay 初始農(nóng)歷日,如大年三十
*/
public static Calendar addLunarMonth(Calendar calendar, int startLunarDay) {
int tmpYear = calendar.get(Calendar.YEAR);
int tmpMonth = calendar.get(Calendar.MONTH) + 1;
int tmpDay = calendar.get(Calendar.DAY_OF_MONTH);
int[] lunar = solarToLunar(tmpYear, tmpMonth, tmpDay);
int lunarYear = lunar[0];
int lunarMonth = lunar[1];
int lunarDay = lunar[2];
boolean isLeapMonth = lunar[3] == 1;
int leapMonth = leapMonth(lunarYear); //閏月
boolean lunarFlag = false; //add一個(gè)月后,是否是閏月的標(biāo)記
if (leapMonth > 0) {
//lunarYear 該年有閏月,那么分幾種情況:(1)leapMonth=12 閏12月 ; (2)當(dāng)月不是閏月,但下月是閏月 (3)當(dāng)月是閏月,但下月不是閏月
if (leapMonth == 12) {
if (lunarMonth == 12) {
if (isLeapMonth) {
//如果當(dāng)月剛好是閏12月,那么下一月是下一年的1月
lunarYear += 1;
lunarMonth = 1;
} else {
//如果當(dāng)月剛好是12月,那么下一月是閏12月
lunarFlag = true;
}
} else {
//如果當(dāng)前月不是12月,那么直接把月份加1就行
lunarMonth += 1;
}
} else {
if (lunarMonth == leapMonth) {
if (isLeapMonth) {
//是閏月,那么直接月份加1就行。比如閏4月,下一月是5月
lunarMonth += 1;
} else {
//不是閏月,那么下一月是閏月。比如2020年4月,那么下一月是閏4月
lunarFlag = true;
}
} else {
if (lunarMonth == 12) {
lunarYear += 1;
lunarMonth = 1;
} else {
lunarMonth += 1;
}
}
}
} else {
//lunarYear 該年沒(méi)有閏月,那么直接把lunarMonth加1,如果超出了,就把lunarYear加1,lunarMonth置為1月
if (lunarMonth == 12) {
lunarYear += 1;
lunarMonth = 1;
} else {
lunarMonth += 1;
}
}
int maxDay = getLunarMaxMonth(lunarYear, lunarMonth, lunarFlag);
if (lunarDay > maxDay) {
lunarDay = maxDay;
}
//使用初始日糾正,有那天的使用那天;使用最恰當(dāng)?shù)娜掌? if (startLunarDay <= maxDay && lunarDay != startLunarDay) {
lunarDay = startLunarDay;
}
int[] solar = lunarToSolar(lunarYear, lunarMonth, lunarDay, lunarFlag);
int solarYear = solar[0];
int solarMonth = solar[1];
int solarDay = solar[2];
calendar.set(Calendar.YEAR, solarYear);
calendar.set(Calendar.MONTH, solarMonth - 1);
calendar.set(Calendar.DAY_OF_MONTH, solarDay);
return calendar;
}
/**
* 獲取農(nóng)歷year和month 對(duì)應(yīng)的最大天數(shù)
*
* @param year 農(nóng)歷年
* @param month 農(nóng)歷月
* @return
*/
private static int getLunarMaxMonth(int year, int month, boolean isLeapMonth) {
int leapMonth = leapMonth(year);
if (leapMonth > 0 && month == leapMonth && isLeapMonth) {
return leapDays(year);
}
return monthDays(year, month);
}
????再看每月循環(huán)事件的處理:
//每月循環(huán)事件,是否在這兩個(gè)日期之間
private static boolean isBetweenMonth(Calendar beginCalendar, Calendar endCanlendar, Event event) {
//event開(kāi)始日期
Calendar eventStartCalendar = Calendar.getInstance();
eventStartCalendar.setTime(event.startTime);
int eventStartDay, eventStartMonth, eventStartYear;
int beginDay, beginMonth, beginYear;
int endDay, endMonth, endYear;
boolean isBeginLeap = false, isEndLeap = false, isEventStartLeap = false;
int leapBeginMonth = 0, leapEndMonth = 0, leapEventStartMonth = 0;
if (event.isLunar == 1) {
int[] eventStarLunar = solarToLunar(eventStartCalendar.get(Calendar.YEAR), eventStartCalendar.get(Calendar.MONTH) + 1, eventStartCalendar.get(Calendar.DAY_OF_MONTH));
eventStartDay = eventStarLunar[2];
eventStartMonth = eventStarLunar[1];
eventStartYear = eventStarLunar[0];
isEventStartLeap = eventStarLunar[3] == 1;
int[] beginLunar = solarToLunar(beginCalendar.get(Calendar.YEAR), beginCalendar.get(Calendar.MONTH) + 1, beginCalendar.get(Calendar.DAY_OF_MONTH));
beginDay = beginLunar[2];
beginMonth = beginLunar[1];
beginYear = beginLunar[0];
isBeginLeap = beginLunar[3] == 1;
int[] endLunar = solarToLunar(endCanlendar.get(Calendar.YEAR), endCanlendar.get(Calendar.MONTH) + 1, endCanlendar.get(Calendar.DAY_OF_MONTH));
endDay = endLunar[2];
endMonth = endLunar[1];
endYear = endLunar[0];
isEndLeap = endLunar[3] == 1;
leapBeginMonth = leapMonth(beginYear);
leapEndMonth = leapMonth(endYear);
leapEventStartMonth = leapMonth(eventStartYear);
} else {
eventStartDay = eventStartCalendar.get(Calendar.DAY_OF_MONTH);
eventStartMonth = eventStartCalendar.get(Calendar.MONTH) + 1;
eventStartYear = eventStartCalendar.get(Calendar.YEAR);
beginDay = beginCalendar.get(Calendar.DAY_OF_MONTH);
beginMonth = beginCalendar.get(Calendar.MONTH) + 1;
beginYear = beginCalendar.get(Calendar.YEAR);
endDay = endCanlendar.get(Calendar.DAY_OF_MONTH);
endMonth = endCanlendar.get(Calendar.MONTH) + 1;
endYear = endCanlendar.get(Calendar.YEAR);
}
boolean isBeginAdd = false, isEndAdd = false, isEventStartAdd = false;
if (leapBeginMonth > 0) { //有閏月的情況
if (beginMonth > leapBeginMonth) {
isBeginAdd = true;
} else if (beginMonth == leapBeginMonth && isBeginLeap) {
isBeginAdd = true;
}
}
if (leapEndMonth > 0) {//有閏月的情況
if (endMonth > leapEndMonth) {
isEndAdd = true;
} else if (endMonth == leapEndMonth && isEndLeap) {
isEndAdd = true;
}
}
if (leapEventStartMonth > 0) {
if (eventStartMonth > leapEventStartMonth) {
isEventStartAdd = true;
} else if (eventStartMonth == leapEventStartMonth && isEventStartLeap) {
isEventStartAdd = true;
}
}
int beginKey = beginYear * 366 + (isBeginAdd ? beginMonth + 1 : beginMonth) * 31 + beginDay;
int endKey = endYear * 366 + (isEndAdd ? endMonth + 1 : endMonth) * 31 + endDay;
int eventStartKey = eventStartYear * 366 + (isEventStartAdd ? eventStartMonth + 1 : eventStartMonth) * 31 + eventStartDay;
if (eventStartKey >= beginKey && eventStartKey <= endKey) {
return true;
}
if (eventStartYear < beginYear - 1) { //如果初始時(shí)間設(shè)置非常早,將年數(shù)設(shè)置近一些
eventStartYear = beginYear - 1;
eventStartCalendar.set(Calendar.YEAR, eventStartYear);
}
int tmpStartDay = eventStartDay;
while (eventStartKey < beginKey) {
boolean isTmpLeap = false, isTmpAdd = false;
int tmpLeapMonth = 0;
if (event.isLunar == 1) {
addLunarMonth(eventStartCalendar, tmpStartDay);
int[] eventStarLunar = solarToLunar(eventStartCalendar.get(Calendar.YEAR), eventStartCalendar.get(Calendar.MONTH) + 1, eventStartCalendar.get(Calendar.DAY_OF_MONTH));
eventStartDay = eventStarLunar[2];
eventStartMonth = eventStarLunar[1];
eventStartYear = eventStarLunar[0];
isTmpLeap = eventStarLunar[3] == 1;
tmpLeapMonth = leapMonth(eventStartYear);
if (tmpLeapMonth > 0) {
if (eventStartMonth > tmpLeapMonth) {
isTmpAdd = true;
} else if (eventStartMonth == tmpLeapMonth && isTmpLeap) {
isTmpAdd = true;
}
}
} else {
eventStartCalendar.add(Calendar.MONTH, 1);
int maxDay = eventStartCalendar.getActualMaximum(Calendar.DAY_OF_MONTH);
eventStartDay = eventStartCalendar.get(Calendar.DAY_OF_MONTH);
if (tmpStartDay <= maxDay && tmpStartDay != eventStartDay) { //大小月處理
eventStartDay = tmpStartDay;
eventStartCalendar.set(Calendar.DAY_OF_MONTH, eventStartDay);
}
eventStartMonth = eventStartCalendar.get(Calendar.MONTH) + 1;
eventStartYear = eventStartCalendar.get(Calendar.YEAR);
}
eventStartKey = eventStartYear * 366 + (isTmpAdd ? eventStartMonth + 1 : eventStartMonth) * 31 + eventStartDay;
}
if (eventStartKey < endKey) {
return true;
} else {
return false;
}
}
????第二部分,修正顯示時(shí)間。根據(jù)類型,分別處理:
private void correctShowTimeBetween(Event event, Calendar beginCalendar, Calendar endCalendar) {
if (event.repeatType == Event.REPEAT_EVERY_WEEK) {
correctWeekShowTime(beginCalendar, endCalendar, event);
}
if (event.repeatType == Event.REPEAT_EVERY_MONTH) {
correctMonthShowTime(beginCalendar, endCalendar, event);
}
if (event.repeatType == Event.REPEAT_EVERY_YEAR) {
correctYearShowTime(beginCalendar, endCalendar, event);
}
}
????糾正每周循環(huán)顯示時(shí)間:
private void correctWeekShowTime(Calendar beginCalendar, Calendar endCalendar, Event event) {
Calendar eventStartCalendar = Calendar.getInstance();
eventStartCalendar.setTime(event.startTime);
int eventStartYear = eventStartCalendar.get(Calendar.YEAR);
int eventStartMonth = eventStartCalendar.get(Calendar.MONTH) + 1;
int eventStartDay = eventStartCalendar.get(Calendar.DAY_OF_MONTH);
int beginYear = beginCalendar.get(Calendar.YEAR);
int beginMonth = beginCalendar.get(Calendar.MONTH) + 1;
int beginDay = beginCalendar.get(Calendar.DAY_OF_MONTH);
if (eventStartYear < beginYear - 1) { //如果初始時(shí)間設(shè)置非常早,將年數(shù)設(shè)置近一些
eventStartYear = beginYear - 1;
eventStartCalendar.set(Calendar.YEAR, eventStartYear);
}
if (eventStartMonth < beginMonth -1 ){
eventStartMonth = beginMonth -1;
eventStartCalendar.set(Calendar.MONTH, eventStartMonth);
}
int eventStartKey = eventStartYear * 366 + eventStartMonth * 31 + eventStartDay;
int beginKey = beginYear * 366 + beginMonth * 31 + beginDay;
if (eventStartKey < beginKey) {
while (eventStartKey < beginKey) {
eventStartCalendar.add(Calendar.DAY_OF_MONTH, 7);
eventStartYear = eventStartCalendar.get(Calendar.YEAR);
eventStartMonth = eventStartCalendar.get(Calendar.MONTH) + 1;
eventStartDay = eventStartCalendar.get(Calendar.DAY_OF_MONTH);
eventStartKey = eventStartYear * 366 + eventStartMonth * 31 + eventStartDay;
}
event.startTime = eventStartCalendar.getTime();
}
}
????糾正每年顯示時(shí)間:
private void correctYearShowTime(Calendar beginCalendar, Calendar endCalendar, Event event) {
Calendar eventStartCalendar = Calendar.getInstance();
eventStartCalendar.setTime(event.startTime);
int eventStartYear = eventStartCalendar.get(Calendar.YEAR);
int eventStartMonth = eventStartCalendar.get(Calendar.MONTH) + 1;
int eventStartDay = eventStartCalendar.get(Calendar.DAY_OF_MONTH);
int eventStartKey = eventStartYear * 366 + eventStartMonth * 31 + eventStartDay;
int beginYear = beginCalendar.get(Calendar.YEAR);
int beginMonth = beginCalendar.get(Calendar.MONTH) + 1;
int beginDay = beginCalendar.get(Calendar.DAY_OF_MONTH);
int beginKey = beginYear * 366 + beginMonth * 31 + beginDay;
boolean isLunar = event.isLunar == 1;
if (isLunar) {
int[] eventStartLunar = solarToLunar(eventStartCalendar.get(Calendar.YEAR), eventStartCalendar.get(Calendar.MONTH) + 1, eventStartCalendar.get(Calendar.DAY_OF_MONTH));
eventStartDay = eventStartLunar[2];
eventStartMonth = eventStartLunar[1];
eventStartYear = eventStartLunar[0];
eventStartKey = eventStartYear * 366 + eventStartMonth * 31 + eventStartDay;
}
if (isLunar){
int[] eventBeginLunar = solarToLunar(beginCalendar.get(Calendar.YEAR), beginCalendar.get(Calendar.MONTH) + 1, beginCalendar.get(Calendar.DAY_OF_MONTH));
beginDay = eventBeginLunar[2];
beginMonth = eventBeginLunar[1];
beginYear = eventBeginLunar[0];
beginKey = beginYear * 366 + beginMonth * 31 + beginDay;
}
if (eventStartYear < beginYear - 1) { //如果初始時(shí)間設(shè)置非常早,將年數(shù)設(shè)置近一些
eventStartYear = beginYear - 1;
eventStartCalendar.set(Calendar.YEAR, eventStartYear);
}
if (eventStartKey < beginKey) {
while (eventStartKey < beginKey) {
if (isLunar){
eventStartCalendar = addLunarYear(eventStartCalendar,eventStartDay);
int[] eventStartLunar = solarToLunar(eventStartCalendar.get(Calendar.YEAR), eventStartCalendar.get(Calendar.MONTH) + 1, eventStartCalendar.get(Calendar.DAY_OF_MONTH));
eventStartDay = eventStartLunar[2];
eventStartMonth = eventStartLunar[1];
eventStartYear = eventStartLunar[0];
eventStartKey = eventStartYear * 366 + eventStartMonth * 31 + eventStartDay;
} else {
eventStartCalendar.add(Calendar.YEAR, 1);
eventStartYear = eventStartCalendar.get(Calendar.YEAR);
eventStartMonth = eventStartCalendar.get(Calendar.MONTH) + 1;
eventStartDay = eventStartCalendar.get(Calendar.DAY_OF_MONTH);
eventStartKey = eventStartYear * 366 + eventStartMonth * 31 + eventStartDay;
}
}
event.startTime = eventStartCalendar.getTime();
}
}
/**
* 農(nóng)歷年加1
* <p>
* 需要處理閏月等情況
*
* @param startLunarDay 初始農(nóng)歷日,如大年三十
* @return
*/
public static Calendar addLunarYear(Calendar calendar, int startLunarDay) {
int tmpYear = calendar.get(Calendar.YEAR);
int tmpMonth = calendar.get(Calendar.MONTH) + 1;
int tmpDay = calendar.get(Calendar.DAY_OF_MONTH);
int[] lunar = solarToLunar(tmpYear, tmpMonth, tmpDay);
int lunarYear = lunar[0];
int lunarMonth = lunar[1];
int lunarDay = lunar[2];
lunarYear += 1;
int maxDay = getLunarMaxMonth(lunarYear, lunarMonth, false);
if (lunarDay > maxDay) {
lunarDay = maxDay;
}
//使用初始日糾正,有那天的使用那天;使用最恰當(dāng)?shù)娜掌? if (startLunarDay <= maxDay && lunarDay != startLunarDay) {
lunarDay = startLunarDay;
}
int[] solar = lunarToSolar(lunarYear, lunarMonth, lunarDay, false);
int solarYear = solar[0];
int solarMonth = solar[1];
int solarDay = solar[2];
calendar.set(Calendar.YEAR, solarYear);
calendar.set(Calendar.MONTH, solarMonth - 1);
calendar.set(Calendar.DAY_OF_MONTH, solarDay);
return calendar;
}
????通過(guò)上面每年、每周的糾正方法可以看出,其實(shí)是根據(jù)權(quán)值對(duì)比,設(shè)置正確的時(shí)間。這個(gè)權(quán)值對(duì)比,在第一部分的每月循環(huán)事件里已經(jīng)見(jiàn)到過(guò)了。不過(guò),那里并沒(méi)有更改Event的startTime值。每月的糾正方法,只需更改一下它即可。這里不再重復(fù)了。
????Over !