class SwedishPIN::Parser

@api private

Parser for Personnummer.

Please use {SwedishPIN.parse} or {SwedishPIN.valid?} instead.

Constants

MATCHER

Attributes

now[R]

Public Class Methods

new(input, now = Time.now) click to toggle source

Setup a new parser.

# File lib/swedish_pin/parser.rb, line 24
def initialize(input, now = Time.now)
  unless input.is_a?(String)
    raise ArgumentError, "Expected String, got #{input.inspect}"
  end

  @input = input
  @now = now
  @matches = MATCHER.match(input.strip)
end

Public Instance Methods

parse() click to toggle source

Return Hash of parsed values to be used with {SwedishPIN::Personnummer#initialize}.

# File lib/swedish_pin/parser.rb, line 50
def parse
  validate

  {
    year: full_year,
    month: month,
    day: day,
    sequence_number: sequence_number,
    control_digit: control_digit
  }
end
valid?() click to toggle source

Check validity without raising.

# File lib/swedish_pin/parser.rb, line 42
def valid?
  validate
  true
rescue
  false
end
validate() click to toggle source

Raise {ParseError} if anything in the input isn't valid.

# File lib/swedish_pin/parser.rb, line 35
def validate
  validate_match
  validate_luhn
  validate_date
end

Private Instance Methods

century() click to toggle source
# File lib/swedish_pin/parser.rb, line 70
def century
  if @matches["century"]
    Integer(@matches["century"], 10)
  else
    guess_century
  end
end
control_digit() click to toggle source
# File lib/swedish_pin/parser.rb, line 103
def control_digit
  Integer(@matches["control_digit"], 10)
end
day() click to toggle source
# File lib/swedish_pin/parser.rb, line 86
def day
  @day ||= Integer(@matches["day"], 10)
end
full_year() click to toggle source
# File lib/swedish_pin/parser.rb, line 66
def full_year
  century * 100 + year
end
guess_century() click to toggle source
# File lib/swedish_pin/parser.rb, line 107
def guess_century
  guessed_year = (now.year / 100) * 100 + year

  # Don't guess future dates; skip back a century when that happens.
  if Time.new(guessed_year, month, real_day) > now
    guessed_year -= 100
  end

  # The "+" separator means another century back.
  if @matches["separator"] == "+"
    guessed_year -= 100
  end

  guessed_year / 100
end
month() click to toggle source
# File lib/swedish_pin/parser.rb, line 82
def month
  @month ||= Integer(@matches["month"], 10)
end
real_day() click to toggle source

Day, but adjusted for coordination numbers being possible.

# File lib/swedish_pin/parser.rb, line 91
def real_day
  if day > 60
    day - 60
  else
    day
  end
end
sequence_number() click to toggle source
# File lib/swedish_pin/parser.rb, line 99
def sequence_number
  Integer(@matches["sequence_number"], 10)
end
validate_date() click to toggle source
# File lib/swedish_pin/parser.rb, line 142
def validate_date
  raise InvalidDate.new("#{month} is not a valid month", @input) unless (1..12).cover?(month)
  raise InvalidDate.new("#{day} is not a valid day", @input) unless (1..31).cover?(real_day)

  unless Date.valid_date?(full_year, month, real_day)
    raise InvalidDate.new("Input had invalid date", @input)
  end
end
validate_luhn() click to toggle source
# File lib/swedish_pin/parser.rb, line 129
def validate_luhn
  comparator = [
    @matches["year"],
    @matches["month"],
    @matches["day"],
    @matches["sequence_number"]
  ].join("")

  if SwedishPIN.luhn(comparator) != control_digit
    raise InvalidChecksum.new("Control digit did not match expected value", @input)
  end
end
validate_match() click to toggle source
# File lib/swedish_pin/parser.rb, line 123
def validate_match
  unless @matches
    raise InvalidFormat.new("Input did not match expected format", @input)
  end
end
year() click to toggle source
# File lib/swedish_pin/parser.rb, line 78
def year
  Integer(@matches["year"], 10)
end