resident 0.0.11 → 0.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/README.md +7 -3
- data/lib/national_identification_number/base.rb +4 -1
- data/lib/national_identification_number/danish.rb +43 -0
- data/lib/national_identification_number/norwegian.rb +61 -0
- data/lib/national_identification_number.rb +2 -0
- data/spec/national_identification_number/base_spec.rb +19 -0
- data/spec/national_identification_number/danish_spec.rb +81 -0
- data/spec/national_identification_number/norwegian_spec.rb +97 -0
- metadata +7 -32
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 90fe6f9288f6764f383ff5f8e74cc2a2e31854df1eaf42ebacd505ee586b9328
|
4
|
+
data.tar.gz: 9fb294f619eeaf83066b315ab3daeedcc79cf713ff8b64ce188289313e54d554
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bfa89495903de491d933a9d76f603213f8a84d28b7a828de49f79c5c9cee25c598c18b298e02296afc134f161ac99386e36d49c05733976553a213b57922c36e
|
7
|
+
data.tar.gz: 7c1725cea3d9e57f7efe7cebc63d91e461a1feac2edfbcffac1bad466b4cdce96d0f3a3c3463d5c851893ea0482434f7a0168c05ec364f1f4ffb1aec5f8a317f
|
data/README.md
CHANGED
@@ -2,9 +2,13 @@
|
|
2
2
|
|
3
3
|
Validates a National Identification Number (See http://en.wikipedia.org/wiki/National_identification_number).
|
4
4
|
There are some 62 countries that have this type of number.
|
5
|
-
This gem currently only supports Swedish (Personnummer) and Finnish (Henkilöasiakkaat) ones.
|
6
5
|
|
7
|
-
|
6
|
+
This gem currently only supports Swedish (Personnummer), Finnish
|
7
|
+
(Henkilöasiakkaat), Norwegian (Fødelsenummer) and Danish (CPR).
|
8
|
+
|
9
|
+
There is a special focus on identifying these numbers robustly
|
10
|
+
(i.e. you can safely validate a param[:number] directly, or cleanup
|
11
|
+
your database by running every non-normalized value through this gem).
|
8
12
|
|
9
13
|
## Installation
|
10
14
|
|
@@ -38,7 +42,7 @@ Some marked parts of the code are inspired or taken from MIT licensed code of
|
|
38
42
|
|
39
43
|
Other than that you got:
|
40
44
|
|
41
|
-
Copyright (c) 2011-
|
45
|
+
Copyright (c) 2011-2022 Bukowskis.
|
42
46
|
|
43
47
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
44
48
|
of this software and associated documentation files (the "Software"), to deal
|
@@ -25,6 +25,10 @@ module NationalIdentificationNumber
|
|
25
25
|
age_for_dob date
|
26
26
|
end
|
27
27
|
|
28
|
+
def ==(other)
|
29
|
+
other.class == self.class && other&.to_s == to_s
|
30
|
+
end
|
31
|
+
|
28
32
|
protected
|
29
33
|
|
30
34
|
# stackoverflow.com/questions/819263
|
@@ -41,6 +45,5 @@ module NationalIdentificationNumber
|
|
41
45
|
def validate
|
42
46
|
raise 'You need to implement at least this method in subclasses.'
|
43
47
|
end
|
44
|
-
|
45
48
|
end
|
46
49
|
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'national_identification_number/base'
|
2
|
+
|
3
|
+
module NationalIdentificationNumber
|
4
|
+
class Danish < Base
|
5
|
+
attr_reader :date
|
6
|
+
|
7
|
+
protected
|
8
|
+
|
9
|
+
def repair
|
10
|
+
super
|
11
|
+
@number.gsub!(/[^0-9]/, '')
|
12
|
+
if (matches = @number.match(/\A(?<birthday>[0-9]{6})(?<checksum>[0-9]{4})\z/))
|
13
|
+
birthday, checksum = %w[birthday checksum].map { |group| matches[group] }
|
14
|
+
@number = "#{birthday}-#{checksum}"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def validate
|
19
|
+
if (matches = @number.match(/\A(?<day>\d{2})(?<month>\d{2})(?<year>\d{2})-(?<serial>\d{3})(?<checksum>\d{1})\z/))
|
20
|
+
day, month, year, serial, checksum = %i[day month year serial checksum].map { |group| matches[group] }
|
21
|
+
begin
|
22
|
+
@date = Date.parse("#{full_year(serial, year)}-#{month}-#{day}")
|
23
|
+
@valid = true
|
24
|
+
@number = "#{day}#{month}#{year}-#{serial}#{checksum}"
|
25
|
+
rescue ArgumentError
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def full_year(serial, year)
|
33
|
+
century(serial, year.to_i) + year.to_i
|
34
|
+
end
|
35
|
+
|
36
|
+
def century(serial, year)
|
37
|
+
first_digit = serial[0].to_i
|
38
|
+
return 1800 if (5..8).include?(first_digit) && year > 57
|
39
|
+
return 2000 if (4..9).include?(first_digit) && year < 37
|
40
|
+
1900
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'national_identification_number/base'
|
2
|
+
|
3
|
+
module NationalIdentificationNumber
|
4
|
+
class Norwegian < Base
|
5
|
+
attr_reader :date
|
6
|
+
|
7
|
+
protected
|
8
|
+
|
9
|
+
def repair
|
10
|
+
super
|
11
|
+
@number.gsub!(/[^0-9]/, '')
|
12
|
+
end
|
13
|
+
|
14
|
+
def validate
|
15
|
+
if (matches = @number.match(/\A(?<day>\d{2})(?<month>\d{2})(?<year>\d{2})(?<serial>\d{3})(?<checksum>\d{2})\z/))
|
16
|
+
day, month, year, serial, checksum = %i[day month year serial checksum].map { |group| matches[group] }
|
17
|
+
|
18
|
+
sans_checksum = "#{day}#{month}#{year}#{serial}"
|
19
|
+
|
20
|
+
if checksum == calculate_checksum(sans_checksum)
|
21
|
+
begin
|
22
|
+
@date = Date.parse("#{full_year(serial, year)}-#{month}-#{day}")
|
23
|
+
@valid = true
|
24
|
+
@number = "#{sans_checksum}#{checksum}"
|
25
|
+
rescue ArgumentError
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def full_year(serial, year)
|
34
|
+
century(serial) + year.to_i
|
35
|
+
end
|
36
|
+
|
37
|
+
def century(serial)
|
38
|
+
case serial.to_i
|
39
|
+
when 0..499 then 1900
|
40
|
+
else 2000 # or 1800, not possible to tell.
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def calculate_checksum(number)
|
45
|
+
digits = number.split('').map(&:to_i)
|
46
|
+
w1 = [3, 7, 6, 1, 8, 9, 4, 5, 2]
|
47
|
+
w2 = [5, 4, 3, 2, 7, 6, 5, 4, 3, 2]
|
48
|
+
|
49
|
+
k1 = weighted_modulo_11(digits.zip(w1))
|
50
|
+
k2 = weighted_modulo_11([*digits, k1].zip(w2))
|
51
|
+
"#{k1}#{k2}"
|
52
|
+
end
|
53
|
+
|
54
|
+
def weighted_modulo_11(digits_and_weights)
|
55
|
+
result = 11 - (digits_and_weights.map do |terms|
|
56
|
+
terms.reduce(:*)
|
57
|
+
end.reduce(:+) % 11)
|
58
|
+
result > 9 ? 0 : result
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -74,4 +74,23 @@ describe NationalIdentificationNumber::Base do
|
|
74
74
|
end
|
75
75
|
end
|
76
76
|
|
77
|
+
describe '#==' do
|
78
|
+
it 'should be true if same class and string representation' do
|
79
|
+
one = NationalIdentificationNumber::Swedish.new('040626-5488')
|
80
|
+
other = NationalIdentificationNumber::Swedish.new('20040626-5488')
|
81
|
+
expect(one == other).to be true
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'should be false if other string representation' do
|
85
|
+
one = NationalIdentificationNumber::Swedish.new('040626-5488')
|
86
|
+
other = NationalIdentificationNumber::Swedish.new('040626-8128')
|
87
|
+
expect(one == other).to be false
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'should be false if other class' do
|
91
|
+
one = NationalIdentificationNumber::Swedish.new('040626-5488')
|
92
|
+
other = NationalIdentificationNumber::Finnish.new('040626-5488')
|
93
|
+
expect(one == other).to be false
|
94
|
+
end
|
95
|
+
end
|
77
96
|
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'resident'
|
3
|
+
|
4
|
+
describe NationalIdentificationNumber::Danish do
|
5
|
+
describe '#valid?' do
|
6
|
+
it 'recognizes valid numbers' do
|
7
|
+
expect(described_class.new('311257-1735')).to be_valid
|
8
|
+
expect(described_class.new('290444-0305')).to be_valid
|
9
|
+
expect(described_class.new('300168-0330')).to be_valid
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'recognizes invalid numbers' do
|
13
|
+
expect(described_class.new('251379-4001')).not_to be_valid # invalid month
|
14
|
+
expect(described_class.new('300279-4001')).not_to be_valid # invalid day
|
15
|
+
expect(described_class.new('asdf')).not_to be_valid
|
16
|
+
expect(described_class.new('1234567890')).not_to be_valid
|
17
|
+
expect(described_class.new('0000000000000')).not_to be_valid
|
18
|
+
expect(described_class.new('000000-0000')).not_to be_valid
|
19
|
+
expect(described_class.new('')).not_to be_valid
|
20
|
+
expect(described_class.new(1234567890)).not_to be_valid
|
21
|
+
expect(described_class.new(:really_bad_input_value)).not_to be_valid
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe 'sanitize' do
|
26
|
+
context 'with valid numbers' do
|
27
|
+
it 'is the formatted number' do
|
28
|
+
expect(described_class.new('3112571735').sanitize).to eq '311257-1735'
|
29
|
+
expect(described_class.new('290444.0305').sanitize).to eq '290444-0305'
|
30
|
+
expect(described_class.new('30.01.68.03.30').sanitize).to eq '300168-0330'
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
context 'with invalid numbers' do
|
35
|
+
it 'is nil' do
|
36
|
+
expect(described_class.new('251379-4001').sanitize).to be_nil
|
37
|
+
expect(described_class.new('300279-4001').sanitize).to be_nil
|
38
|
+
expect(described_class.new('asdf').sanitize).to be_nil
|
39
|
+
expect(described_class.new('1234567890').sanitize).to be_nil
|
40
|
+
expect(described_class.new('0000000000000').sanitize).to be_nil
|
41
|
+
expect(described_class.new('000000-0000').sanitize).to be_nil
|
42
|
+
expect(described_class.new('').sanitize).to be_nil
|
43
|
+
expect(described_class.new(1234567890).sanitize).to be_nil
|
44
|
+
expect(described_class.new(:really_bad_input_value).sanitize).to be_nil
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe 'age' do
|
50
|
+
before do
|
51
|
+
Timecop.freeze Date.parse('2022-03-04')
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'recognizes valid numbers' do
|
55
|
+
expect(described_class.new('110779-4101').age).to eq 42
|
56
|
+
expect(described_class.new('010100-1000').age).to eq 122
|
57
|
+
expect(described_class.new('110736-4002').age).to be_nil # Birthday in the future
|
58
|
+
expect(described_class.new('030320-5006').age).to eq 2
|
59
|
+
expect(described_class.new('110952-3113').age).to eq 69
|
60
|
+
expect(described_class.new('211164-2234').age).to eq 57
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'is nil for invalid numbers' do
|
64
|
+
expect(described_class.new('251379-4001').age).to be_nil # invalid month
|
65
|
+
expect(described_class.new('300279-4001').age).to be_nil # invalid day
|
66
|
+
expect(described_class.new('asdf').age).to be_nil
|
67
|
+
expect(described_class.new('1234567890').age).to be_nil
|
68
|
+
expect(described_class.new('0000000000000').age).to be_nil
|
69
|
+
expect(described_class.new('000000-0000').age).to be_nil
|
70
|
+
expect(described_class.new('').age).to be_nil
|
71
|
+
expect(described_class.new(1234567890).age).to be_nil
|
72
|
+
expect(described_class.new(:really_bad_input_value).age).to be_nil
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
describe '#to_s' do
|
77
|
+
it 'returns the number normalized regardless of wether its valid' do
|
78
|
+
expect(described_class.new('251.379.4001').to_s).to eq '251379-4001'
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'resident'
|
3
|
+
|
4
|
+
describe NationalIdentificationNumber::Norwegian do
|
5
|
+
describe '#valid?' do
|
6
|
+
it 'recognizes valid numbers' do
|
7
|
+
expect(described_class.new('11077941012')).to be_valid
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'recognizes invalid numbers' do
|
11
|
+
expect(described_class.new('671301A172V')).not_to be_valid # valid checksum, invalid month
|
12
|
+
expect(described_class.new('830231A172M')).not_to be_valid # valid checksum, invalid day
|
13
|
+
expect(described_class.new('11077941010')).not_to be_valid
|
14
|
+
expect(described_class.new('670368A172X')).not_to be_valid
|
15
|
+
expect(described_class.new('310145--586A')).not_to be_valid
|
16
|
+
expect(described_class.new('asdf')).not_to be_valid
|
17
|
+
expect(described_class.new('1234567890')).not_to be_valid
|
18
|
+
expect(described_class.new('0000000000000')).not_to be_valid
|
19
|
+
expect(described_class.new('000000-0000')).not_to be_valid
|
20
|
+
expect(described_class.new('')).not_to be_valid
|
21
|
+
expect(described_class.new(1234567890)).not_to be_valid
|
22
|
+
expect(described_class.new(:really_bad_input_value)).not_to be_valid
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe 'sanitize' do
|
27
|
+
context 'with valid numbers' do
|
28
|
+
it 'is the formatted number' do
|
29
|
+
expect(described_class.new('11077941012').sanitize).to eq '11077941012'
|
30
|
+
expect(described_class.new('11.07.79.410.12').sanitize).to eq '11077941012'
|
31
|
+
expect(described_class.new('11 07 79 41012').sanitize).to eq '11077941012'
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
context 'with invalid numbers' do
|
36
|
+
it 'is nil' do
|
37
|
+
expect(described_class.new('11077941010').sanitize).to be_nil
|
38
|
+
expect(described_class.new('671301A172V').sanitize).to be_nil
|
39
|
+
expect(described_class.new('830231A172M').sanitize).to be_nil
|
40
|
+
expect(described_class.new('311180-999J').sanitize).to be_nil
|
41
|
+
expect(described_class.new('131052-308S').sanitize).to be_nil
|
42
|
+
expect(described_class.new('290164X862U').sanitize).to be_nil
|
43
|
+
expect(described_class.new('670368A172X').sanitize).to be_nil
|
44
|
+
expect(described_class.new('310145--586A').sanitize).to be_nil
|
45
|
+
expect(described_class.new('asdf').sanitize).to be_nil
|
46
|
+
expect(described_class.new('1234567890').sanitize).to be_nil
|
47
|
+
expect(described_class.new('0000000000000').sanitize).to be_nil
|
48
|
+
expect(described_class.new('000000-0000').sanitize).to be_nil
|
49
|
+
expect(described_class.new('').sanitize).to be_nil
|
50
|
+
expect(described_class.new(1234567890).sanitize).to be_nil
|
51
|
+
expect(described_class.new(:really_bad_input_value).sanitize).to be_nil
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
describe 'age' do
|
57
|
+
before do
|
58
|
+
Timecop.freeze Date.parse('2022-03-04')
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'recognizes valid numbers' do
|
62
|
+
expect(described_class.new('11077941012').age).to eq 42
|
63
|
+
expect(described_class.new('01010010000').age).to eq 122
|
64
|
+
expect(described_class.new('11077950020').age).to be_nil # Birthday in the future
|
65
|
+
expect(described_class.new('03032050068').age).to eq 2
|
66
|
+
expect(described_class.new('11095231138').age).to eq 69
|
67
|
+
expect(described_class.new('21116422341').age).to eq 57
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'is nil for invalid numbers' do
|
71
|
+
expect(described_class.new('671301A172V').age).to be_nil
|
72
|
+
expect(described_class.new('830231A172M').age).to be_nil
|
73
|
+
expect(described_class.new('311180-999J').age).to be_nil
|
74
|
+
expect(described_class.new('131052-308S').age).to be_nil
|
75
|
+
expect(described_class.new('290164X862U').age).to be_nil
|
76
|
+
expect(described_class.new('670368A172X').age).to be_nil
|
77
|
+
expect(described_class.new('310145--586A').age).to be_nil
|
78
|
+
expect(described_class.new('asdf').age).to be_nil
|
79
|
+
expect(described_class.new('1234567890').age).to be_nil
|
80
|
+
expect(described_class.new('0000000000000').age).to be_nil
|
81
|
+
expect(described_class.new('000000-0000').age).to be_nil
|
82
|
+
expect(described_class.new('').age).to be_nil
|
83
|
+
expect(described_class.new(1234567890).age).to be_nil
|
84
|
+
expect(described_class.new(:really_bad_input_value).age).to be_nil
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
describe '#to_s' do
|
89
|
+
it 'returns the number normalized regardless of wether it is valid' do
|
90
|
+
expect(described_class.new('311280999J').to_s).to eq '311280999'
|
91
|
+
expect(described_class.new('27..036.8A172X ').to_s).to eq '270368172'
|
92
|
+
expect(described_class.new('xx270368A172X ').to_s).to eq '270368172'
|
93
|
+
expect(described_class.new('xxx270368A172Xt ').to_s).to eq '270368172'
|
94
|
+
expect(described_class.new('\t050126-1853').to_s).to eq '0501261853'
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: resident
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.14
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Bukowskis
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-06-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rspec
|
@@ -24,34 +24,6 @@ dependencies:
|
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: guard-rspec
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
30
|
-
requirements:
|
31
|
-
- - ">="
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: '0'
|
34
|
-
type: :development
|
35
|
-
prerelease: false
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
37
|
-
requirements:
|
38
|
-
- - ">="
|
39
|
-
- !ruby/object:Gem::Version
|
40
|
-
version: '0'
|
41
|
-
- !ruby/object:Gem::Dependency
|
42
|
-
name: rb-fsevent
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
44
|
-
requirements:
|
45
|
-
- - ">="
|
46
|
-
- !ruby/object:Gem::Version
|
47
|
-
version: '0'
|
48
|
-
type: :development
|
49
|
-
prerelease: false
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
51
|
-
requirements:
|
52
|
-
- - ">="
|
53
|
-
- !ruby/object:Gem::Version
|
54
|
-
version: '0'
|
55
27
|
- !ruby/object:Gem::Dependency
|
56
28
|
name: timecop
|
57
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -76,11 +48,15 @@ files:
|
|
76
48
|
- README.md
|
77
49
|
- lib/national_identification_number.rb
|
78
50
|
- lib/national_identification_number/base.rb
|
51
|
+
- lib/national_identification_number/danish.rb
|
79
52
|
- lib/national_identification_number/finnish.rb
|
53
|
+
- lib/national_identification_number/norwegian.rb
|
80
54
|
- lib/national_identification_number/swedish.rb
|
81
55
|
- lib/resident.rb
|
82
56
|
- spec/national_identification_number/base_spec.rb
|
57
|
+
- spec/national_identification_number/danish_spec.rb
|
83
58
|
- spec/national_identification_number/finnish_spec.rb
|
59
|
+
- spec/national_identification_number/norwegian_spec.rb
|
84
60
|
- spec/national_identification_number/swedish_spec.rb
|
85
61
|
- spec/spec_helper.rb
|
86
62
|
homepage: http://github.com/bukowskis/national_identification_number/
|
@@ -103,8 +79,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
103
79
|
- !ruby/object:Gem::Version
|
104
80
|
version: '0'
|
105
81
|
requirements: []
|
106
|
-
|
107
|
-
rubygems_version: 2.5.1
|
82
|
+
rubygems_version: 3.0.3.1
|
108
83
|
signing_key:
|
109
84
|
specification_version: 4
|
110
85
|
summary: Validate National Identification Numbers.
|