Skip to content

Instantly share code, notes, and snippets.

@kgilles
Last active March 15, 2022 09:10
Show Gist options
  • Save kgilles/f29df5cc5af25d20240941a0a88aef53 to your computer and use it in GitHub Desktop.
Save kgilles/f29df5cc5af25d20240941a0a88aef53 to your computer and use it in GitHub Desktop.
Generate Random Finnish, Norwegian and Swedish Personal Identification Numbers
# NOTE: Gender is something these countries use to calculate the serial/individual number(making them even or odd)
# It is not taken into consideration in this snippet but the generated numbers are still valid
require 'date'
age = 21 # Whatever you want it to be
# Randomize month and day equal to, or lower, than the current month
# to make sure the generated birth month and day has occurred in <current_year>
t = Time.new
year = t.year - age
month = t.month - Random.rand(1..t.month) + 1
day = t.day - Random.rand(1..t.day) + 1
# Make sure we don't end up with Feb 29th on a non-leap year(or other invalid dates)
# This will happen extremely rarely, but better safe than sorry
while not Date.valid_date?(year, month, day)
day = t.day - Random.rand(1..t.day) + 1
end
birthdate = Date.new(year, month, day)
# ================================
# Generate Finnish Personal Number
# ================================
date = birthdate.strftime('%d%m%y')
individual_number = format('%03d', 2 + rand(899)) # 002-899
# The checksum is calculated as the remainder of the date + individual number divided by 31
# If the remainder is > 9 then the checksum is grabbed from a given character string with the remainder as index
remainder = "#{date}#{individual_number}".to_i % 31
if remainder < 10
fi_personal_number = "#{date}#{individual_number}#{remainder}"
else
char_string = '0123456789ABCDEFHJKLMNPRSTUVWXY'
checksum = char_string[remainder]
fi_personal_number = "#{date}#{individual_number}#{checksum}"
end
puts "Finnish: #{fi_personal_number}"
# ==================================
# Generate Norwegian Personal Number
# ==================================
def weigh_numbers(number, weight)
weighted = 0
number.split('').each_with_index do |digit, indx|
weighted += (digit.to_i * weight[indx].to_i)
end
weighted
end
date = birthdate.strftime('%d%m%y')
checksum1 = 10
checksum2 = 10
while checksum1 == 10 || checksum2 > 9
if year >= 1900 && year <= 1999
individual_number = format('%03d', rand(499)) # 000-499
elsif year >= 2000 && year <= 2039 # 2039 is the current limit
individual_number = format('%03d', 500 + rand(999)) # 500-999
end
# The nine digits we have so far are weighted to calculate the 10th digit
weighted = weigh_numbers("#{date}#{individual_number}", '376189452')
checksum1 = 11 - (weighted % 11)
checksum1 = 0 if checksum1 == 11
# Then, the now ten digits we have are weighted to calculate the 11th digit
weighted = weigh_numbers("#{date}#{individual_number}#{checksum1}", '5432765432')
checksum2 = 11 - (weighted % 11)
end
puts "Norwegian: #{date}#{individual_number}#{checksum1}#{checksum2}"
# ================================
# Generate Swedish Personal Number
# ================================
date = birthdate.strftime('%y%m%d') # If you want the 4-digit year you'll have to convert it after the calculactions
serial = format('%03d', 1 + rand(999)) # 001-999
# To calculate the checksum we start with multiplying every other number with 2
digits = []
"#{date}#{serial}".split('').each_with_index do |number, indx|
product = number.to_i * (2 - indx % 2)
# "a two digit product, such as 16, would be converted to 1 + 6"
product = product.to_s[0].to_i + product.to_s[1].to_i if product.to_s.length == 2
digits << product
end
# Add all products together into one sum
sum = digits.inject(0) { |prv, nxt| prv + nxt }
# Subtract the last digit in the sum from 10 to get the checksum
# If the last digit is 0, the checksum is automatically 0
checksum = 10 - sum % 10
checksum = 0 if checksum == 10
puts "Swedish: #{date}#{serial}#{checksum}"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment