├── app
├── .gitignore
├── src
│ └── main
│ │ ├── res
│ │ ├── mipmap-hdpi
│ │ │ └── ic_launcher.png
│ │ ├── mipmap-mdpi
│ │ │ └── ic_launcher.png
│ │ ├── mipmap-xhdpi
│ │ │ └── ic_launcher.png
│ │ ├── mipmap-xxhdpi
│ │ │ └── ic_launcher.png
│ │ ├── mipmap-xxxhdpi
│ │ │ └── ic_launcher.png
│ │ ├── values
│ │ │ ├── strings.xml
│ │ │ ├── colors.xml
│ │ │ ├── dimens.xml
│ │ │ └── styles.xml
│ │ ├── values-v21
│ │ │ └── styles.xml
│ │ ├── values-w820dp
│ │ │ └── dimens.xml
│ │ ├── menu
│ │ │ └── menu_main.xml
│ │ └── layout
│ │ │ ├── content_main.xml
│ │ │ └── activity_main.xml
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ └── com
│ │ └── coolerfall
│ │ └── lunarview
│ │ └── MainActivity.java
├── proguard-rules.pro
└── build.gradle
├── library
├── .gitignore
├── src
│ └── main
│ │ ├── AndroidManifest.xml
│ │ ├── res
│ │ └── values
│ │ │ └── attrs.xml
│ │ └── java
│ │ └── com
│ │ └── coolerfall
│ │ └── widget
│ │ └── lunar
│ │ ├── WeekLabelView.java
│ │ ├── MonthPagerAdapter.java
│ │ ├── MonthDay.java
│ │ ├── Month.java
│ │ ├── MonthView.java
│ │ ├── LunarView.java
│ │ └── Lunar.java
├── build.gradle
└── proguard-rules.pro
├── settings.gradle
├── lunar.gif
├── .gitignore
├── gradle
├── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
└── maven_push.gradle
├── gradle.properties
├── README.md
├── gradlew.bat
├── gradlew
└── LICENSE
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/library/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':library'
2 |
--------------------------------------------------------------------------------
/lunar.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolerfall/Android-LunarView/HEAD/lunar.gif
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .gradle
2 | /local.properties
3 | /.idea
4 | .DS_Store
5 | /build
6 | /captures
7 | *.iml
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolerfall/Android-LunarView/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolerfall/Android-LunarView/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolerfall/Android-LunarView/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolerfall/Android-LunarView/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolerfall/Android-LunarView/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolerfall/Android-LunarView/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/library/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
17 | * | 0 - 11(bit) | 12 - 15(bit) | 18 | * month leap month 19 | * If last 4bit is 1111 or 0000 means no leap month. 20 | * If the last 4bit in next data is 1111, the days of leap month is 30 days, 21 | * otherwise, the days of leap month is 29days. 22 | */ 23 | private static final int[] LUNR_INFO = { 24 | 0x4bd8, 0x4ae0, 0xa570, 0x54d5, 0xd260, 0xd950, 0x5554, 0x56af, 0x9ad0, 0x55d2, 0x4ae0, 25 | 0xa5b6, 0xa4d0, 0xd250, 0xd295, 0xb54f, 0xd6a0, 0xada2, 0x95b0, 0x4977, 0x497f, 0xa4b0, 26 | 0xb4b5, 0x6a50, 0x6d40, 0xab54, 0x2b6f, 0x9570, 0x52f2, 0x4970, 0x6566, 0xd4a0, 0xea50, 27 | 0x6a95, 0x5adf, 0x2b60, 0x86e3, 0x92ef, 0xc8d7, 0xc95f, 0xd4a0, 0xd8a6, 0xb55f, 0x56a0, 28 | 0xa5b4, 0x25df, 0x92d0, 0xd2b2, 0xa950, 0xb557, 0x6ca0, 0xb550, 0x5355, 0x4daf, 0xa5b0, 29 | 0x4573, 0x52bf, 0xa9a8, 0xe950, 0x6aa0, 0xaea6, 0xab50, 0x4b60, 0xaae4, 0xa570, 0x5260, 30 | 0xf263, 0xd950, 0x5b57, 0x56a0, 0x96d0, 0x4dd5, 0x4ad0, 0xa4d0, 0xd4d4, 0xd250, 0xd558, 31 | 0xb540, 0xb6a0, 0x95a6, 0x95bf, 0x49b0, 0xa974, 0xa4b0, 0xb27a, 0x6a50, 0x6d40, 0xaf46, 32 | 0xab60, 0x9570, 0x4af5, 0x4970, 0x64b0, 0x74a3, 0xea50, 0x6b58, 0x5ac0, 0xab60, 0x96d5, 33 | 0x92e0, 0xc960, 0xd954, 0xd4a0, 0xda50, 0x7552, 0x56a0, 0xabb7, 0x25d0, 0x92d0, 0xcab5, 34 | 0xa950, 0xb4a0, 0xbaa4, 0xad50, 0x55d9, 0x4ba0, 0xa5b0, 0x5176, 0x52bf, 0xa930, 0x7954, 35 | 0x6aa0, 0xad50, 0x5b52, 0x4b60, 0xa6e6, 0xa4e0, 0xd260, 0xea65, 0xd530, 0x5aa0, 0x76a3, 36 | 0x96d0, 0x4afb, 0x4ad0, 0xa4d0, 0xd0b6, 0xd25f, 0xd520, 0xdd45, 0xb5a0, 0x56d0, 0x55b2, 37 | 0x49b0, 0xa577, 0xa4b0, 0xaa50, 0xb255, 0x6d2f, 0xada0, 0x4b63, 0x937f, 0x49f8, 0x4970, 38 | 0x64b0, 0x68a6, 0xea5f, 0x6b20, 0xa6c4, 0xaaef, 0x92e0, 0xd2e3, 0xc960, 0xd557, 0xd4a0, 39 | 0xda50, 0x5d55, 0x56a0, 0xa6d0, 0x55d4, 0x52d0, 0xa9b8, 0xa950, 0xb4a0, 0xb6a6, 0xad50, 40 | 0x55a0, 0xaba4, 0xa5b0, 0x52b0, 0xb273, 0x6930, 0x7337, 0x6aa0, 0xad50, 0x4b55, 0x4b6f, 41 | 0xa570, 0x54e4, 0xd260, 0xe968, 0xd520, 0xdaa0, 0x6aa6, 0x56df, 0x4ae0, 0xa9d4, 0xa4d0, 42 | 0xd150, 0xf252, 0xd520 43 | }; 44 | 45 | /* solar terms information */ 46 | private static final int[] SOLAR_TERM_INFO = { 47 | 0, 21208, 42467, 63836, 85337, 107014, 128867, 150921, 173149, 195551, 218072, 240693, 263343, 48 | 285989, 308563, 331033, 353350, 375494, 397447, 419210, 440795, 462224, 483532, 504758 49 | }; 50 | 51 | /* Heavenly Stems */ 52 | private static final String[] HEAVENLY_STEMS = { 53 | "甲", "乙", "丙", "丁", "戊", "己", "庚", "辛", "壬", "癸" 54 | }; 55 | 56 | /* Earthly Branches */ 57 | private static final String[] EARTHLY_BRANCHES = { 58 | "子", "丑", "寅", "卯", "辰", "巳", "午", "未", "申", "酉", "戌", "亥" 59 | }; 60 | 61 | /* Chinese zodiac */ 62 | private static final String[] ZODIAC = { 63 | "鼠", "牛", "虎", "兔", "龙", "蛇", "马", "羊", "猴", "鸡", "狗", "猪" 64 | }; 65 | 66 | /* solar terms */ 67 | private static final String[] SOLAR_TERM = { 68 | "小寒", "大寒", "立春", "雨水", "惊蛰", "春分", "清明", "谷雨", "立夏", "小满", "芒种", "夏至", "小暑", "大暑", "立秋", 69 | "处暑", "白露", "秋分", "寒露", "霜降", "立冬", "小雪", "大雪", "冬至" 70 | }; 71 | 72 | /* lunar string used in month and day */ 73 | private static final String[] LUNAR_STRING = { 74 | "零", "一", "二", "三", "四", "五", "六", "七", "八", "九" 75 | }; 76 | 77 | /* special lunar string */ 78 | private static final String[] LUNAR_SPEC_STRING = { 79 | "初", "十", "廿", "卅", "正", "冬", "腊", "闰" 80 | }; 81 | 82 | /* day of week in Chinese */ 83 | private static final String[] DAY_OF_WEEK_IN_CHINESE = { 84 | "日", "一", "二", "三", "四", "五", "六", 85 | }; 86 | 87 | /* all the lunar holidays */ 88 | private final Holiday[] lunarHolidays = new Holiday[] { 89 | new Holiday(1, 1, "春节"), new Holiday(1, 15, "元宵节"), new Holiday(5, 5, "端午节"), 90 | new Holiday(7, 7, "七夕节"), new Holiday(7, 15, "中元节"), new Holiday(8, 15, "中秋节"), 91 | new Holiday(9, 9, "重阳节"), new Holiday(12, 8, "腊八节"), new Holiday(12, 23, "北方小年"), 92 | new Holiday(12, 24, "南方小年") 93 | }; 94 | 95 | /* all the solar days */ 96 | private final Holiday[] solarHolidays = new Holiday[] { 97 | new Holiday(1, 1, "元旦节"), new Holiday(2, 14, "情人节"), new Holiday(3, 8, "妇女节"), 98 | new Holiday(3, 12, "植树节"), new Holiday(3, 15, "消费者权益日"), new Holiday(3, 21, "世界森林日"), 99 | new Holiday(4, 1, "愚人节"), new Holiday(4, 7, "世界卫生日"), new Holiday(4, 22, "世界地球日"), 100 | new Holiday(5, 1, "劳动节"), new Holiday(5, 4, "青年节"), new Holiday(5, 31, "世界无烟日"), 101 | new Holiday(6, 1, "儿童节"), new Holiday(6, 26, "禁毒日"), new Holiday(7, 1, "建党节"), 102 | new Holiday(8, 1, "建军节"), new Holiday(8, 15, "抗战胜利"), new Holiday(9, 10, "教师节"), 103 | new Holiday(9, 28, "孔子诞辰"), new Holiday(10, 1, "国庆节"), new Holiday(12, 20, "澳门回归"), 104 | new Holiday(12, 24, "平安夜"), new Holiday(12, 25, "圣诞节"), 105 | }; 106 | 107 | /* Pengzu hundred dread Heavenly Stems */ 108 | private static final String[] PENG_ZU_HEAVENLY = { 109 | "甲不开仓\n财物耗亡", "乙不栽植\n千株不长", "丙不修灶\n必见灾殃", "丁不剃头\n头主生疮", "戊不受田\n田主不祥", "己不破券\n二比并亡", 110 | "庚不经络\n织机虚张", "辛不合酱\n主人不尝", "壬不决水\n更难提防", "癸不词讼\n理弱敌强", 111 | }; 112 | 113 | /* Pengzu hundred dread Earthly Branches */ 114 | private static final String[] PENG_ZU_EARTHLY = { 115 | "子不问卜\n自惹祸殃", "丑不冠带\n主不还乡", "寅不祭祀\n神鬼不尝", "卯不穿井\n水泉不香", "辰不哭泣\n必主重丧", "巳不远行\n财物伏藏", 116 | "午不苫盖\n屋主更张", "未不服药\n毒气入肠", "申不安床\n鬼祟入房", "酉不宴客\n醉坐颠狂", "戌不吃犬\n作怪上床", "亥不嫁娶\n不利新郎" 117 | }; 118 | 119 | /* direction of evil spirit */ 120 | private static final String[] EVIL_SPIRIT = { 121 | "南", "东", "北", "西" 122 | }; 123 | 124 | /* fetus god */ 125 | private static final String[] FETUS_GOD_DIRECTION = { 126 | "外东北", "外正东", "外东南", "外正南", "外西南", "外正西", "外西北", "外正北", "房内北", "房内南", "房内东" 127 | }; 128 | 129 | /* fetus god position in Heavenlu Stems */ 130 | private static final String[] FETUS_GOD_HEAVENLY = { 131 | "门", "碓磨", "厨灶", "仓库", "房床" 132 | }; 133 | 134 | /* fetus god position in Earthly Braches */ 135 | private static final String[] FETUS_GOD_EARTHLY = { 136 | "碓", "厕", "炉灶", "大门", "栖", "床" 137 | }; 138 | 139 | /* twelve duty */ 140 | private static final String[] TWELVE_DURY = { 141 | "开", "闭", "建", "除", "满", "平", "定", "执", "破", "危", "成", "收" 142 | }; 143 | 144 | /* five elements */ 145 | private static final String[] FIVE_ELEMENTS = { 146 | "海中金", "炉中火", "大林木", "路旁土", "剑锋金", "山头火", "涧下水", "城头土", "白蜡金", "杨柳木", "泉中水", "屋上土", "霹雳火", 147 | "松柏木", "长流水", "砂石金", "山下火", "平地木", "壁上土", "金箔金", "灯头火", "天河水", "大驿土", "钗钏金", "桑柘木", "大溪水", 148 | "沙中土", "天上火", "石榴木", "大海水" 149 | }; 150 | 151 | /* twenty eight stars with direction and fortune */ 152 | private final Star[][] mTwentyEightStars = new Star[][] { 153 | new Star[] { 154 | new Star("房日兔", "吉", "东方"), new Star("心月狐", "凶", "东方"), new Star("尾火虎", "吉", "东方"), 155 | new Star("箕水豹", "吉", "东方"), new Star("角木蛟", "吉", "东方"), new Star("亢金龙", "凶", "东方"), 156 | new Star("氐土貉", "凶", "东方"), 157 | }, 158 | 159 | new Star[] { 160 | new Star("虚日鼠", "凶", "北方"), new Star("危月燕", "凶", "北方"), new Star("室火猪", "吉", "北方"), 161 | new Star("壁水貐", "吉", "北方"), new Star("斗木獬", "吉", "北方"), new Star("牛金牛", "凶", "北方"), 162 | new Star("女士蝠", "凶", "北方"), 163 | }, 164 | 165 | new Star[] { 166 | new Star("昴日鸡", "凶", "西方"), new Star("毕月乌", "吉", "西方"), new Star("觜火猴", "凶", "西方"), 167 | new Star("参水猿", "凶", "西方"), new Star("奎水狼", "凶", "西方"), new Star("娄金狗", "吉", "西方"), 168 | new Star("胃土雉", "吉", "西方"), 169 | }, 170 | 171 | new Star[] { 172 | new Star("星日马", "凶", "南方"), new Star("张月鹿", "吉", "南方"), new Star("翼火蛇", "凶", "南方"), 173 | new Star("轸水蚓", "吉", "南方"), new Star("井木犴", "吉", "南方"), new Star("鬼金羊", "凶", "南方"), 174 | new Star("柳土獐", "凶", "南方"), 175 | }, 176 | }; 177 | 178 | private Calendar solar; 179 | private GregorianCalendar gregorianCalendar; 180 | private int lunarYear; 181 | private int lunarMonth; 182 | private int lunarDay; 183 | private int daysInLuarMonth; 184 | private boolean isLeap; 185 | private int solarYear; 186 | private int solarMonth; 187 | private int solarDay; 188 | private int cyclicalYear = 0; 189 | private int cyclicalMonth = 0; 190 | private int cyclicalDay = 0; 191 | 192 | /** 193 | * Create a new instance of Lunar Calendar. 194 | * 195 | * @return new instance 196 | */ 197 | public static synchronized Lunar newInstance() { 198 | return new Lunar(); 199 | } 200 | 201 | /** 202 | * The default constructor of lunar, create an empty Lunar, don't 203 | * forget to use {@link #setDate} or {@link #setTimeInMillis}. 204 | */ 205 | public Lunar() { 206 | 207 | } 208 | 209 | /** 210 | * The constructor of Lunar calendar. 211 | * 212 | * @param year the year 213 | * @param month the month 214 | * @param day the day 215 | */ 216 | public Lunar(int year, int month, int day) { 217 | setDate(year, month, day); 218 | } 219 | 220 | /** 221 | * The constructor of Lunar calendar. 222 | * 223 | * @param millisec millisecond 224 | */ 225 | public Lunar(long millisec) { 226 | init(millisec); 227 | } 228 | 229 | /* Twenty-eight stars class */ 230 | private class Star { 231 | private String star; 232 | private String direction; 233 | private String fortune; 234 | 235 | public Star(String star, String fortune, String direction) { 236 | this.star = star; 237 | this.fortune = fortune; 238 | this.direction = direction; 239 | } 240 | 241 | public String getDirection() { 242 | return direction; 243 | } 244 | 245 | public String getFortune() { 246 | return fortune; 247 | } 248 | 249 | public String getStar() { 250 | return star; 251 | } 252 | } 253 | 254 | /* lunar or solar holiday */ 255 | private class Holiday { 256 | private int month; 257 | private int day; 258 | private String name; 259 | 260 | public Holiday(int month, int day, String name) { 261 | this.month = month; 262 | this.day = day; 263 | this.name = name; 264 | } 265 | 266 | public int getDay() { 267 | return day; 268 | } 269 | 270 | public int getMonth() { 271 | return month; 272 | } 273 | 274 | public String getName() { 275 | return name; 276 | } 277 | } 278 | 279 | /* init lunar calendar with millisecond */ 280 | private void init(long millisec) { 281 | gregorianCalendar = new GregorianCalendar(TimeZone.getTimeZone("UTC")); 282 | solar = Calendar.getInstance(); 283 | solar.setTimeInMillis(millisec); 284 | lunarYear = 1900; 285 | 286 | Calendar baseDate = new GregorianCalendar(1900, 0, 31); 287 | long offset = (millisec - baseDate.getTimeInMillis()) / 86400000; 288 | 289 | int daysInLunarYear = getLunarYearDays(lunarYear); 290 | /* get current lunar year */ 291 | while (lunarYear < 2100 && offset >= daysInLunarYear) { 292 | offset -= daysInLunarYear; 293 | daysInLunarYear = getLunarYearDays(++lunarYear); 294 | } 295 | 296 | /* get current lunar month */ 297 | int lunarMonth = 1; 298 | int leapMonth = getLunarLeapMonth(lunarYear); 299 | boolean leapDec = false; 300 | boolean isLeap = false; 301 | int daysInLunarMonth = 0; 302 | 303 | /* to get lunar year, month and day */ 304 | while (lunarMonth < 13 && offset > 0) { 305 | if (isLeap && leapDec) { 306 | daysInLunarMonth = getLunarLeapDays(lunarYear); 307 | leapDec = false; 308 | } else { 309 | daysInLunarMonth = getLunarMonthDays(lunarYear, lunarMonth); 310 | } 311 | 312 | if (offset < daysInLunarMonth) { 313 | break; 314 | } 315 | offset -= daysInLunarMonth; 316 | 317 | if (leapMonth == lunarMonth && !isLeap) { 318 | leapDec = true; 319 | isLeap = true; 320 | } else { 321 | lunarMonth++; 322 | } 323 | } 324 | 325 | daysInLuarMonth = daysInLunarMonth; 326 | this.lunarMonth = lunarMonth; 327 | this.isLeap = (lunarMonth == leapMonth && isLeap); 328 | lunarDay = (int) offset + 1; 329 | 330 | getCyclicalData(); 331 | } 332 | 333 | /* init with lunar date */ 334 | private void initLunar(int lunarYear, int lunarMonth, int lunarDay, boolean isLeap) { 335 | int initYear = lunarYear; 336 | int initMonth = lunarMonth; 337 | long offset = lunarDay - 1; 338 | 339 | /* calculate month into days */ 340 | while (--initMonth > 0) { 341 | offset += getLunarMonthDays(lunarYear, initMonth); 342 | } 343 | 344 | int leapMonth = getLunarLeapMonth(lunarYear); 345 | if (leapMonth <= lunarMonth) { 346 | offset += getLunarLeapDays(lunarYear); 347 | } 348 | 349 | /* calculate year into days */ 350 | while (initYear > 1900) { 351 | offset += getLunarYearDays(--initYear); 352 | } 353 | 354 | /* init the all variable */ 355 | this.lunarYear = lunarYear; 356 | this.lunarMonth = lunarMonth; 357 | this.lunarDay = lunarDay; 358 | gregorianCalendar = new GregorianCalendar(TimeZone.getTimeZone("UTC")); 359 | Calendar baseDate = new GregorianCalendar(1900, 0, 31); 360 | solar = Calendar.getInstance(); 361 | solar.setTimeInMillis(offset * 86400000 + baseDate.getTimeInMillis()); 362 | 363 | getCyclicalData(); 364 | } 365 | 366 | /** 367 | * Get the leap month in lunar year. 368 | * 369 | * @param lunarYear lunar year 370 | * @return the month in specified lunar year, otherwise return 0 371 | */ 372 | private int getLunarLeapMonth(int lunarYear) { 373 | int leapMonth = LUNR_INFO[lunarYear - 1900] & 0xf; 374 | return leapMonth == 0xf ? 0 : leapMonth; 375 | } 376 | 377 | /** 378 | * Get total days of leap month in lunar year. 379 | * 380 | * @param lunarYear lunar year 381 | * @return total days of leap month, otherwise return 0 if no leap month. 382 | */ 383 | private int getLunarLeapDays(int lunarYear) { 384 | return getLunarLeapMonth(lunarYear) > 0 ? ((LUNR_INFO[lunarYear - 1899] & 0xf) == 0xf ? 30 : 29) 385 | : 0; 386 | } 387 | 388 | /** 389 | * Get total days of lunar year. 390 | * 391 | * @param lunarYear lunar year 392 | * @return total days of lunar year 393 | */ 394 | private int getLunarYearDays(int lunarYear) { 395 | /* lunar year has (12 * 29 =) 348 days at least */ 396 | int totalDays = 348; 397 | for (int i = 0x8000; i > 0x8; i >>= 1) { 398 | totalDays += ((LUNR_INFO[lunarYear - 1900] & i) != 0) ? 1 : 0; 399 | } 400 | 401 | return totalDays + getLunarLeapDays(lunarYear); 402 | } 403 | 404 | /** 405 | * Get total days of lunar month in normal case. 406 | * 407 | * @param lunarYear lunar year 408 | * @param lunarMonth lunar month 409 | * @return total days 410 | */ 411 | private int getLunarMonthDays(int lunarYear, int lunarMonth) { 412 | return ((LUNR_INFO[lunarYear - 1900] & (0x10000 >> lunarMonth)) != 0) ? 30 : 29; 413 | } 414 | 415 | /** 416 | * Get Coordinated Universal Time for given params. 417 | * 418 | * @param year the year to set 419 | * @param month the month to set 420 | * @param day the day to set 421 | * @param hour the hour to set 422 | * @param min the minute to set 423 | * @param sec the second to set 424 | * @return Coordinated Universal Time 425 | */ 426 | private synchronized long getUTC(int year, int month, int day, int hour, int min, int sec) { 427 | synchronized (Lunar.class) { 428 | gregorianCalendar.clear(); 429 | gregorianCalendar.set(year, month, day, hour, min, sec); 430 | return gregorianCalendar.getTimeInMillis(); 431 | } 432 | } 433 | 434 | /** 435 | * Convert current date into Coordinated Universal Time. 436 | * 437 | * @param date current date 438 | * @return get day in Coordinated Universal Time 439 | */ 440 | private synchronized int getUTCDay(Date date) { 441 | synchronized (Lunar.class) { 442 | gregorianCalendar.clear(); 443 | gregorianCalendar.setTimeInMillis(date.getTime()); 444 | return gregorianCalendar.get(Calendar.DAY_OF_MONTH); 445 | } 446 | } 447 | 448 | /** 449 | * Convert current date into Coordinated Universal Time. 450 | * 451 | * @param date current date 452 | * @return get month in Coordinated Universal Time 453 | */ 454 | private synchronized int getUTCMonth(Date date) { 455 | synchronized (Lunar.class) { 456 | gregorianCalendar.clear(); 457 | gregorianCalendar.setTimeInMillis(date.getTime()); 458 | return gregorianCalendar.get(Calendar.MONTH); 459 | } 460 | } 461 | 462 | /** 463 | * Get the day of solar terms. 464 | * 465 | * @param solarYear the specified solar year 466 | * @param index the index of solar terms in {@link #SOLAR_TERM} 467 | * @return the day of solar terms 468 | */ 469 | private int getSolarTermDay(int solarYear, int index) { 470 | long millisec = (long) 31556925974.7 * (solarYear - 1900) + SOLAR_TERM_INFO[index] * 60000L; 471 | return getUTCDay(new Date(millisec + getUTC(1900, 0, 6, 2, 5, 0))); 472 | } 473 | 474 | /** 475 | * Get the month of solar terms. 476 | * 477 | * @param solarYear the specified solar year 478 | * @param index the index of solar terms in {@link #SOLAR_TERM} 479 | * @return the month of solar terms 480 | */ 481 | private int getSolarTermMonth(int solarYear, int index) { 482 | long millisec = (long) 31556925974.7 * (solarYear - 1900) + SOLAR_TERM_INFO[index] * 60000L; 483 | return getUTCMonth(new Date(millisec + getUTC(1900, 0, 6, 2, 5, 0))); 484 | } 485 | 486 | /* get Heavenly Stems and Earthly Branches data */ 487 | private void getCyclicalData() { 488 | solarYear = solar.get(Calendar.YEAR); 489 | solarMonth = solar.get(Calendar.MONTH); 490 | solarDay = solar.get(Calendar.DAY_OF_MONTH); 491 | 492 | int cyclicalYear; 493 | int cyclicalMonth; 494 | int cyclicalDay; 495 | 496 | /* year in Heavenly Stems and Earthly Branches */ 497 | int term = getSolarTermDay(solarYear, 2); 498 | if (solarMonth < 1 || (solarMonth == 1 && solarDay < term)) { 499 | cyclicalYear = (solarYear - 1900 + 36 - 1) % 60; 500 | } else { 501 | cyclicalYear = (solarYear - 1900 + 36) % 60; 502 | } 503 | 504 | /* month in Heavenly Stems and Earthly Branches */ 505 | int firstNode = getSolarTermDay(solarYear, solarMonth * 2); 506 | if (solarDay < firstNode) { 507 | cyclicalMonth = ((solarYear - 1900) * 12 + solarMonth + 12) % 60; 508 | } else { 509 | cyclicalMonth = ((solarYear - 1900) * 12 + solarMonth + 13) % 60; 510 | } 511 | 512 | cyclicalDay = 513 | (int) (getUTC(solarYear, solarMonth, solarDay, 0, 0, 0) / 86400000 + 25567 + 10) % 60; 514 | 515 | this.cyclicalYear = cyclicalYear; 516 | this.cyclicalMonth = cyclicalMonth; 517 | this.cyclicalDay = cyclicalDay; 518 | } 519 | 520 | /** 521 | * Get Heavenly Stems with specified cyclical number. 522 | * 523 | * @param cyclicalNum cyclical number in Heavenly Stems and Earthly Branches 524 | * @return Heavenly stems index 525 | */ 526 | private int getHeavenlyStems(int cyclicalNum) { 527 | return cyclicalNum % 10; 528 | } 529 | 530 | /** 531 | * Get Earthly Branches with specified cyclical number. 532 | * 533 | * @param cyclicalNum cyclical number in Heavenly Stems and Earthly Branches 534 | * @return Earthly Branches index 535 | */ 536 | private int getEarthlyBranches(int cyclicalNum) { 537 | return cyclicalNum % 12; 538 | } 539 | 540 | /** 541 | * Get Heavenly Stems and Earthly Branches. 542 | * 543 | * @param cyclicalNum cyclical number in Heavenly Stems and Earthly Branches 544 | * @return Heavenly Stems and Earthly Branches string 545 | */ 546 | private String getCyclical(int cyclicalNum) { 547 | return HEAVENLY_STEMS[getHeavenlyStems(cyclicalNum)] + EARTHLY_BRANCHES[getEarthlyBranches( 548 | cyclicalNum)]; 549 | } 550 | 551 | /** 552 | * Set date to get lunar data. 553 | * 554 | * @param year the year 555 | * @param month the month 556 | * @param day the day 557 | */ 558 | public void setDate(int year, int month, int day) { 559 | Calendar calendar = Calendar.getInstance(); 560 | calendar.set(year, month, day); 561 | init(year == 0 ? System.currentTimeMillis() : calendar.getTimeInMillis()); 562 | } 563 | 564 | /** 565 | * Set lunar date, so then can get GregorianCalendar date. 566 | * 567 | * @param year lunar year 568 | * @param month lunar month 569 | * @param day lunar day 570 | * @param isLeap the month is leap month or not 571 | */ 572 | public void setLunarDate(int year, int month, int day, boolean isLeap) { 573 | initLunar(year, month, day, isLeap); 574 | } 575 | 576 | /** 577 | * Set time in millisecond. 578 | * 579 | * @param millisecond millisecond to set 580 | */ 581 | public void setTimeInMillis(long millisecond) { 582 | init(millisecond); 583 | } 584 | 585 | /** 586 | * Get Chinese zodiac string. 587 | * 588 | * @return Chinese zodiac 589 | */ 590 | public String getZodiac() { 591 | return ZODIAC[(lunarYear - 4) % 12]; 592 | } 593 | 594 | /** 595 | * Get solar terms for current day. 596 | * 597 | * @return solar terms, otherwise return null if was not solar term. 598 | */ 599 | public String getSolarTerm() { 600 | if (getSolarTermDay(solarYear, solarMonth * 2) == solarDay) { 601 | return SOLAR_TERM[solarMonth * 2]; 602 | } else if (getSolarTermDay(solarYear, solarMonth * 2 + 1) == solarDay) { 603 | return SOLAR_TERM[solarMonth * 2 + 1]; 604 | } 605 | 606 | return null; 607 | } 608 | 609 | /** 610 | * Get the index of current day in Heavenly Stems and Earthly Branches. 611 | * 612 | * @return the index 613 | */ 614 | public int getHeavenlyAndEarthly() { 615 | return cyclicalDay; 616 | } 617 | 618 | /** 619 | * Get year in Heavenly Stems and Earthly Branches. 620 | * 621 | * @return year in Heavenly Stems and Earthly Branches 622 | */ 623 | public String getCyclicalYear() { 624 | return getCyclical(cyclicalYear); 625 | } 626 | 627 | /** 628 | * Get month in Heavenly Stems and Earthly Branches. 629 | * 630 | * @return month in Heavenly Stems and Earthly Branches 631 | */ 632 | public String getCyclicalMonth() { 633 | return getCyclical(cyclicalMonth); 634 | } 635 | 636 | /** 637 | * Get day in Heavenly Stems and Earthly Branches. 638 | * 639 | * @return day in Heavenly Stems and Earthly Branches 640 | */ 641 | public String getCyclicalDay() { 642 | return getCyclical(cyclicalDay); 643 | } 644 | 645 | /** 646 | * Get year in lunar calendar. 647 | * 648 | * @return year in lunar calendar 649 | */ 650 | public String getLunarYear() { 651 | return getCyclical(lunarYear - 1900 + 36); 652 | } 653 | 654 | /** 655 | * Get lunar month in Chinese according to lunar month numeric. 656 | * 657 | * @param lunarMonth lunar month numeric 658 | * @param isLeap is current month leap or not 659 | * @return lunar month in Chinese 660 | */ 661 | public String getLunarMonth(int lunarMonth, boolean isLeap) { 662 | String lunarMonthStr = ""; 663 | if (lunarMonth == 1) { 664 | lunarMonthStr = LUNAR_SPEC_STRING[4]; 665 | } else { 666 | switch (lunarMonth) { 667 | case 10: 668 | lunarMonthStr = LUNAR_SPEC_STRING[1]; 669 | break; 670 | 671 | case 11: 672 | lunarMonthStr = LUNAR_SPEC_STRING[5]; 673 | break; 674 | 675 | case 12: 676 | lunarMonthStr = LUNAR_SPEC_STRING[6]; 677 | break; 678 | 679 | default: 680 | lunarMonthStr += LUNAR_STRING[lunarMonth % 10]; 681 | break; 682 | } 683 | } 684 | 685 | return (isLeap ? "闰" : "") + lunarMonthStr; 686 | } 687 | 688 | /** 689 | * Get month in Chinese of lunar calendar. 690 | * 691 | * @return month in lunar calendar 692 | */ 693 | public String getLunarMonth() { 694 | return getLunarMonth(lunarMonth, isLeap); 695 | } 696 | 697 | /** 698 | * Get lunar day in Chinese according to lunar day numeric. 699 | * 700 | * @param lunarDay lunar day numeric 701 | * @return lunar day in Chinese 702 | */ 703 | public String getLunarDay(int lunarDay) { 704 | if (lunarDay < 1 || lunarDay > 30) { 705 | return ""; 706 | } 707 | 708 | int decade = lunarDay / 10; 709 | int unit = lunarDay % 10; 710 | String decadeStr = LUNAR_SPEC_STRING[decade]; 711 | String unitStr = LUNAR_STRING[unit]; 712 | 713 | if (lunarDay < 11) { 714 | decadeStr = LUNAR_SPEC_STRING[0]; 715 | } 716 | 717 | if (unit == 0) { 718 | unitStr = LUNAR_SPEC_STRING[1]; 719 | } 720 | 721 | return decadeStr + unitStr; 722 | } 723 | 724 | /** 725 | * Get day in lunar calendar. 726 | * 727 | * @return day in lunar calendar 728 | */ 729 | public String getLunarDay() { 730 | return getLunarDay(lunarDay); 731 | } 732 | 733 | public int getLunarYearNum() { 734 | return lunarYear; 735 | } 736 | 737 | /** 738 | * Get lunar month in numeric. 739 | * 740 | * @return the numeric of lunar month 741 | */ 742 | public int getLunarMonthNum() { 743 | return lunarMonth; 744 | } 745 | 746 | /** 747 | * Get lunar day in numeric. 748 | * 749 | * @return the numeric of lunar day 750 | */ 751 | public int getLunarDayNum() { 752 | return lunarDay; 753 | } 754 | 755 | /** 756 | * Get max days in current lunar month. 757 | * 758 | * @return max days 759 | */ 760 | public int getMaxDaysInLunarMonth() { 761 | return daysInLuarMonth; 762 | } 763 | 764 | /** 765 | * Get solar calendar. 766 | * 767 | * @return solar calendar 768 | */ 769 | public Calendar getCalendar() { 770 | return solar; 771 | } 772 | 773 | /** 774 | * Get year in the Gregorian calendar. 775 | * 776 | * @return year in the Gregorian calendar 777 | */ 778 | public int getSolarYear() { 779 | return solarYear; 780 | } 781 | 782 | /** 783 | * Get month in the Gregorian calendar, based-on 0. 784 | * 785 | * @return month in the Gregorian calendar 786 | */ 787 | public int getSolarMonth() { 788 | return solarMonth; 789 | } 790 | 791 | /** 792 | * Get day in the Gregorian calendar. 793 | * 794 | * @return day in the Gregorian calendar 795 | */ 796 | public int getSolarDay() { 797 | return solarDay; 798 | } 799 | 800 | /** 801 | * Get the day of week. 802 | * 803 | * @return day of week(1-Sunday, 7-Saturday). 804 | */ 805 | public int getDayOfWeek() { 806 | return solar.get(Calendar.DAY_OF_WEEK); 807 | } 808 | 809 | /** 810 | * Get the week of year. 811 | * 812 | * @return week of year 813 | */ 814 | public int getWeekOfYear() { 815 | return solar.get(Calendar.WEEK_OF_YEAR); 816 | } 817 | 818 | /** 819 | * Get day of week in Chinese. 820 | * 821 | * @return day of week in Chinese 822 | */ 823 | public String getDayOfWeekInChinese() { 824 | return DAY_OF_WEEK_IN_CHINESE[getDayOfWeek() - 1]; 825 | } 826 | 827 | /** 828 | * To check if current date is today. 829 | * 830 | * @return true if was today, otherwise return false 831 | */ 832 | public boolean isToday() { 833 | Calendar calendar = Calendar.getInstance(); 834 | return calendar.get(Calendar.YEAR) == solarYear 835 | && calendar.get(Calendar.MONTH) == solarMonth 836 | && calendar.get(Calendar.DAY_OF_MONTH) == solarDay; 837 | } 838 | 839 | /** 840 | * Get Pengzu one hundred dread. 841 | * 842 | * @return Pengzu one hundred dread 843 | */ 844 | public String[] getPengzu() { 845 | String[] pengzu = new String[2]; 846 | pengzu[0] = PENG_ZU_HEAVENLY[getHeavenlyStems(cyclicalDay)]; 847 | pengzu[1] = PENG_ZU_EARTHLY[getEarthlyBranches(cyclicalDay)]; 848 | 849 | return pengzu; 850 | } 851 | 852 | /** 853 | * Get lunar holidy if existed. 854 | * 855 | * @return lunar holiday, null if not existed 856 | */ 857 | public String getLunarHoliday() { 858 | if (isLeap) { 859 | return null; 860 | } 861 | 862 | /* New Year's Eve is special, maybe 29, maybe 30 */ 863 | if (lunarMonth == 12 && lunarDay == daysInLuarMonth) { 864 | return "除夕"; 865 | } 866 | 867 | for (Holiday holiday : lunarHolidays) { 868 | if (holiday.getMonth() == lunarMonth && holiday.getDay() == lunarDay) { 869 | return holiday.getName(); 870 | } 871 | } 872 | 873 | return null; 874 | } 875 | 876 | /** 877 | * Get solar holiday if existed. 878 | * 879 | * @return solar holiday, null if not existed 880 | */ 881 | public String getSolarHolidy() { 882 | for (Holiday holiday : solarHolidays) { 883 | if (holiday.getMonth() == solarMonth + 1 && holiday.getDay() == solarDay) { 884 | return holiday.getName(); 885 | } 886 | } 887 | 888 | return null; 889 | } 890 | 891 | /** 892 | * Get confilict evil spirit for current day. 893 | * 894 | * @return evil spirit 895 | */ 896 | public String getConflictEvilSpirit() { 897 | int heavenlyIndex = getHeavenlyStems(cyclicalDay); 898 | int earthlyIndex = getEarthlyBranches(cyclicalDay); 899 | int conflictHeavenlyIndex; 900 | int conflictEarthlyIndex; 901 | 902 | if (heavenlyIndex < 6) { 903 | conflictHeavenlyIndex = heavenlyIndex + 4; 904 | } else { 905 | conflictHeavenlyIndex = heavenlyIndex - 6; 906 | } 907 | 908 | if (earthlyIndex < 6) { 909 | conflictEarthlyIndex = earthlyIndex + 6; 910 | } else { 911 | conflictEarthlyIndex = earthlyIndex - 6; 912 | } 913 | 914 | return "冲" 915 | + ZODIAC[conflictEarthlyIndex] 916 | + "(" 917 | + HEAVENLY_STEMS[conflictHeavenlyIndex] 918 | + EARTHLY_BRANCHES[conflictEarthlyIndex] 919 | + ")" 920 | + "煞" 921 | + EVIL_SPIRIT[earthlyIndex % 4]; 922 | } 923 | 924 | /** 925 | * Get star description for current day. 926 | * 927 | * @return star description 928 | */ 929 | public String getTwentyEightStar() { 930 | int weekOfYear = (getWeekOfYear() - 1) % 4; 931 | int dayOfWeek = getDayOfWeek() - 1; 932 | Star star = mTwentyEightStars[weekOfYear][dayOfWeek]; 933 | 934 | return star.getDirection() + star.getStar() + "-" + star.getFortune(); 935 | } 936 | 937 | /** 938 | * Get the index of star duty. 939 | * 940 | * @return the index 941 | */ 942 | public int getWielding() { 943 | int earthlyIndexOfMonth = getEarthlyBranches(cyclicalMonth); 944 | int earthlyIndexOfDay = getEarthlyBranches(cyclicalDay); 945 | int monthIndex = earthlyIndexOfMonth >= 2 ? earthlyIndexOfMonth - 2 : 12 - earthlyIndexOfMonth; 946 | int offset = 12 - (monthIndex == 0 ? 12 : monthIndex); 947 | if (earthlyIndexOfDay + offset < 12) { 948 | return earthlyIndexOfDay + offset; 949 | } else { 950 | return earthlyIndexOfDay + offset - 12; 951 | } 952 | } 953 | 954 | /** 955 | * Get five elements for current day. 956 | * 957 | * @return five elements description 958 | */ 959 | public String getFiveElements() { 960 | return FIVE_ELEMENTS[cyclicalDay / 2] + " " + TWELVE_DURY[getWielding()] + "执位"; 961 | } 962 | 963 | /** 964 | * Get the index of fetus god direction. 965 | * 966 | * @return the index 967 | */ 968 | private int getFetusGodDirectionIndex() { 969 | if (cyclicalDay < 2 || cyclicalDay > 55) { 970 | return 2; 971 | } else if (cyclicalDay < 6) { 972 | return 3; 973 | } else if (cyclicalDay < 12) { 974 | return 4; 975 | } else if (cyclicalDay < 17) { 976 | return 5; 977 | } else if (cyclicalDay < 23) { 978 | return 6; 979 | } else if (cyclicalDay < 28) { 980 | return 7; 981 | } else if (cyclicalDay < 33) { 982 | return 8; 983 | } else if (cyclicalDay < 39) { 984 | return 9; 985 | } else if (cyclicalDay < 44) { 986 | return 10; 987 | } else if (cyclicalDay < 50) { 988 | return 0; 989 | } else { 990 | return 1; 991 | } 992 | } 993 | 994 | /** 995 | * Get the fetus god description for current day. 996 | * 997 | * @return fetus god description 998 | */ 999 | public String getFetusGod() { 1000 | int heavenlyIndex = getHeavenlyStems(cyclicalDay); 1001 | int earthlyIndex = getEarthlyBranches(cyclicalDay); 1002 | 1003 | String fetusGod; 1004 | String heavenlyPosition = FETUS_GOD_HEAVENLY[heavenlyIndex % 5]; 1005 | String earthlyPosition = FETUS_GOD_EARTHLY[earthlyIndex % 5]; 1006 | if (heavenlyPosition.contains(earthlyPosition)) { 1007 | fetusGod = heavenlyPosition; 1008 | } else if (earthlyPosition.contains(heavenlyPosition)) { 1009 | fetusGod = earthlyPosition; 1010 | } else { 1011 | fetusGod = (heavenlyPosition + earthlyPosition); 1012 | } 1013 | 1014 | fetusGod = (fetusGod.length() <= 2 ? "占" : "") + fetusGod; 1015 | fetusGod += FETUS_GOD_DIRECTION[getFetusGodDirectionIndex()]; 1016 | 1017 | return fetusGod; 1018 | } 1019 | } 1020 | --------------------------------------------------------------------------------