2026-01-13

[C#] 處理農曆及潤月自由輸入生日的 DateTime 解析

最近遇到一個老專案,他之前設計都讓客戶自由輸入生日,不過幸好 placeholder 提示

至少他的客戶是乖乖輸入 國曆xx年xx月xx日,或是農曆xx年xx月xx日,但是他現在跟我說要算

天干地支跟五行...WT...


基本上這問題,給 AI 處理就是最適合的,但是我也是跟 AI 周旋了一下,畢竟因為中間有遇到 潤的問題

因為他讓客戶自由輸入字串,其中包含 "農曆108年七月初七" 或是  "農曆112年潤2月初一" 重點是有閏月

這邊我就直接提供程式碼,中間其實就是無聊的 Regex  跟推算這邊有興趣,就自己再把程式碼貼給 AI 去解釋

程式碼:

public static class DateParseUtil { private static readonly Regex RocDateRegex = new Regex(@"(?:國曆\s*)?(\d{2,3})[年/](\d{1,2})[月/](\d{1,2})[日號]?", RegexOptions.Compiled); private static readonly Regex LunarDateRegex = // new Regex(@"農曆(\d{2,3})年(潤)?(\d{1,2})月(.+)", new Regex( @"農曆(\d{2,3})年(潤)?(" + @"\d{1,2}|正|一|二|三|四|五|六|七|八|九|十|十一|十二" + @")月(.+)", RegexOptions.Compiled); private static readonly TaiwanLunisolarCalendar LunarCalendar = new TaiwanLunisolarCalendar(); // ===== Public function ===== public static bool TryParseAnyToDateTime(string input, out DateTime date) { date = default; if (string.IsNullOrWhiteSpace(input)) return false; if (input.Contains("農曆")) return TryParseLunar(input, out date); return TryParseRoc(input, out date); } // ===== 國曆 / 民國 ===== private static bool TryParseRoc(string input, out DateTime date) { date = default; var match = RocDateRegex.Match(input); if (!match.Success) return false; int rocYear = int.Parse(match.Groups[1].Value); int month = int.Parse(match.Groups[2].Value); int day = int.Parse(match.Groups[3].Value); int year = rocYear + 1911; try { date = new DateTime(year, month, day); return true; } catch { return false; } } // ===== 農曆 ===== private static bool TryParseLunar(string input, out DateTime date) { date = default; var match = LunarDateRegex.Match(input); if (!match.Success) return false; int lunarYear = int.Parse(match.Groups[1].Value); // 不要 +1911 bool isLeapMonth = match.Groups[2].Success; int month = ParseChineseMonth(match.Groups[3].Value); if (month <= 0 || month > 12) return false; int day = ParseChineseDay(match.Groups[4].Value); if (day <= 0) return false; int leapMonth = LunarCalendar.GetLeapMonth(lunarYear); //驗證潤的是不是這個月 // 驗證潤月合法性 if (isLeapMonth) { if (leapMonth == 0 || leapMonth != month + 1) return false; } if (leapMonth > 0) { if (isLeapMonth) { // 潤月一定要往後推一格 month++; } else if (month >= leapMonth) { // 非潤月&#65292;但在潤月之後 month++; } } try { date = LunarCalendar.ToDateTime( lunarYear, month, day, 0, 0, 0, 0); return true; } catch { return false; } } private static readonly Dictionary<string, int> ChineseDayMap = new Dictionary<string, int> { { "初一", 1 }, { "初二", 2 }, { "初三", 3 }, { "初四", 4 }, { "初五", 5 }, { "初六", 6 }, { "初七", 7 }, { "初八", 8 }, { "初九", 9 }, { "初十", 10 }, { "十一", 11 }, { "十二", 12 }, { "十三", 13 }, { "十四", 14 }, { "十五", 15 }, { "十六", 16 }, { "十七", 17 }, { "十八", 18 }, { "十九", 19 }, { "二十", 20 }, { "廿一", 21 }, { "廿二", 22 }, { "廿三", 23 }, { "廿四", 24 }, { "廿五", 25 }, { "廿六", 26 }, { "廿七", 27 }, { "廿八", 28 }, { "廿九", 29 }, { "三十", 30 } }; private static readonly Dictionary<string, int> ChineseMonthMap = new Dictionary<string, int> { { "正", 1 }, { "一", 1 }, { "二", 2 }, { "三", 3 }, { "四", 4 }, { "五", 5 }, { "六", 6 }, { "七", 7 }, { "八", 8 }, { "九", 9 }, { "十", 10 }, { "十一", 11 }, { "十二", 12 } }; private static int ParseChineseMonth(string text) { text = text.Trim(); if (int.TryParse(text, out var m)) return m; if (ChineseMonthMap.TryGetValue(text, out var cm)) return cm; return -1; } private static int ParseChineseDay(string text) { if (string.IsNullOrWhiteSpace(text)) return -1; text = text.Replace("日", "").Trim(); // 先試中文 if (ChineseDayMap.TryGetValue(text, out var d)) return d; // 再試數字 if (int.TryParse(text, out var n)) return n; return -1; } }


呼叫方式

bool success = DateParseUtil.TryParseAnyToDateTime(input, out var dt);


測試結果透過 AI 去給我測試資料我實際跑出來後去驗證的

# 輸入字串 TryParse DateTime 判定
011年1月1日False合理(規格不支援 1 位數民國)
0210年12月31日True1921-12-31正確
0387年1月1日True1998-01-01正確
0499/12/31True2010-12-31正確
05100年1月1日True2011-01-01正確
06101/01/09True2012-01-09正確
07109年6月30日True2020-06-30正確
08110年02月28日True2021-02-28正確
09111/12/01True2022-12-01正確
10112年7月7日True2023-07-07正確
11113/03/03True2024-03-03正確
12114年11月11日True2025-11-11正確
13120/1/1True2031-01-01正確
14國曆120年12月31日True2031-12-31正確
15國曆95/6/5True2006-06-05正確
16農曆111年1月初一True2022-02-01正確
17農曆111年2月初二True2022-03-04正確
18農曆111年3月初三True2022-04-03正確
19農曆111年4月初四True2022-05-04正確
20農曆111年5月初五True2022-06-03正確
21農曆111年6月初六True2022-07-04正確
22農曆111年7月初七True2022-08-04正確
23農曆111年8月初八True2022-09-03正確
24農曆111年9月初九True2022-10-04正確
25農曆111年10月初十True2022-11-03正確
26農曆111年11月十一True2022-12-04正確
27農曆111年12月十二True2023-01-03正確
28農曆110年8月廿九True2021-10-05正確
29農曆110年12月三十False合理(該年無此日)
30農曆109年1月十五True2020-02-08正確
31農曆108年正月初一True2019-02-05正確
32農曆108年二月初二True2019-03-08正確
33農曆108年三月初三True2019-04-07正確
34農曆108年四月初四True2019-05-08正確
35農曆108年五月初五True2019-06-07正確
36農曆108年六月初六True2019-07-08正確
37農曆108年七月初七True2019-08-07正確
38農曆108年八月初八True2019-09-06正確
39農曆108年九月初九True2019-10-07正確
40農曆108年十月初十True2019-11-06正確
41農曆112年潤2月初一True2023-03-22正確(潤月)
42農曆112年2月初一True2023-02-20正確
43農曆112年3月初一True2023-04-20正確
44農曆112年潤三月初一False合理(潤月錯誤)
45農曆112年0月初一False合理(月非法)
46農曆112年13月初一False合理(月超界)
47農曆112年2月廿False合理(日不完整)
48農曆112年2月三十一False合理(日超界)
49農曆一一二年正月初一False合理(不支援中文年)
50農曆112年正月初零False合理(非法日)


這邊就不過多解釋,畢竟這是方便自己之後使用的,有需要就自取吧


--

The bug existed in all possible states. Until I ran the code.

如果這篇文章有幫助到您幫我分享一下,讓我有寫下去的動力...