iceland 0.1.3 → 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 95b30f1477b431842ba1b7aec7b3c6be294c6292
4
- data.tar.gz: 60ef980798f7a838bf414d71515102afa83c2f0a
3
+ metadata.gz: b11f96ebbc86010620fd65728d6c85a8b92a4ec9
4
+ data.tar.gz: aaa75e67c18e513b6bfbe0f548800d5883c538df
5
5
  SHA512:
6
- metadata.gz: 7124e9c2324f5d529664e6aac438d2d850a45cee3a0bf683b51b001e499ef9dfb4cf352939c0b683fb5865853564a15896fb833e1d06e091586249d2b443a184
7
- data.tar.gz: 31dc4830e351222cc4ad901cddbc9f82d5628fe4bff5250e1ef5d003961d2e50a130fa6d6ea909069fff8bd8c20b40ae37a10b66e962b73931873b7bc8c80917
6
+ metadata.gz: e9e6b8a66efb724a012b19ebc1e3c9fd9b0920725ca06732254cd723015670fad5c079c50ecbc1986932ae4134c762ccc43539187ac59e20c4f4450a7a2020f9
7
+ data.tar.gz: 4c9e279e51b271f70dd2441256c92aea3f494e156bff0a2b97604f8a403311ce8ac6e60f6f72ebbe52d2c8ab1f4546ebdfbe96fe7303efa138b7842cd2c2849c
data/.rubocop.yml CHANGED
@@ -1,2 +1,4 @@
1
1
  Metrics/AbcSize:
2
2
  Max: 20
3
+ Metrics/ClassLength:
4
+ Max: 150
data/README.md CHANGED
@@ -22,6 +22,8 @@ Or install it yourself as:
22
22
 
23
23
  ### Postal Codes
24
24
 
25
+ Icelandic postal codes are 3-digit numeric identifiers, with the first digit indicating a region and the rest identifying a specific locale within the region.
26
+
25
27
  Names of locales are provided in dative form by default as per Icelandic postal convention, but nomative forms can be returned by setting the `force_nominative` parameter to `true` when using the `Iceland.all_postal_codes` and `Iceland.locale_by_postal_code` methods.
26
28
 
27
29
  (Note that we use the term "locale" as per Universal Postal Union convention to name town, city or other location the postal code is assigned to.)
@@ -52,18 +54,46 @@ Iceland.locale_by_postal_code 311, true
52
54
 
53
55
  The Iceland Gem provides a class to handle "kennitala" identifier codes. The class can be used to sanitize the identifiers and read information like the date of birth (or date of registration in the case of companies and organization), age and the type of entity.
54
56
 
