304 lines
8.9 KiB
C#
304 lines
8.9 KiB
C#
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; }
|
|
}
|