tin_valid 0.1.1 → 1.1.0
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 +4 -4
- data/.rspec +0 -1
- data/.rubocop.yml +5 -0
- data/CHANGELOG.md +27 -1
- data/README.md +81 -2
- data/lib/tin_valid/austria_tin.rb +1 -0
- data/lib/tin_valid/belgium_tin.rb +11 -15
- data/lib/tin_valid/bulgaria_tin.rb +10 -16
- data/lib/tin_valid/croatia_tin.rb +7 -1
- data/lib/tin_valid/cyprus_tin.rb +5 -2
- data/lib/tin_valid/czechia_tin.rb +24 -23
- data/lib/tin_valid/denmark_tin.rb +10 -8
- data/lib/tin_valid/estonia_tin.rb +7 -8
- data/lib/tin_valid/finland_tin.rb +71 -0
- data/lib/tin_valid/france_tin.rb +28 -0
- data/lib/tin_valid/germany_tin.rb +16 -5
- data/lib/tin_valid/greece_tin.rb +10 -2
- data/lib/tin_valid/helpers.rb +14 -0
- data/lib/tin_valid/hungary_tin.rb +7 -3
- data/lib/tin_valid/ireland_tin.rb +7 -1
- data/lib/tin_valid/italy_tin.rb +161 -0
- data/lib/tin_valid/latvia_tin.rb +63 -0
- data/lib/tin_valid/lithuania_tin.rb +75 -0
- data/lib/tin_valid/luxembourg_tin.rb +106 -0
- data/lib/tin_valid/malta_tin.rb +51 -0
- data/lib/tin_valid/netherlands_tin.rb +36 -0
- data/lib/tin_valid/poland_tin.rb +79 -0
- data/lib/tin_valid/portugal_tin.rb +46 -0
- data/lib/tin_valid/romania_tin.rb +83 -0
- data/lib/tin_valid/slovakia_tin.rb +67 -0
- data/lib/tin_valid/slovenia_tin.rb +38 -0
- data/lib/tin_valid/spain_tin.rb +51 -0
- data/lib/tin_valid/sweden_tin.rb +7 -9
- data/lib/tin_valid/united_kingdom_tin.rb +32 -0
- data/lib/tin_valid/version.rb +1 -1
- data/lib/tin_valid.rb +16 -0
- metadata +18 -2
| @@ -1,7 +1,13 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 3 | 
             
            module TinValid
         | 
| 4 | 
            -
              class IrelandTin | 
| 4 | 
            +
              class IrelandTin
         | 
| 5 | 
            +
                def initialize(tin:)
         | 
| 6 | 
            +
                  @tin = tin
         | 
| 7 | 
            +
                end
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                attr_reader :tin
         | 
| 10 | 
            +
             | 
| 5 11 | 
             
                def valid?
         | 
| 6 12 | 
             
                  /\A[0-9]{7}[A-W][A-IW]?\z/.match?(tin) &&
         | 
| 7 13 | 
             
                    tin.chars[7] == check
         | 
| @@ -0,0 +1,161 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module TinValid
         | 
| 4 | 
            +
              # rubocop:disable Metrics/ClassLength
         | 
| 5 | 
            +
              class ItalyTin
         | 
| 6 | 
            +
                include TinValid::Helpers
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                def initialize(tin:, birth_date: nil)
         | 
| 9 | 
            +
                  @tin = tin
         | 
| 10 | 
            +
                  @birth_date = birth_date
         | 
| 11 | 
            +
                end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                attr_reader :tin, :birth_date
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                def valid?
         | 
| 16 | 
            +
                  match = MATCHER.match(tin)
         | 
| 17 | 
            +
                  return false unless match
         | 
| 18 | 
            +
                  return false unless valid_date?(match)
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                  tin[-1] == check
         | 
| 21 | 
            +
                end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                private
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                def valid_date?(match)
         | 
| 26 | 
            +
                  # C7,C8: Two last digits of a year.
         | 
| 27 | 
            +
                  year = replacement_to_i(match[:year])
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                  # C9: A letter representing a month;
         | 
| 30 | 
            +
                  month = MONTH_LETTERS.fetch(match[:month])
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                  # C10,C11: Day of month (in the range 1...31 for men)
         | 
