.NET 彈性日期時間字串解析及小地雷
0 |
專案裡有個需求,希望當使用者輸入字串為日期或日期時間時自動轉成 DateTime 型別,我想到用 DateTime.TryParse 來做(註:更嚴謹的做法是改用 DateTime.TryParseExact 正向表列所有支援格式,但我選擇借用 TryParse 內建的彈性較省事),目標是要能支援多種格式,例如:
- 2021/01/01 00:00:00
- 2021-01-01 08:00
- 2021/01/01 08:00
- 2021/1/1 8:00
- 2021-01-01T00:00:00+0800
- 2021-12-31T16:00:00Z
我選擇的解析參數是 DateTime.TryParse(dateString, culture, styles, out dateResult),culture 傳 null,styles 為 DateTimeStyles 列舉,實測 AssumeLocal、AssumeUniversal、AdjustToUniversal、RoundtripKind 幾種選項的結果:
void Main()
{
string v = null;
DateTime d = DateTime.Now;
Action<System.Globalization.DateTimeStyles> testStyle = (style) => {
Console.Write($"{style,-18} => ");
if (DateTime.TryParse(v, null, style, out d))
Console.WriteLine($"{d:yyyy-MM-dd HH:mm:ss} ({d.Kind})");
else
Console.WriteLine("invalid");
};
Action<string> test = (dateStr) => {
v = dateStr;
Console.WriteLine($"**** {v} ****");
testStyle(System.Globalization.DateTimeStyles.AssumeLocal);
testStyle(System.Globalization.DateTimeStyles.AssumeUniversal);
testStyle(System.Globalization.DateTimeStyles.AdjustToUniversal);
testStyle(System.Globalization.DateTimeStyles.RoundtripKind);
Console.WriteLine();
};
test("2021/01/01 00:00:00");
test("2021-01-01 08:00");
test("2021/01/01 08:00");
test("2021/1/1 8:00");
test("2021-01-01T00:00:00+0800");
test("2021-12-31T16:00:00Z");
}
評估 AssumeLocal 最接近我要的結果。RoundtripKind 的優點是遇到 2021-01-01T00:00:00+0800 或 2021-12-31T16:00:00Z 可保留 DateTimeKind 資訊,但字串未標明時區時會傳回 DateKind.Unspecified;AssumeLocal 除了會將 2021-12-31T16:00:00Z 硬轉成本地時間(系統以用本地時間為準,例也無所謂),其他輸入字串的結果都符合預期。
不過,後來發現我踩到小地雷 - 有一些小數,例如:3.1416、12.345、12.3456 會被當成日期。
在微軟文件 Standard date and time format strings 只有年月日用 "-" 或 "/" 分隔的範例,沒提到可以用 dot "."。但我查到這篇 - How to write dates - English Language Help Desk:
When writing the date by numbers only, one can separate them by using a hyphen (-), a slash (/), or a dot (.): 05-07-2013, or 05/07/2013, or 05.07.2013.
Omitting the initial zero in the numbers smaller than 10 is also accepted: 5-7-2013, 5/7/2013, or 5.7.2013.
所以的確有人會用 . 分隔日月年,但倒也沒提到「月.年」這種格式。依實測,DateTime.Parse 會將整數介於 1 - 12、小數三位或四位的數字解讀為 M.yyyy。
最後我決定修改程式,加上 Regex.IsMatch(dateStr, @"^\d{4}[/-]") 限定以年份起始並以 / 或 - 分隔年月的字串才當成日期時間,收工。
Action<string> parse = (dateStr) => {
DateTime dateVal;
Console.Write($"{dateStr, -20} => ");
if (System.Text.RegularExpressions.Regex.IsMatch(dateStr, @"^\d{4}[/-]") &&
DateTime.TryParse(dateStr, null,
System.Globalization.DateTimeStyles.AssumeLocal, out dateVal))
Console.WriteLine($"{dateVal:yyyy-MM-dd HH:mm:ss} ({dateVal.Kind})");
else
Console.WriteLine("invalid");
};
parse("2021.1.1 00:00:00");
parse("3.1416");
parse("12.34");
parse("12.345");
parse("13.3456");
parse("12.3456");
parse("2021/01/01 00:00:00");
parse("2021-01-01 08:00");
parse("2021/01/01 08:00");
parse("2021/1/1 8:00");
parse("2021-01-01T00:00:00+0800");
parse("2021-12-31T16:00:00Z");
Example of parsing different formats of date time string with DateTime.TryParse, and a pitfall for numbers with decimal point.
Comments
Be the first to post a comment