Scala中字符串被错误解析为日期格式的问题排查与解决
哎,这个问题我之前处理过!核心原因在于SimpleDateFormat的两个特性:宽松解析模式和部分解析成功就返回。当你用yyyy/MM/dd解析02/02/2017时,它会把第一个02当成年份(公元2年),第二个02当成月份,第三个2017当成日期——虽然这看起来很离谱,但在宽松模式下是合法的,所以方法直接返回了这个格式,根本不会继续检查后面的MM/dd/yyyy。
无需正则的解决方案
我们可以通过三个调整彻底解决这个问题:
- 关闭宽松解析:调用
setLenient(false)让SimpleDateFormat严格校验日期合法性,比如月份不能超过12、日期不能超过当月最大天数。 - 确保完全解析:使用
ParsePosition跟踪解析位置,只有当整个字符串都被解析完成时,才认为匹配成功(避免只解析部分内容就返回)。 - 调整格式顺序:把更符合日常输入习惯的格式(比如
MM/dd/yyyy)放在前面,易产生歧义的格式(比如yyyy/MM/dd)往后放,优先匹配更常见的场景。
修改后的Scala代码如下:
import java.text.SimpleDateFormat import java.text.ParsePosition def checkDateFormat(dateStr: String): Option[String] = { val dateFormats = List( "MM/dd/yyyy", "yyyy/MM/dd", "MMM dd, yyyy", "dd MMM yyyy", "yyyy-MM-dd" ) dateFormats.foreach { format => val sdf = new SimpleDateFormat(format) sdf.setLenient(false) // 关闭宽松解析,严格校验日期 val pos = new ParsePosition(0) val date = sdf.parse(dateStr, pos) // 只有解析成功,且整个字符串都被解析完毕,才返回对应格式 if (date != null && pos.getIndex == dateStr.length) { return Some(format) } } None // 无匹配格式时返回None,比原方法的Any更符合类型安全原则 }
测试验证:
- 输入
"02/02/2017"会匹配到"MM/dd/yyyy":用yyyy/MM/dd解析时,2017作为日期显然不合法(2月最多29天),宽松模式关闭后解析失败,会继续检查下一个格式。 - 输入
"2017/02/02"会正确匹配到"yyyy/MM/dd"。
正则辅助方案(应对极端歧义场景)
如果遇到更复杂的边缘场景,还可以先用正则过滤格式结构,再进行日期解析,进一步降低误匹配概率:
def checkDateFormatWithRegex(dateStr: String): Option[String] = { val formatRegexMap = Map( "MM/dd/yyyy" -> """^\d{2}/\d{2}/\d{4}$""".r, "yyyy/MM/dd" -> """^\d{4}/\d{2}/\d{2}$""".r, "MMM dd, yyyy" -> """^[A-Za-z]{3} \d{2}, \d{4}$""".r, "dd MMM yyyy" -> """^\d{2} [A-Za-z]{3} \d{4}$""".r, "yyyy-MM-dd" -> """^\d{4}-\d{2}-\d{2}$""".r ) formatRegexMap.foreach { case (format, regex) => if (regex.matches(dateStr)) { val sdf = new SimpleDateFormat(format) sdf.setLenient(false) val pos = new ParsePosition(0) val date = sdf.parse(dateStr, pos) if (date != null && pos.getIndex == dateStr.length) { return Some(format) } } } None }
这个方法先通过正则匹配格式的结构特征,再执行日期解析,比如"02/02/2017"会先命中MM/dd/yyyy的正则,直接进入该格式的解析流程,不会再去尝试yyyy/MM/dd。
内容的提问来源于stack exchange,提问作者Zzrot
相关产品推荐
相关产品推荐

