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 StemElements = new() { ["甲"] = "木", ["乙"] = "木", ["丙"] = "火", ["丁"] = "火", ["戊"] = "土", ["己"] = "土", ["庚"] = "金", ["辛"] = "金", ["壬"] = "水", ["癸"] = "水" }; private static readonly Dictionary BranchElements = new() { ["子"] = "水", ["丑"] = "土", ["寅"] = "木", ["卯"] = "木", ["辰"] = "土", ["巳"] = "火", ["午"] = "火", ["未"] = "土", ["申"] = "金", ["酉"] = "金", ["戌"] = "土", ["亥"] = "水" }; private static readonly Dictionary HourStemBaseIndex = new() { ["甲"] = 0, ["己"] = 0, ["乙"] = 2, ["庚"] = 2, ["丙"] = 4, ["辛"] = 4, ["丁"] = 6, ["壬"] = 6, ["戊"] = 8, ["癸"] = 8 }; private static readonly Dictionary 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(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() : 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 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 elementCounts, IReadOnlyList elementOrder, IReadOnlyList weakElements, IReadOnlyList 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 ElementCounts { get; } public IReadOnlyList ElementOrder { get; } public IReadOnlyList WeakElements { get; } public IReadOnlyList StrongElements { get; } public bool IsBalanced { get; } }