| 33 | 
            +
                  # or day of month + 40 (in the range 41...71 for women).
         | 
| 34 | 
            +
                  day = replacement_to_i(match[:day])
         | 
| 35 | 
            +
                  day -= 40 if day > 40
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                  if birth_date
         | 
| 38 | 
            +
                    birth_date == date("#{birth_century}#{year}", month, day)
         | 
| 39 | 
            +
                  else
         | 
| 40 | 
            +
                    date("19#{year}", month, day) || date("20#{year}", month, day)
         | 
| 41 | 
            +
                  end
         | 
| 42 | 
            +
                end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                def check
         | 
| 45 | 
            +
                  # 1. Each of the first fifteen characters,depending on its relevant
         | 
| 46 | 
            +
                  # position (even or odd), is converted into a numeric value,according to
         | 
| 47 | 
            +
                  # correspondence shown in the tables below:
         | 
| 48 | 
            +
                  sum = tin[..14].chars.each.with_index(1).sum do |char, index|
         | 
| 49 | 
            +
                    index.even? ? even_number(char) : odd_number(char)
         | 
| 50 | 
            +
                  end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                  # 2. The numerical values thus determined are added together and their sum
         | 
| 53 | 
            +
                  # is divided by 26. The check character (C16) is obtained by converting
         | 
| 54 | 
            +
                  # the remainder of the division in the corresponding alphabetic character
         | 
| 55 | 
            +
                  # according to the table below:
         | 
| 56 | 
            +
                  ((sum % 26) + 65).chr
         | 
| 57 | 
            +
                end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                def birth_century = birth_date.strftime("%Y")[..1]
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                def replacement_to_i(string)
         | 
| 62 | 
            +
                  string.chars.map { NUMERICAL_REPLACEMENTS.fetch(_1, _1) }.join.to_i
         | 
| 63 | 
            +
                end
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                MATCHER = %r{
         | 
| 66 | 
            +
                  \A
         | 
| 67 | 
            +
                  [A-Z]{6}
         | 
| 68 | 
            +
                  (?<year>[0-9LMNPQRSTUV]{2})
         | 
| 69 | 
            +
                  (?<month>[ABCDEHLMPRST])
         | 
| 70 | 
            +
                  (?<day>[0-7LMNPQRST][0-9LMNPQRSTUV])
         | 
| 71 | 
            +
                  [A-Z]
         | 
| 72 | 
            +
                  [0-9LMNPQRSTUV]{3}
         | 
| 73 | 
            +
                  [A-Z]
         | 
| 74 | 
            +
                  \z
         | 
| 75 | 
            +
                }x
         | 
| 76 | 
            +
                private_constant :MATCHER
         | 
| 77 | 
            +
             | 
| 78 | 
            +
                # C9: A letter representing a month; the letter can only take the values:
         | 
| 79 | 
            +
                MONTH_LETTERS = {
         | 
| 80 | 
            +
                  "A" => 1,
         | 
| 81 | 
            +
                  "B" => 2,
         | 
| 82 | 
            +
                  "C" => 3,
         | 
| 83 | 
            +
                  "D" => 4,
         | 
| 84 | 
            +
                  "E" => 5,
         | 
| 85 | 
            +
                  "H" => 6,
         | 
| 86 | 
            +
                  "L" => 7,
         | 
| 87 | 
            +
                  "M" => 8,
         | 
| 88 | 
            +
                  "P" => 9,
         | 
| 89 | 
            +
                  "R" => 10,
         | 
| 90 | 
            +
                  "S" => 11,
         | 
| 91 | 
            +
                  "T" => 12
         | 
| 92 | 
            +
                }.freeze
         | 
| 93 | 
            +
                private_constant :MONTH_LETTERS
         | 
| 94 | 
            +
             | 
| 95 | 
            +
                NUMERICAL_REPLACEMENTS = {
         | 
| 96 | 
            +
                  "L" => 0,
         | 
| 97 | 
            +
                  "M" => 1,
         | 
| 98 | 
            +
                  "N" => 2,
         | 
| 99 | 
            +
                  "P" => 3,
         | 
| 100 | 
            +
                  "Q" => 4,
         | 
| 101 | 
            +
                  "R" => 5,
         | 
| 102 | 
            +
                  "S" => 6,
         | 
| 103 | 
            +
                  "T" => 7,
         | 
| 104 | 
            +
                  "U" => 8,
         | 
| 105 | 
            +
                  "V" => 9
         | 
| 106 | 
            +
                }.freeze
         | 
