package com.haibin.oldcalendarview;
|
|
import android.text.TextUtils;
|
|
import java.util.Date;
|
import java.util.GregorianCalendar;
|
import java.util.TimeZone;
|
|
@SuppressWarnings("unused")
|
class LunarCalendar {
|
/**
|
* 支持转换的最小农历年份
|
*/
|
private static final int MIN_YEAR = 1900;
|
/**
|
* 支持转换的最大农历年份
|
*/
|
private static final int MAX_YEAR = 2099;
|
|
/**
|
* 公历每月前的天数
|
*/
|
private static final int DAYS_BEFORE_MONTH[] = {0, 31, 59, 90, 120, 151, 181,
|
212, 243, 273, 304, 334, 365};
|
|
/**
|
* 农历月份第一天转写
|
*/
|
private static final String[] monthStr = {"春节", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "冬月", "腊月"};
|
|
/**
|
* 传统农历节日
|
*/
|
private static final String[] traditionFestivalStr = {"除夕", "0101春节", "0115元宵", "0505端午", "0707七夕", "0815中秋", "0909重阳"};
|
|
/**
|
* 农历大写
|
*/
|
private static final String daysStr[] = {"初一", "初二", "初三", "初四", "初五", "初六",
|
"初七", "初八", "初九", "初十", "十一", "十二", "十三", "十四", "十五", "十六", "十七",
|
"十八", "十九", "二十", "廿一", "廿二", "廿三", "廿四", "廿五", "廿六", "廿七", "廿八",
|
"廿九", "三十"};
|
|
/**
|
* 公历节日
|
*/
|
private final static String[] mSolarCalendar = {
|
"0101元旦", "0214情人节", "0315消权日", "0401愚人节", "0501劳动节", "0504青年节",
|
"0601儿童节", "0701建党节", "0801建军节", "0910教师节", "1001国庆节", "1224平安夜",
|
"1225圣诞节"
|
};
|
|
/**
|
* 返回传统农历节日
|
* @param year 农历年
|
* @param month 农历月
|
* @param day 农历日
|
* @return 返回传统农历节日
|
*/
|
private static String getTraditionFestivalText(int year,int month,int day){
|
if(month == 12){
|
int count = daysInLunarMonth(year, month);
|
if(day == count){
|
return traditionFestivalStr[0];//除夕
|
}
|
}
|
String text = getString(month, day);
|
String festivalStr = "";
|
for (String festival : traditionFestivalStr) {
|
if (festival.contains(text)) {
|
festivalStr = festival.replace(text, "");
|
break;
|
}
|
}
|
return festivalStr;
|
}
|
|
|
/**
|
* 数字转换为汉字月份
|
*
|
* @param month 月
|
* @return 数字转换为汉字月份
|
*/
|
private static String numToChineseMonth(int month) {
|
return monthStr[month - 1];
|
}
|
|
/**
|
* 数字转换为汉字日
|
*
|
* @param day day
|
* @return 数字转换为汉字日
|
*/
|
static String numToChineseDay(int day) {
|
return daysStr[day - 1];
|
}
|
|
/**
|
* 数字转换为农历节日或者日期
|
*
|
* @param month 月
|
* @param day 日
|
* @return 数字转换为汉字日
|
*/
|
private static String numToChinese(int month, int day) {
|
if (day == 1) {
|
return numToChineseMonth(month);
|
}
|
return daysStr[day - 1];
|
}
|
|
/**
|
* 用来表示1900年到2099年间农历年份的相关信息,共24位bit的16进制表示,其中:
|
* 1. 前4位表示该年闰哪个月;
|
* 2. 5-17位表示农历年份13个月的大小月分布,0表示小,1表示大;
|
* 3. 最后7位表示农历年首(正月初一)对应的公历日期。
|
* <p/>
|
* 以2014年的数据0x955ABF为例说明:
|
* 1001 0101 0101 1010 1011 1111
|
* 闰九月 农历正月初一对应公历1月31号
|
*/
|
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*/
|
};
|
|
/**
|
* 将农历日期转换为公历日期
|
*
|
* @param year 农历年份
|
* @param month 农历月
|
* @param monthDay 农历日
|
* @param isLeapMonth 该月是否是闰月
|
* @return 返回农历日期对应的公历日期,year0, month1, day2.
|
*/
|
static 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 || (year % 4 != 0 && dayOffset > 365)) {
|
year += 1;
|
if (year % 4 == 1)
|
dayOffset -= 366;
|
else
|
dayOffset -= 365;
|
}
|
|
int[] solarInfo = new int[3];
|
for (i = 1; i < 13; i++) {
|
int iPos = DAYS_BEFORE_MONTH[i];
|
if (year % 4 == 0 && i > 2) {
|
iPos += 1;
|
}
|
|
if (year % 4 == 0 && 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 (year % 4 == 0 && i > 2) {
|
iPos += 1;
|
}
|
if (dayOffset > iPos)
|
solarInfo[2] = dayOffset - iPos;
|
else if (dayOffset == iPos) {
|
if (year % 4 == 0 && 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;
|
}
|
|
/**
|
* 将公历日期转换为农历日期,且标识是否是闰月
|
*
|
* @param year year
|
* @param month month
|
* @param monthDay monthDay
|
* @return 返回公历日期对应的农历日期,year0,month1,day2,leap3
|
*/
|
static int[] solarToLunar(int year, int month, int monthDay) {
|
int[] lunarDate = new int[4];
|
Date baseDate = new GregorianCalendar(1900, 0, 31).getTime();
|
Date objDate = new GregorianCalendar(year, month - 1, monthDay).getTime();
|
int offset = (int) ((objDate.getTime() - baseDate.getTime()) / 86400000L);
|
|
// 用offset减去每农历年的天数计算当天是农历第几天
|
// iYear最终结果是农历的年份, offset是当年的第几天
|
int iYear, daysOfYear = 0;
|
for (iYear = MIN_YEAR; iYear <= MAX_YEAR && offset > 0; iYear++) {
|
daysOfYear = daysInLunarYear(iYear);
|
offset -= daysOfYear;
|
}
|
if (offset < 0) {
|
offset += daysOfYear;
|
iYear--;
|
}
|
|
// 农历年份
|
lunarDate[0] = iYear;
|
|
int leapMonth = leapMonth(iYear); // 闰哪个月,1-12
|
boolean isLeap = false;
|
// 用当年的天数offset,逐个减去每月(农历)的天数,求出当天是本月的第几天
|
int iMonth, daysOfMonth = 0;
|
for (iMonth = 1; iMonth <= 13 && offset > 0; iMonth++) {
|
daysOfMonth = daysInLunarMonth(iYear, iMonth);
|
offset -= daysOfMonth;
|
}
|
// 当前月超过闰月,要校正
|
if (leapMonth != 0 && iMonth > leapMonth) {
|
--iMonth;
|
|
if (iMonth == leapMonth) {
|
isLeap = true;
|
}
|
}
|
// offset小于0时,也要校正
|
if (offset < 0) {
|
offset += daysOfMonth;
|
--iMonth;
|
}
|
|
lunarDate[1] = iMonth;
|
lunarDate[2] = offset + 1;
|
lunarDate[3] = isLeap ? 1 : 0;
|
|
return lunarDate;
|
}
|
|
/**
|
* y
|
* 传回农历year年month月的总天数
|
*
|
* @param year 要计算的年份
|
* @param month 要计算的月
|
* @return 传回天数
|
*/
|
private static int daysInMonth(int year, int month) {
|
return daysInMonth(year, month, false);
|
}
|
|
/**
|
* 传回农历year年month月的总天数
|
*
|
* @param year 要计算的年份
|
* @param month 要计算的月
|
* @param leap 当月是否是闰月
|
* @return 传回天数,如果闰月是错误的,返回0.
|
*/
|
@SuppressWarnings("all")
|
private static int daysInMonth(int year, int month, boolean leap) {
|
int leapMonth = leapMonth(year);
|
int offset = 0;
|
|
// 如果本年有闰月且month大于闰月时,需要校正
|
if (leapMonth != 0 && month > leapMonth) {
|
offset = 1;
|
}
|
|
// 不考虑闰月
|
if (!leap) {
|
return daysInLunarMonth(year, month + offset);
|
} else {
|
// 传入的闰月是正确的月份
|
if (leapMonth != 0 && leapMonth == month) {
|
return daysInLunarMonth(year, month + 1);
|
}
|
}
|
|
return 0;
|
}
|
|
/**
|
* 传回农历 year年的总天数
|
*
|
* @param year 将要计算的年份
|
* @return 返回传入年份的总天数
|
*/
|
private static int daysInLunarYear(int year) {
|
int i, sum = 348;
|
if (leapMonth(year) != 0) {
|
sum = 377;
|
}
|
int monthInfo = LUNAR_INFO[year - MIN_YEAR] & 0x0FFF80;
|
for (i = 0x80000; i > 0x7; i >>= 1) {
|
if ((monthInfo & i) != 0)
|
sum += 1;
|
}
|
return sum;
|
}
|
|
/**
|
* 传回农历 year年month月的总天数,总共有13个月包括闰月
|
*
|
* @param year 将要计算的年份
|
* @param month 将要计算的月份
|
* @return 传回农历 year年month月的总天数
|
*/
|
private static int daysInLunarMonth(int year, int month) {
|
if ((LUNAR_INFO[year - MIN_YEAR] & (0x100000 >> month)) == 0)
|
return 29;
|
else
|
return 30;
|
}
|
|
/**
|
* 传回农历 year年闰哪个月 1-12 , 没闰传回 0
|
*
|
* @param year 将要计算的年份
|
* @return 传回农历 year年闰哪个月1-12, 没闰传回 0
|
*/
|
private static int leapMonth(int year) {
|
return ((LUNAR_INFO[year - MIN_YEAR] & 0xF00000)) >> 20;
|
}
|
|
private final static int[] solarTermInfo = {
|
0, 21208, 42467, 63836, 85337, 107014, 128867, 150921,
|
173149, 195551, 218072, 240693, 263343, 285989, 308563, 331033,
|
353350, 375494, 397447, 419210, 440795, 462224, 483532, 504758
|
};
|
|
|
/**
|
* 获取公历节日
|
*
|
* @param month 公历月份
|
* @param day 公历日期
|
* @return 公历节日
|
*/
|
private static String getSolarCalendar(int month, int day) {
|
String text = getString(month, day);
|
String solar = "";
|
for (String aMSolarCalendar : mSolarCalendar) {
|
if (aMSolarCalendar.contains(text)) {
|
solar = aMSolarCalendar.replace(text, "");
|
break;
|
}
|
}
|
return solar;
|
}
|
|
private static String getString(int month, int day) {
|
return (month >= 10 ? String.valueOf(month) : "0" + month) + (day >= 10 ? day : "0" + day);
|
}
|
|
/**
|
* 农历24节气
|
*/
|
private final static String[] mSolarTerm = {
|
"小寒", "大寒", "立春", "雨水", "惊蛰", "春分",
|
"清明", "谷雨", "立夏", "小满", "芒种", "夏至",
|
"小暑", "大暑", "立秋", "处暑", "白露", "秋分",
|
"寒露", "霜降", "立冬", "小雪", "大雪", "冬至"
|
};
|
|
/**
|
* 返回24节气
|
*/
|
private static String getTermString(int solarYear, int solarMonth, int solarDay) {
|
String termString = "";
|
if (getSolarTermDay(solarYear, solarMonth * 2) == solarDay) {
|
termString = mSolarTerm[solarMonth * 2];
|
} else if (getSolarTermDay(solarYear, solarMonth * 2 + 1) == solarDay) {
|
termString = mSolarTerm[solarMonth * 2 + 1];
|
}
|
return termString;
|
}
|
|
/**
|
* 返回公历年节气的日期
|
*
|
* @param solarYear 指定公历年份(数字)
|
* @param index 指定节气序号(数字,0从小寒算起)
|
* @return 日期(数字, 所在月份的第几天)
|
*/
|
private static int getSolarTermDay(int solarYear, int index) {
|
return getUTCDay(getSolarTermCalendar(solarYear, index));
|
}
|
|
/**
|
* 返回公历年节气的日期
|
*
|
* @param solarYear 指定公历年份(数字)
|
* @param index 指定节气序号(数字,0从小寒算起)
|
* @return 日期(数字, 所在月份的第几天)
|
*/
|
private static Date getSolarTermCalendar(int solarYear, int index) {
|
long l = (long) 31556925974.7 * (solarYear - 1900)
|
+ solarTermInfo[index] * 60000L;
|
l = l + UTC(1900, 0, 6, 2, 5, 0);
|
return new Date(l);
|
}
|
|
/**
|
* 返回全球标准时间 (UTC) (或 GMT) 的 1970 年 1 月 1 日到所指定日期之间所间隔的毫秒数。
|
*
|
* @param y 指定年份
|
* @param m 指定月份
|
* @param d 指定日期
|
* @param h 指定小时
|
* @param min 指定分钟
|
* @param sec 指定秒数
|
* @return 全球标准时间 (UTC) (或 GMT) 的 1970 年 1 月 1 日到所指定日期之间所间隔的毫秒数
|
*/
|
@SuppressWarnings("all")
|
private static synchronized long UTC(int y, int m, int d, int h, int min, int sec) {
|
makeUTCCalendar();
|
synchronized (utcCal) {
|
utcCal.clear();
|
utcCal.set(y, m, d, h, min, sec);
|
return utcCal.getTimeInMillis();
|
}
|
}
|
|
private static GregorianCalendar utcCal = null;
|
|
/**
|
* 取 Date 对象中用全球标准时间 (UTC) 表示的日期
|
*
|
* @param date 指定日期
|
* @return UTC 全球标准时间 (UTC) 表示的日期
|
*/
|
@SuppressWarnings("SynchronizeOnNonFinalField")
|
private static synchronized int getUTCDay(Date date) {
|
makeUTCCalendar();
|
synchronized (utcCal) {
|
utcCal.clear();
|
utcCal.setTimeInMillis(date.getTime());
|
return utcCal.get(java.util.Calendar.DAY_OF_MONTH);
|
}
|
}
|
|
private static void makeUTCCalendar() {
|
if (utcCal == null) {
|
utcCal = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
|
}
|
}
|
|
|
/**
|
* 检查节气是否加1 ,例如青梅是4号还是5号
|
*
|
* @param year 今年
|
* @return 检查节气偏移量
|
*/
|
@SuppressWarnings("all")
|
static int getTermsOffset(int year) {
|
boolean isLeapYear = isLeapYear(year);
|
int d = getWinterSolstice(year - 1);//获取去年冬至是21还是22,农历
|
if (d == 22 && isLeapYear) return 2;//几百年才几次出现清明是6号
|
if (d == 22) return 1;
|
return 0;
|
}
|
|
/**
|
* 判断是否闰年
|
*
|
* @param year year
|
* @return 是否闰年
|
*/
|
private static boolean isLeapYear(int year) {
|
return ((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0);
|
}
|
|
private final static double D = 0.2422;
|
private final static double C = 21.94;// (year*D + C) - leap 20世纪C=22.60
|
|
/**
|
* 冬至是每年的第几日 21 or 22
|
*/
|
private static int getWinterSolstice(int year) {
|
int dYear = Integer.parseInt(String.valueOf(year).substring(2, 4));
|
return (int) (dYear * D + C - (dYear / 4));
|
}
|
|
|
|
/**
|
* 获取农历节日
|
*
|
* @param year 年
|
* @param month 月
|
* @param day 日
|
* @return 农历节日
|
*/
|
static String getLunarText(int year, int month, int day) {
|
String termText = LunarCalendar.getTermString(year, month - 1, day);
|
String solar = LunarCalendar.getSolarCalendar(month, day);
|
if (!TextUtils.isEmpty(solar))
|
return solar;
|
if (!TextUtils.isEmpty(termText))
|
return termText;
|
int[] lunar = LunarCalendar.solarToLunar(year, month, day);
|
String festival = getTraditionFestivalText(lunar[0],lunar[1],lunar[2]);
|
if (!TextUtils.isEmpty(festival))
|
return festival;
|
return LunarCalendar.numToChinese(lunar[1], lunar[2]);
|
}
|
|
|
}
|