为什么DateTime.ParseExact无法识别无前置零的Mdyyyy格式?
根本原因
你对M、d格式符的定义理解完全正确,问题出在ParseExact对无分隔符格式串的解析规则上:
解析无分隔符的自定义格式串时,会优先匹配固定长度的格式段,对相邻的可变长度格式段采用贪婪匹配规则。Mdyyyy格式里yyyy是固定4位的格式段,解析时会先从输入字符串末尾截取4位作为年份,剩余的前缀会被优先匹配最左侧的可变长度格式符M:
- 输入
812021时,后4位取为2021,剩余前缀是81,M作为可变长度格式符最多允许2位值,解析器会直接把81整体作为月份值,81超出1-12的合法范围,因此抛出格式异常。 - 补前导零后的
08012021长度为8,后4位取为2021,剩余前缀是0801,贪婪匹配2位给M得到08(合法,解析为8月),剩余的01匹配d得到1日,因此能正常解析。
带时分秒的MdyyyyHms格式同理,多个相邻可变长度格式段在无分隔符时会触发贪婪匹配错误,只有补全固定2位后才能被正确分段。
解决方案
Mdyyyy无分隔符的格式本身存在天然二义性(比如1112021无法直接区分是1月11日还是11月1日),结合数据源无前置零的特点,推荐两种处理方案:
方案1:插入分隔符后解析
先手动拆分字符串插入分隔符,转换为带分隔符的标准格式后再解析,完全避开贪婪匹配问题:
$raw = '812021' $year = $raw.Substring($raw.Length - 4) $mdPart = $raw.Substring(0, $raw.Length - 4) # 按无前置零规则拆分月和日 if ($mdPart.Length -eq 2) { $month = $mdPart[0].ToString() $day = $mdPart[1].ToString() } elseif ($mdPart.Length -eq 3) { # 优先匹配1位合法月份 $testMonth = [int]$mdPart[0].ToString() if ($testMonth -ge 1 -and $testMonth -le 12) { $month = $testMonth.ToString() $day = $mdPart.Substring(1) } else { $month = $mdPart.Substring(0,2) $day = $mdPart[2].ToString() } } else { # 长度为4时月日都是2位 $month = $mdPart.Substring(0,2) $day = $mdPart.Substring(2) } # 拼接为带分隔符的格式解析 $dateStr = "$month/$day/$year" $date = [DateTime]::ParseExact($dateStr, 'M/d/yyyy', [CultureInfo]::InvariantCulture)
方案2:传入多格式列表匹配
如果业务场景下日期没有二义性,也可以直接给ParseExact传入所有可能的格式组合,让解析器自动匹配:
$allowedFormats = @('Mdyyyy', 'MMdyyyy', 'Mddyyyy', 'MMddyyyy') $date = [DateTime]::ParseExact('812021', $allowedFormats, [CultureInfo]::InvariantCulture, [Globalization.DateTimeStyles]::None)
该方案同样适用于带时分秒的场景,只需把时分秒的可能格式补充到格式列表即可。
内容的提问来源于stack exchange,提问作者NamedArray
相关产品推荐
相关产品推荐