| 107 | 
            +
                private_constant :NUMERICAL_REPLACEMENTS
         | 
| 108 | 
            +
             | 
| 109 | 
            +
                def even_number(character)
         | 
| 110 | 
            +
                  case character
         | 
| 111 | 
            +
                  in "0".."9" then character.to_i
         | 
| 112 | 
            +
                  in "A".."Z" then character.ord - 65
         | 
| 113 | 
            +
                  end
         | 
| 114 | 
            +
                end
         | 
| 115 | 
            +
             | 
| 116 | 
            +
                def odd_number(character)
         | 
| 117 | 
            +
                  ODD_NUMBER_TABLE.fetch(character)
         | 
| 118 | 
            +
                end
         | 
| 119 | 
            +
             | 
| 120 | 
            +
                ODD_NUMBER_TABLE = {
         | 
| 121 | 
            +
                  "A" => 1,
         | 
| 122 | 
            +
                  "0" => 1,
         | 
| 123 | 
            +
                  "B" => 0,
         | 
| 124 | 
            +
                  "1" => 0,
         | 
| 125 | 
            +
                  "C" => 5,
         | 
| 126 | 
            +
                  "2" => 5,
         | 
| 127 | 
            +
                  "D" => 7,
         | 
| 128 | 
            +
                  "3" => 7,
         | 
| 129 | 
            +
                  "E" => 9,
         | 
| 130 | 
            +
                  "4" => 9,
         | 
| 131 | 
            +
                  "F" => 13,
         | 
| 132 | 
            +
                  "5" => 13,
         | 
| 133 | 
            +
                  "G" => 15,
         | 
| 134 | 
            +
                  "6" => 15,
         | 
| 135 | 
            +
                  "H" => 17,
         | 
| 136 | 
            +
                  "7" => 17,
         | 
| 137 | 
            +
                  "I" => 19,
         | 
| 138 | 
            +
                  "8" => 19,
         | 
| 139 | 
            +
                  "J" => 21,
         | 
| 140 | 
            +
                  "9" => 21,
         | 
| 141 | 
            +
                  "K" => 2,
         | 
| 142 | 
            +
                  "L" => 4,
         | 
| 143 | 
            +
                  "M" => 18,
         | 
| 144 | 
            +
                  "N" => 20,
         | 
| 145 | 
            +
                  "O" => 11,
         | 
| 146 | 
            +
                  "P" => 3,
         | 
| 147 | 
            +
                  "Q" => 6,
         | 
| 148 | 
            +
                  "R" => 8,
         | 
| 149 | 
            +
                  "S" => 12,
         | 
| 150 | 
            +
                  "T" => 14,
         | 
| 151 | 
            +
                  "U" => 16,
         | 
| 152 | 
            +
                  "V" => 10,
         | 
| 153 | 
            +
                  "W" => 22,
         | 
| 154 | 
            +
                  "X" => 25,
         | 
| 155 | 
            +
                  "Y" => 24,
         | 
| 156 | 
            +
                  "Z" => 23
         | 
| 157 | 
            +
                }.freeze
         | 
| 158 | 
            +
                private_constant :ODD_NUMBER_TABLE
         | 
| 159 | 
            +
              end
         | 
| 160 | 
            +
              # rubocop:enable Metrics/ClassLength
         | 
| 161 | 
            +
            end
         | 
| @@ -0,0 +1,63 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module TinValid
         | 
| 4 | 
            +
              class LatviaTin
         | 
| 5 | 
            +
                def initialize(tin:, birth_date: nil)
         | 
| 6 | 
            +
                  @tin = tin
         | 
| 7 | 
            +
                  @birth_date = birth_date
         | 
| 8 | 
            +
                end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                attr_reader :tin, :birth_date
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                def valid? = valid_v1? || valid_v2?
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                private
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                MATCHER_V1 = %r{
         | 
| 17 | 
            +
                  \A
         | 
| 18 | 
            +
                  (?<day>[0-3][0-9])
         | 
| 19 | 
            +
                  (?<month>[0-1][0-9])
         | 
| 20 | 
            +
                  (?<year>[0-9]{2})
         | 
| 21 | 
            +
                  (?<century>[0-2])
         | 
| 22 | 
            +
                  -?
         | 
| 23 | 
            +
                  [0-9]{4}
         | 
| 24 | 
            +
                  \z
         | 
| 25 | 
            +
                }x
         | 