57
+ The class does not access external APIs or databases such National Registry or the Company Registry, so names and status (death, bankruptcy, credit rating etc.) cannot be accessed using the class. However, it can be used to sanitize and validate such data before being sent to external APIs, as such services are provided by private companies, which often charge a specific amount for each query.
58
+
59
+ #### Uses of kennitala
60
+
61
+ Unlike the US Social Security number and equivalents, the kennitala is only used for identification of persons and companies (as well as other registered organizations) — and is often used internally by educational institutions, companies and other organization as a primary identifier for persons (e.g. school, employee, customer and frequent flyer ID). It is not to be used for authentication (i.e. a password) and is not considered a secret per se. While a kennitala can be kept unencrypted in a database, publishing a kennitala or a list of them is generally not considered good practice and might cause liability.
62
+
63
+ A kennitala is assigned to every newborn person and foreign nationals residing in Iceland as well as organizations and companies operating there. It is statically assigned and can not be changed.
64
+
65
+ Article II, paragraph 10 of the 77/2000 Act on Data Protection (http://www.althingi.is/lagas/nuna/2000077.html) provides the legal framework regarding the use and processing of the kennitala in Iceland:
66
+
67
+ > The use of a kennitala is allowed if it has a an objective cause and is necessary to ensure reliable identification of persons. The Data Protection Authority may ban or order the use of kennitala.
68
+
69
+ #### Technicalities
70
+
71
+ The kennitala (`DDMMYY-RRCM`) is a 10-digit numeric string consisting on a date (date of birth for persons, date of registration for companies) in the form of `DDMMYY`, three two random digits (`RR`) a check digit (`C`) and a century identifier (`M`). A hyphen or space is often added between the year and random values (Example: `010130-2989`).
72
+
73
+ The number 40 is added to the registration day of companies and organizations. Hence, a kennitala for a company registered at January 1 1990 starts with `410190` as opposed to `010190` for a person born that day.
74
+
75
+ The century identifier has 3 legal values. `8` for the 19th century, `9` for the 20th century and `0` for the 21st century.
76
+
77
+ The check digit is described
78
+
55
79
  #### Examples
56
80
 
81
+ ##### Working with Kennitala objects
82
+
57
83
  ```ruby
58
84
  # Initialize a Kennitala object.
59
85
  # The string provided may include spaces and hyphens.
60
86
  k = Kennitala.new('010130-2989')
61
87
  # => #<Kennitala:0x007fe35d041bc0 @value="0101302989">
62
88
 
63
- # Invalid strings are rejected
89
+ # Invalid strings are rejected with an argument error
64
90
  f = Kennitala.new('010130-2979')
65
91
  # ArgumentError: Kennitala is invalid
66
92
 
93
+ # If no kennitala string is specified, a random one will be generated
94
+ r = Kennitala.new
95
+ # => #<Kennitala:0x007fc589339f18 @value="2009155509">
96
+
67
97
  # Retrieve the kennitala as a string.
68
98
  # This is a sanitized string, without any non-numeric characters.
69
99
  k.to_s
@@ -89,10 +119,22 @@ k.age
89
119
  # => 86
90
120
  ```
91
121
 
122
+ ####
123
+
124
+ ```ruby
125
+ # Casting a string to a Kennitala object
126
+ '0101302989'.to_kt
127
+ # => #<Kennitala:0x007fc5893286a0 @value="0101302989">
128
+
129
+ # Get the current age based on a String
130
+ '0101302989'.to_kt.age
131
+ # => 86
132
+ ```
133
+
92
134
  ## Todo
93
135
 
94
136
  * Administrative Divisions
95
- * A Kennitala faker, similar to ffaker and ffaker
137
+ * Bank accounts
96
138
 
97
139
  ## About the data
98
140
 
@@ -110,7 +152,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
110
152
 
111
153
  Bug reports and pull requests are welcome on GitHub at https://github.com/stefanvignir/iceland_gem.
112
154
 
113
- Do make sure that the `rspec` unit tests run before sending a pull request and write tests for any new functionality you add.
155
+ Do make sure that the `rspec` unit tests run before sending a pull request (hint: try running `rspec` a couple of times in a row as some tests might fail randomly) and write tests for any new functionality you add. Also run `rubocop` to check if your code adheres to the Ruby Style Guide.
114
156
 
115
157
  ## License
116
158
 
@@ -0,0 +1,217 @@
1
+ # The Kennitala Class
2
+ class Kennitala
3
+ def initialize(kt_string = false, is_company = false)
4
+ kt_string = fake_kt_string(is_company) if kt_string == false
5
+ unless kt_string.class == String
6
+ raise ArgumentError, 'Kennitala needs to be provided as a string'
7
+ end
8
+ sanitised_kt = sanitize(kt_string)
9
+ raise ArgumentError, 'Kennitala is invalid' if sanitised_kt.nil?
10
+ @value = sanitised_kt
11
+ end
12
+
13
+ # Get the type of entity - If it is a person or an organization
14
+ #
15
+ # @return [String] Either 'person' or 'company'
16
+ def entity_type
17
+ date_integer = @value[0, 2].to_i
18
+ return 'person' if date_integer < 32
19
+ return 'company' if (date_integer > 40) && (date_integer < 71)
20
+ false
21
+ end
22
+
23
+ # Check if the entity is a company
24
+ #
25
+ # @return [Boolean]
26
+ def company?
27
+ date_integer = @value[0, 2].to_i
28
+ return true if (date_integer > 40) && (date_integer < 71)
29
+ false
30
+ end
31
+
32
+ # Check if the entity is a person
33
+ #
34
+ # @return [Type] description of returned object
35
+ def person?
36
+ date_integer = @value[0, 2].to_i
37
+ return true if date_integer < 32
38
+ false
39
+ end
40
+
41
+ # Get the year of birth or registration
42
+ #
43
+ # @return [Fixnum]
44
+ def year
45
+ century = (10 + @value[9].to_i) * 100
46
+ year = @value[4, 2].to_i
47
+ return century + year if (1800..1900).cover?(century)
48
+ return 2000 + year if century == 1000
49
+ end
50
+
51
+ # Get the day of the month of birth or registration
52
+ #
53
+ # @return [Fixnum]
54
+ def day
55
+ date_integer = @value[0, 2].to_i
56
+ return @value[0, 2].to_i if date_integer < 32
57
+ return @value[0, 2].to_i - 40 if (date_integer > 40) && (date_integer < 71)
58
+ end
59
+
60
+ # Get a numeric representation of the month of birth or registration
61
+ #
62
+ # @return [Fixnum]
63
+ def month
64
+ @value[2, 2].to_i
65
+ end
66
+
67
+ # Get the age of entity in years. Useful when dealing with age restrictions.
68
+ #
69
+ # @return [Fixnum]
70
+ def age
71
+ year_diff = Date.today.year - to_date.year
72
+ month_diff = Date.today.month - to_date.month
73
+ day_diff = Date.today.month - to_date.month
74
+
75
+ return year_diff -= 1 if month_diff < 0 || (month_diff == 0 && day_diff < 0)
76
+ year_diff
77
+ end
78
+
79
+ # Cast the kennitala to a Date object
80
+ #
81
+ # @return [Date]
82
+ def to_date
83
+ Date.new(year, month, day)
84
+ end
85
+
86
+ # Cast the kennitala to a String object
87
+ #
88
+ # @return [Date]
89
+ def to_s
90
+ @value.to_s
91
+ end
92
+
93
+ private
94
+
95
+ # Generate fake a birth number and check digit based on the first 6 digits
96
+ #
97
+ # @param [String] The first six digits is a kennitala
98
+ # @return [Hash, nil]
99
+ def fake_randoms(date_hash)
100
+ first_six = date_hash[:day] + date_hash[:month] + date_hash[:year]
101
+ loop do
102
+ birth_number = Random.rand(1..99).to_s.rjust(2, '0')
103
+ first_eight = "#{first_six}#{birth_number}"
104
+ check_digit = calculate_check_digit(first_eight)
105
+ if check_checksum(first_eight)
106
+ return { check_digit: check_digit, birth_number: birth_number }
107
+ end
108
+ end
109
+ end
110
+
111
+ # Generate a fake year and century Hash
112
+ #
113
+ # @return [Hash] description of returned object
114
+ def fake_year
115
+ century = [9, 9, 9, 8, 0, 0].sample
116
+ current_year = Date.today.strftime('%y').to_i
117
+ if century == 0
118
+ return { year: Random.rand(0..current_year), century: century }
119
+ else
120
+ return { year: Random.rand(0..99), century: century }
121
+ end
122
+ end
123
+
124
+ # Generate a fake hash that includes randomly generated date elements
125
+ #
126
+ # @param [Boolean] is_company true if the day string is for a company
127
+ # @return [Hash]
128
+ def fake_date_hash(is_company = false)
129
+ year_hash = fake_year
130
+
131
+ month_days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
132
+ month = Random.rand(1..12)
133
+
134
+ day = Random.rand(1..month_days[month - 1])
135
+ day = (day.to_i + 40).to_s.rjust(2, '0') if is_company == true
136
+
137
+ { century: year_hash[:century].to_s,
138
+ year: year_hash[:year].to_s.rjust(2, '0'),
139
+ month: month.to_s.rjust(2, '0'), day: day.to_s.rjust(2, '0') }
140
+ end
141
+
142
+ def fake_kt_string(is_company = false)
143
+ date_hash = fake_date_hash(is_company)
144
+ randoms_hash = fake_randoms(date_hash)
145
+
146
+ first_six = date_hash[:day] + date_hash[:month] + date_hash[:year]
147
+ randoms = randoms_hash[:birth_number].to_s + randoms_hash[:check_digit].to_s
148
+
149
+ first_six + randoms + date_hash[:century]
150
+ end
151
+
152
+ def get_year_from_string(kt_string)
153
+ century_code = kt_string[9, 1].to_i
154
+ case century_code
155
+ when 0
156
+ return "20#{kt_string[4, 2]}".to_i
157
+ when 8..9
158
+ return "1#{century_code}#{kt_string[4, 2]}".to_i
159
+ end
160
+ end
161
+
162
+ # Sanitize the kennitala
163
+ #
164
+ # @param [String] kt_string Unsanitised string representing a kennitala
165
+ # @return [String, nil] Sanitized kennitala, nil if invalid
166
+ def sanitize(kt_string)
167
+ sanitized_kt = kt_string.gsub(/\D/, '')
168
+ checks = check_checksum(sanitized_kt)
169
+
170
+ year = get_year_from_string(sanitized_kt)
171
+ day = sanitized_kt[0, 2].to_i
172
+ day -= 40 if day > 40
173
+ month = sanitized_kt[2, 2].to_i
174
+ date = Date.new(year, month, day)
175
+
176
+ return sanitized_kt if checks == true && date.class == Date
177
+
178
+ rescue ArgumentError, 'invalid date'
179
+ nil
180
+ end
181
+
182
+ # Calculate the checksum
183
+ #
184
+ # @param [String] kt_string Sanitized kennitala
185
+ # @return [Fixnum] The checksum
186
+ def checksum(kt_string)
187
+ checksum = 0
188
+ multipliers = [3, 2, 7, 6, 5, 4, 3, 2]
189
+ multipliers.each_with_index do |multiplier, index|
190
+ checksum += multiplier * kt_string[index].to_i
191
+ end
192
+ checksum
193
+ end
194
+
195
+ def calculate_check_digit(kt_string)
196
+ remainder = checksum(kt_string).modulo(11)
197
+
198
+ # A kennitala with a remainder of 10 is always considered to be invalid
199
+ return nil if remainder == 10
200
+
201
+ # The check digit should be 11 minus the remainder,
202
+ # unless the remainder is 0, then the theck digit becomes 0.
203
+ return 0 if remainder == 0
204
+ 11 - remainder
205
+ end
206
+
207
+ # Check validity of the check digit
208
+ #
209
+ # @param [String] kt_string Sanitized kennitala
210
+ # @return [Boolean, nil] true on success, false if the check digit is invalid
211
+ def check_checksum(kt_string)
212
+ expected_check_digit = calculate_check_digit(kt_string)
213
+ actual_check_digit = kt_string[8].to_i
214
+ return true if expected_check_digit == actual_check_digit
215
+ false
216
+ end
217
+ end
@@ -0,0 +1,9 @@
1
+ # Monkey patch for the String class
2
+ class String
3
+ # Converts a String to a Kennitala object
4
+ #
5
+ # @return [Kennitala]
6
+ def to_kt
7
+ Kennitala.new(self)
8
+ end
9
+ end
@@ -0,0 +1,41 @@
1
+ # Postal Codes
2
+ module Iceland
3
+ POSTAL_CODES = YAML.load_file(File.expand_path('../../postcodes.yml',
4
+ __FILE__))
5
+
6
+ # Get an array of hashes with postal_code and locale attributes
7
+ #
8
+ # @param [Boolean] include_po_boxes Include postal codes for P.O. boxes
9
+ # @param [Boolean] force_nominative Use the nomative version of locale name
10
+ # @return [Array]
11
+ def all_postal_codes(include_po_boxes = false, force_nominative = false)
12
+ pairs = []
13
+ POSTAL_CODES.each do |postal_code, p|
14
+ # Skip P.O. boxes
15
+ next if (include_po_boxes == false) && (p['is_po_box'] == true)
16
+ # Retun the dative form of the locale by default
17
+ pairs << if p['dative'].nil? || force_nominative == true
18
+ { postal_code: postal_code, locale: p['locale'] }
19
+ else
20
+ { postal_code: postal_code, locale: p['dative'] }
21
+ end
22
+ end
23
+ pairs
24
+ end
25
+
26
+ # Find the name of locale (city/town/village) by postal code
27
+ #
28
+ # @param [Integer, String] postal_code The postal code
29
+ # @param [Boolean] force_nominative Display locale name in nomative form
30
+ # @return [String] description of returned object
31
+ def locale_by_postal_code(postal_code, force_nominative = false)
32
+ postal_code = postal_code.to_i
33
+ postal_code_hash = POSTAL_CODES[postal_code]
34
+ unless postal_code_hash.nil?
35
+ if postal_code_hash['dative'].nil? || force_nominative == true
36
+ return postal_code_hash['locale']
37
+ end
38
+ postal_code_hash['dative']
39
+ end
40
+ end
41
+ end
@@ -1,3 +1,3 @@
1
1
  module Iceland
2
- VERSION = '0.1.3'.freeze
2
+ VERSION = '0.1.4'.freeze
3
3
  end
data/lib/iceland.rb CHANGED
@@ -2,181 +2,6 @@ require 'date'
2
2
  require 'yaml'
3
3
 
4
4
  require 'iceland/version'
5
-
6
- # The Iceland Module
7
- module Iceland
8
- POSTAL_CODES = YAML.load_file(File.expand_path('../postcodes.yml', __FILE__))
9
-
10
- # Get an array of hashes with postal_code and locale attributes
11
- #
12
- # @param [Boolean] include_po_boxes Include postal codes for P.O. boxes
13
- # @param [Boolean] force_nominative Use the nomative version of locale name
14
- # @return [Array]
15
- def all_postal_codes(include_po_boxes = false, force_nominative = false)
16
- pairs = []
17
- POSTAL_CODES.each do |postal_code, p|
18
- # Skip P.O. boxes
19
- next if (include_po_boxes == false) && (p['is_po_box'] == true)
20
- # Retun the dative form of the locale by default
21
- pairs << if p['dative'].nil? || force_nominative == true
22
- { postal_code: postal_code, locale: p['locale'] }
23
- else
24
- { postal_code: postal_code, locale: p['dative'] }
25
- end
26
- end
27
- pairs
28
- end
29
-
30
- # Find the name of locale (city/town/village) by postal code
31
- #
32
- # @param [Integer, String] postal_code The postal code
33
- # @param [Boolean] force_nominative Display locale name in nomative form
34
- # @return [String] description of returned object
35
- def locale_by_postal_code(postal_code, force_nominative = false)
36
- postal_code = postal_code.to_i
37
- postal_code_hash = POSTAL_CODES[postal_code]
38
- unless postal_code_hash.nil?
39
- if postal_code_hash['dative'].nil? || force_nominative == true
40
- return postal_code_hash['locale']
41
- end
42
- postal_code_hash['dative']
43
- end
44
- end
45
- end
46
-
47
- # The Kennitala Class
48
- class Kennitala
49
- def initialize(kt_string)
50
- unless kt_string.class == String
51
- raise ArgumentError, 'Kennitala needs to be provided as a string'
52
- end
53
- sanitised_kt = sanitize(kt_string)
54
- raise ArgumentError, 'Kennitala is invalid' if sanitised_kt.nil?
55
- @value = sanitised_kt
56
- end
57
-
58
- # Get the type of entity - If it is a person or an organization
59
- #
60
- # @return [String] Either 'person' or 'company'
61
- def entity_type
62
- date_integer = @value[0, 2].to_i
63
- return 'person' if date_integer < 32
64
- return 'company' if (date_integer > 40) && (date_integer < 71)
65
- false
66
- end
67
-
68
- # Check if the entity is a company
69
- #
70
- # @return [Boolean]
71
- def company?
72
- date_integer = @value[0, 2].to_i
73
- return true if (date_integer > 40) && (date_integer < 71)
74
- false
75
- end
76
-
77
- # Check if the entity is a person
78
- #
79
- # @return [Type] description of returned object
80
- def person?
81
- date_integer = @value[0, 2].to_i
82
- return true if date_integer < 32
83
- false
84
- end
85
-
86
- # Get the year of birth or registration
87
- #
88
- # @return [Fixnum]
89
- def year
90
- century = (10 + @value[9].to_i) * 100
91
- year = @value[4, 2].to_i
92
- return century + year if (1800..1900).cover?(century)
93
- return 2000 + year if century == 1000
94
- end
95
-
96
- # Get the day of the month of birth or registration
97
- #
98
- # @return [Fixnum]
99
- def day
100
- date_integer = @value[0, 2].to_i
101
- return @value[0, 2].to_i if date_integer < 32
102
- return @value[0, 2].to_i - 40 if (date_integer > 40) && (date_integer < 71)
103
- end
104
-
105
- # Get a numeric representation of the month of birth or registration
106
- #
107
- # @return [Fixnum]
108
- def month
109
- @value[2, 2].to_i
110
- end
111
-
112
- # Get the age of entity in years. Useful when dealing with age restrictions.
113
- #
114
- # @return [Fixnum]
115
- def age
116
- year_diff = Date.today.year - to_date.year
117
- month_diff = Date.today.month - to_date.month
118
- day_diff = Date.today.month - to_date.month
119
-
120
- return year_diff -= 1 if month_diff < 0 || (month_diff == 0 && day_diff < 0)
121
- year_diff
122
- end
123
-
124
- # Cast the kennitala to a Date object
125
- #
126
- # @return [Date]
127
- def to_date
128
- Date.new(year, month, day)
129
- end
130
-
131
- # Cast the kennitala to a String object
132
- #
133
- # @return [Date]
134
- def to_s
135
- @value.to_s
136
- end
137
-
138
- private
139
-
140
- # Sanitize the kennitala
141
- #
142
- # @param [String] kt_string Unsanitised string representing a kennitala
143
- # @return [String] Sanitized kennitala
144
- def sanitize(kt_string)
145
- sanitized_kt = kt_string.gsub(/\D/, '')
146
- checks = check_checksum(sanitized_kt)
147
- return sanitized_kt if (/\A\d{10}\z/ =~ sanitized_kt) && (checks == true)
148
- end
149
-
150
- # Calculate the checksum
151
- #
152
- # @param [String] kt_string Sanitized kennitala
153
- # @return [Fixnum] The checksum
154
- def checksum(kt_string)
155
- checksum = 0
156
- multipliers = [3, 2, 7, 6, 5, 4, 3, 2]
157
- multipliers.each_with_index do |multiplier, index|
158
- checksum += multiplier * kt_string[index].to_i
159
- end
160
- checksum
161
- end
162
-
163
- # Calculate remainder and check validity of the check digit
164
- #
165
- # @param [String] kt_string Sanitized kennitala
166
- # @return [Boolean, nil] true on success, nil if the check digit is invalid
167
- def check_checksum(kt_string)
168
- remainder = checksum(kt_string).modulo(11)
169
-
170
- # A kennitala with a remainder of 10 is always considered to be invalid
171
- return nil if remainder == 10
172
-
173
- # The check digit should be 11 minus the remainder,
174
- # unless the remainder is 0, then the theck digit becomes 0.
175
- expected_check_digit = 11 - remainder
176
- expected_check_digit = 0 if remainder == 0
177
-
178
- actual_check_digit = kt_string[8].to_i
179
-
180
- return true if expected_check_digit == actual_check_digit
181
- end
182
- end
5
+ require 'iceland/postal_codes'
6
+ require 'iceland/kennitala'
7
+ require 'iceland/kennitala_string'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: iceland
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stefan Vignir
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-05-13 00:00:00.000000000 Z
11
+ date: 2016-05-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -86,6 +86,9 @@ files:
86
86
  - bin/setup
87
87
  - iceland.gemspec
88
88
  - lib/iceland.rb
89
+ - lib/iceland/kennitala.rb
90
+ - lib/iceland/kennitala_string.rb
91
+ - lib/iceland/postal_codes.rb
89
92
  - lib/iceland/version.rb
90
93
  - lib/postcodes.yml
91
94
  homepage: https://github.com/stefanvignir/iceland_gem