Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
cn.hutool.core.date.ChineseDate Maven / Gradle / Ivy
package cn.hutool.core.date;
import cn.hutool.core.convert.NumberChineseFormatter;
import cn.hutool.core.date.chinese.ChineseMonth;
import cn.hutool.core.date.chinese.GanZhi;
import cn.hutool.core.date.chinese.LunarFestival;
import cn.hutool.core.date.chinese.LunarInfo;
import cn.hutool.core.date.chinese.SolarTerms;
import cn.hutool.core.util.StrUtil;
import java.time.LocalDate;
import java.util.Calendar;
import java.util.Date;
/**
* 农历日期工具,最大支持到2099年,支持:
*
*
* 通过公历日期构造获取对应农历
* 通过农历日期直接构造
*
*
* @author zjw, looly
* @since 5.1.1
*/
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;
//公历月,从1开始计数
private final int gmonthBase1;
//公历日
private final int gday;
/**
* 通过公历日期构造
*
* @param date 公历日期
*/
public ChineseDate(Date date) {
this(LocalDateTimeUtil.ofDate(date.toInstant()));
}
/**
* 通过公历日期构造
*
* @param localDate 公历日期
* @since 5.7.22
*/
public ChineseDate(LocalDate localDate) {
// 公历
gyear = localDate.getYear();
gmonthBase1 = localDate.getMonthValue();
gday = localDate.getDayOfMonth();
// 求出和1900年1月31日相差的天数
int offset = (int) (localDate.toEpochDay() - LunarInfo.BASE_DAY);
// 计算农历年份
// 用offset减去每农历年的天数,计算当天是农历第几天,offset是当年的第几天
int daysOfYear;
int iYear;
for (iYear = LunarInfo.BASE_YEAR; iYear <= LunarInfo.MAX_YEAR; iYear++) {
daysOfYear = LunarInfo.yearDays(iYear);
if (offset < daysOfYear) {
break;
}
offset -= daysOfYear;
}
year = iYear;
// 计算农历月份
final int leapMonth = LunarInfo.leapMonth(iYear); // 闰哪个月,1-12
// 用当年的天数offset,逐个减去每月(农历)的天数,求出当天是本月的第几天
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 {
// 普通月,当前面的月份存在闰月时,普通月份要-1,递补闰月的数字
// 如2月是闰月,此时3月实际是第四个月
daysOfMonth = LunarInfo.monthDays(year, hasLeapMonth ? month - 1 : month);
}
if (offset < daysOfMonth) {
// offset不足月,结束
break;
}
offset -= daysOfMonth;
}
this.isLeapMonth = leapMonth > 0 && (month == (leapMonth + 1));
if (hasLeapMonth && false == this.isLeapMonth) {
// 当前月份前有闰月,则月份显示要-1,除非当前月份就是润月
month--;
}
this.month = month;
this.day = offset + 1;
}
/**
* 构造方法传入日期
* 此方法自动判断闰月,如果chineseMonth为本年的闰月,则按照闰月计算
*
* @param chineseYear 农历年
* @param chineseMonth 农历月,1表示一月(正月)
* @param chineseDay 农历日,1表示初一
* @since 5.2.4
*/
public ChineseDate(int chineseYear, int chineseMonth, int chineseDay) {
this(chineseYear, chineseMonth, chineseDay, chineseMonth == LunarInfo.leapMonth(chineseYear));
}
/**
* 构造方法传入日期
* 通过isLeapMonth参数区分是否闰月,如五月是闰月,当isLeapMonth为{@code true}时,表示润五月,{@code false}表示五月
*
* @param chineseYear 农历年
* @param chineseMonth 农历月,1表示一月(正月),如果isLeapMonth为{@code true},1表示润一月
* @param chineseDay 农历日,1表示初一
* @param isLeapMonth 当前月份是否闰月
* @since 5.7.18
*/
public ChineseDate(int chineseYear, int chineseMonth, int chineseDay, boolean isLeapMonth) {
if(chineseMonth != LunarInfo.leapMonth(chineseYear)){
// issue#I5YB1A,用户传入的月份可能非闰月,此时此参数无效。
isLeapMonth = false;
}
this.day = chineseDay;
// 当月是闰月的后边的月定义为闰月,如润的是五月,则5表示五月,6表示润五月
this.isLeapMonth = isLeapMonth;
// 闰月时,农历月份+1,如6表示润五月
this.month = isLeapMonth ? chineseMonth + 1 : chineseMonth;
this.year = chineseYear;
final DateTime dateTime = lunar2solar(chineseYear, chineseMonth, chineseDay, isLeapMonth);
if (null != dateTime) {
//初始化公历年
this.gday = dateTime.dayOfMonth();
//初始化公历月
this.gmonthBase1 = dateTime.month() + 1;
//初始化公历日
this.gyear = dateTime.year();
} else {
//初始化公历年
this.gday = -1;
//初始化公历月
this.gmonthBase1 = -1;
//初始化公历日
this.gyear = -1;
}
}
/**
* 获得农历年份
*
* @return 返回农历年份
*/
public int getChineseYear() {
return this.year;
}
/**
* 获取公历的年
*
* @return 公历年
* @since 5.6.1
*/
public int getGregorianYear() {
return this.gyear;
}
/**
* 获取农历的月,从1开始计数
* 此方法返回实际的月序号,如一月是闰月,则一月返回1,润一月返回2
*
* @return 农历的月
* @since 5.2.4
*/
public int getMonth() {
return this.month;
}
/**
* 获取公历的月,从1开始计数
*
* @return 公历月
* @since 5.6.1
*/
public int getGregorianMonthBase1() {
return this.gmonthBase1;
}
/**
* 获取公历的月,从0开始计数
*
* @return 公历月
* @since 5.6.1
*/
public int getGregorianMonth() {
return this.gmonthBase1 - 1;
}
/**
* 当前农历月份是否为闰月
*
* @return 是否为闰月
* @since 5.4.2
*/
public boolean isLeapMonth() {
return this.isLeapMonth;
}
/**
* 获得农历月份(中文,例如二月,十二月,或者润一月)
*
* @return 返回农历月份
*/
public String getChineseMonth() {
return getChineseMonth(false);
}
/**
* 获得农历月称呼(中文,例如二月,腊月,或者润正月)
*
* @return 返回农历月份称呼
*/
public String getChineseMonthName() {
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);
}
/**
* 获取农历的日,从1开始计数
*
* @return 农历的日,从1开始计数
* @since 5.2.4
*/
public int getDay() {
return this.day;
}
/**
* 获取公历的日
*
* @return 公历日
* @since 5.6.1
*/
public int getGregorianDay() {
return this.gday;
}
/**
* 获得农历日
*
* @return 获得农历日
*/
public String getChineseDay() {
String[] chineseTen = {"初", "十", "廿", "卅"};
int n = (day % 10 == 0) ? 9 : (day % 10 - 1);
if (day > 30) {
return "";
}
switch (day) {
case 10:
return "初十";
case 20:
return "二十";
case 30:
return "三十";
default:
return chineseTen[day / 10] + NumberChineseFormatter.format(n + 1, false);
}
}
/**
* 获取公历的Date
*
* @return 公历Date
* @since 5.6.1
*/
public Date getGregorianDate() {
return DateUtil.date(getGregorianCalendar());
}
/**
* 获取公历的Calendar
*
* @return 公历Calendar
* @since 5.6.1
*/
public Calendar getGregorianCalendar() {
final Calendar calendar = CalendarUtil.calendar();
//noinspection MagicConstant
calendar.set(this.gyear, getGregorianMonth(), this.gday, 0, 0, 0);
return calendar;
}
/**
* 获得节日,闰月不计入节日中
*
* @return 获得农历节日
*/
public String getFestivals() {
return StrUtil.join(",", LunarFestival.getFestivals(this.year, this.month, day));
}
/**
* 获得年份生肖
*
* @return 获得年份生肖
*/
public String getChineseZodiac() {
return Zodiac.getChineseZodiac(this.year);
}
/**
* 获得年的天干地支
*
* @return 获得天干地支
*/
public String getCyclical() {
return GanZhi.getGanzhiOfYear(this.year);
}
/**
* 干支纪年信息
*
* @return 获得天干地支的年月日信息
*/
public String getCyclicalYMD() {
if (gyear >= LunarInfo.BASE_YEAR && gmonthBase1 > 0 && gday > 0) {
return cyclicalm(gyear, gmonthBase1, gday);
}
return null;
}
/**
* 获得节气
*
* @return 获得节气
* @since 5.6.3
*/
public String getTerm() {
return SolarTerms.getTerm(gyear, gmonthBase1, gday);
}
/**
* 转换为标准的日期格式来表示农历日期,例如2020-01-13
* 如果存在闰月,显示闰月月份,如润二月显示2
*
* @return 标准的日期格式
* @since 5.2.4
*/
public String toStringNormal() {
return String.format("%04d-%02d-%02d", this.year,
isLeapMonth() ? this.month - 1 : this.month, this.day);
}
@Override
public String toString() {
return String.format("%s%s年 %s%s", getCyclical(), getChineseZodiac(), getChineseMonthName(), getChineseDay());
}
// ------------------------------------------------------- private method start
/**
* 这里同步处理年月日的天干地支信息
*
* @param year 公历年
* @param month 公历月,从1开始
* @param day 公历日
* @return 天干地支信息
*/
private String cyclicalm(int year, int month, int day) {
return StrUtil.format("{}年{}月{}日",
GanZhi.getGanzhiOfYear(this.year),
GanZhi.getGanzhiOfMonth(year, month, day),
GanZhi.getGanzhiOfDay(year, month, day));
}
/**
* 通过农历年月日信息 返回公历信息 提供给构造函数
*
* @param chineseYear 农历年
* @param chineseMonth 农历月
* @param chineseDay 农历日
* @param isLeapMonth 传入的月是不是闰月
* @return 公历信息
*/
private DateTime lunar2solar(int chineseYear, int chineseMonth, int chineseDay, boolean isLeapMonth) {
//超出了最大极限值
if ((chineseYear == 2100 && chineseMonth == 12 && chineseDay > 1) ||
(chineseYear == LunarInfo.BASE_YEAR && chineseMonth == 1 && chineseDay < 31)) {
return null;
}
int day = LunarInfo.monthDays(chineseYear, chineseMonth);
int _day = day;
if (isLeapMonth) {
_day = LunarInfo.leapDays(chineseYear);
}
//参数合法性效验
if (chineseYear < LunarInfo.BASE_YEAR || chineseYear > 2100 || chineseDay > _day) {
return null;
}
//计算农历的时间差
int offset = 0;
for (int i = LunarInfo.BASE_YEAR; i < chineseYear; i++) {
offset += LunarInfo.yearDays(i);
}
int leap;
boolean isAdd = false;
for (int i = 1; i < chineseMonth; i++) {
leap = LunarInfo.leapMonth(chineseYear);
if (false == isAdd) {//处理闰月
if (leap <= i && leap > 0) {
offset += LunarInfo.leapDays(chineseYear);
isAdd = true;
}
}
offset += LunarInfo.monthDays(chineseYear, i);
}
//转换闰月农历 需补充该年闰月的前一个月的时差
if (isLeapMonth) {
offset += day;
}
//1900年农历正月一日的公历时间为1900年1月30日0时0分0秒(该时间也是本农历的最开始起始点) -2203804800000
return DateUtil.date(((offset + chineseDay - 31) * 86400000L) - 2203804800000L);
}
// ------------------------------------------------------- private method end
}