| 26 | 
            +
                private_constant :MATCHER_V1
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                MATCHER_V2 = %r{
         | 
| 29 | 
            +
                  \A
         | 
| 30 | 
            +
                  32
         | 
| 31 | 
            +
                  [0-9]{4}
         | 
| 32 | 
            +
                  -?
         | 
| 33 | 
            +
                  [0-9]{5}
         | 
| 34 | 
            +
                  \z
         | 
| 35 | 
            +
                }x
         | 
| 36 | 
            +
                private_constant :MATCHER_V2
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                def valid_v1?
         | 
| 39 | 
            +
                  match = MATCHER_V1.match(tin)
         | 
| 40 | 
            +
                  return false unless match
         | 
| 41 | 
            +
                  return false if tin == "00000000000"
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                  if birth_date
         | 
| 44 | 
            +
                    tin[..5] == birth_date.strftime("%d%m%y") &&
         | 
| 45 | 
            +
                      tin[6] == birth_century_digit
         | 
| 46 | 
            +
                  else
         | 
| 47 | 
            +
                    true
         | 
| 48 | 
            +
                  end
         | 
| 49 | 
            +
                end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                def valid_v2?
         | 
| 52 | 
            +
                  MATCHER_V2.match?(tin)
         | 
| 53 | 
            +
                end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                def birth_century_digit
         | 
| 56 | 
            +
                  case birth_date.year
         | 
| 57 | 
            +
                  when 1800..1899 then "0"
         | 
| 58 | 
            +
                  when 1900..1999 then "1"
         | 
| 59 | 
            +
                  when 2000.. then "2"
         | 
| 60 | 
            +
                  end
         | 
| 61 | 
            +
                end
         | 
| 62 | 
            +
              end
         | 
| 63 | 
            +
            end
         | 
| @@ -0,0 +1,75 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module TinValid
         | 
| 4 | 
            +
              class LithuaniaTin
         | 
| 5 | 
            +
                def initialize(tin:, birth_date: nil)
         | 
| 6 | 
            +
                  @tin = tin
         | 
| 7 | 
            +
                  @birth_date = birth_date
         | 
| 8 | 
            +
                end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                attr_reader :tin, :birth_date
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                def valid?
         | 
| 13 | 
            +
                  matcher = MATCHER.match(tin)
         | 
| 14 | 
            +
                  return false unless matcher
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                  if birth_date && (matcher[:birth_date] != birth_date.strftime("%y%m%d"))
         | 
| 17 | 
            +
                    return false
         | 
| 18 | 
            +
                  end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                  tin[-1].to_i == check
         | 
| 21 | 
            +
                end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                private
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                MATCHER = %r{
         | 
| 26 | 
            +
                  [1-6]
         | 
| 27 | 
            +
                  (?<birth_date>
         | 
| 28 | 
            +
                    [0-9]{2}
         | 
| 29 | 
            +
                    [0-1][0-9]
         | 
| 30 | 
            +
                    [0-3][0-9]
         | 
| 31 | 
            +
                  )
         | 
| 32 | 
            +
                  [0-9]{4}
         | 
| 33 | 
            +
                }x
         | 
| 34 | 
            +
                private_constant :MATCHER
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                # rubocop:disable Metrics/AbcSize
         | 
| 37 | 
            +
                # rubocop:disable Metrics/MethodLength
         | 
| 38 | 
            +
                def check
         | 
| 39 | 
            +
                  # 1. Multiply the values of each position by the corresponding weight:
         | 
| 40 | 
            +
                  weights =
         | 
| 41 | 
            +
                    [1, 2, 3, 4, 5, 6, 7, 8, 9, 1]
         | 
| 42 | 
            +
                      .each_with_index
         | 
| 43 | 
            +
                      .map { |weight, index| weight * tin[index].to_i }
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                  # 2. Add up the results of the above multiplications;
         | 
| 46 | 
            +
                  sum = weights.sum
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                  # 3. Get modulo 11 of the result of the previous addition;
         | 
