最近遇到一個老專案,他之前設計都讓客戶自由輸入生日,不過幸好 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)
{
// 非潤月,但在潤月之後
month++;
}
}
try
{
date = LunarCalendar.ToDateTime(
lunarYear,
month,
day,
0, 0, 0, 0);
return true;
}
catch
{
return false;
}
}
private static readonly Dictionary ChineseDayMap =
new Dictionary
{
{ "初一", 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 ChineseMonthMap =
new Dictionary
{
{ "正", 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 |
判定 |
| 01 | 1年1月1日 | False | — | 合理(規格不支援 1 位數民國) |
| 02 | 10年12月31日 | True | 1921-12-31 | 正確 |
| 03 | 87年1月1日 | True | 1998-01-01 | 正確 |
| 04 | 99/12/31 | True | 2010-12-31 | 正確 |
| 05 | 100年1月1日 | True | 2011-01-01 | 正確 |
| 06 | 101/01/09 | True | 2012-01-09 | 正確 |
| 07 | 109年6月30日 | True | 2020-06-30 | 正確 |
| 08 | 110年02月28日 | True | 2021-02-28 | 正確 |
| 09 | 111/12/01 | True | 2022-12-01 | 正確 |
| 10 | 112年7月7日 | True | 2023-07-07 | 正確 |
| 11 | 113/03/03 | True | 2024-03-03 | 正確 |
| 12 | 114年11月11日 | True | 2025-11-11 | 正確 |
| 13 | 120/1/1 | True | 2031-01-01 | 正確 |
| 14 | 國曆120年12月31日 | True | 2031-12-31 | 正確 |
| 15 | 國曆95/6/5 | True | 2006-06-05 | 正確 |
| 16 | 農曆111年1月初一 | True | 2022-02-01 | 正確 |
| 17 | 農曆111年2月初二 | True | 2022-03-04 | 正確 |
| 18 | 農曆111年3月初三 | True | 2022-04-03 | 正確 |
| 19 | 農曆111年4月初四 | True | 2022-05-04 | 正確 |
| 20 | 農曆111年5月初五 | True | 2022-06-03 | 正確 |
| 21 | 農曆111年6月初六 | True | 2022-07-04 | 正確 |
| 22 | 農曆111年7月初七 | True | 2022-08-04 | 正確 |
| 23 | 農曆111年8月初八 | True | 2022-09-03 | 正確 |
| 24 | 農曆111年9月初九 | True | 2022-10-04 | 正確 |
| 25 | 農曆111年10月初十 | True | 2022-11-03 | 正確 |
| 26 | 農曆111年11月十一 | True | 2022-12-04 | 正確 |
| 27 | 農曆111年12月十二 | True | 2023-01-03 | 正確 |
| 28 | 農曆110年8月廿九 | True | 2021-10-05 | 正確 |
| 29 | 農曆110年12月三十 | False | — | 合理(該年無此日) |
| 30 | 農曆109年1月十五 | True | 2020-02-08 | 正確 |
| 31 | 農曆108年正月初一 | True | 2019-02-05 | 正確 |
| 32 | 農曆108年二月初二 | True | 2019-03-08 | 正確 |
| 33 | 農曆108年三月初三 | True | 2019-04-07 | 正確 |
| 34 | 農曆108年四月初四 | True | 2019-05-08 | 正確 |
| 35 | 農曆108年五月初五 | True | 2019-06-07 | 正確 |
| 36 | 農曆108年六月初六 | True | 2019-07-08 | 正確 |
| 37 | 農曆108年七月初七 | True | 2019-08-07 | 正確 |
| 38 | 農曆108年八月初八 | True | 2019-09-06 | 正確 |
| 39 | 農曆108年九月初九 | True | 2019-10-07 | 正確 |
| 40 | 農曆108年十月初十 | True | 2019-11-06 | 正確 |
| 41 | 農曆112年潤2月初一 | True | 2023-03-22 | 正確(潤月) |
| 42 | 農曆112年2月初一 | True | 2023-02-20 | 正確 |
| 43 | 農曆112年3月初一 | True | 2023-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.
如果這篇文章有幫助到您幫我分享一下,讓我有寫下去的動力...