diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5620fdddd..978850aff 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,7 +3,7 @@
-------------------------------------------------------------------------------------------------------------
-# 5.7.18 (2021-12-24)
+# 5.7.18 (2021-12-25)
### 🐣新特性
* 【core 】 新增CollStreamUtil.groupKeyValue(pr#479@Gitee)
@@ -27,6 +27,7 @@
* 【core 】 修复UserAgentUtil解析EdgA无法识别问题(issue#I4MCBP@Gitee)
* 【extra 】 修复Archiver路径前带/问题(issue#I4NS0F@Gitee)
* 【extra 】 修复getMainColor方法中参数rgbFilters无效问题(pr#2034@Github)
+* 【core 】 修复ChineseDate无法区分闰月问题(issue#I4NQQW@Gitee)
-------------------------------------------------------------------------------------------------------------
# 5.7.17 (2021-12-09)
diff --git a/hutool-core/src/main/java/cn/hutool/core/date/ChineseDate.java b/hutool-core/src/main/java/cn/hutool/core/date/ChineseDate.java
index b9027beb3..061489bf3 100644
--- a/hutool-core/src/main/java/cn/hutool/core/date/ChineseDate.java
+++ b/hutool-core/src/main/java/cn/hutool/core/date/ChineseDate.java
@@ -13,7 +13,7 @@ import java.util.Date;
/**
- * 农历日期工具,最大支持到2055年,支持:
+ * 农历日期工具,最大支持到2099年,支持:
*
*
* - 通过公历日期构造获取对应农历
@@ -27,34 +27,39 @@ public class ChineseDate {
//农历年
private final int year;
- //农历月
+ //农历月,润N月这个值就是N+1,其他月按照显示月份赋值
private final int month;
+ // 当前月份是否闰月
+ private final boolean isLeapMonth;
//农历日
private final int day;
//公历年
private final int gyear;
- //公历月
- private final int gmonth;
+ //公历月,从1开始计数
+ private final int gmonthBase1;
//公历日
private final int gday;
- //是否闰年
- private boolean leap;
-
/**
* 通过公历日期构造
*
* @param date 公历日期
*/
public ChineseDate(Date date) {
+ // 公历
+ final DateTime dt = DateUtil.beginOfDay(date);
+ gyear = dt.year();
+ gmonthBase1 = dt.month() + 1;
+ gday = dt.dayOfMonth();
+
// 求出和1900年1月31日相差的天数
- int offset = (int) ((DateUtil.beginOfDay(date).getTime() / DateUnit.DAY.getMillis()) - LunarInfo.BASE_DAY);
+ int offset = (int) ((dt.getTime() / DateUnit.DAY.getMillis()) - LunarInfo.BASE_DAY);
// 计算农历年份
// 用offset减去每农历年的天数,计算当天是农历第几天,offset是当年的第几天
int daysOfYear;
- int iYear = LunarInfo.BASE_YEAR;
- for (; iYear <= LunarInfo.MAX_YEAR; iYear++) {
+ int iYear;
+ for (iYear = LunarInfo.BASE_YEAR; iYear <= LunarInfo.MAX_YEAR; iYear++) {
daysOfYear = LunarInfo.yearDays(iYear);
if (offset < daysOfYear) {
break;
@@ -66,48 +71,34 @@ public class ChineseDate {
// 计算农历月份
int leapMonth = LunarInfo.leapMonth(iYear); // 闰哪个月,1-12
// 用当年的天数offset,逐个减去每月(农历)的天数,求出当天是本月的第几天
- int iMonth;
- int daysOfMonth = 0;
- for (iMonth = 1; iMonth < 13 && offset > 0; iMonth++) {
- // 闰月
- if (leapMonth > 0 && iMonth == (leapMonth + 1) && false == leap) {
- --iMonth;
- leap = true;
+ int month;
+ int daysOfMonth;
+ boolean hasLeapMonth = false;
+ for (month = 1; month < 13; month++) {
+ // 闰月,如润的是五月,则5表示五月,6表示润五月
+ if (leapMonth > 0 && month == (leapMonth + 1)) {
daysOfMonth = LunarInfo.leapDays(year);
+ hasLeapMonth = true;
} else {
- daysOfMonth = LunarInfo.monthDays(year, iMonth);
+ // 普通月,当前面的月份存在闰月时,普通月份要-1,递补闰月的数字
+ // 如2月是闰月,此时3月实际是第四个月
+ daysOfMonth = LunarInfo.monthDays(year, hasLeapMonth ? month - 1 : month);
}
+ if (offset < daysOfMonth) {
+ // offset不足月,结束
+ break;
+ }
offset -= daysOfMonth;
- // 解除闰月
- if (leap && iMonth == (leapMonth + 1)) {
- leap = false;
- }
}
- // offset为0时,并且刚才计算的月份是闰月,要校正
- if (offset == 0 && leapMonth > 0 && iMonth == leapMonth + 1) {
- if (leap) {
- leap = false;
- } else {
- leap = true;
- --iMonth;
- }
+ this.isLeapMonth = month == (leapMonth + 1);
+ if (hasLeapMonth && false == this.isLeapMonth) {
+ // 当前月份前有闰月,则月份显示要-1,除非当前月份就是润月
+ month--;
}
- // offset小于0时,也要校正
- if (offset < 0) {
- offset += daysOfMonth;
- --iMonth;
- }
-
- month = iMonth;
- day = offset + 1;
-
- // 公历
- final DateTime dt = DateUtil.date(date);
- gyear = dt.year();
- gmonth = dt.month() + 1;
- gday = dt.dayOfMonth();
+ this.month = month;
+ this.day = offset + 1;
}
/**
@@ -119,10 +110,24 @@ public class ChineseDate {
* @since 5.2.4
*/
public ChineseDate(int chineseYear, int chineseMonth, int chineseDay) {
+ this(chineseYear, chineseMonth, chineseDay, chineseMonth == LunarInfo.leapMonth(chineseYear) + 1);
+ }
+
+ /**
+ * 构造方法传入日期
+ *
+ * @param chineseYear 农历年
+ * @param chineseMonth 农历月,1表示一月(正月)
+ * @param chineseDay 农历日,1表示初一
+ * @param isLeapMonth 当前月份是否闰月
+ * @since 5.7.18
+ */
+ public ChineseDate(int chineseYear, int chineseMonth, int chineseDay, boolean isLeapMonth) {
this.day = chineseDay;
this.month = chineseMonth;
+ // 当月是闰月的后边的月定义为闰月,如润的是五月,则5表示五月,6表示润五月
+ this.isLeapMonth = isLeapMonth;
this.year = chineseYear;
- this.leap = DateUtil.isLeapYear(chineseYear);
//先判断传入的月份是不是闰月
int leapMonth = LunarInfo.leapMonth(chineseYear);
@@ -131,14 +136,14 @@ public class ChineseDate {
//初始化公历年
this.gday = dateTime.dayOfMonth();
//初始化公历月
- this.gmonth = dateTime.month() + 1;
+ this.gmonthBase1 = dateTime.month() + 1;
//初始化公历日
this.gyear = dateTime.year();
} else {
//初始化公历年
this.gday = -1;
//初始化公历月
- this.gmonth = -1;
+ this.gmonthBase1 = -1;
//初始化公历日
this.gyear = -1;
}
@@ -159,12 +164,13 @@ public class ChineseDate {
* @return 公历年
* @since 5.6.1
*/
- public int getGregorianYear(){
+ public int getGregorianYear() {
return this.gyear;
}
/**
- * 获取农历的月,从1开始计数
+ * 获取农历的月,从1开始计数
+ * 此方法返回实际的月序号,如一月是闰月,则一月返回1,润一月返回2
*
* @return 农历的月
* @since 5.2.4
@@ -179,8 +185,8 @@ public class ChineseDate {
* @return 公历月
* @since 5.6.1
*/
- public int getGregorianMonthBase1(){
- return this.gmonth;
+ public int getGregorianMonthBase1() {
+ return this.gmonthBase1;
}
/**
@@ -189,8 +195,8 @@ public class ChineseDate {
* @return 公历月
* @since 5.6.1
*/
- public int getGregorianMonth(){
- return this.gmonth -1;
+ public int getGregorianMonth() {
+ return this.gmonthBase1 - 1;
}
/**
@@ -200,7 +206,7 @@ public class ChineseDate {
* @since 5.4.2
*/
public boolean isLeapMonth() {
- return ChineseMonth.isLeapMonth(this.year, this.month);
+ return this.isLeapMonth;
}
@@ -210,7 +216,7 @@ public class ChineseDate {
* @return 返回农历月份
*/
public String getChineseMonth() {
- return ChineseMonth.getChineseMonthName(isLeapMonth(), this.month, false);
+ return getChineseMonth(false);
}
/**
@@ -219,7 +225,19 @@ public class ChineseDate {
* @return 返回农历月份称呼
*/
public String getChineseMonthName() {
- return ChineseMonth.getChineseMonthName(isLeapMonth(), this.month, true);
+ return getChineseMonth(true);
+ }
+
+ /**
+ * 获得农历月份(中文,例如二月,十二月,或者润一月)
+ *
+ * @param isTraditional 是否传统表示,例如一月传统表示为正月
+ * @return 返回农历月份
+ * @since 5.7.18
+ */
+ public String getChineseMonth(boolean isTraditional) {
+ return ChineseMonth.getChineseMonthName(isLeapMonth(),
+ isLeapMonth() ? this.month - 1 : this.month, isTraditional);
}
/**
@@ -238,7 +256,7 @@ public class ChineseDate {
* @return 公历日
* @since 5.6.1
*/
- public int getGregorianDay(){
+ public int getGregorianDay() {
return this.gday;
}
@@ -271,7 +289,7 @@ public class ChineseDate {
* @return 公历Date
* @since 5.6.1
*/
- public Date getGregorianDate(){
+ public Date getGregorianDate() {
return DateUtil.date(getGregorianCalendar());
}
@@ -281,7 +299,7 @@ public class ChineseDate {
* @return 公历Calendar
* @since 5.6.1
*/
- public Calendar getGregorianCalendar(){
+ public Calendar getGregorianCalendar() {
final Calendar calendar = CalendarUtil.calendar();
//noinspection MagicConstant
calendar.set(this.gyear, getGregorianMonth(), this.gday, 0, 0, 0);
@@ -289,7 +307,7 @@ public class ChineseDate {
}
/**
- * 获得节日
+ * 获得节日,闰月不计入节日中
*
* @return 获得农历节日
*/
@@ -322,8 +340,8 @@ public class ChineseDate {
* @return 获得天干地支的年月日信息
*/
public String getCyclicalYMD() {
- if (gyear >= LunarInfo.BASE_YEAR && gmonth > 0 && gday > 0) {
- return cyclicalm(gyear, gmonth, gday);
+ if (gyear >= LunarInfo.BASE_YEAR && gmonthBase1 > 0 && gday > 0) {
+ return cyclicalm(gyear, gmonthBase1, gday);
}
return null;
}
@@ -331,21 +349,24 @@ public class ChineseDate {
/**
* 获得节气
+ *
* @return 获得节气
* @since 5.6.3
*/
public String getTerm() {
- return SolarTerms.getTerm(gyear, gmonth, gday);
+ return SolarTerms.getTerm(gyear, gmonthBase1, gday);
}
/**
- * 转换为标准的日期格式来表示农历日期,例如2020-01-13
+ * 转换为标准的日期格式来表示农历日期,例如2020-01-13
+ * 如果存在闰月,显示闰月月份,如润二月显示2
*
* @return 标准的日期格式
* @since 5.2.4
*/
public String toStringNormal() {
- return String.format("%04d-%02d-%02d", this.year, this.month, this.day);
+ return String.format("%04d-%02d-%02d", this.year,
+ isLeapMonth() ? this.month - 1 : this.month, this.day);
}
@Override
diff --git a/hutool-core/src/main/java/cn/hutool/core/date/chinese/ChineseMonth.java b/hutool-core/src/main/java/cn/hutool/core/date/chinese/ChineseMonth.java
index ccf1573db..3dabe0b87 100644
--- a/hutool-core/src/main/java/cn/hutool/core/date/chinese/ChineseMonth.java
+++ b/hutool-core/src/main/java/cn/hutool/core/date/chinese/ChineseMonth.java
@@ -29,7 +29,7 @@ public class ChineseMonth {
* 当为非传统表示时,二月,十二月,或者润一月等
*
* @param isLeapMonth 是否闰月
- * @param month 月份,从1开始
+ * @param month 月份,从1开始,如果是闰月,应传入需要显示的月份
* @param isTraditional 是否传统表示,例如一月传统表示为正月
* @return 返回农历月份称呼
*/
diff --git a/hutool-core/src/main/java/cn/hutool/core/date/chinese/LunarInfo.java b/hutool-core/src/main/java/cn/hutool/core/date/chinese/LunarInfo.java
index ac95d47a0..d558c7a86 100644
--- a/hutool-core/src/main/java/cn/hutool/core/date/chinese/LunarInfo.java
+++ b/hutool-core/src/main/java/cn/hutool/core/date/chinese/LunarInfo.java
@@ -66,9 +66,9 @@ public class LunarInfo {
}
/**
- * 传回农历 y年闰月的天数
+ * 传回农历 y年闰月的天数,如果本年无闰月,返回0,区分大小月
*
- * @param y 年
+ * @param y 农历年
* @return 闰月的天数
*/
public static int leapDays(int y) {
@@ -80,7 +80,7 @@ public class LunarInfo {
}
/**
- * 传回农历 y年m月的总天数
+ * 传回农历 y年m月的总天数,区分大小月
*
* @param y 年
* @param m 月
@@ -91,7 +91,8 @@ public class LunarInfo {
}
/**
- * 传回农历 y年闰哪个月 1-12 , 没闰传回 0
+ * 传回农历 y年闰哪个月 1-12 , 没闰传回 0
+ * 此方法会返回润N月中的N,如二月、闰二月都返回2
*
* @param y 年
* @return 润的月, 没闰传回 0
diff --git a/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java b/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java
index b360389bf..da29455fb 100644
--- a/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java
+++ b/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java
@@ -55,6 +55,9 @@ public class ChineseDateTest {
Assert.assertEquals("六月", chineseDate.getChineseMonth());
chineseDate = new ChineseDate(2020,4,15);
+ Assert.assertEquals("四月", chineseDate.getChineseMonth());
+
+ chineseDate = new ChineseDate(2020,5,15);
Assert.assertEquals("闰四月", chineseDate.getChineseMonth());
}
@@ -77,7 +80,29 @@ public class ChineseDateTest {
@Test
public void dateTest2(){
- ChineseDate date = new ChineseDate(DateUtil.parse("2020-10-19 11:12:23"));
+ ChineseDate date = new ChineseDate(DateUtil.parse("2020-10-19"));
Assert.assertEquals("庚子鼠年 九月初三", date.toString());
}
+
+ @Test
+ public void dateTest2_2(){
+ ChineseDate date = new ChineseDate(DateUtil.parse("2020-07-20"));
+ Assert.assertEquals("庚子鼠年 五月三十", date.toString());
+ }
+
+ @Test
+ public void dateTest3(){
+ // 初一,offset为0测试
+ ChineseDate date = new ChineseDate(DateUtil.parse("2099-03-22"));
+ Assert.assertEquals("己未羊年 闰二月初一", date.toString());
+ }
+
+ @Test
+ public void leapMonthTest(){
+ final ChineseDate c1 = new ChineseDate(DateUtil.parse("2028-05-28"));
+ final ChineseDate c2 = new ChineseDate(DateUtil.parse("2028-06-27"));
+
+ Assert.assertEquals("戊申猴年 五月初五", c1.toString());
+ Assert.assertEquals("戊申猴年 闰五月初五", c2.toString());
+ }
}