| 49 | 
            +
                  remainder = sum % 11
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                  # 4. C11 = remainder if remainder is not 10;
         | 
| 52 | 
            +
                  return remainder if remainder != 10
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                  # 5. If remainder is 10, calculate a new check digit with over
         | 
| 55 | 
            +
                  # corresponding weight:
         | 
| 56 | 
            +
                  weights =
         | 
| 57 | 
            +
                    [3, 4, 5, 6, 7, 8, 9, 1, 2, 3]
         | 
| 58 | 
            +
                      .each_with_index
         | 
| 59 | 
            +
                      .map { |weight, index| weight * tin[index].to_i }
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                  # 6. Add up the results of the above multiplications;
         | 
| 62 | 
            +
                  sum = weights.sum
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                  # 7. Get modulo 11 of the result of the previous addition;
         | 
| 65 | 
            +
                  remainder = sum % 11
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                  # 8. C11 = remainder if remainder is not 10; if remainder is 10, C11 = 0.
         | 
| 68 | 
            +
                  return remainder if remainder != 10
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                  0
         | 
| 71 | 
            +
                end
         | 
| 72 | 
            +
                # rubocop:enable Metrics/AbcSize
         | 
| 73 | 
            +
                # rubocop:enable Metrics/MethodLength
         | 
| 74 | 
            +
              end
         | 
| 75 | 
            +
            end
         | 
| @@ -0,0 +1,106 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module TinValid
         | 
| 4 | 
            +
              class LuxembourgTin
         | 
| 5 | 
            +
                include TinValid::Helpers
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                def initialize(tin:, birth_date: nil)
         | 
| 8 | 
            +
                  @tin = tin
         | 
| 9 | 
            +
                  @birth_date = birth_date
         | 
| 10 | 
            +
                end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                attr_reader :tin, :birth_date
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                def valid?
         | 
| 15 | 
            +
                  match = MATCHER.match(tin)
         | 
| 16 | 
            +
                  return false unless match
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                  tin_date = date(match[:year], match[:month], match[:day])
         | 
| 19 | 
            +
                  return false unless tin_date
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                  return false if birth_date && birth_date != tin_date
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                  check1? && check2?
         | 
| 24 | 
            +
                end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                private
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                MATCHER = %r{
         | 
| 29 | 
            +
                  \A
         | 
| 30 | 
            +
                  (?<year>[0-9]{4})
         | 
| 31 | 
            +
                  (?<month>[0-1][0-9])
         | 
| 32 | 
            +
                  (?<day>[0-3][0-9])
         | 
| 33 | 
            +
                  [0-9]{5}
         | 
| 34 | 
            +
                  \z
         | 
| 35 | 
            +
                }x
         | 
| 36 | 
            +
                private_constant :MATCHER
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                def check1?
         | 
| 39 | 
            +
                  # 1. Multiply the values of each position by the corresponding weight:
         | 
| 40 | 
            +
                  weights =
         | 
| 41 | 
            +
                    [2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1]
         | 
| 42 | 
            +
                      .each_with_index
         | 
| 43 | 
            +
                      .map { |weight, index| weight * tin[index].to_i }
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                  # 2. If the product of a doubling operation is > 9, sum the digits of the
         | 
| 46 | 
            +
                  # product;
         | 
| 47 | 
            +
                  weights = weights.map { _1 > 9 ? _1.to_s.chars.sum(&:to_i) : _1 }
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                  # 3. Add up the results of the above multiplications;
         | 
| 50 | 
            +
                  sum = weights.sum
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                  # 4. Get modulo 10 of the result of the previous addition;
         | 
| 53 | 
            +
                  remainder = sum % 10
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                  # 5. If remainder = 0, C12 is valid. Otherwise the TIN is not valid.
         | 
| 56 | 
            +
                  remainder == 0
         | 
| 57 | 
            +
                end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                def check2?
         | 
| 60 | 
            +
                  # 1. Create an array n containing the individual C1 to C11 and C13 of the
         | 
| 61 | 
            +
                  # TIN (where ni = the value of the corresponding C), taken from right to
         | 
| 62 | 
            +
                  # left:
         | 
