You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Ruby on Rails中如何验证从PESEL提取的日期有效性

处理PESEL提取出生日期时的无效日期错误

问题背景

我在模型中实现了一个before_save方法,用于从PESEL号码中解析出生日期,但偶尔会遇到ArgumentError: invalid date or strptime format的错误,比如解析12/14/2022时失败。我尝试用valid?和valid_date?验证生成的日期,但完全没用——因为错误在调用strptime时就直接抛出了,根本到不了验证那一步。

另外,我没办法直接对PESEL号码做完整校验,只能在提取日期的环节处理合法性问题。PESEL的月份规则是:

  • 19XX年:月份1-12
  • 20XX年:月份21-32(实际月份=数值-20)
  • 21XX年:月份41-52(实际月份=数值-40)
  • 22XX年:月份61-72(实际月份=数值-60)

我的原始代码如下:

def set_birth_date
  return unless pesel_changed?
  day = pesel[4..5]
  case pesel[2..3].to_i
  when 0..19
    month = pesel[2..3]
    year = pesel[0..1].prepend('19')
  when 20..39
    month = (pesel[2..3].to_i - 20).to_s
    year = pesel[0..1].prepend('20')
  when 40..59
    month = (pesel[2..3].to_i - 40).to_s
    year = pesel[0..1].prepend('21')
  when 60..79
    month = (pesel[2..3].to_i - 60).to_s
    year = pesel[0..1].prepend('22')
  end
  # 原本打算加的验证逻辑,但根本走不到这一步
  birth_date = Time.strptime("#{day}/#{month}/#{year}", '%d/%m/%Y')
  if birth_date.valid?
    self.birth_date = birth_date
  else
    errors.add(:pesel, I18n.t('activerecord.errors.models.profile.attributes.pesel.invalid'))
  end
end

解决方案

核心问题是:Time.strptime遇到无效日期(比如14月、32日)时会直接抛出ArgumentError,而不是返回一个可验证的Time对象。所以我们需要先捕获这个异常,再结合提前的月份合法性校验来处理。

1. 捕获strptime的异常

用begin-rescue块包裹日期解析逻辑,当解析失败时直接添加错误提示。

2. 提前校验月份范围

在计算出实际月份后,先检查是否在1-12之间,避免无效月份进入解析步骤。

优化后的完整代码

def set_birth_date
  return unless pesel_changed?

  day = pesel[4..5]
  pesel_month = pesel[2..3].to_i
  month = nil
  year = nil

  case pesel_month
  when 0..19
    month = pesel_month.to_s
    year = "19#{pesel[0..1]}"
  when 20..39
    month = (pesel_month - 20).to_s
    year = "20#{pesel[0..1]}"
  when 40..59
    month = (pesel_month - 40).to_s
    year = "21#{pesel[0..1]}"
  when 60..79
    month = (pesel_month - 60).to_s
    year = "22#{pesel[0..1]}"
  else
    # 处理不在规则内的月份,直接添加错误
    errors.add(:pesel, I18n.t('activerecord.errors.models.profile.attributes.pesel.invalid'))
    return
  end

  # 提前校验月份是否在1-12之间
  unless month.to_i.between?(1, 12)
    errors.add(:pesel, I18n.t('activerecord.errors.models.profile.attributes.pesel.invalid'))
    return
  end

  begin
    birth_date = Time.strptime("#{day}/#{month}/#{year}", '%d/%m/%Y')
    self.birth_date = birth_date
  rescue ArgumentError
    errors.add(:pesel, I18n.t('activerecord.errors.models.profile.attributes.pesel.invalid'))
  end
end

关键说明

  • 异常捕获:因为strptime对无效日期(比如2月30日、4月31日)会直接抛错,所以必须用rescue来捕获,才能正常添加错误而不中断程序。
  • 提前月份校验:虽然PESEL规则里转换后的月份应该是1-12,但如果PESEL本身有错误(比如输入了80作为月份),提前校验可以避免进入解析步骤,提高效率。
  • 返回逻辑:每次添加错误后都用return终止方法,避免后续代码执行。

内容的提问来源于stack exchange,提问作者Shruikan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.28 19:53:18