module SwedishPIN

Validate, parse, and generate Swedish Personal Identity Numbers.

In Swedish these are called Personnummer. There is also a variant called “coordination number” (Samordningsnummer). Both of these are supported using the same API; see {SwedishPIN::Personnummer#coordination_number?}.

To get started, look at {SwedishPIN.valid?} and {SwedishPIN.parse}.

Constants

VERSION

The version number of this library.

Public Class Methods

generate(birthday = nil, sequence_number = nil) click to toggle source

Generates a valid Personnummer given certain inputs. Inputs not provided will be randomized.

This is mainly useful in order to generate test data or “Lorem Ipsum”-like values for use in demonstrations. Note that valid PINs might actually correspond to a real person, so don't use these generated PINs for anything that has a real effect.

@example FactoryBot sequence

FactoryBot.define do
  sequence(:swedish_pin) do |n|
    # Will generate every PIN for a full day, then flip over to the next
    # day and start the sequence over.
    sequence_number = n % 1000
    date = Date.civil(1950, 1, 1) + (n / 1000)
    SwedishPIN.generate(date, sequence_number)
  end
end

@example Test data

user = User.new(name: "Jane Doe", pin: SwedishPIN.generate)

@raise [ArgumentError] if given numbers are outside of the valid range. @param [Date, Time, nil] birthday The birthday of the person the PIN identifies, or nil for a random date in the past. @param [String, Integer, nil] sequence_number The sequence number that correspond to the three digits after the birthday, or nil to pick a random one.

# File lib/swedish_pin.rb, line 83
def self.generate(birthday = nil, sequence_number = nil)
  Generator.new(birthday).generate(sequence_number)
end
luhn(digits) click to toggle source

@api private

Implementation of Luhn algorithm.

@param [String] digits String of digits to calculate a control digit for. @return [Integer] Control digit.

# File lib/swedish_pin.rb, line 93
def self.luhn(digits)
  sum = 0

  (0...digits.length).each do |i|
    v = digits[i].to_i
    v *= 2 - (i % 2)
    if v > 9
      v -= 9
    end
    sum += v
  end

  ((sum.to_f / 10).ceil * 10 - sum.to_f).to_i
end
parse(string, now = Time.now) click to toggle source

Parses a string of a personnummer and returns a {SwedishPIN::Personnummer} or raises an error.

Some numbers will have to relate to the current time in order to be parsed correctly. For example, the PIN 201231-… could be in many different years, including 1820, 1920, 2020, and so on. This library will guess that the year is in the most recent guess that are in the past. So during the year 2020 it would guess 2020, and in 2019 it will guess 1920.

@param [String] string The number to parse. @param [Time] now Provide a different “parse time” context. @return [SwedishPIN::Personnummer] The parsed PIN @raise {SwedishPIN::ParseError} When the provided string was not valid. @raise {ArgumentError} When the provided value was not a String.

# File lib/swedish_pin.rb, line 36
def self.parse(string, now = Time.now)
  result = Parser.new(string, now).parse
  Personnummer.new(
    year: result.fetch(:year),
    month: result.fetch(:month),
    day: result.fetch(:day),
    sequence_number: result.fetch(:sequence_number),
    control_digit: result.fetch(:control_digit)
  )
end
valid?(string, now = Time.now) click to toggle source

Checks if a provided string is a valid Personnummer.

@param [String] string The number to parse. @param [Time] now Provide a different “parse time” context. See {.parse}. @return [true, false] if the string was valid

# File lib/swedish_pin.rb, line 52
def self.valid?(string, now = Time.now)
  Parser.new(string, now).valid?
rescue ArgumentError
  false
end