| 63 | 
            +
                  array = [*tin[..10].chars, tin[12]].map(&:to_i).reverse
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                  # 2. Initialize the checksum c to 0;
         | 
| 66 | 
            +
                  # 3. For each index i of the array n, starting at 0, replace c by
         | 
| 67 | 
            +
                  # d(c,p(i mod 8, ni)), according to the following tables:
         | 
| 68 | 
            +
                  checksum = array.each_with_index.inject(0) do |c, (ni, index)|
         | 
| 69 | 
            +
                    table_d(c, table_p(index % 8, ni))
         | 
| 70 | 
            +
                  end
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                  # 4. Check digit c if c = 0, C13 is valid. Otherwise, the TIN is not
         | 
| 73 | 
            +
                  # valid.
         | 
| 74 | 
            +
                  checksum == 0
         | 
| 75 | 
            +
                end
         | 
| 76 | 
            +
             | 
| 77 | 
            +
                def table_d(number_i, number_j) = TABLE_D.dig(number_i, number_j)
         | 
| 78 | 
            +
                def table_p(number_m, number_n) = TABLE_P.dig(number_m, number_n)
         | 
| 79 | 
            +
             | 
| 80 | 
            +
                TABLE_D = [
         | 
| 81 | 
            +
                  [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
         | 
| 82 | 
            +
                  [1, 2, 3, 4, 0, 6, 7, 8, 9, 5],
         | 
| 83 | 
            +
                  [2, 3, 4, 0, 1, 7, 8, 9, 5, 6],
         | 
| 84 | 
            +
                  [3, 4, 0, 1, 2, 8, 9, 5, 6, 7],
         | 
| 85 | 
            +
                  [4, 0, 1, 2, 3, 9, 5, 6, 7, 8],
         | 
| 86 | 
            +
                  [5, 9, 8, 7, 6, 0, 4, 3, 2, 1],
         | 
| 87 | 
            +
                  [6, 5, 9, 8, 7, 1, 0, 4, 3, 2],
         | 
| 88 | 
            +
                  [7, 6, 5, 9, 8, 2, 1, 0, 4, 3],
         | 
| 89 | 
            +
                  [8, 7, 6, 5, 9, 3, 2, 1, 0, 4],
         | 
| 90 | 
            +
                  [9, 8, 7, 6, 5, 4, 3, 2, 1, 0],
         | 
| 91 | 
            +
                ].freeze
         | 
| 92 | 
            +
                private_constant :TABLE_D
         | 
| 93 | 
            +
             | 
| 94 | 
            +
                TABLE_P = [
         | 
| 95 | 
            +
                  [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
         | 
| 96 | 
            +
                  [1, 5, 7, 6, 2, 8, 3, 0, 9, 4],
         | 
| 97 | 
            +
                  [5, 8, 0, 3, 7, 9, 6, 1, 4, 2],
         | 
| 98 | 
            +
                  [8, 9, 1, 6, 0, 5, 3, 5, 2, 7],
         | 
| 99 | 
            +
                  [9, 4, 5, 3, 1, 2, 6, 8, 7, 0],
         | 
| 100 | 
            +
                  [4, 2, 8, 6, 5, 7, 3, 9, 0, 1],
         | 
| 101 | 
            +
                  [2, 7, 9, 3, 8, 0, 6, 4, 1, 5],
         | 
| 102 | 
            +
                  [7, 0, 4, 6, 9, 1, 3, 2, 5, 8],
         | 
| 103 | 
            +
                ].freeze
         | 
| 104 | 
            +
                private_constant :TABLE_P
         | 
| 105 | 
            +
              end
         | 
| 106 | 
            +
            end
         | 
| @@ -0,0 +1,51 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module TinValid
         | 
| 4 | 
            +
              class MaltaTin
         | 
| 5 | 
            +
                def initialize(tin:)
         | 
| 6 | 
            +
                  @tin = tin
         | 
| 7 | 
            +
                end
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                attr_reader :tin
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                def valid? = valid_format_1? || valid_format_2?
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                private
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                MATCHER_V1 = %r{
         | 
| 16 | 
            +
                  \A
         | 
| 17 | 
            +
                  (?<number>
         | 
| 18 | 
            +
                    (?<part1>[0-9]{0,5})
         | 
| 19 | 
            +
                    [0-9]{1,2}
         | 
| 20 | 
            +
                  )
         | 
| 21 | 
            +
                  (?<letter>[MGAPLHBZ])
         | 
| 22 | 
            +
                  \z
         | 
| 23 | 
            +
                }x
         | 
| 24 | 
            +
                private_constant :MATCHER_V1
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                MATCHER_V2 = %r{
         | 
| 27 | 
            +
                  \A
         | 
| 28 | 
            +
                  (11|22|33|44|55|66|77|88)
         | 
| 29 | 
            +
                  [0-9]{7}
         | 
| 30 | 
            +
                  \z
         | 
| 31 | 
            +
                }x
         | 
| 32 | 
            +
                private_constant :MATCHER_V2
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                def valid_format_1?
         | 
| 35 | 
            +
                  match = MATCHER_V1.match(tin)
         | 
| 36 | 
            +
                  return false unless match
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                  case match[:letter]
         | 
| 39 | 
            +
                  when "A", "P"
         | 
| 40 | 
            +
                    true
         | 
| 41 | 
            +
                  when "M", "G", "L", "H", "B", "Z"
         | 
| 42 | 
            +
                    (0..32_000).cover?(match[:part1].to_i) &&
         | 
| 43 | 
            +
                      match[:number].to_i != 0
         | 
| 44 | 
            +
                  end
         | 
| 45 | 
            +
                end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                def valid_format_2?
         | 
| 48 | 
            +
                  MATCHER_V2.match?(tin)
         | 
| 49 | 
            +
                end
         | 
| 50 | 
            +
              end
         | 
| 51 | 
            +
            end
         | 
| @@ -0,0 +1,36 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module TinValid
         | 
| 4 | 
            +
              class NetherlandsTin
         | 
| 5 | 
            +
                def initialize(tin:)
         | 
| 6 | 
            +
                  @tin = tin
         | 
| 7 | 
            +
                end
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                attr_reader :tin
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                def valid?
         | 
| 12 | 
            +
                  return false unless /\A[0-9]{9}\z/.match?(tin)
         | 
| 13 | 
            +
                  return false if tin == "000000000"
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                  tin[-1].to_i == check
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                private
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                def check
         | 
| 21 | 
            +
                  # 1. Multiply the values of each position by the corresponding weight:
         | 
| 22 | 
            +
                  multipliers =
         | 
| 23 | 
            +
                    9
         | 
| 24 | 
            +
                      .downto(2)
         | 
| 25 | 
            +
                      .each_with_index
         | 
| 26 | 
            +
                      .map { |multiplier, position| multiplier * tin[position].to_i }
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                  # 2. Add up the results of the above multiplications;
         | 
| 29 | 
            +
                  sum = multipliers.sum
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                  # 3. Get modulo 11 of the result of the previous addition;
         | 
| 32 | 
            +
                  # 4. Check digit = remainder (if remainder = 10, the TIN is not valid).
         | 
| 33 | 
            +
                  sum % 11
         | 
| 34 | 
            +
                end
         | 
| 35 | 
            +
              end
         | 
| 36 | 
            +
            end
         | 
| @@ -0,0 +1,79 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module TinValid
         | 
| 4 | 
            +
              class PolandTin
         | 
| 5 | 
            +
                include TinValid::Helpers
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                def initialize(tin:, birth_date: nil)
         | 
| 8 | 
            +
                  @tin = tin
         | 
| 9 | 
            +
                  @birth_date = birth_date
         | 
| 10 | 
            +
                end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                attr_reader :tin, :birth_date
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                def valid? = valid_v1? || valid_v2?
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                private
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                MATCHER_V1 = /\A[0-9]{10}\z/
         | 
| 19 | 
            +
                private_constant :MATCHER_V1
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                MATCHER_V2 = %r{
         | 
| 22 | 
            +
                  \A
         | 
| 23 | 
            +
                  (?<year>[0-9]{2})
         | 
| 24 | 
            +
                  (?<month>[0-9]{2})
         | 
| 25 | 
            +
                  (?<day>[0-3][0-9])
         | 
| 26 | 
            +
                  [0-9]{5}
         | 
| 27 | 
            +
                  \z
         | 
| 28 | 
            +
                }x
         | 
| 29 | 
            +
                private_constant :MATCHER_V2
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                def valid_v1?
         | 
| 32 | 
            +
                  return false unless MATCHER_V1.match?(tin)
         | 
| 33 | 
            +
                  return false if tin == "0000000000"
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                  tin[-1].to_i == check
         | 
| 36 | 
            +
                end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                def valid_v2?
         | 
| 39 | 
            +
                  match = MATCHER_V2.match(tin)
         | 
| 40 | 
            +
                  return false unless match
         | 
| 41 | 
            +
                  return false if tin == "00000000000"
         | 
| 42 | 
            +
                  return true unless birth_date
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                  tin_date = date(
         | 
| 45 | 
            +
                    "#{birth_century}#{match[:year]}",
         | 
| 46 | 
            +
                    match[:month].to_i - month_increase,
         | 
| 47 | 
            +
                    match[:day],
         | 
| 48 | 
            +
                  )
         | 
| 49 | 
            +
                  tin_date == birth_date
         | 
| 50 | 
            +
                end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                def check
         | 
| 53 | 
            +
                  # 1. Multiply the values of each position by the corresponding weight:
         | 
| 54 | 
            +
                  weights =
         | 
| 55 | 
            +
                    [6, 5, 7, 2, 3, 4, 5, 6, 7]
         | 
| 56 | 
            +
                      .each_with_index
         | 
| 57 | 
            +
                      .map { |weight, index| weight * tin[index].to_i }
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                  # 2. Add up the results of the above multiplications;
         | 
| 60 | 
            +
                  sum = weights.sum
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                  # 3. Get modulo 11 of the result of the previous addition;
         | 
| 63 | 
            +
                  # 4. Check digit = remainder (if remainder = 10, the TIN is not valid).
         | 
| 64 | 
            +
                  sum % 11
         | 
| 65 | 
            +
                end
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                def birth_century = birth_date.year.to_s[..1].to_i
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                def month_increase
         | 
| 70 | 
            +
                  case birth_century
         | 
| 71 | 
            +
                  when 18 then 80
         | 
| 72 | 
            +
                  when 20 then 20
         | 
| 73 | 
            +
                  when 21 then 40
         | 
| 74 | 
            +
                  when 22 then 60
         | 
| 75 | 
            +
                  else 0
         | 
| 76 | 
            +
                  end
         | 
| 77 | 
            +
                end
         | 
| 78 | 
            +
              end
         | 
| 79 | 
            +
            end
         | 
| @@ -0,0 +1,46 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module TinValid
         | 
| 4 | 
            +
              class PortugalTin
         | 
| 5 | 
            +
                def initialize(tin:)
         | 
| 6 | 
            +
                  @tin = tin
         | 
| 7 | 
            +
                end
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                attr_reader :tin
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                def valid?
         | 
| 12 | 
            +
                  return false unless /\A[0-9]{9}\z/.match?(tin)
         | 
| 13 | 
            +
                  return false if tin == "000000000"
         | 
| 14 | 
            +
                  return false if tin == "123456789"
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                  tin[-1].to_i == check
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                private
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                def check
         | 
| 22 | 
            +
                  # 1. Multiply the values of each position by the corresponding weight:
         | 
| 23 | 
            +
                  multipliers =
         | 
| 24 | 
            +
                    9
         | 
| 25 | 
            +
                      .downto(2)
         | 
| 26 | 
            +
                      .with_index
         | 
| 27 | 
            +
                      .map { |multiplier, index| multiplier * tin[index].to_i }
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                  # 2. Add up the results of the above multiplications;
         | 
| 30 | 
            +
                  sum = multipliers.sum
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                  # 3. Get modulo 11 of the result of the previous addition;
         | 
| 33 | 
            +
                  remainder = sum % 11
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                  # 4. Check digit = 11 - remainder:
         | 
| 36 | 
            +
                  digit = 11 - remainder
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                  # If check digit < = 9 then check digit is OK (11 – remainder);
         | 
| 39 | 
            +
                  return digit if digit <= 9
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                  # If check digit = 10 then check digit is 0 (zero);
         | 
| 42 | 
            +
                  # If check digit = 11 then check digit is 0 (zero).
         | 
| 43 | 
            +
                  0
         | 
| 44 | 
            +
                end
         | 
| 45 | 
            +
              end
         | 
| 46 | 
            +
            end
         |