-
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathpersonnummer.v
231 lines (179 loc) · 5.14 KB
/
personnummer.v
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
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
module personnummer
import time
import math
const err_invalid_number = error('Invalid swedish personal identity number')
const interim_letters = 'TRSUWXJKLMN'.runes()
// luhn will test if the given string is a valid luhn string.
fn luhn(str string) int {
mut sum := 0
for i, s in str {
mut v := s.ascii_str().int()
v *= 2 - (i % 2)
if v > 9 {
v -= 9
}
sum += v
}
return int(math.pow(math.ceil(math.round(sum) / 10) * 10 - sum, 1.0))
}
// validate_date if the input parameters are a valid date or not.
fn validate_date(year string, month string, day string) bool {
y := year.int()
m := month.int()
dd := day.int()
d := time.new(time.Time{
year: y
month: m
day: dd
})
return d.year == y && d.month == m && d.day == dd && dd > 0
}
// personnummer represents the personnummer struct.
struct Personnummer {
mut:
century string
full_year string
year string
month string
day string
sep string
num string
check string
}
// options represents the personnummer options.
pub struct Options {
pub:
allow_coordination_number bool = true
allow_interim_number bool
}
type Any = Options | string
// new parse a Swedish personal identity numbers and returns a new struct or a error.
pub fn new(args ...Any) !Personnummer {
return parse(...args) or { return personnummer.err_invalid_number }
}
// parse function will parse a Swedish personal identity numbers and returns a new struct or a error.
pub fn parse(args ...Any) !Personnummer {
pin := (args[0] as string).clone()
mut options := Options{}
if args.len > 1 {
options = args[1] as Options
}
mut p := Personnummer{}
p.parse(pin, options) or { return personnummer.err_invalid_number }
return p
}
// valid validates a Swedish personal identity number.
pub fn valid(pin string) bool {
p := parse(pin) or { return false }
return p.valid()
}
// format will format a Swedish personal identity number as one of
// the official formats, long format or a short format.
pub fn (p Personnummer) format(longFormat bool) string {
if longFormat {
return p.century + p.year + p.month + p.day + p.num + p.check
}
return p.year + p.month + p.day + p.sep + p.num + p.check
}
// is_female will check if a Swedish personal identity number is for a female.
pub fn (p Personnummer) is_female() bool {
return !p.is_male()
}
// is_male will check if a Swedish personal identity number is for a male.
pub fn (p Personnummer) is_male() bool {
sex_digit := p.num[2..3].int()
return sex_digit % 2 == 1
}
// is_coordination_number will check if a Swedish personal identity number
// is a coordination number or not.
pub fn (p Personnummer) is_coordination_number() bool {
return validate_date(p.full_year, p.month, (p.day.int() - 60).str())
}
// is_interim_number will check if a Swedish personal identity number
// is a interim number or not.
pub fn (p Personnummer) is_interim_number() bool {
return p.num[0] in personnummer.interim_letters
}
// get_date will return the date from a Swedish personal identity number
pub fn (p Personnummer) get_date() time.Time {
mut age_day := p.day
if p.is_coordination_number() {
age_day = (age_day.int() - 60).str()
}
return time.new(time.Time{
year: p.full_year.int()
month: p.month.int()
day: age_day.int()
})
}
// get_age will return the age from a Swedish personal identity number.
pub fn (p Personnummer) get_age() int {
date := p.get_date()
now := time.now()
if date.month > now.month {
return now.year - date.year - 1
}
if date.month == now.month && date.day > now.day {
return now.year - date.year - 1
}
return now.year - date.year
}
// parse a Swedish personal identity numbers and set struct properpties or return a error.
fn (mut p Personnummer) parse(input string, options Options) !bool {
mut pin := input
plus := pin.contains('+')
pin = pin.replace('+', '')
pin = pin.replace('-', '')
if pin.len == 12 {
p.century = pin[0..2]
p.year = pin[2..4]
p.month = pin[4..6]
p.day = pin[6..8]
p.num = pin[8..11]
p.check = pin[11..12]
} else if pin.len == 10 {
p.year = pin[0..2]
p.month = pin[2..4]
p.day = pin[4..6]
p.num = pin[6..9]
p.check = pin[9..10]
} else {
return personnummer.err_invalid_number
}
if p.num == '000' {
return personnummer.err_invalid_number
}
p.sep = '-'
now := time.now()
if p.century.len == 0 {
mut base_year := now.year
if plus {
p.sep = '+'
base_year -= 100
}
p.century = (base_year - (base_year - p.year.int()) % 100).str()[0..2]
} else {
if now.year - (p.century + p.year).int() < 100 {
p.sep = '-'
} else {
p.sep = '+'
}
}
p.full_year = p.century + p.year
if p.is_coordination_number() && !options.allow_coordination_number {
return personnummer.err_invalid_number
}
if p.is_interim_number() && !options.allow_interim_number {
return personnummer.err_invalid_number
}
return true
}
// valid validates a Swedish personal identity number.
pub fn (p Personnummer) valid() bool {
num := if p.is_interim_number() { '1' + p.num[1..] } else { p.num }
valid := luhn(p.year + p.month + p.day + num) == p.check.int()
if valid && validate_date(p.full_year, p.month, p.day) {
return true
}
return valid && p.is_coordination_number()
}