-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathluhn.go
180 lines (147 loc) · 3.54 KB
/
luhn.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
package personnummer
import (
"errors"
"fmt"
"regexp"
"strconv"
"strings"
)
// Divider represents the divider between birth date and control digits.
type Divider string
const (
DividerPlus Divider = "+"
DividerMinus Divider = "-"
DividerNone Divider = ""
)
// Parsed represents a parsed string. The fields are named as date parts but may
// be of other types in case of an organisation number or coordination number.
type Parsed struct {
Century int
Year int
Month int
Day int
Serial int
ControlDigit *int
Divider Divider
}
// nolint: gochecknoglobal
var validFormatRe = regexp.MustCompile(`^(\d{2})?(\d{2})(\d{2})(\d{2})([-+])?(\d{3})(\d)?$`)
// Parse will parse a string and returned a pointer to a Parsed type. If the
// string passed isn't in a valid format an error will be returned.
func Parse(input string) (*Parsed, error) {
matches := validFormatRe.FindStringSubmatch(input)
if len(matches) != 8 {
return nil, errors.New("invalid format")
}
var (
century, _ = strconv.Atoi(matches[1])
year, _ = strconv.Atoi(matches[2])
month, _ = strconv.Atoi(matches[3])
day, _ = strconv.Atoi(matches[4])
serial, _ = strconv.Atoi(matches[6])
divider = Divider(strings.ToUpper(matches[5]))
)
p := &Parsed{
Year: year,
Month: month,
Day: day,
Serial: serial,
Divider: divider,
}
if century > 0 {
p.Century = century * 100
}
if p.Divider == DividerNone {
p.Divider = DividerMinus
}
if cd, err := strconv.Atoi(matches[7]); err == nil {
p.ControlDigit = &cd
}
if p.ControlDigit == nil {
cd := p.LuhnControlDigit(p.LuhnChecksum())
p.ControlDigit = &cd
}
return p, nil
}
// LuhnCHecksum calculates the sum of the parsed digits with the Luhn algorithm.
func (p *Parsed) LuhnChecksum() int {
var (
sum = 0
digits = fmt.Sprintf("%02d%02d%02d%03d", p.Year, p.Month, p.Day, p.Serial)
)
for i := range digits {
digit, err := strconv.Atoi(string(digits[i]))
if err != nil {
panic("invalid luhn iteration value")
}
if i%2 == 0 {
digit *= 2
}
if digit > 9 {
digit -= 9
}
sum += digit
}
return sum
}
// LuhnControlDigit calculates the control digit based on a checksum.
func (p *Parsed) LuhnControlDigit(cs int) int {
checksum := 10 - (cs % 10)
if checksum == 10 {
return 0
}
return checksum
}
// Valid returns if a parsed string is valid, that is if the given control digit
// matches the checksum of the digits.
func (p *Parsed) Valid() bool {
var (
controlDigit = p.LuhnControlDigit(p.LuhnChecksum())
cd = controlDigit
)
if p.ControlDigit != nil {
cd = *p.ControlDigit
}
return controlDigit == cd
}
// ValidPerson returns if a parsed string is valid if validated as a private
// person.
func (p *Parsed) ValidPerson() bool {
person, err := NewPersonFromParsed(p)
if err != nil {
return false
}
return person.Valid()
}
// ValidOrganization returns if a parsed string is valid if validated as an
// organization.
func (p *Parsed) ValidOrganization() bool {
org, err := NewOrganizationFromParsed(p)
if err != nil {
return false
}
return org.Valid()
}
// stringFromInterface returns the string value from an interface.
func stringFromInterface(input interface{}) string {
var nr string
switch v := input.(type) {
case string:
nr = v
case []byte:
nr = string(v)
case int:
nr = strconv.Itoa(v)
case int32:
nr = strconv.Itoa(int(v))
case int64:
nr = strconv.Itoa(int(v))
case float32:
nr = strconv.Itoa(int(v))
case float64:
nr = strconv.Itoa(int(v))
default:
nr = ""
}
return nr
}