Files
Api/DouyinApi.Services/Naming/BaziCalculator.cs

304 lines
8.9 KiB
C#
Raw Normal View History

2025-11-05 17:26:45 +08:00
using System;
using System.Collections.Generic;
using System.Linq;
namespace DouyinApi.Services.Naming;
internal sealed class BaziCalculator
{
private static readonly string[] HeavenlyStems =
{
"甲", "乙", "丙", "丁", "戊", "己", "庚", "辛", "壬", "癸"
};
private static readonly string[] EarthlyBranches =
{
"子", "丑", "寅", "卯", "辰", "巳", "午", "未", "申", "酉", "戌", "亥"
};
private static readonly int[] MonthNumberToBranchIndex =
{
2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 0, 1
};
private static readonly string[] ElementOrder = { "木", "火", "土", "金", "水" };
private static readonly Dictionary<string, string> StemElements = new()
{
["甲"] = "木",
["乙"] = "木",
["丙"] = "火",
["丁"] = "火",
["戊"] = "土",
["己"] = "土",
["庚"] = "金",
["辛"] = "金",
["壬"] = "水",
["癸"] = "水"
};
private static readonly Dictionary<string, string> BranchElements = new()
{
["子"] = "水",
["丑"] = "土",
["寅"] = "木",
["卯"] = "木",
["辰"] = "土",
["巳"] = "火",
["午"] = "火",
["未"] = "土",
["申"] = "金",
["酉"] = "金",
["戌"] = "土",
["亥"] = "水"
};
private static readonly Dictionary<string, int> HourStemBaseIndex = new()
{
["甲"] = 0,
["己"] = 0,
["乙"] = 2,
["庚"] = 2,
["丙"] = 4,
["辛"] = 4,
["丁"] = 6,
["壬"] = 6,
["戊"] = 8,
["癸"] = 8
};
private static readonly Dictionary<string, int> MonthStemBaseIndex = new()
{
["甲"] = 2,
["己"] = 2,
["乙"] = 4,
["庚"] = 4,
["丙"] = 6,
["辛"] = 6,
["丁"] = 8,
["壬"] = 8,
["戊"] = 0,
["癸"] = 0
};
private static readonly DateTime DayBaseDate = new(1900, 1, 31);
private const int DayBaseIndex = 0; // 1900-01-31 对应甲子日
public BaziProfile Calculate(DateTime birthDateTime)
{
var solarYear = birthDateTime.Year;
var currentYearSpring = new DateTime(birthDateTime.Year, 2, 4);
if (birthDateTime < currentYearSpring)
{
solarYear -= 1;
}
var yearStemIndex = Mod(solarYear - 4, HeavenlyStems.Length);
var yearBranchIndex = Mod(solarYear - 4, EarthlyBranches.Length);
var monthNumber = ResolveMonthNumber(birthDateTime, solarYear);
var monthStemBase = MonthStemBaseIndex[HeavenlyStems[yearStemIndex]];
var monthStemIndex = Mod(monthStemBase + monthNumber - 1, HeavenlyStems.Length);
var monthBranchIndex = MonthNumberToBranchIndex[monthNumber - 1];
var dayCycleIndex = Mod(DayBaseIndex + (int)(birthDateTime.Date - DayBaseDate).TotalDays, 60);
var dayStemIndex = Mod(dayCycleIndex, HeavenlyStems.Length);
var dayBranchIndex = Mod(dayCycleIndex, EarthlyBranches.Length);
var hourBranchIndex = GetHourBranchIndex(birthDateTime.Hour);
var hourStemIndex = Mod(HourStemBaseIndex[HeavenlyStems[dayStemIndex]] + hourBranchIndex, HeavenlyStems.Length);
var yearStem = HeavenlyStems[yearStemIndex];
var yearBranch = EarthlyBranches[yearBranchIndex];
var monthStem = HeavenlyStems[monthStemIndex];
var monthBranch = EarthlyBranches[monthBranchIndex];
var dayStem = HeavenlyStems[dayStemIndex];
var dayBranch = EarthlyBranches[dayBranchIndex];
var hourStem = HeavenlyStems[hourStemIndex];
var hourBranch = EarthlyBranches[hourBranchIndex];
var elementCounts = ElementOrder.ToDictionary(element => element, _ => 0);
IncrementElement(elementCounts, StemElements[yearStem]);
IncrementElement(elementCounts, BranchElements[yearBranch]);
IncrementElement(elementCounts, StemElements[monthStem]);
IncrementElement(elementCounts, BranchElements[monthBranch]);
IncrementElement(elementCounts, StemElements[dayStem]);
IncrementElement(elementCounts, BranchElements[dayBranch]);
IncrementElement(elementCounts, StemElements[hourStem]);
IncrementElement(elementCounts, BranchElements[hourBranch]);
var counts = ElementOrder
.Select(element => new KeyValuePair<string, int>(element, elementCounts[element]))
.ToList();
var max = counts.Max(pair => pair.Value);
var min = counts.Min(pair => pair.Value);
var isBalanced = max - min <= 1;
var weakElements = isBalanced
? Array.Empty<string>()
: counts.Where(pair => pair.Value == min).Select(pair => pair.Key).ToArray();
var strongElements = counts.Where(pair => pair.Value == max).Select(pair => pair.Key).ToArray();
return new BaziProfile(
yearStem,
yearBranch,
monthStem,
monthBranch,
dayStem,
dayBranch,
hourStem,
hourBranch,
elementCounts,
ElementOrder,
weakElements,
strongElements,
isBalanced);
}
private static int ResolveMonthNumber(DateTime dt, int solarYear)
{
var springStart = new DateTime(solarYear, 2, 4);
var jingZhe = new DateTime(solarYear, 3, 6);
var qingMing = new DateTime(solarYear, 4, 5);
var liXia = new DateTime(solarYear, 5, 5);
var mangZhong = new DateTime(solarYear, 6, 6);
var xiaoShu = new DateTime(solarYear, 7, 7);
var liQiu = new DateTime(solarYear, 8, 8);
var baiLu = new DateTime(solarYear, 9, 8);
var hanLu = new DateTime(solarYear, 10, 8);
var liDong = new DateTime(solarYear, 11, 7);
var daXue = new DateTime(solarYear, 12, 7);
var xiaoHan = new DateTime(solarYear + 1, 1, 6);
var nextSpring = new DateTime(solarYear + 1, 2, 4);
if (dt >= springStart && dt < jingZhe)
{
return 1;
}
if (dt >= jingZhe && dt < qingMing)
{
return 2;
}
if (dt >= qingMing && dt < liXia)
{
return 3;
}
if (dt >= liXia && dt < mangZhong)
{
return 4;
}
if (dt >= mangZhong && dt < xiaoShu)
{
return 5;
}
if (dt >= xiaoShu && dt < liQiu)
{
return 6;
}
if (dt >= liQiu && dt < baiLu)
{
return 7;
}
if (dt >= baiLu && dt < hanLu)
{
return 8;
}
if (dt >= hanLu && dt < liDong)
{
return 9;
}
if (dt >= liDong && dt < daXue)
{
return 10;
}
if (dt >= daXue && dt < xiaoHan)
{
return 11;
}
if (dt >= xiaoHan && dt < nextSpring)
{
return 12;
}
return 11;
}
private static int GetHourBranchIndex(int hour)
{
return ((hour + 1) / 2) % EarthlyBranches.Length;
}
private static void IncrementElement(IDictionary<string, int> container, string element)
{
if (container.TryGetValue(element, out var value))
{
container[element] = value + 1;
}
}
private static int Mod(int value, int modulus)
{
var result = value % modulus;
return result < 0 ? result + modulus : result;
}
}
internal sealed class BaziProfile
{
public BaziProfile(
string yearStem,
string yearBranch,
string monthStem,
string monthBranch,
string dayStem,
string dayBranch,
string hourStem,
string hourBranch,
IReadOnlyDictionary<string, int> elementCounts,
IReadOnlyList<string> elementOrder,
IReadOnlyList<string> weakElements,
IReadOnlyList<string> strongElements,
bool isBalanced)
{
YearStem = yearStem;
YearBranch = yearBranch;
MonthStem = monthStem;
MonthBranch = monthBranch;
DayStem = dayStem;
DayBranch = dayBranch;
HourStem = hourStem;
HourBranch = hourBranch;
ElementCounts = elementCounts;
ElementOrder = elementOrder;
WeakElements = weakElements;
StrongElements = strongElements;
IsBalanced = isBalanced;
}
public string YearStem { get; }
public string YearBranch { get; }
public string MonthStem { get; }
public string MonthBranch { get; }
public string DayStem { get; }
public string DayBranch { get; }
public string HourStem { get; }
public string HourBranch { get; }
public IReadOnlyDictionary<string, int> ElementCounts { get; }
public IReadOnlyList<string> ElementOrder { get; }
public IReadOnlyList<string> WeakElements { get; }
public IReadOnlyList<string> StrongElements { get; }
public bool IsBalanced { get; }
}