feat(admin): 添加用户管理相关文件

添加用户管理视图、API和状态管理文件
This commit is contained in:
张翔
2026-03-28 14:37:29 +08:00
commit 08ea5fbe98
1643 changed files with 255646 additions and 0 deletions
@@ -0,0 +1,612 @@
package io.destiny.biz.util;
import io.destiny.biz.domain.Almanac;
import io.destiny.biz.domain.LunarDate;
import io.destiny.biz.enums.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class AlmanacCalculator {
private static final Logger log = LoggerFactory.getLogger(AlmanacCalculator.class);
private static final int[] MONTH_BRANCHES = {
3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 1, 2
};
private static final String[][] SUITABLE_ACTIVITIES_BY_JIANCHU = {
{"开业", "嫁娶", "出行", "求财", "祭祀", "祈福"},
{"扫除", "治病", "破土", "安葬", "出行", "求医"},
{"祈福", "祭祀", "安葬", "修造", "动土", "交易"},
{"修造", "动土", "交易", "纳财", "签约", "安床"},
{"纳财", "签约", "安床", "嫁娶", "开市", "交易"},
{"上任", "出行", "求医", "祭祀", "祈福", "安葬"},
{"破屋", "坏垣", "拆卸", "破土", "修坟", "安葬"},
{"安葬", "破土", "修坟", "祭祀", "祈福", "出行"},
{"嫁娶", "开市", "交易", "纳财", "签约", "安床"},
{"纳财", "买田", "置业", "交易", "签约", "安床"},
{"开业", "开工", "开张", "嫁娶", "出行", "求财"},
{"塞穴", "断蚁", "筑堤", "修造", "动土", "交易"}
};
private static final String[][] UNSUITABLE_ACTIVITIES_BY_JIANCHU = {
{"安葬", "破土", "修坟", "祭祀", "祈福", "出行"},
{"开业", "嫁娶", "出行", "求财", "祭祀", "祈福"},
{"开业", "嫁娶", "出行", "求财", "祭祀", "祈福"},
{"开业", "嫁娶", "出行", "求财", "祭祀", "祈福"},
{"开业", "嫁娶", "出行", "求财", "祭祀", "祈福"},
{"开业", "嫁娶", "出行", "求财", "祭祀", "祈福"},
{"开业", "嫁娶", "出行", "求财", "祭祀", "祈福"},
{"开业", "嫁娶", "出行", "求财", "祭祀", "祈福"},
{"安葬", "破土", "修坟", "祭祀", "祈福", "出行"},
{"安葬", "破土", "修坟", "祭祀", "祈福", "出行"},
{"安葬", "破土", "修坟", "祭祀", "祈福", "出行"},
{"开业", "嫁娶", "出行", "求财", "祭祀", "祈福"}
};
private static final int[] CLASH_BRANCHES = {7, 8, 9, 10, 11, 12, 1, 2, 3, 4, 5, 6};
private static final String[] EVIL_DIRECTIONS = {"南", "东", "北", "西", "南", "东", "北", "西", "南", "东", "北", "西"};
private static final String[] STAR_GODS = {
"青龙", "明堂", "天刑", "朱雀", "金匮", "天德", "白虎", "玉堂", "天牢", "玄武", "司命", "勾陈"
};
private static final String[] NA_YIN_FIVE_ELEMENTS = {
"海中金", "海中金", "炉中火", "炉中火", "大林木", "大林木", "路旁土", "路旁土", "剑锋金", "剑锋金",
"山头火", "山头火", "涧下水", "涧下水", "城头土", "城头土", "白蜡金", "白蜡金", "杨柳木", "杨柳木",
"泉中水", "泉中水", "屋上土", "屋上土", "霹雳火", "霹雳火", "松柏木", "松柏木", "长流水", "长流水",
"沙中金", "沙中金", "山下火", "山下火", "平地木", "平地木", "壁上土", "壁上土", "金箔金", "金箔金",
"覆灯火", "覆灯火", "天河水", "天河水", "大驿土", "大驿土", "钗钏金", "钗钏金", "桑柘木", "桑柘木",
"大溪水", "大溪水", "沙中土", "沙中土", "天上火", "天上火", "石榴木", "石榴木", "大海水", "大海水"
};
private static final String[] PENGZU_STEM_TABOOS = {
"甲不开仓财物耗散",
"乙不栽植千株不长",
"丙不修灶必见灾殃",
"丁不剃头头必生疮",
"戊不受田田主不祥",
"己不破券二比并亡",
"庚不经络织机虚张",
"辛不合酱主人不尝",
"壬不汲水更难提防",
"癸不词讼理弱敌强"
};
private static final String[] PENGZU_BRANCH_TABOOS = {
"子不问卜自惹祸殃",
"丑不冠带主不还乡",
"寅不祭祀神鬼不尝",
"卯不穿井水泉不香",
"辰不哭泣必主重丧",
"巳不远行财物伏藏",
"午不苫盖屋主更张",
"未不服药毒气入肠",
"申不安床鬼祟入房",
"酉不会客醉坐颠狂",
"戌不吃犬作怪上床",
"亥不嫁娶不利新郎"
};
private AlmanacCalculator() {
throw new UnsupportedOperationException("Utility class");
}
public static Almanac calculateAlmanac(LocalDateTime solarDateTime, LunarDate lunarDate) {
log.debug("开始计算黄历 - 阳历:{},农历:{}", solarDateTime, lunarDate);
Almanac.Builder builder = Almanac.builder()
.solarDate(solarDateTime)
.lunarDate(lunarDate);
String jianChu = calculateJianChu(lunarDate);
builder.jianChu(jianChu);
log.debug("建除十二神:{}", jianChu);
List<String> suitable = calculateSuitable(jianChu);
builder.suitable(suitable);
log.debug("宜事项:{}", suitable);
List<String> unsuitable = calculateUnsuitable(jianChu);
builder.unsuitable(unsuitable);
log.debug("忌事项:{}", unsuitable);
String godDirection = calculateGodDirection(solarDateTime);
builder.godDirection(godDirection);
log.debug("财神方位:{}", godDirection);
String joyDirection = calculateJoyDirection(solarDateTime);
builder.joyDirection(joyDirection);
log.debug("喜神方位:{}", joyDirection);
String fortuneDirection = calculateFortuneDirection(solarDateTime);
builder.fortuneDirection(fortuneDirection);
log.debug("福神方位:{}", fortuneDirection);
String nobleDirection = calculateNobleDirection(solarDateTime);
builder.nobleDirection(nobleDirection);
log.debug("贵人方位:{}", nobleDirection);
String clash = calculateClash(lunarDate);
builder.clash(clash);
log.debug("冲:{}", clash);
String evil = calculateEvil(lunarDate);
builder.evil(evil);
log.debug("煞:{}", evil);
String starGod = calculateStarGod(solarDateTime);
builder.starGod(starGod);
log.debug("值日星神:{}", starGod);
String naYin = calculateNaYin(solarDateTime);
builder.naYin(naYin);
log.debug("纳音五行:{}", naYin);
String fetusGod = calculateFetusGod(lunarDate);
builder.fetusGod(fetusGod);
log.debug("胎神方位:{}", fetusGod);
String pengzuTaboo = calculatePengzuTaboo(solarDateTime);
builder.pengzuTaboo(pengzuTaboo);
log.debug("彭祖百忌:{}", pengzuTaboo);
Almanac almanac = builder.build();
log.debug("黄历计算完成:{}", almanac);
return almanac;
}
public static String calculateJianChu(LunarDate lunarDate) {
log.debug("计算建除十二神 - 农历:{}", lunarDate);
if (lunarDate == null) {
log.error("农历日期不能为空");
throw new IllegalArgumentException("农历日期不能为空");
}
int monthBranch = parseLunarMonthBranch(lunarDate.getLunarMonth());
int dayNumber = parseLunarDayNumber(lunarDate.getLunarDay());
int jianChuIndex = (monthBranch + dayNumber - 1) % 12;
if (jianChuIndex == 0) {
jianChuIndex = 12;
}
JianChuDeity jianChuDeity = JianChuDeity.fromIndex(jianChuIndex);
String result = jianChuDeity.getName();
log.debug("建除十二神计算结果:{}", result);
return result;
}
private static int parseLunarMonthBranch(String lunarMonth) {
try {
int monthNum = Integer.parseInt(lunarMonth);
if (monthNum < 1 || monthNum > 12) {
log.error("农历月份超出范围:{}", lunarMonth);
throw new IllegalArgumentException("农历月份超出范围:" + lunarMonth);
}
return MONTH_BRANCHES[monthNum - 1];
} catch (NumberFormatException e) {
log.error("无法解析农历月份:{}", lunarMonth);
throw new IllegalArgumentException("无法解析农历月份:" + lunarMonth);
}
}
private static int parseLunarDayNumber(String lunarDay) {
try {
int dayNum = Integer.parseInt(lunarDay);
if (dayNum < 1 || dayNum > 30) {
log.error("农历日期超出范围:{}", lunarDay);
throw new IllegalArgumentException("农历日期超出范围:" + lunarDay);
}
return dayNum;
} catch (NumberFormatException e) {
log.error("无法解析农历日期:{}", lunarDay);
throw new IllegalArgumentException("无法解析农历日期:" + lunarDay);
}
}
public static List<String> calculateSuitable(String jianChu) {
log.debug("计算宜事项 - 建除十二神:{}", jianChu);
if (jianChu == null || jianChu.isEmpty()) {
log.error("建除十二神不能为空");
throw new IllegalArgumentException("建除十二神不能为空");
}
JianChuDeity jianChuDeity = JianChuDeity.fromName(jianChu);
int index = jianChuDeity.getIndex() - 1;
String[] suitableActivities = SUITABLE_ACTIVITIES_BY_JIANCHU[index];
List<String> result = new ArrayList<>(Arrays.asList(suitableActivities));
log.debug("宜事项计算结果:{}", result);
return result;
}
public static List<String> calculateUnsuitable(String jianChu) {
log.debug("计算忌事项 - 建除十二神:{}", jianChu);
if (jianChu == null || jianChu.isEmpty()) {
log.error("建除十二神不能为空");
throw new IllegalArgumentException("建除十二神不能为空");
}
JianChuDeity jianChuDeity = JianChuDeity.fromName(jianChu);
int index = jianChuDeity.getIndex() - 1;
String[] unsuitableActivities = UNSUITABLE_ACTIVITIES_BY_JIANCHU[index];
List<String> result = new ArrayList<>(Arrays.asList(unsuitableActivities));
log.debug("忌事项计算结果:{}", result);
return result;
}
public static String calculateGodDirection(LocalDateTime solarDateTime) {
log.debug("计算财神方位 - 阳历:{}", solarDateTime);
if (solarDateTime == null) {
log.error("阳历日期不能为空");
throw new IllegalArgumentException("阳历日期不能为空");
}
int dayStemIndex = getDayStemIndex(solarDateTime);
String result;
switch (dayStemIndex) {
case 1:
case 2:
result = "东北";
break;
case 3:
case 4:
result = "正西";
break;
case 5:
case 6:
result = "正北";
break;
case 7:
case 8:
result = "正东";
break;
case 9:
case 10:
result = "正南";
break;
default:
result = "";
}
log.debug("财神方位计算结果:{}", result);
return result;
}
public static String calculateJoyDirection(LocalDateTime solarDateTime) {
log.debug("计算喜神方位 - 阳历:{}", solarDateTime);
if (solarDateTime == null) {
log.error("阳历日期不能为空");
throw new IllegalArgumentException("阳历日期不能为空");
}
int dayStemIndex = getDayStemIndex(solarDateTime);
String result;
switch (dayStemIndex) {
case 1:
case 3:
case 5:
case 7:
case 9:
result = "正北";
break;
case 2:
case 4:
case 6:
case 8:
case 10:
result = "东北";
break;
default:
result = "";
}
log.debug("喜神方位计算结果:{}", result);
return result;
}
public static String calculateFortuneDirection(LocalDateTime solarDateTime) {
log.debug("计算福神方位 - 阳历:{}", solarDateTime);
if (solarDateTime == null) {
log.error("阳历日期不能为空");
throw new IllegalArgumentException("阳历日期不能为空");
}
int dayStemIndex = getDayStemIndex(solarDateTime);
String result;
switch (dayStemIndex) {
case 1:
case 2:
result = "正北";
break;
case 3:
case 4:
result = "西北";
break;
case 5:
case 7:
case 8:
result = "西南";
break;
case 6:
result = "正南";
break;
case 9:
case 10:
result = "东南";
break;
default:
result = "";
}
log.debug("福神方位计算结果:{}", result);
return result;
}
public static String calculateNobleDirection(LocalDateTime solarDateTime) {
log.debug("计算贵人方位 - 阳历:{}", solarDateTime);
if (solarDateTime == null) {
log.error("阳历日期不能为空");
throw new IllegalArgumentException("阳历日期不能为空");
}
int dayStemIndex = getDayStemIndex(solarDateTime);
String result;
switch (dayStemIndex) {
case 1:
result = "丑、未";
break;
case 2:
result = "子、申";
break;
case 3:
case 4:
result = "亥、酉";
break;
case 5:
case 6:
result = "丑、未";
break;
case 7:
case 8:
result = "午、寅";
break;
case 9:
case 10:
result = "卯、巳";
break;
default:
result = "";
}
log.debug("贵人方位计算结果:{}", result);
return result;
}
private static int getDayStemIndex(LocalDateTime solarDateTime) {
int year = solarDateTime.getYear();
int month = solarDateTime.getMonthValue();
int day = solarDateTime.getDayOfMonth();
int totalDays = calculateTotalDays(year, month, day);
int dayStemIndex = (totalDays + 3) % 10;
if (dayStemIndex == 0) {
dayStemIndex = 10;
}
return dayStemIndex;
}
private static int calculateTotalDays(int year, int month, int day) {
int totalDays = 0;
for (int y = 1900; y < year; y++) {
totalDays += isLeapYear(y) ? 366 : 365;
}
int[] daysInMonth = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
if (isLeapYear(year)) {
daysInMonth[1] = 29;
}
for (int m = 1; m < month; m++) {
totalDays += daysInMonth[m - 1];
}
totalDays += day;
return totalDays;
}
private static boolean isLeapYear(int year) {
return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
}
public static String calculateClash(LunarDate lunarDate) {
log.debug("计算冲 - 农历:{}", lunarDate);
if (lunarDate == null) {
log.error("农历日期不能为空");
throw new IllegalArgumentException("农历日期不能为空");
}
int dayBranchIndex = getDayBranchIndex(lunarDate.getLunarDay());
int clashBranchIndex = CLASH_BRANCHES[dayBranchIndex - 1];
EarthlyBranch clashBranch = EarthlyBranch.fromIndex(clashBranchIndex);
String result = clashBranch.getName();
log.debug("冲计算结果:{}", result);
return result;
}
public static String calculateEvil(LunarDate lunarDate) {
log.debug("计算煞 - 农历:{}", lunarDate);
if (lunarDate == null) {
log.error("农历日期不能为空");
throw new IllegalArgumentException("农历日期不能为空");
}
int dayBranchIndex = getDayBranchIndex(lunarDate.getLunarDay());
String result = EVIL_DIRECTIONS[dayBranchIndex - 1];
log.debug("煞计算结果:{}", result);
return result;
}
private static int getDayBranchIndex(String lunarDay) {
int dayNumber = parseLunarDayNumber(lunarDay);
int dayBranchIndex = (dayNumber + 2) % 12;
if (dayBranchIndex == 0) {
dayBranchIndex = 12;
}
return dayBranchIndex;
}
public static String calculateStarGod(LocalDateTime solarDateTime) {
log.debug("计算值日星神 - 阳历:{}", solarDateTime);
if (solarDateTime == null) {
log.error("阳历日期不能为空");
throw new IllegalArgumentException("阳历日期不能为空");
}
int dayBranchIndex = getDayBranchIndexFromSolar(solarDateTime);
String result = STAR_GODS[dayBranchIndex - 1];
log.debug("值日星神计算结果:{}", result);
return result;
}
private static int getDayBranchIndexFromSolar(LocalDateTime solarDateTime) {
int year = solarDateTime.getYear();
int month = solarDateTime.getMonthValue();
int day = solarDateTime.getDayOfMonth();
int totalDays = calculateTotalDays(year, month, day);
int dayBranchIndex = (totalDays + 1) % 12;
if (dayBranchIndex == 0) {
dayBranchIndex = 12;
}
return dayBranchIndex;
}
public static String calculateNaYin(LocalDateTime solarDateTime) {
log.debug("计算纳音五行 - 阳历:{}", solarDateTime);
if (solarDateTime == null) {
log.error("阳历日期不能为空");
throw new IllegalArgumentException("阳历日期不能为空");
}
int dayStemIndex = getDayStemIndex(solarDateTime);
int dayBranchIndex = getDayBranchIndexFromSolar(solarDateTime);
int naYinIndex = (dayStemIndex - 1) * 6 + (dayBranchIndex - 1) / 2;
String result = NA_YIN_FIVE_ELEMENTS[naYinIndex];
log.debug("纳音五行计算结果:{}", result);
return result;
}
public static String calculateFetusGod(LunarDate lunarDate) {
log.debug("计算胎神方位 - 农历:{}", lunarDate);
if (lunarDate == null) {
log.error("农历日期不能为空");
throw new IllegalArgumentException("农历日期不能为空");
}
int dayBranchIndex = getDayBranchIndex(lunarDate.getLunarDay());
String result;
switch (dayBranchIndex) {
case 1:
result = "房床北";
break;
case 2:
result = "厕厕东北";
break;
case 3:
result = "炉灶东北";
break;
case 4:
result = "大门东";
break;
case 5:
result = "栖床东南";
break;
case 6:
result = "房床东南";
break;
case 7:
result = "房床南";
break;
case 8:
result = "厕厕西南";
break;
case 9:
result = "炉灶西南";
break;
case 10:
result = "大门西";
break;
case 11:
result = "栖床西北";
break;
case 12:
result = "房床西北";
break;
default:
result = "";
}
log.debug("胎神方位计算结果:{}", result);
return result;
}
public static String calculatePengzuTaboo(LocalDateTime solarDateTime) {
log.debug("计算彭祖百忌 - 阳历:{}", solarDateTime);
if (solarDateTime == null) {
log.error("阳历日期不能为空");
throw new IllegalArgumentException("阳历日期不能为空");
}
int dayStemIndex = getDayStemIndex(solarDateTime);
int dayBranchIndex = getDayBranchIndexFromSolar(solarDateTime);
String stemTaboo = PENGZU_STEM_TABOOS[dayStemIndex - 1];
String branchTaboo = PENGZU_BRANCH_TABOOS[dayBranchIndex - 1];
String result = stemTaboo + "," + branchTaboo;
log.debug("彭祖百忌计算结果:{}", result);
return result;
}
}
@@ -0,0 +1,278 @@
package io.destiny.biz.util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.time.format.TextStyle;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class DateTimeFormatUtil {
private static final Logger log = LoggerFactory.getLogger(DateTimeFormatUtil.class);
private static final String DEFAULT_TIMEZONE = "Asia/Shanghai";
private static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
private static final String DEFAULT_DATETIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
private static final Map<String, DateTimeFormatter> FORMATTER_CACHE = new ConcurrentHashMap<>();
static {
log.debug("初始化日期时间格式工具类");
initializeFormatters();
log.debug("日期时间格式工具类初始化完成");
}
private static void initializeFormatters() {
log.debug("初始化日期时间格式化器缓存");
FORMATTER_CACHE.put("yyyy-MM-dd", DateTimeFormatter.ofPattern("yyyy-MM-dd"));
FORMATTER_CACHE.put("yyyy/MM/dd", DateTimeFormatter.ofPattern("yyyy/MM/dd"));
FORMATTER_CACHE.put("yyyy年MM月dd日", DateTimeFormatter.ofPattern("yyyy年MM月dd日"));
FORMATTER_CACHE.put("yyyyMMdd", DateTimeFormatter.ofPattern("yyyyMMdd"));
FORMATTER_CACHE.put("yyyy-MM-dd HH:mm:ss", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
FORMATTER_CACHE.put("yyyy/MM/dd HH:mm:ss", DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss"));
FORMATTER_CACHE.put("yyyy年MM月dd日 HH时mm分ss秒", DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH时mm分ss秒"));
FORMATTER_CACHE.put("yyyyMMddHHmmss", DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
FORMATTER_CACHE.put("MM-dd", DateTimeFormatter.ofPattern("MM-dd"));
FORMATTER_CACHE.put("MM月dd日", DateTimeFormatter.ofPattern("MM月dd日"));
log.debug("日期时间格式化器缓存初始化完成,共{}个格式", FORMATTER_CACHE.size());
}
public static String formatDate(LocalDate date) {
return formatDate(date, DEFAULT_DATE_FORMAT);
}
public static String formatDate(LocalDate date, String pattern) {
log.debug("格式化日期:{},格式:{}", date, pattern);
if (date == null) {
log.debug("日期为空,返回null");
return null;
}
DateTimeFormatter formatter = getFormatter(pattern);
String result = date.format(formatter);
log.debug("日期格式化完成:{}", result);
return result;
}
public static String formatDateTime(LocalDateTime dateTime) {
return formatDateTime(dateTime, DEFAULT_DATETIME_FORMAT);
}
public static String formatDateTime(LocalDateTime dateTime, String pattern) {
log.debug("格式化日期时间:{},格式:{}", dateTime, pattern);
if (dateTime == null) {
log.debug("日期时间为空,返回null");
return null;
}
DateTimeFormatter formatter = getFormatter(pattern);
String result = dateTime.format(formatter);
log.debug("日期时间格式化完成:{}", result);
return result;
}
public static String formatDateWithTimezone(LocalDate date, String timeZone) {
log.debug("格式化日期(带时区):{},时区:{}", date, timeZone);
if (date == null) {
log.debug("日期为空,返回null");
return null;
}
ZoneId zoneId = parseZoneId(timeZone);
String result = formatDate(date) + " (" + zoneId.getId() + ")";
log.debug("日期格式化完成(带时区):{}", result);
return result;
}
public static String formatDateTimeWithTimezone(LocalDateTime dateTime, String timeZone) {
log.debug("格式化日期时间(带时区):{},时区:{}", dateTime, timeZone);
if (dateTime == null) {
log.debug("日期时间为空,返回null");
return null;
}
ZoneId zoneId = parseZoneId(timeZone);
ZonedDateTime zonedDateTime = dateTime.atZone(zoneId);
String result = zonedDateTime.format(getFormatter(DEFAULT_DATETIME_FORMAT)) + " (" + zoneId.getId() + ")";
log.debug("日期时间格式化完成(带时区):{}", result);
return result;
}
public static LocalDate parseDate(String dateStr) {
return parseDate(dateStr, DEFAULT_DATE_FORMAT);
}
public static LocalDate parseDate(String dateStr, String pattern) {
log.debug("解析日期字符串:{},格式:{}", dateStr, pattern);
if (dateStr == null || dateStr.trim().isEmpty()) {
log.debug("日期字符串为空,返回null");
return null;
}
try {
DateTimeFormatter formatter = getFormatter(pattern);
LocalDate result = LocalDate.parse(dateStr, formatter);
log.debug("日期解析完成:{}", result);
return result;
} catch (DateTimeParseException e) {
log.error("日期解析失败:{},格式:{},错误:{}", dateStr, pattern, e.getMessage());
throw new IllegalArgumentException("日期格式错误:" + dateStr + ",期望格式:" + pattern, e);
}
}
public static LocalDateTime parseDateTime(String dateTimeStr) {
return parseDateTime(dateTimeStr, DEFAULT_DATETIME_FORMAT);
}
public static LocalDateTime parseDateTime(String dateTimeStr, String pattern) {
log.debug("解析日期时间字符串:{},格式:{}", dateTimeStr, pattern);
if (dateTimeStr == null || dateTimeStr.trim().isEmpty()) {
log.debug("日期时间字符串为空,返回null");
return null;
}
try {
DateTimeFormatter formatter = getFormatter(pattern);
LocalDateTime result = LocalDateTime.parse(dateTimeStr, formatter);
log.debug("日期时间解析完成:{}", result);
return result;
} catch (DateTimeParseException e) {
log.error("日期时间解析失败:{},格式:{},错误:{}", dateTimeStr, pattern, e.getMessage());
throw new IllegalArgumentException("日期时间格式错误:" + dateTimeStr + ",期望格式:" + pattern, e);
}
}
public static LocalDateTime convertTimezone(LocalDateTime dateTime, String fromTimeZone, String toTimeZone) {
log.debug("转换时区:{},从{}到{}", dateTime, fromTimeZone, toTimeZone);
if (dateTime == null) {
log.debug("日期时间为空,返回null");
return null;
}
ZoneId fromZoneId = parseZoneId(fromTimeZone);
ZoneId toZoneId = parseZoneId(toTimeZone);
ZonedDateTime fromZoned = dateTime.atZone(fromZoneId);
ZonedDateTime toZoned = fromZoned.withZoneSameInstant(toZoneId);
LocalDateTime result = toZoned.toLocalDateTime();
log.debug("时区转换完成:{} -> {}", dateTime, result);
return result;
}
public static String formatDateLocalized(LocalDate date, String timeZone, Locale locale) {
log.debug("格式化本地化日期:{},时区:{},语言环境:{}", date, timeZone, locale);
if (date == null) {
log.debug("日期为空,返回null");
return null;
}
String year = String.valueOf(date.getYear());
String month = date.getMonth().getDisplayName(TextStyle.FULL, locale);
String day = String.valueOf(date.getDayOfMonth());
String weekday = date.getDayOfWeek().getDisplayName(TextStyle.FULL, locale);
String result = String.format("%s年%s月%s日 %s", year, month, day, weekday);
log.debug("本地化日期格式化完成:{}", result);
return result;
}
public static boolean isValidDateRange(LocalDate startDate, LocalDate endDate) {
log.debug("验证日期范围:{} - {}", startDate, endDate);
if (startDate == null || endDate == null) {
log.debug("开始日期或结束日期为空,返回false");
return false;
}
boolean result = !startDate.isAfter(endDate);
log.debug("日期范围验证结果:{}", result);
return result;
}
public static int daysBetween(LocalDate startDate, LocalDate endDate) {
log.debug("计算日期间隔:{} - {}", startDate, endDate);
if (startDate == null || endDate == null) {
log.debug("开始日期或结束日期为空,返回0");
return 0;
}
long days = java.time.temporal.ChronoUnit.DAYS.between(startDate, endDate);
int result = (int) Math.abs(days);
log.debug("日期间隔计算完成:{}天", result);
return result;
}
public static boolean isSameDay(LocalDate date1, LocalDate date2) {
log.debug("判断是否为同一天:{} 和 {}", date1, date2);
if (date1 == null || date2 == null) {
log.debug("任一日期为空,返回false");
return false;
}
boolean result = date1.isEqual(date2);
log.debug("同一天判断结果:{}", result);
return result;
}
public static boolean isToday(LocalDate date) {
log.debug("判断是否为今天:{}", date);
if (date == null) {
log.debug("日期为空,返回false");
return false;
}
boolean result = date.isEqual(LocalDate.now());
log.debug("今天判断结果:{}", result);
return result;
}
public static boolean isFutureDate(LocalDate date) {
log.debug("判断是否为未来日期:{}", date);
if (date == null) {
log.debug("日期为空,返回false");
return false;
}
boolean result = date.isAfter(LocalDate.now());
log.debug("未来日期判断结果:{}", result);
return result;
}
public static boolean isPastDate(LocalDate date) {
log.debug("判断是否为过去日期:{}", date);
if (date == null) {
log.debug("日期为空,返回false");
return false;
}
boolean result = date.isBefore(LocalDate.now());
log.debug("过去日期判断结果:{}", result);
return result;
}
private static DateTimeFormatter getFormatter(String pattern) {
return FORMATTER_CACHE.computeIfAbsent(pattern, DateTimeFormatter::ofPattern);
}
private static ZoneId parseZoneId(String timeZone) {
if (timeZone == null || timeZone.trim().isEmpty()) {
log.debug("时区为空,使用默认时区:{}", DEFAULT_TIMEZONE);
return ZoneId.of(DEFAULT_TIMEZONE);
}
try {
return ZoneId.of(timeZone);
} catch (Exception e) {
log.error("时区解析失败:{},使用默认时区:{}", timeZone, DEFAULT_TIMEZONE);
return ZoneId.of(DEFAULT_TIMEZONE);
}
}
}
@@ -0,0 +1,490 @@
package io.destiny.biz.util;
import io.destiny.biz.domain.Palace;
import io.destiny.biz.domain.StarInfo;
import io.destiny.biz.domain.ZiweiChart;
import io.destiny.biz.enums.MajorStar;
import io.destiny.biz.enums.PalaceType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
/**
* 格局识别工具类
* 用于识别紫微斗数中的各种格局
* 通过分析命盘中的星曜组合,识别出特殊的格局并给出评分
*
* @author 张翔
* @date 2025-12-29
*/
public class PatternRecognitionUtil {
private static final Logger log = LoggerFactory.getLogger(PatternRecognitionUtil.class);
/**
* 格局内部类,用于表示识别到的格局信息
*/
public static class Pattern {
/** 格局名称 */
private String name;
/** 格局描述 */
private String description;
/** 宫位类型 */
private PalaceType palaceType;
/** 涉及的主星列表 */
private List<MajorStar> stars;
/** 格局评分 */
private int score;
/**
* 构造函数
*
* @param name 格局名称
* @param description 格局描述
* @param palaceType 宫位类型
* @param stars 涉及的主星列表
* @param score 格局评分
*/
public Pattern(String name, String description, PalaceType palaceType, List<MajorStar> stars, int score) {
this.name = name;
this.description = description;
this.palaceType = palaceType;
this.stars = stars;
this.score = score;
}
/**
* 获取格局名称
*
* @return 格局名称
*/
public String getName() {
return name;
}
/**
* 获取格局描述
*
* @return 格局描述
*/
public String getDescription() {
return description;
}
/**
* 获取宫位类型
*
* @return 宫位类型
*/
public PalaceType getPalaceType() {
return palaceType;
}
/**
* 获取涉及的主星列表
*
* @return 主星列表
*/
public List<MajorStar> getStars() {
return stars;
}
/**
* 获取格局评分
*
* @return 格局评分
*/
public int getScore() {
return score;
}
}
/**
* 识别命盘中的格局
*
* @param chart 紫微盘对象
* @return 识别到的格局列表
*/
public static List<Pattern> recognizePatterns(ZiweiChart chart) {
log.info("【格局识别】开始识别命盘格局");
if (chart == null) {
log.error("【格局识别错误】命盘对象为空,无法识别格局");
return new ArrayList<>();
}
log.debug("【格局识别】开始分析命盘中的格局");
List<Pattern> patterns = new ArrayList<>();
Palace mingGong = chart.getPalaceByType(PalaceType.MING.getName());
if (mingGong != null) {
List<MajorStar> mingGongStars = getMajorStars(mingGong);
log.debug("【格局识别】命宫主星:{}", mingGongStars);
if (mingGongStars.contains(MajorStar.ZIWEI) && mingGongStars.contains(MajorStar.TIANFU)) {
int score = calculatePatternScore(mingGongStars, PalaceType.MING, chart);
patterns.add(new Pattern("紫府同宫格", "紫微星与天府星同宫,主富贵双全,格局极佳", PalaceType.MING, mingGongStars, score));
log.info("识别到格局:紫府同宫格(评分:{})", score);
} else if (mingGongStars.contains(MajorStar.TAIYANG) && mingGongStars.contains(MajorStar.TAIYIN)) {
int score = calculatePatternScore(mingGongStars, PalaceType.MING, chart);
patterns.add(new Pattern("日月同宫格", "太阳星与太阴星同宫,主光明磊落,名声远扬", PalaceType.MING, mingGongStars, score));
log.info("识别到格局:日月同宫格(评分:{})", score);
} else if (mingGongStars.contains(MajorStar.TIANJI) && mingGongStars.contains(MajorStar.TAIYIN) && mingGongStars.contains(MajorStar.TIANLIANG)) {
int score = calculatePatternScore(mingGongStars, PalaceType.MING, chart);
patterns.add(new Pattern("机月同梁格", "天机、太阴、天梁三星同宫,主智慧谋略,稳重踏实", PalaceType.MING, mingGongStars, score));
log.info("识别到格局:机月同梁格(评分:{})", score);
} else if (mingGongStars.contains(MajorStar.QISHA) && mingGongStars.contains(MajorStar.POJUN) && mingGongStars.contains(MajorStar.TANLANG)) {
int score = calculatePatternScore(mingGongStars, PalaceType.MING, chart);
patterns.add(new Pattern("杀破狼格", "七杀、破军、贪狼三星同宫,主变动开创,消耗较大", PalaceType.MING, mingGongStars, score));
log.info("识别到格局:杀破狼格(评分:{})", score);
} else if (mingGongStars.contains(MajorStar.ZIWEI) && mingGongStars.size() == 1) {
int score = calculatePatternScore(mingGongStars, PalaceType.MING, chart);
patterns.add(new Pattern("紫微独坐格", "紫微星独坐命宫,主权威尊贵,领导才能强", PalaceType.MING, mingGongStars, score));
log.info("识别到格局:紫微独坐格(评分:{})", score);
} else if (mingGongStars.contains(MajorStar.TIANFU) && mingGongStars.size() == 1) {
int score = calculatePatternScore(mingGongStars, PalaceType.MING, chart);
patterns.add(new Pattern("天府独坐格", "天府星独坐命宫,主稳重保守,财富丰厚", PalaceType.MING, mingGongStars, score));
log.info("识别到格局:天府独坐格(评分:{})", score);
} else if (mingGongStars.contains(MajorStar.TAIYANG) && mingGongStars.size() == 1) {
int score = calculatePatternScore(mingGongStars, PalaceType.MING, chart);
patterns.add(new Pattern("太阳独坐格", "太阳星独坐命宫,主光明磊落,热情大方", PalaceType.MING, mingGongStars, score));
log.info("识别到格局:太阳独坐格(评分:{})", score);
} else if (mingGongStars.contains(MajorStar.TAIYIN) && mingGongStars.size() == 1) {
int score = calculatePatternScore(mingGongStars, PalaceType.MING, chart);
patterns.add(new Pattern("太阴独坐格", "太阴星独坐命宫,主温柔内敛,母性光辉", PalaceType.MING, mingGongStars, score));
log.info("识别到格局:太阴独坐格(评分:{})", score);
} else if (mingGongStars.contains(MajorStar.WUQU) && mingGongStars.size() == 1) {
int score = calculatePatternScore(mingGongStars, PalaceType.MING, chart);
patterns.add(new Pattern("武曲独坐格", "武曲星独坐命宫,主刚毅务实,财帛丰盈", PalaceType.MING, mingGongStars, score));
log.info("识别到格局:武曲独坐格(评分:{})", score);
} else if (mingGongStars.contains(MajorStar.TIANTONG) && mingGongStars.size() == 1) {
int score = calculatePatternScore(mingGongStars, PalaceType.MING, chart);
patterns.add(new Pattern("天同独坐格", "天同星独坐命宫,主温和福气,享受人生", PalaceType.MING, mingGongStars, score));
log.info("识别到格局:天同独坐格(评分:{})", score);
} else if (mingGongStars.contains(MajorStar.TANLANG) && mingGongStars.size() == 1) {
int score = calculatePatternScore(mingGongStars, PalaceType.MING, chart);
patterns.add(new Pattern("贪狼独坐格", "贪狼星独坐命宫,主欲望强烈,魅力非凡", PalaceType.MING, mingGongStars, score));
log.info("识别到格局:贪狼独坐格(评分:{})", score);
} else if (mingGongStars.contains(MajorStar.JUMEN) && mingGongStars.size() == 1) {
int score = calculatePatternScore(mingGongStars, PalaceType.MING, chart);
patterns.add(new Pattern("巨门独坐格", "巨门星独坐命宫,主口才出众,是非较多", PalaceType.MING, mingGongStars, score));
log.info("识别到格局:巨门独坐格(评分:{})", score);
} else if (mingGongStars.contains(MajorStar.TIANXIANG) && mingGongStars.size() == 1) {
int score = calculatePatternScore(mingGongStars, PalaceType.MING, chart);
patterns.add(new Pattern("天相独坐格", "天相星独坐命宫,主辅佐贵人,稳重可靠", PalaceType.MING, mingGongStars, score));
log.info("识别到格局:天相独坐格(评分:{})", score);
} else if (mingGongStars.contains(MajorStar.TIANLIANG) && mingGongStars.size() == 1) {
int score = calculatePatternScore(mingGongStars, PalaceType.MING, chart);
patterns.add(new Pattern("天梁独坐格", "天梁星独坐命宫,主恩泽长辈,稳重踏实", PalaceType.MING, mingGongStars, score));
log.info("识别到格局:天梁独坐格(评分:{})", score);
} else if (mingGongStars.contains(MajorStar.QISHA) && mingGongStars.size() == 1) {
int score = calculatePatternScore(mingGongStars, PalaceType.MING, chart);
patterns.add(new Pattern("七杀独坐格", "七杀星独坐命宫,主威猛果断,开创能力强", PalaceType.MING, mingGongStars, score));
log.info("识别到格局:七杀独坐格(评分:{})", score);
} else if (mingGongStars.contains(MajorStar.POJUN) && mingGongStars.size() == 1) {
int score = calculatePatternScore(mingGongStars, PalaceType.MING, chart);
patterns.add(new Pattern("破军独坐格", "破军星独坐命宫,主破旧立新,变动较多", PalaceType.MING, mingGongStars, score));
log.info("识别到格局:破军独坐格(评分:{})", score);
} else if (mingGongStars.contains(MajorStar.TIANJI) && mingGongStars.size() == 1) {
int score = calculatePatternScore(mingGongStars, PalaceType.MING, chart);
patterns.add(new Pattern("天机独坐格", "天机星独坐命宫,主智慧谋略,变通能力强", PalaceType.MING, mingGongStars, score));
log.info("识别到格局:天机独坐格(评分:{})", score);
} else if (mingGongStars.contains(MajorStar.LIANCHEN) && mingGongStars.size() == 1) {
int score = calculatePatternScore(mingGongStars, PalaceType.MING, chart);
patterns.add(new Pattern("廉贞独坐格", "廉贞星独坐命宫,主感情丰富,才华出众", PalaceType.MING, mingGongStars, score));
log.info("识别到格局:廉贞独坐格(评分:{})", score);
}
} else {
log.warn("【格局识别警告】未找到命宫,无法分析命宫格局");
}
Palace caiBoGong = chart.getPalaceByType(PalaceType.CAI.getName());
if (caiBoGong != null) {
List<MajorStar> caiBoStars = getMajorStars(caiBoGong);
log.debug("【格局识别】财帛宫主星:{}", caiBoStars);
if (caiBoStars.contains(MajorStar.WUQU)) {
int score = calculatePatternScore(caiBoStars, PalaceType.CAI, chart);
patterns.add(new Pattern("武曲守财格", "武曲星守财帛宫,主财帛丰盈,财运亨通", PalaceType.CAI, caiBoStars, score));
log.info("识别到格局:武曲守财格(评分:{})", score);
} else if (caiBoStars.contains(MajorStar.TANLANG)) {
int score = calculatePatternScore(caiBoStars, PalaceType.CAI, chart);
patterns.add(new Pattern("贪狼守财格", "贪狼星守财帛宫,主欲望强烈,追求财富", PalaceType.CAI, caiBoStars, score));
log.info("识别到格局:贪狼守财格(评分:{})", score);
} else if (caiBoStars.contains(MajorStar.TIANFU)) {
int score = calculatePatternScore(caiBoStars, PalaceType.CAI, chart);
patterns.add(new Pattern("天府守财格", "天府星守财帛宫,主财富丰厚,财运稳健", PalaceType.CAI, caiBoStars, score));
log.info("识别到格局:天府守财格(评分:{})", score);
}
} else {
log.debug("【格局识别】未找到财帛宫");
}
Palace guanLuGong = chart.getPalaceByType(PalaceType.GUANLU.getName());
if (guanLuGong != null) {
List<MajorStar> guanLuStars = getMajorStars(guanLuGong);
log.debug("【格局识别】官禄宫主星:{}", guanLuStars);
if (guanLuStars.contains(MajorStar.TIANXIANG)) {
int score = calculatePatternScore(guanLuStars, PalaceType.GUANLU, chart);
patterns.add(new Pattern("天相守官格", "天相星守官禄宫,主辅佐贵人,官运亨通", PalaceType.GUANLU, guanLuStars, score));
log.info("识别到格局:天相守官格(评分:{})", score);
} else if (guanLuStars.contains(MajorStar.QISHA)) {
int score = calculatePatternScore(guanLuStars, PalaceType.GUANLU, chart);
patterns.add(new Pattern("七杀守官格", "七杀星守官禄宫,主威猛果断,开创事业", PalaceType.GUANLU, guanLuStars, score));
log.info("识别到格局:七杀守官格(评分:{})", score);
} else if (guanLuStars.contains(MajorStar.TIANJI)) {
int score = calculatePatternScore(guanLuStars, PalaceType.GUANLU, chart);
patterns.add(new Pattern("天机守官格", "天机星守官禄宫,主智慧谋略,善于规划", PalaceType.GUANLU, guanLuStars, score));
log.info("识别到格局:天机守官格(评分:{})", score);
}
} else {
log.debug("【格局识别】未找到官禄宫");
}
Palace qianYiGong = chart.getPalaceByType(PalaceType.QIAN.getName());
if (qianYiGong != null) {
List<MajorStar> qianYiStars = getMajorStars(qianYiGong);
log.debug("【格局识别】迁移宫主星:{}", qianYiStars);
if (qianYiStars.contains(MajorStar.TAIYANG)) {
int score = calculatePatternScore(qianYiStars, PalaceType.QIAN, chart);
patterns.add(new Pattern("太阳守迁格", "太阳星守迁移宫,主光明磊落,外出顺利", PalaceType.QIAN, qianYiStars, score));
log.info("识别到格局:太阳守迁格(评分:{})", score);
} else if (qianYiStars.contains(MajorStar.TAIYIN)) {
int score = calculatePatternScore(qianYiStars, PalaceType.QIAN, chart);
patterns.add(new Pattern("太阴守迁格", "太阴星守迁移宫,主温柔内敛,外出平安", PalaceType.QIAN, qianYiStars, score));
log.info("识别到格局:太阴守迁格(评分:{})", score);
} else if (qianYiStars.contains(MajorStar.TANLANG)) {
int score = calculatePatternScore(qianYiStars, PalaceType.QIAN, chart);
patterns.add(new Pattern("贪狼守迁格", "贪狼星守迁移宫,主魅力非凡,外出受欢迎", PalaceType.QIAN, qianYiStars, score));
log.info("识别到格局:贪狼守迁格(评分:{})", score);
}
} else {
log.debug("未找到迁移宫");
}
log.info("格局识别完成,共识别到{}个格局", patterns.size());
log.debug("识别到的格局:{}", patterns);
return patterns;
}
/**
* 计算格局评分
* 综合考虑星曜性质、宫位重要性、三方四正支持等因素
*
* @param stars 涉及的主星列表
* @param palaceType 宫位类型
* @param chart 紫微盘对象
* @return 格局评分(0-100)
*/
private static int calculatePatternScore(List<MajorStar> stars, PalaceType palaceType, ZiweiChart chart) {
int baseScore = calculateBaseScore(stars, palaceType);
int sanFangSiZhengBonus = calculateSanFangSiZhengBonus(palaceType, chart);
int starSynergyBonus = calculateStarSynergyBonus(stars);
int totalScore = baseScore + sanFangSiZhengBonus + starSynergyBonus;
log.debug("格局评分计算 - 基础分:{},三方四正加分:{},星曜协同加分:{},总分:{}",
baseScore, sanFangSiZhengBonus, starSynergyBonus, totalScore);
return Math.min(100, Math.max(0, totalScore));
}
/**
* 计算基础评分
* 根据星曜性质和宫位类型计算基础分
*
* @param stars 主星列表
* @param palaceType 宫位类型
* @return 基础评分
*/
private static int calculateBaseScore(List<MajorStar> stars, PalaceType palaceType) {
int baseScore = 0;
for (MajorStar star : stars) {
int starScore = getStarBaseScore(star, palaceType);
baseScore += starScore;
}
if (stars.size() > 0) {
baseScore = baseScore / stars.size();
}
return baseScore;
}
/**
* 获取星曜基础评分
* 根据星曜性质和宫位类型计算单星基础分
*
* @param star 主星
* @param palaceType 宫位类型
* @return 星曜基础评分
*/
private static int getStarBaseScore(MajorStar star, PalaceType palaceType) {
int score = 60;
switch (star) {
case ZIWEI:
score = (palaceType == PalaceType.MING) ? 85 : 75;
break;
case TIANFU:
score = (palaceType == PalaceType.CAI) ? 78 : 72;
break;
case TAIYANG:
score = (palaceType == PalaceType.QIAN) ? 75 : 70;
break;
case TAIYIN:
score = (palaceType == PalaceType.QIAN) ? 70 : 65;
break;
case WUQU:
score = (palaceType == PalaceType.CAI) ? 80 : 70;
break;
case TIANXIANG:
score = (palaceType == PalaceType.GUANLU) ? 80 : 70;
break;
case QISHA:
score = (palaceType == PalaceType.GUANLU) ? 75 : 65;
break;
case TIANJI:
score = (palaceType == PalaceType.GUANLU) ? 72 : 68;
break;
case TIANTONG:
score = 70;
break;
case TIANLIANG:
score = 70;
break;
case TANLANG:
score = (palaceType == PalaceType.CAI || palaceType == PalaceType.QIAN) ? 68 : 62;
break;
case JUMEN:
score = 60;
break;
case LIANCHEN:
score = 60;
break;
case POJUN:
score = 65;
break;
default:
score = 60;
break;
}
return score;
}
/**
* 计算三方四正加分
* 根据三方四正宫位的星曜情况计算加分
*
* @param palaceType 宫位类型
* @param chart 紫微盘对象
* @return 三方四正加分
*/
private static int calculateSanFangSiZhengBonus(PalaceType palaceType, ZiweiChart chart) {
SanFangSiZhengUtil.SanFangSiZheng sanFangSiZheng = chart.getSanFangSiZheng();
if (sanFangSiZheng == null) {
return 0;
}
int bonus = 0;
int sanFangScore = sanFangSiZheng.calculateSanFangScore();
int siZhengScore = sanFangSiZheng.calculateSiZhengScore();
if (sanFangScore >= 75) {
bonus += 5;
} else if (sanFangScore >= 70) {
bonus += 3;
}
if (siZhengScore >= 75) {
bonus += 3;
} else if (siZhengScore >= 70) {
bonus += 2;
}
return bonus;
}
/**
* 计算星曜协同加分
* 根据星曜之间的协同效应计算加分
*
* @param stars 主星列表
* @return 星曜协同加分
*/
private static int calculateStarSynergyBonus(List<MajorStar> stars) {
if (stars.size() < 2) {
return 0;
}
int bonus = 0;
if (stars.contains(MajorStar.ZIWEI) && stars.contains(MajorStar.TIANFU)) {
bonus += 8;
}
if (stars.contains(MajorStar.TAIYANG) && stars.contains(MajorStar.TAIYIN)) {
bonus += 6;
}
if (stars.contains(MajorStar.TIANJI) && stars.contains(MajorStar.TAIYIN) && stars.contains(MajorStar.TIANLIANG)) {
bonus += 7;
}
if (stars.contains(MajorStar.QISHA) && stars.contains(MajorStar.POJUN) && stars.contains(MajorStar.TANLANG)) {
bonus += 5;
}
if (stars.contains(MajorStar.TIANJI) && stars.contains(MajorStar.TIANLIANG)) {
bonus += 4;
}
if (stars.contains(MajorStar.TIANXIANG) && stars.contains(MajorStar.TIANLIANG)) {
bonus += 4;
}
return bonus;
}
private static List<MajorStar> getMajorStars(Palace palace) {
List<MajorStar> stars = new ArrayList<>();
for (StarInfo starInfo : palace.getMajorStars()) {
stars.add(starInfo.getStar());
}
return stars;
}
public static String analyzePatterns(List<Pattern> patterns) {
log.info("开始分析格局,格局数量:{}", patterns != null ? patterns.size() : 0);
if (patterns == null || patterns.isEmpty()) {
log.debug("未识别到特殊格局");
return "未识别到特殊格局";
}
StringBuilder analysis = new StringBuilder();
analysis.append("【格局分析】\n");
analysis.append("共识别到").append(patterns.size()).append("个格局:\n\n");
for (Pattern pattern : patterns) {
analysis.append("格局名称:").append(pattern.getName()).append("\n");
analysis.append("格局描述:").append(pattern.getDescription()).append("\n");
analysis.append("所在宫位:").append(pattern.getPalaceType().getName()).append("\n");
analysis.append("涉及星曜:");
for (MajorStar star : pattern.getStars()) {
analysis.append(star.getName()).append(" ");
}
analysis.append("\n");
analysis.append("格局评分:").append(pattern.getScore()).append("\n\n");
log.debug("分析格局:{}(评分:{})", pattern.getName(), pattern.getScore());
}
log.info("格局分析完成,共分析{}个格局", patterns.size());
return analysis.toString();
}
}
@@ -0,0 +1,249 @@
package io.destiny.biz.util;
import io.destiny.biz.domain.Palace;
import io.destiny.biz.domain.ZiweiChart;
import io.destiny.biz.enums.EarthlyBranch;
import io.destiny.biz.enums.PalaceType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
/**
* 三方四正工具类
* 用于计算和分析紫微斗数中的三方四正
* 三方:命宫、财帛宫、官禄宫
* 四正:命宫、财帛宫、官禄宫、迁移宫
*
* @author 张翔
* @date 2025-12-29
*/
public class SanFangSiZhengUtil {
private static final Logger log = LoggerFactory.getLogger(SanFangSiZhengUtil.class);
/**
* 三方四正内部类
* 封装三方四正的四个宫位信息
*/
public static class SanFangSiZheng {
/** 命宫 */
private Palace mingGong;
/** 财帛宫 */
private Palace caiBoGong;
/** 官禄宫 */
private Palace guanLuGong;
/** 迁移宫 */
private Palace qianYiGong;
/**
* 构造函数
* @param mingGong 命宫
* @param caiBoGong 财帛宫
* @param guanLuGong 官禄宫
* @param qianYiGong 迁移宫
*/
public SanFangSiZheng(Palace mingGong, Palace caiBoGong, Palace guanLuGong, Palace qianYiGong) {
this.mingGong = mingGong;
this.caiBoGong = caiBoGong;
this.guanLuGong = guanLuGong;
this.qianYiGong = qianYiGong;
}
/**
* 获取命宫
* @return 命宫对象
*/
public Palace getMingGong() {
return mingGong;
}
/**
* 获取财帛宫
* @return 财帛宫对象
*/
public Palace getCaiBoGong() {
return caiBoGong;
}
/**
* 获取官禄宫
* @return 官禄宫对象
*/
public Palace getGuanLuGong() {
return guanLuGong;
}
/**
* 获取迁移宫
* @return 迁移宫对象
*/
public Palace getQianYiGong() {
return qianYiGong;
}
/**
* 获取三方宫位列表
* 三方包括:命宫、财帛宫、官禄宫
* @return 三方宫位列表
*/
public List<Palace> getSanFang() {
List<Palace> sanFang = new ArrayList<>();
sanFang.add(mingGong);
sanFang.add(caiBoGong);
sanFang.add(guanLuGong);
return sanFang;
}
/**
* 获取四正宫位列表
* 四正包括:命宫、财帛宫、官禄宫、迁移宫
* @return 四正宫位列表
*/
public List<Palace> getSiZheng() {
List<Palace> siZheng = new ArrayList<>();
siZheng.add(mingGong);
siZheng.add(caiBoGong);
siZheng.add(guanLuGong);
siZheng.add(qianYiGong);
return siZheng;
}
/**
* 计算三方平均分
* @return 三方平均分值
*/
public int calculateSanFangScore() {
int totalScore = 0;
for (Palace palace : getSanFang()) {
totalScore += palace.getScore();
}
return totalScore / 3;
}
/**
* 计算四正平均分
* @return 四正平均分值
*/
public int calculateSiZhengScore() {
int totalScore = 0;
for (Palace palace : getSiZheng()) {
totalScore += palace.getScore();
}
return totalScore / 4;
}
}
/**
* 计算三方四正
* 根据命宫地支计算财帛宫、官禄宫、迁移宫的位置
* @param chart 紫微盘对象
* @return 三方四正对象
*/
public static SanFangSiZheng calculateSanFangSiZheng(ZiweiChart chart) {
log.info("【三方四正计算】开始计算三方四正");
Palace mingGong = chart.getPalaceByType(PalaceType.MING.getName());
if (mingGong == null) {
log.error("【三方四正计算错误】未找到命宫,无法计算三方四正");
return null;
}
EarthlyBranch mingGongBranch = mingGong.getEarthlyBranch();
log.debug("【三方四正计算】命宫地支:{}", mingGongBranch.getName());
EarthlyBranch caiBoGongBranch = calculateCaiBoGong(mingGongBranch);
EarthlyBranch guanLuGongBranch = calculateGuanLuGong(mingGongBranch);
EarthlyBranch qianYiGongBranch = calculateQianYiGong(mingGongBranch);
Palace caiBoGong = chart.getPalaceByBranch(caiBoGongBranch);
Palace guanLuGong = chart.getPalaceByBranch(guanLuGongBranch);
Palace qianYiGong = chart.getPalaceByBranch(qianYiGongBranch);
log.info("【三方四正计算完成】三方四正计算完成 - 命宫:{},财帛宫:{},官禄宫:{},迁移宫:{}",
mingGongBranch.getName(), caiBoGongBranch.getName(),
guanLuGongBranch.getName(), qianYiGongBranch.getName());
log.debug("【三方四正计算详情】财帛宫地支:{}", caiBoGongBranch.getName());
log.debug("【三方四正计算详情】官禄宫地支:{}", guanLuGongBranch.getName());
log.debug("【三方四正计算详情】迁移宫地支:{}", qianYiGongBranch.getName());
return new SanFangSiZheng(mingGong, caiBoGong, guanLuGong, qianYiGong);
}
/**
* 计算财帛宫地支
* 财帛宫位于命宫对冲位置(命宫+5)
* @param mingGongBranch 命宫地支
* @return 财帛宫地支
*/
private static EarthlyBranch calculateCaiBoGong(EarthlyBranch mingGongBranch) {
int mingGongIndex = mingGongBranch.getIndex();
int caiBoGongIndex = mingGongIndex + 5;
if (caiBoGongIndex > 12) {
caiBoGongIndex -= 12;
}
return EarthlyBranch.fromIndex(caiBoGongIndex);
}
/**
* 计算官禄宫地支
* 官禄宫位于命宫对冲位置(命宫+10)
* @param mingGongBranch 命宫地支
* @return 官禄宫地支
*/
private static EarthlyBranch calculateGuanLuGong(EarthlyBranch mingGongBranch) {
int mingGongIndex = mingGongBranch.getIndex();
int guanLuGongIndex = mingGongIndex + 10;
if (guanLuGongIndex > 12) {
guanLuGongIndex -= 12;
}
return EarthlyBranch.fromIndex(guanLuGongIndex);
}
/**
* 计算迁移宫地支
* 迁移宫位于命宫对冲位置(命宫+6)
* @param mingGongBranch 命宫地支
* @return 迁移宫地支
*/
private static EarthlyBranch calculateQianYiGong(EarthlyBranch mingGongBranch) {
int mingGongIndex = mingGongBranch.getIndex();
int qianYiGongIndex = mingGongIndex + 6;
if (qianYiGongIndex > 12) {
qianYiGongIndex -= 12;
}
return EarthlyBranch.fromIndex(qianYiGongIndex);
}
/**
* 分析三方四正
* 生成三方四正的详细分析报告
* @param sanFangSiZheng 三方四正对象
* @return 分析报告字符串
*/
public static String analyzeSanFangSiZheng(SanFangSiZheng sanFangSiZheng) {
StringBuilder analysis = new StringBuilder();
analysis.append("【三方四正分析】\n");
analysis.append("\n【三方分析】\n");
analysis.append("命宫:").append(sanFangSiZheng.getMingGong().getPalaceType().getName())
.append("(").append(sanFangSiZheng.getMingGong().getEarthlyBranch().getName()).append(")")
.append(",得分:").append(sanFangSiZheng.getMingGong().getScore()).append("\n");
analysis.append("财帛宫:").append(sanFangSiZheng.getCaiBoGong().getPalaceType().getName())
.append("(").append(sanFangSiZheng.getCaiBoGong().getEarthlyBranch().getName()).append(")")
.append(",得分:").append(sanFangSiZheng.getCaiBoGong().getScore()).append("\n");
analysis.append("官禄宫:").append(sanFangSiZheng.getGuanLuGong().getPalaceType().getName())
.append("(").append(sanFangSiZheng.getGuanLuGong().getEarthlyBranch().getName()).append(")")
.append(",得分:").append(sanFangSiZheng.getGuanLuGong().getScore()).append("\n");
analysis.append("三方平均分:").append(sanFangSiZheng.calculateSanFangScore()).append("\n");
analysis.append("\n【四正分析】\n");
analysis.append("迁移宫:").append(sanFangSiZheng.getQianYiGong().getPalaceType().getName())
.append("(").append(sanFangSiZheng.getQianYiGong().getEarthlyBranch().getName()).append(")")
.append(",得分:").append(sanFangSiZheng.getQianYiGong().getScore()).append("\n");
analysis.append("四正平均分:").append(sanFangSiZheng.calculateSiZhengScore()).append("\n");
return analysis.toString();
}
}
@@ -0,0 +1,152 @@
package io.destiny.biz.util;
import io.destiny.biz.domain.Almanac;
import io.destiny.biz.dto.SearchCondition;
import io.destiny.biz.dto.MatchedItems;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
public class SearchConditionOptimizer {
private static final int CACHE_SIZE = 1000;
private static final Map<String, Boolean> matchCache = new ConcurrentHashMap<>(CACHE_SIZE);
public static void clearCache() {
matchCache.clear();
}
public static boolean matchConditionsOptimized(Almanac almanac, List<SearchCondition> conditions) {
if (conditions == null || conditions.isEmpty()) {
return true;
}
String cacheKey = generateCacheKey(almanac, conditions);
Boolean cachedResult = matchCache.get(cacheKey);
if (cachedResult != null) {
return cachedResult;
}
boolean result = conditions.stream().allMatch(condition -> {
List<String> items = "suitable".equals(condition.getType())
? almanac.getSuitable()
: almanac.getUnsuitable();
Set<String> itemSet = new HashSet<>(items);
Set<String> conditionItems = new HashSet<>(condition.getItems());
if (condition.getExclude()) {
return Collections.disjoint(itemSet, conditionItems);
}
if ("and".equals(condition.getOperator())) {
return itemSet.containsAll(conditionItems);
} else {
return !Collections.disjoint(itemSet, conditionItems);
}
});
if (matchCache.size() >= CACHE_SIZE) {
matchCache.clear();
}
matchCache.put(cacheKey, result);
return result;
}
public static List<io.destiny.biz.dto.SearchResult> createSearchResultsOptimized(
List<Almanac> almanacs,
List<SearchCondition> conditions,
io.destiny.biz.service.ICalendarService calendarService,
java.time.format.DateTimeFormatter dateFormatter) {
return almanacs.parallelStream()
.filter(almanac -> matchConditionsOptimized(almanac, conditions))
.map(almanac -> createSearchResult(almanac, conditions, calendarService, dateFormatter))
.collect(Collectors.toList());
}
private static io.destiny.biz.dto.SearchResult createSearchResult(
Almanac almanac,
List<SearchCondition> conditions,
io.destiny.biz.service.ICalendarService calendarService,
java.time.format.DateTimeFormatter dateFormatter) {
io.destiny.biz.dto.SearchResult result = new io.destiny.biz.dto.SearchResult();
result.setDate(almanac.getSolarDate().toLocalDate().format(dateFormatter));
io.destiny.biz.dto.AlmanacResponse almanacResponse = io.destiny.biz.dto.AlmanacResponse.fromAlmanac(almanac,
calendarService);
result.setLunarDate(formatLunarDate(almanacResponse));
result.setWeekday(getWeekday(almanac.getSolarDate().toLocalDate()));
List<String> matchedSuitable = new ArrayList<>();
List<String> matchedUnsuitable = new ArrayList<>();
int matchCount = 0;
Set<String> suitableSet = new HashSet<>(almanac.getSuitable());
Set<String> unsuitableSet = new HashSet<>(almanac.getUnsuitable());
for (SearchCondition condition : conditions) {
Set<String> itemSet = "suitable".equals(condition.getType())
? suitableSet
: unsuitableSet;
Set<String> conditionItems = new HashSet<>(condition.getItems());
List<String> matched = conditionItems.stream()
.filter(itemSet::contains)
.collect(Collectors.toList());
if ("suitable".equals(condition.getType())) {
matchedSuitable.addAll(matched);
} else {
matchedUnsuitable.addAll(matched);
}
matchCount += matched.size();
}
result.setMatchedItems(new MatchedItems(matchedSuitable, matchedUnsuitable));
result.setMatchCount(matchCount);
result.setAlmanacData(almanacResponse);
return result;
}
private static String generateCacheKey(Almanac almanac, List<SearchCondition> conditions) {
StringBuilder keyBuilder = new StringBuilder();
keyBuilder.append(almanac.getSolarDate().toString());
keyBuilder.append("|");
keyBuilder.append(almanac.getSuitable().hashCode());
keyBuilder.append("|");
keyBuilder.append(almanac.getUnsuitable().hashCode());
keyBuilder.append("|");
for (SearchCondition condition : conditions) {
keyBuilder.append(condition.getType());
keyBuilder.append(":");
keyBuilder.append(condition.getOperator());
keyBuilder.append(":");
keyBuilder.append(condition.getExclude());
keyBuilder.append(":");
keyBuilder.append(condition.getItems().hashCode());
keyBuilder.append("|");
}
return keyBuilder.toString();
}
private static String formatLunarDate(io.destiny.biz.dto.AlmanacResponse almanacResponse) {
if (almanacResponse == null || almanacResponse.getLunarDate() == null) {
return "";
}
return "农历" + almanacResponse.getLunarDate();
}
private static String getWeekday(java.time.LocalDate date) {
String[] weekdays = { "星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六" };
return weekdays[date.getDayOfWeek().getValue() % 7];
}
}
@@ -0,0 +1,316 @@
package io.destiny.biz.util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.TextStyle;
import java.time.zone.ZoneRules;
import java.util.*;
import java.util.stream.Collectors;
public class TimeZoneUtil {
private static final Logger log = LoggerFactory.getLogger(TimeZoneUtil.class);
private static final String DEFAULT_TIMEZONE = "Asia/Shanghai";
private static final Map<String, String> COMMON_TIMEZONE_NAMES = createCommonTimezoneNames();
private static Map<String, String> createCommonTimezoneNames() {
log.debug("初始化常用时区名称映射");
Map<String, String> names = new HashMap<>();
names.put("Asia/Shanghai", "中国标准时间 (北京)");
names.put("Asia/Hong_Kong", "香港时间");
names.put("Asia/Taipei", "台北时间");
names.put("Asia/Tokyo", "日本标准时间");
names.put("Asia/Seoul", "韩国标准时间");
names.put("Asia/Singapore", "新加坡时间");
names.put("Asia/Kolkata", "印度标准时间");
names.put("Asia/Dubai", "海湾标准时间");
names.put("Europe/London", "格林威治标准时间");
names.put("Europe/Paris", "中欧时间");
names.put("Europe/Berlin", "中欧时间");
names.put("Europe/Moscow", "莫斯科时间");
names.put("America/New_York", "美国东部时间");
names.put("America/Chicago", "美国中部时间");
names.put("America/Denver", "美国山地时间");
names.put("America/Los_Angeles", "美国太平洋时间");
names.put("America/Toronto", "加拿大东部时间");
names.put("Australia/Sydney", "澳大利亚东部时间");
names.put("Pacific/Auckland", "新西兰标准时间");
log.debug("常用时区名称映射初始化完成,共{}个时区", names.size());
return Collections.unmodifiableMap(names);
}
static {
log.debug("初始化时区工具类");
log.debug("时区工具类初始化完成");
}
public static ZoneId getDefaultZoneId() {
return ZoneId.of(DEFAULT_TIMEZONE);
}
public static ZoneId parseZoneId(String timeZone) {
log.debug("解析时区:{}", timeZone);
if (timeZone == null || timeZone.trim().isEmpty()) {
log.debug("时区为空,使用默认时区:{}", DEFAULT_TIMEZONE);
return ZoneId.of(DEFAULT_TIMEZONE);
}
try {
ZoneId zoneId = ZoneId.of(timeZone);
log.debug("时区解析成功:{}", zoneId);
return zoneId;
} catch (Exception e) {
log.error("时区解析失败:{},使用默认时区:{}", timeZone, DEFAULT_TIMEZONE);
return ZoneId.of(DEFAULT_TIMEZONE);
}
}
public static boolean isValidTimeZone(String timeZone) {
log.debug("验证时区有效性:{}", timeZone);
if (timeZone == null || timeZone.trim().isEmpty()) {
log.debug("时区为空,返回false");
return false;
}
try {
ZoneId.of(timeZone);
log.debug("时区验证通过:{}", timeZone);
return true;
} catch (Exception e) {
log.debug("时区验证失败:{}", timeZone);
return false;
}
}
public static ZonedDateTime convertTimeZone(LocalDateTime dateTime, String fromTimeZone, String toTimeZone) {
log.debug("转换时区:{},从{}到{}", dateTime, fromTimeZone, toTimeZone);
if (dateTime == null) {
log.debug("日期时间为空,返回null");
return null;
}
ZoneId fromZoneId = parseZoneId(fromTimeZone);
ZoneId toZoneId = parseZoneId(toTimeZone);
ZonedDateTime fromZoned = dateTime.atZone(fromZoneId);
ZonedDateTime toZoned = fromZoned.withZoneSameInstant(toZoneId);
log.debug("时区转换完成:{} -> {}", fromZoned, toZoned);
return toZoned;
}
public static ZonedDateTime convertTimeZone(ZonedDateTime zonedDateTime, String toTimeZone) {
log.debug("转换时区:{},到{}", zonedDateTime, toTimeZone);
if (zonedDateTime == null) {
log.debug("日期时间为空,返回null");
return null;
}
ZoneId toZoneId = parseZoneId(toTimeZone);
ZonedDateTime result = zonedDateTime.withZoneSameInstant(toZoneId);
log.debug("时区转换完成:{} -> {}", zonedDateTime, result);
return result;
}
public static String getTimeZoneDisplayName(String timeZone, Locale locale) {
log.debug("获取时区显示名称:{},语言环境:{}", timeZone, locale);
ZoneId zoneId = parseZoneId(timeZone);
String displayName = COMMON_TIMEZONE_NAMES.getOrDefault(
zoneId.getId(),
zoneId.getDisplayName(TextStyle.FULL, locale != null ? locale : Locale.CHINESE)
);
log.debug("时区显示名称:{}", displayName);
return displayName;
}
public static String getTimeZoneOffset(String timeZone) {
log.debug("获取时区偏移量:{}", timeZone);
ZoneId zoneId = parseZoneId(timeZone);
ZonedDateTime now = ZonedDateTime.now(zoneId);
ZoneRules rules = zoneId.getRules();
int offsetSeconds = rules.getOffset(now.toInstant()).getTotalSeconds();
int hours = offsetSeconds / 3600;
int minutes = Math.abs((offsetSeconds % 3600) / 60);
String offset = String.format("UTC%+d:%02d", hours, minutes);
log.debug("时区偏移量:{}", offset);
return offset;
}
public static boolean isDaylightSavingTime(String timeZone) {
log.debug("判断是否为夏令时:{}", timeZone);
ZoneId zoneId = parseZoneId(timeZone);
ZonedDateTime now = ZonedDateTime.now(zoneId);
ZoneRules rules = zoneId.getRules();
boolean result = rules.isDaylightSavings(now.toInstant());
log.debug("夏令时判断结果:{}", result);
return result;
}
public static List<TimeZoneInfo> getCommonTimeZones() {
log.debug("获取常用时区列表");
List<TimeZoneInfo> timeZones = COMMON_TIMEZONE_NAMES.entrySet().stream()
.map(entry -> {
ZoneId zoneId = ZoneId.of(entry.getKey());
ZonedDateTime now = ZonedDateTime.now(zoneId);
ZoneRules rules = zoneId.getRules();
int offsetSeconds = rules.getOffset(now.toInstant()).getTotalSeconds();
int hours = offsetSeconds / 3600;
int minutes = Math.abs((offsetSeconds % 3600) / 60);
String offset = String.format("UTC%+d:%02d", hours, minutes);
return new TimeZoneInfo(
entry.getKey(),
entry.getValue(),
offset,
rules.isDaylightSavings(now.toInstant())
);
})
.sorted(Comparator.comparingInt(TimeZoneInfo::getOffsetHours))
.collect(Collectors.toList());
log.debug("常用时区列表获取完成,共{}个时区", timeZones.size());
return timeZones;
}
public static List<String> getAllAvailableTimeZones() {
log.debug("获取所有可用时区列表");
List<String> timeZones = ZoneId.getAvailableZoneIds().stream()
.sorted()
.collect(Collectors.toList());
log.debug("所有可用时区列表获取完成,共{}个时区", timeZones.size());
return timeZones;
}
public static String getCurrentTimeInTimeZone(String timeZone) {
log.debug("获取时区当前时间:{}", timeZone);
ZoneId zoneId = parseZoneId(timeZone);
ZonedDateTime now = ZonedDateTime.now(zoneId);
String result = DateTimeFormatUtil.formatDateTime(now.toLocalDateTime());
log.debug("时区当前时间:{}", result);
return result;
}
public static int getTimeDifference(String timeZone1, String timeZone2) {
log.debug("计算时区时间差:{} 和 {}", timeZone1, timeZone2);
ZoneId zoneId1 = parseZoneId(timeZone1);
ZoneId zoneId2 = parseZoneId(timeZone2);
ZonedDateTime now = ZonedDateTime.now(zoneId1);
ZoneRules rules1 = zoneId1.getRules();
ZoneRules rules2 = zoneId2.getRules();
int offset1 = rules1.getOffset(now.toInstant()).getTotalSeconds();
int offset2 = rules2.getOffset(now.toInstant()).getTotalSeconds();
int diffHours = (offset2 - offset1) / 3600;
log.debug("时区时间差:{}小时", diffHours);
return diffHours;
}
public static String getTimeDifferenceDescription(String timeZone1, String timeZone2) {
log.debug("生成时区时间差描述:{} 和 {}", timeZone1, timeZone2);
int diffHours = getTimeDifference(timeZone1, timeZone2);
String description;
if (diffHours == 0) {
description = "两个时区时间相同";
} else if (diffHours > 0) {
description = String.format("%s比%s快%d小时", timeZone2, timeZone1, diffHours);
} else {
description = String.format("%s比%s慢%d小时", timeZone2, timeZone1, Math.abs(diffHours));
}
log.debug("时区时间差描述:{}", description);
return description;
}
public static TimeZoneInfo getTimeZoneInfo(String timeZone) {
log.debug("获取时区信息:{}", timeZone);
ZoneId zoneId = parseZoneId(timeZone);
ZonedDateTime now = ZonedDateTime.now(zoneId);
ZoneRules rules = zoneId.getRules();
int offsetSeconds = rules.getOffset(now.toInstant()).getTotalSeconds();
int hours = offsetSeconds / 3600;
int minutes = Math.abs((offsetSeconds % 3600) / 60);
String offset = String.format("UTC%+d:%02d", hours, minutes);
String displayName = COMMON_TIMEZONE_NAMES.getOrDefault(
zoneId.getId(),
zoneId.getDisplayName(TextStyle.FULL, Locale.CHINESE)
);
TimeZoneInfo info = new TimeZoneInfo(
zoneId.getId(),
displayName,
offset,
rules.isDaylightSavings(now.toInstant())
);
log.debug("时区信息获取完成:{}", info);
return info;
}
public static class TimeZoneInfo {
private final String id;
private final String displayName;
private final String offset;
private final boolean isDaylightSavingTime;
public TimeZoneInfo(String id, String displayName, String offset, boolean isDaylightSavingTime) {
this.id = id;
this.displayName = displayName;
this.offset = offset;
this.isDaylightSavingTime = isDaylightSavingTime;
}
public String getId() {
return id;
}
public String getDisplayName() {
return displayName;
}
public String getOffset() {
return offset;
}
public boolean isDaylightSavingTime() {
return isDaylightSavingTime;
}
public int getOffsetHours() {
String offsetStr = offset.replace("UTC", "");
int hours = Integer.parseInt(offsetStr.split(":")[0]);
return hours;
}
@Override
public String toString() {
return String.format("%s (%s) %s%s",
displayName,
id,
offset,
isDaylightSavingTime ? " [夏令时]" : "");
}
}
}
@@ -0,0 +1,107 @@
package io.destiny.biz.util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
public class TrueSolarTimeUtil {
private static final Logger log = LoggerFactory.getLogger(TrueSolarTimeUtil.class);
private static final double BEIJING_LONGITUDE = 120.0;
private static final int MINUTES_PER_DEGREE = 4;
public static LocalTime calculateTrueSolarTime(LocalTime beijingTime, double longitude) {
log.debug("计算真太阳时 - 北京时间:{},经度:{}", beijingTime, longitude);
double longitudeDiff = longitude - BEIJING_LONGITUDE;
int timeDiffMinutes = (int) Math.round(longitudeDiff * MINUTES_PER_DEGREE);
LocalTime trueSolarTime = beijingTime.plusMinutes(timeDiffMinutes);
log.debug("真太阳时计算完成:{},时间差:{}分钟", trueSolarTime, timeDiffMinutes);
return trueSolarTime;
}
public static LocalDateTime calculateTrueSolarDateTime(LocalDateTime beijingDateTime, double longitude) {
log.debug("计算真太阳日期时间 - 北京日期时间:{},经度:{}", beijingDateTime, longitude);
LocalTime beijingTime = beijingDateTime.toLocalTime();
LocalTime trueSolarTime = calculateTrueSolarTime(beijingTime, longitude);
LocalDate date = beijingDateTime.toLocalDate();
int timeDiffMinutes = calculateTimeDifferenceMinutes(longitude);
if (timeDiffMinutes > 0 && trueSolarTime.isBefore(beijingTime)) {
date = date.plusDays(1);
log.debug("因时间差原因,日期向前调整1天");
} else if (timeDiffMinutes < 0 && trueSolarTime.isAfter(beijingTime)) {
date = date.minusDays(1);
log.debug("因时间差原因,日期向后调整1天");
}
LocalDateTime result = LocalDateTime.of(date, trueSolarTime);
log.debug("真太阳日期时间计算完成:{}", result);
return result;
}
public static int calculateTimeDifferenceMinutes(double longitude) {
log.debug("计算经度{}的时间差(分钟)", longitude);
double longitudeDiff = longitude - BEIJING_LONGITUDE;
int diffMinutes = (int) Math.round(longitudeDiff * MINUTES_PER_DEGREE);
log.debug("时间差计算完成:{}分钟", diffMinutes);
return diffMinutes;
}
public static String getTimeDifferenceDescription(double longitude) {
log.debug("生成经度{}的时间差描述", longitude);
int diffMinutes = calculateTimeDifferenceMinutes(longitude);
int hours = diffMinutes / 60;
int minutes = Math.abs(diffMinutes % 60);
String description;
if (diffMinutes == 0) {
description = "与北京时间一致";
} else if (diffMinutes > 0) {
description = String.format("比北京时间快%d小时%d分钟", hours, minutes);
} else {
description = String.format("比北京时间慢%d小时%d分钟", Math.abs(hours), minutes);
}
log.debug("时间差描述:{}", description);
return description;
}
public static boolean isEarlyMorning(LocalTime time) {
boolean result = time.isAfter(LocalTime.of(23, 0)) || time.isBefore(LocalTime.of(1, 0));
log.debug("检查时间{}是否为凌晨:{}", time, result);
return result;
}
public static boolean isLateNight(LocalTime time) {
boolean result = time.isAfter(LocalTime.of(1, 0)) && time.isBefore(LocalTime.of(5, 0));
log.debug("检查时间{}是否为深夜:{}", time, result);
return result;
}
public static String getZiweiHourBranch(LocalTime trueSolarTime) {
log.debug("计算时间{}的紫微时支", trueSolarTime);
int hour = trueSolarTime.getHour();
String hourBranch;
if (hour >= 23 || hour < 1) hourBranch = "子时";
else if (hour >= 1 && hour < 3) hourBranch = "丑时";
else if (hour >= 3 && hour < 5) hourBranch = "寅时";
else if (hour >= 5 && hour < 7) hourBranch = "卯时";
else if (hour >= 7 && hour < 9) hourBranch = "辰时";
else if (hour >= 9 && hour < 11) hourBranch = "巳时";
else if (hour >= 11 && hour < 13) hourBranch = "午时";
else if (hour >= 13 && hour < 15) hourBranch = "未时";
else if (hour >= 15 && hour < 17) hourBranch = "申时";
else if (hour >= 17 && hour < 19) hourBranch = "酉时";
else if (hour >= 19 && hour < 21) hourBranch = "戌时";
else if (hour >= 21 && hour < 23) hourBranch = "亥时";
else hourBranch = "未知时辰";
log.debug("紫微时支计算完成:{}", hourBranch);
return hourBranch;
}
}