@remato/personal-code-to-birthday 1.0.0 β 1.1.1
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.
- package/README.md +5 -5
- package/package.json +9 -5
- package/src/index.test.ts +114 -0
- package/src/index.ts +33 -0
- package/src/parsers/denmark.ts +54 -0
- package/src/parsers/estoniaLithuania.ts +52 -0
- package/src/parsers/finland.ts +40 -0
- package/src/parsers/latvia.ts +48 -0
- package/src/parsers/norway.ts +65 -0
- package/src/parsers/poland.ts +37 -0
- package/src/parsers/sweden.ts +59 -0
- package/src/parsers/ukraine.ts +40 -0
- package/src/types.ts +5 -0
- package/src/utils.ts +9 -0
- package/tsconfig.json +1 -1
- package/index.test.ts +0 -79
- package/index.ts +0 -192
- package/prettier.config.js +0 -1
package/README.md
CHANGED
|
@@ -28,21 +28,21 @@ console.log(birthday) // outputs { day: 26, month: 9, year: 1993 }
|
|
|
28
28
|
- πΈπͺ Sweden
|
|
29
29
|
- π³π΄ Norway
|
|
30
30
|
- π©π° Denmark
|
|
31
|
+
- πΊπ¦ Ukraine
|
|
32
|
+
- π΅π± Poland
|
|
31
33
|
|
|
32
34
|
Countries we would like to support (PRs are welcome):
|
|
33
35
|
|
|
34
36
|
- Iceland
|
|
35
|
-
- South Africa
|
|
36
37
|
- Romania
|
|
37
38
|
- Hungary
|
|
38
39
|
- Bulgaria
|
|
40
|
+
- Moldova
|
|
41
|
+
- Azerbaijan
|
|
39
42
|
- North Macedonia
|
|
40
43
|
- Czech Republic
|
|
41
44
|
- Slovakia
|
|
42
|
-
- Poland
|
|
43
45
|
- South Korea
|
|
44
46
|
- Turkey
|
|
45
|
-
-
|
|
46
|
-
- Azerbaijan
|
|
47
|
+
- South Africa
|
|
47
48
|
- Russia
|
|
48
|
-
- Ukraine
|
package/package.json
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remato/personal-code-to-birthday",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "Converts personal identification codes from various countries into birthdate",
|
|
5
5
|
"license": "MIT",
|
|
6
|
-
"main": "index.ts",
|
|
6
|
+
"main": "src/index.ts",
|
|
7
|
+
"repository": {
|
|
8
|
+
"url": "https://github.com/rematocorp/personal-code-to-birthday"
|
|
9
|
+
},
|
|
7
10
|
"scripts": {
|
|
8
|
-
"build": "ncc build index.ts",
|
|
11
|
+
"build": "ncc build src/index.ts",
|
|
9
12
|
"test": "jest",
|
|
10
13
|
"prettier": "prettier --list-different \"**/*.ts\"",
|
|
11
14
|
"lint": "eslint \"**/*.ts\""
|
|
@@ -40,12 +43,13 @@
|
|
|
40
43
|
},
|
|
41
44
|
"collectCoverage": true,
|
|
42
45
|
"collectCoverageFrom": [
|
|
43
|
-
"
|
|
46
|
+
"src/**"
|
|
44
47
|
]
|
|
45
48
|
},
|
|
46
49
|
"eslintConfig": {
|
|
47
50
|
"extends": [
|
|
48
51
|
"@remato/eslint-config/typescript"
|
|
49
52
|
]
|
|
50
|
-
}
|
|
53
|
+
},
|
|
54
|
+
"prettier": "@remato/prettier-config"
|
|
51
55
|
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import personalCodeToBirthday from './index'
|
|
2
|
+
|
|
3
|
+
describe('valid codes', () => {
|
|
4
|
+
test('Estonia', () => {
|
|
5
|
+
expect(personalCodeToBirthday('39309262754')).toEqual({ day: 26, month: 9, year: 1993 })
|
|
6
|
+
expect(personalCodeToBirthday('49111012217')).toEqual({ day: 1, month: 11, year: 1991 })
|
|
7
|
+
expect(personalCodeToBirthday('30001010060')).toEqual({ day: 1, month: 1, year: 1900 })
|
|
8
|
+
expect(personalCodeToBirthday('62305060020')).toEqual({ day: 6, month: 5, year: 2023 })
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
test('Latvia', () => {
|
|
12
|
+
expect(personalCodeToBirthday('240860-28074')).toEqual({ day: 24, month: 8, year: 1960 })
|
|
13
|
+
expect(personalCodeToBirthday('071023-38935')).toEqual({ day: 7, month: 10, year: 2023 })
|
|
14
|
+
expect(personalCodeToBirthday('240860-20090')).toEqual({ day: 24, month: 8, year: 1960 })
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
test('Lithuania', () => {
|
|
18
|
+
expect(personalCodeToBirthday('39905280216')).toEqual({ day: 28, month: 5, year: 1999 })
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
test('Finland', () => {
|
|
22
|
+
expect(personalCodeToBirthday('101085-7001')).toEqual({ day: 10, month: 10, year: 1985 })
|
|
23
|
+
expect(personalCodeToBirthday('150752-308N')).toEqual({ day: 15, month: 7, year: 1952 })
|
|
24
|
+
expect(personalCodeToBirthday('010100A123D')).toEqual({ day: 1, month: 1, year: 2000 })
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
test('Sweden', () => {
|
|
28
|
+
expect(personalCodeToBirthday('8112289874')).toEqual({ day: 28, month: 12, year: 1981 })
|
|
29
|
+
expect(personalCodeToBirthday('811228-9874')).toEqual({ day: 28, month: 12, year: 1981 })
|
|
30
|
+
expect(personalCodeToBirthday('670919-9530')).toEqual({ day: 19, month: 9, year: 1967 })
|
|
31
|
+
expect(personalCodeToBirthday('230919-9533')).toEqual({ day: 19, month: 9, year: 2023 })
|
|
32
|
+
expect(personalCodeToBirthday('196709199530')).toEqual({ day: 19, month: 9, year: 1967 })
|
|
33
|
+
expect(personalCodeToBirthday('19670919-9530')).toEqual({ day: 19, month: 9, year: 1967 })
|
|
34
|
+
expect(personalCodeToBirthday('20230919-9533')).toEqual({ day: 19, month: 9, year: 2023 })
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
test('Norway', () => {
|
|
38
|
+
expect(personalCodeToBirthday('10021559844')).toEqual({ day: 10, month: 2, year: 2015 })
|
|
39
|
+
expect(personalCodeToBirthday('100215 59844')).toEqual({ day: 10, month: 2, year: 2015 })
|
|
40
|
+
expect(personalCodeToBirthday('10028439895')).toEqual({ day: 10, month: 2, year: 1984 })
|
|
41
|
+
expect(personalCodeToBirthday('27081439368')).toEqual({ day: 27, month: 8, year: 2014 })
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
test('Denmark', () => {
|
|
45
|
+
expect(personalCodeToBirthday('070589-8901')).toEqual({ day: 7, month: 5, year: 1989 })
|
|
46
|
+
expect(personalCodeToBirthday('230309-4456')).toEqual({ day: 23, month: 3, year: 2009 })
|
|
47
|
+
expect(personalCodeToBirthday('050384-4567')).toEqual({ day: 5, month: 3, year: 1984 })
|
|
48
|
+
expect(personalCodeToBirthday('230309-3020')).toEqual({ day: 23, month: 3, year: 1909 })
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
test('Ukraine', () => {
|
|
52
|
+
expect(personalCodeToBirthday('3406105372')).toEqual({ day: 3, month: 4, year: 1993 })
|
|
53
|
+
expect(personalCodeToBirthday('3406105350')).toEqual({ day: 3, month: 4, year: 1993 })
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
test('Poland', () => {
|
|
57
|
+
expect(personalCodeToBirthday('44051401359')).toEqual({ day: 14, month: 5, year: 1944 })
|
|
58
|
+
expect(personalCodeToBirthday('02070803628')).toEqual({ day: 8, month: 7, year: 1902 })
|
|
59
|
+
expect(personalCodeToBirthday('24231012344')).toEqual({ day: 10, month: 3, year: 2024 })
|
|
60
|
+
})
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
describe('invalid personal code', () => {
|
|
64
|
+
test('Estonia', () => {
|
|
65
|
+
expect(personalCodeToBirthday('39911025555')).toBeNull()
|
|
66
|
+
expect(personalCodeToBirthday('39913035552')).toBeNull()
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
test('Latvia', () => {
|
|
70
|
+
expect(personalCodeToBirthday('071023-38934')).toBeNull()
|
|
71
|
+
expect(personalCodeToBirthday('071523-38937')).toBeNull()
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
test('Lithuania', () => {
|
|
75
|
+
expect(personalCodeToBirthday('99905280217')).toBeNull()
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
test('Finland', () => {
|
|
79
|
+
expect(personalCodeToBirthday('999999A123T')).toBeNull()
|
|
80
|
+
expect(personalCodeToBirthday('131052Z3087')).toBeNull()
|
|
81
|
+
expect(personalCodeToBirthday('321052A3087')).toBeNull()
|
|
82
|
+
expect(personalCodeToBirthday('910100A123F')).toBeNull()
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
test('Sweden', () => {
|
|
86
|
+
expect(personalCodeToBirthday('8112289875')).toBeNull()
|
|
87
|
+
expect(personalCodeToBirthday('8115289871')).toBeNull()
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
test('Norway', () => {
|
|
91
|
+
expect(personalCodeToBirthday('32129999999')).toBeNull()
|
|
92
|
+
expect(personalCodeToBirthday('32132811465')).toBeNull()
|
|
93
|
+
expect(personalCodeToBirthday('32112812600')).toBeNull()
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
test('Denmark', () => {
|
|
97
|
+
expect(personalCodeToBirthday('999999-1234')).toBeNull()
|
|
98
|
+
expect(personalCodeToBirthday('051484-4569')).toBeNull()
|
|
99
|
+
expect(personalCodeToBirthday('230309-3080')).toBeNull()
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
test('Ukraine', () => {
|
|
103
|
+
expect(personalCodeToBirthday('3416105373')).toBeNull()
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
test('Poland', () => {
|
|
107
|
+
expect(personalCodeToBirthday('24231012343')).toBeNull()
|
|
108
|
+
expect(personalCodeToBirthday('24234012341')).toBeNull()
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
test('unidentified personal code format', () => {
|
|
112
|
+
expect(personalCodeToBirthday('abcd1234')).toBeNull()
|
|
113
|
+
})
|
|
114
|
+
})
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import denmarkParser from './parsers/denmark'
|
|
2
|
+
import estoniaLithuaniaParser from './parsers/estoniaLithuania'
|
|
3
|
+
import finlandParser from './parsers/finland'
|
|
4
|
+
import latviaParser from './parsers/latvia'
|
|
5
|
+
import norwayParser from './parsers/norway'
|
|
6
|
+
import polandParser from './parsers/poland'
|
|
7
|
+
import swedenParser from './parsers/sweden'
|
|
8
|
+
import ukraineParser from './parsers/ukraine'
|
|
9
|
+
import { ParsedDate } from './types'
|
|
10
|
+
|
|
11
|
+
export default function personalCodeToBirthday(code: string): ParsedDate | null {
|
|
12
|
+
code = code.replace(' ', '')
|
|
13
|
+
|
|
14
|
+
if (/^\d{10}$/.test(code)) {
|
|
15
|
+
return ukraineParser(code) || swedenParser(code)
|
|
16
|
+
} else if (/^\d{11}$/.test(code)) {
|
|
17
|
+
return estoniaLithuaniaParser(code) || polandParser(code) || norwayParser(code)
|
|
18
|
+
} else if (/^\d{12}$/.test(code)) {
|
|
19
|
+
return swedenParser(code)
|
|
20
|
+
} else if (/^\d{6}-\d{5}$/.test(code)) {
|
|
21
|
+
return latviaParser(code)
|
|
22
|
+
} else if (/^\d{6}[+-A]\d{3}[A-Za-z]$/.test(code)) {
|
|
23
|
+
return finlandParser(code)
|
|
24
|
+
} else if (/^\d{6}[-]\d{4}$/.test(code)) {
|
|
25
|
+
return finlandParser(code) || swedenParser(code) || denmarkParser(code)
|
|
26
|
+
} else if (/^\d{6}[A]\d{4}$/.test(code)) {
|
|
27
|
+
return finlandParser(code)
|
|
28
|
+
} else if (/^\d{8}[-]\d{4}$/.test(code)) {
|
|
29
|
+
return swedenParser(code)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return null
|
|
33
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { ParsedDate } from '../types'
|
|
2
|
+
import { isValidDate } from '../utils'
|
|
3
|
+
|
|
4
|
+
export default function denmarkParser(code: string): ParsedDate | null {
|
|
5
|
+
code = code.replace('-', '')
|
|
6
|
+
|
|
7
|
+
const day = parseInt(code.slice(0, 2), 10)
|
|
8
|
+
const month = parseInt(code.slice(2, 4), 10)
|
|
9
|
+
let year = parseInt(code.slice(4, 6), 10)
|
|
10
|
+
const centuryIndicator = parseInt(code.slice(6, 7), 10)
|
|
11
|
+
|
|
12
|
+
// Determine century based on the 7th digit and the year
|
|
13
|
+
if (centuryIndicator >= 0 && centuryIndicator <= 3) {
|
|
14
|
+
year += 1900
|
|
15
|
+
} else if (centuryIndicator >= 4 && centuryIndicator <= 9) {
|
|
16
|
+
if (year >= 0 && year <= 36) {
|
|
17
|
+
year += 2000
|
|
18
|
+
} else {
|
|
19
|
+
year += 1900
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Checksum was dropped in 2007
|
|
24
|
+
// https://en.wikipedia.org/wiki/Personal_identification_number_(Denmark)#New_development_in_2007
|
|
25
|
+
if (year < 2007 && !isValidChecksum(code)) {
|
|
26
|
+
return null
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (!isValidDate(day, month, year)) {
|
|
30
|
+
return null
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return { day, month, year }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function isValidChecksum(code: string): boolean {
|
|
37
|
+
const weights = [4, 3, 2, 7, 6, 5, 4, 3, 2] // Weights for the first 9 digits
|
|
38
|
+
|
|
39
|
+
// Convert the first 9 digits to an array of numbers
|
|
40
|
+
const digits = code.substring(0, 9).split('').map(Number)
|
|
41
|
+
|
|
42
|
+
// Calculate the weighted sum
|
|
43
|
+
const sum = digits.reduce((acc, digit, index) => acc + digit * weights[index], 0)
|
|
44
|
+
|
|
45
|
+
// Calculate the check digit (modulo 11)
|
|
46
|
+
const checksum = sum % 11 === 0 ? 0 : 11 - (sum % 11)
|
|
47
|
+
|
|
48
|
+
// If the modulo result is 10, it's considered invalid (CPR could not use this number)
|
|
49
|
+
if (checksum === 10) {
|
|
50
|
+
return false
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return checksum === parseInt(code[9], 10)
|
|
54
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { ParsedDate } from '../types'
|
|
2
|
+
import { isValidDate } from '../utils'
|
|
3
|
+
|
|
4
|
+
export default function estoniaLithuaniaParser(code: string): ParsedDate | null {
|
|
5
|
+
if (!isValidChecksum(code)) {
|
|
6
|
+
return null
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const centuryCode = parseInt(code[0], 10)
|
|
10
|
+
const day = parseInt(code.slice(5, 7), 10)
|
|
11
|
+
const month = parseInt(code.slice(3, 5), 10)
|
|
12
|
+
let year = parseInt(code.slice(1, 3), 10)
|
|
13
|
+
|
|
14
|
+
if (centuryCode === 3 || centuryCode === 4) {
|
|
15
|
+
year += 1900
|
|
16
|
+
}
|
|
17
|
+
if (centuryCode === 5 || centuryCode === 6) {
|
|
18
|
+
year += 2000
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (!isValidDate(day, month, year)) {
|
|
22
|
+
return null
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return { day, month, year }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function isValidChecksum(code: string): boolean {
|
|
29
|
+
const weights1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 1]
|
|
30
|
+
const weights2 = [3, 4, 5, 6, 7, 8, 9, 1, 2, 3]
|
|
31
|
+
let checksum = 0
|
|
32
|
+
let total = 0
|
|
33
|
+
|
|
34
|
+
for (let i = 0; i < 10; ++i) {
|
|
35
|
+
total += Number(code.charAt(i)) * weights1[i]
|
|
36
|
+
}
|
|
37
|
+
checksum = total % 11
|
|
38
|
+
|
|
39
|
+
total = 0
|
|
40
|
+
if (checksum === 10) {
|
|
41
|
+
for (let i = 0; i < 10; ++i) {
|
|
42
|
+
total += Number(code.charAt(i)) * weights2[i]
|
|
43
|
+
}
|
|
44
|
+
checksum = total % 11
|
|
45
|
+
|
|
46
|
+
if (10 === checksum) {
|
|
47
|
+
checksum = 0
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return checksum === Number(code[10])
|
|
52
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { ParsedDate } from '../types'
|
|
2
|
+
import { isValidDate } from '../utils'
|
|
3
|
+
|
|
4
|
+
export default function finlandParser(code: string): ParsedDate | null {
|
|
5
|
+
if (!isValidChecksum(code)) {
|
|
6
|
+
return null
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const day = parseInt(code.slice(0, 2), 10)
|
|
10
|
+
const month = parseInt(code.slice(2, 4), 10)
|
|
11
|
+
const yearSuffix = parseInt(code.slice(4, 6), 10)
|
|
12
|
+
const centuryMarker = code[6]
|
|
13
|
+
|
|
14
|
+
let year = yearSuffix
|
|
15
|
+
|
|
16
|
+
// Handle the century based on the marker
|
|
17
|
+
if (centuryMarker === '-') {
|
|
18
|
+
year += 1900
|
|
19
|
+
} else if (centuryMarker === 'A') {
|
|
20
|
+
year += 2000
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (!isValidDate(day, month, year)) {
|
|
24
|
+
return null
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return { day, month, year }
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function isValidChecksum(code: string): boolean {
|
|
31
|
+
const validChars = '0123456789ABCDEFHJKLMNPRSTUVWXY'
|
|
32
|
+
|
|
33
|
+
const serial = code.slice(0, 6) + code.slice(7, 10)
|
|
34
|
+
const checksum = code[10]
|
|
35
|
+
const num = parseInt(serial, 10)
|
|
36
|
+
const remainder = num % 31
|
|
37
|
+
const expectedChecksum = validChars.charAt(remainder)
|
|
38
|
+
|
|
39
|
+
return expectedChecksum === checksum.toUpperCase()
|
|
40
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { ParsedDate } from '../types'
|
|
2
|
+
import { isValidDate } from '../utils'
|
|
3
|
+
|
|
4
|
+
// Correct old school format is DDMMYY-CXXXX
|
|
5
|
+
export default function latviaParser(code: string): ParsedDate | null {
|
|
6
|
+
code = code.replace('-', '')
|
|
7
|
+
|
|
8
|
+
if (!isValidChecksum(code)) {
|
|
9
|
+
return null
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const day = parseInt(code.slice(0, 2), 10)
|
|
13
|
+
const month = parseInt(code.slice(2, 4), 10)
|
|
14
|
+
let year = parseInt(code.slice(4, 6), 10)
|
|
15
|
+
const centuryCode = parseInt(code[6], 10)
|
|
16
|
+
|
|
17
|
+
if (centuryCode === 3) {
|
|
18
|
+
year += 2000
|
|
19
|
+
} else {
|
|
20
|
+
year += 1900
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (!isValidDate(day, month, year)) {
|
|
24
|
+
return null
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return { day, month, year }
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Function to validate the check digit of a Latvian personal code
|
|
31
|
+
function isValidChecksum(personalCode: string): boolean {
|
|
32
|
+
const weights = [1, 6, 3, 7, 9, 10, 5, 8, 4, 2]
|
|
33
|
+
|
|
34
|
+
// Convert the first 10 digits to an array of numbers
|
|
35
|
+
const digits = personalCode.substring(0, 10).split('').map(Number)
|
|
36
|
+
|
|
37
|
+
// Calculate the weighted sum
|
|
38
|
+
const sum = digits.reduce((acc, digit, index) => acc + digit * weights[index], 0)
|
|
39
|
+
|
|
40
|
+
// Calculate the check digit
|
|
41
|
+
let checksum = sum % 11
|
|
42
|
+
if (checksum === 10) {
|
|
43
|
+
checksum = 0 // If modulo result is 10, check digit is 0
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Return true if the calculated check digit matches the provided check digit
|
|
47
|
+
return checksum === parseInt(personalCode[10], 10)
|
|
48
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { ParsedDate } from '../types'
|
|
2
|
+
import { isValidDate } from '../utils'
|
|
3
|
+
|
|
4
|
+
export default function norwayParser(code: string): ParsedDate | null {
|
|
5
|
+
if (!isValidChecksum(code)) {
|
|
6
|
+
return null
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const day = parseInt(code.slice(0, 2), 10)
|
|
10
|
+
const month = parseInt(code.slice(2, 4), 10)
|
|
11
|
+
let year = parseInt(code.slice(4, 6), 10)
|
|
12
|
+
|
|
13
|
+
const centuryMarker = parseInt(code.slice(6, 7), 10)
|
|
14
|
+
|
|
15
|
+
// Read more https://en.wikipedia.org/wiki/National_identity_number_(Norway)#Numerical_components
|
|
16
|
+
if (centuryMarker <= 4 || centuryMarker === 9) {
|
|
17
|
+
year += year >= 40 ? 1900 : 2000
|
|
18
|
+
} else if (centuryMarker >= 5 && centuryMarker <= 8) {
|
|
19
|
+
year += 2000
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (!isValidDate(day, month, year)) {
|
|
23
|
+
return null
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return { day, month, year }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Function to validate the check digits of a Norwegian personal code
|
|
30
|
+
function isValidChecksum(code: string): boolean {
|
|
31
|
+
const weights1 = [3, 7, 6, 1, 8, 9, 4, 5, 2] // Weights for the first checksum
|
|
32
|
+
const weights2 = [5, 4, 3, 2, 7, 6, 5, 4, 3, 2] // Weights for the second checksum, including first checksum
|
|
33
|
+
|
|
34
|
+
// Extract the first 9 digits for checksum calculation
|
|
35
|
+
const digits = code.substring(0, 9).split('').map(Number)
|
|
36
|
+
|
|
37
|
+
// Extract the check digits provided in the input
|
|
38
|
+
const providedFirstCheckDigit = parseInt(code[9], 10)
|
|
39
|
+
const providedSecondCheckDigit = parseInt(code[10], 10)
|
|
40
|
+
|
|
41
|
+
// First checksum calculation
|
|
42
|
+
const sum1 = digits.reduce((sum, digit, index) => sum + digit * weights1[index], 0)
|
|
43
|
+
let firstCheckDigit = 11 - (sum1 % 11)
|
|
44
|
+
if (firstCheckDigit === 11) {
|
|
45
|
+
firstCheckDigit = 0 // Handle special case where remainder is 11
|
|
46
|
+
}
|
|
47
|
+
if (firstCheckDigit === 10) {
|
|
48
|
+
return false // Invalid first check digit
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Add the first check digit for the second checksum calculation
|
|
52
|
+
const digitsWithFirstCheck = [...digits, firstCheckDigit]
|
|
53
|
+
|
|
54
|
+
// Second checksum calculation
|
|
55
|
+
const sum2 = digitsWithFirstCheck.reduce((sum, digit, index) => sum + digit * weights2[index], 0)
|
|
56
|
+
let secondCheckDigit = 11 - (sum2 % 11)
|
|
57
|
+
if (secondCheckDigit === 11) {
|
|
58
|
+
secondCheckDigit = 0 // Handle special case where remainder is 11
|
|
59
|
+
}
|
|
60
|
+
if (secondCheckDigit === 10) {
|
|
61
|
+
return false // Invalid second check digit
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return firstCheckDigit === providedFirstCheckDigit && secondCheckDigit === providedSecondCheckDigit
|
|
65
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { ParsedDate } from '../types'
|
|
2
|
+
import { isValidDate } from '../utils'
|
|
3
|
+
|
|
4
|
+
export default function polandParser(code: string): ParsedDate | null {
|
|
5
|
+
if (!isValidChecksum(code)) {
|
|
6
|
+
return null
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const day = parseInt(code.slice(4, 6), 10)
|
|
10
|
+
let month = parseInt(code.slice(2, 4), 10)
|
|
11
|
+
let year = parseInt(code.slice(0, 2), 10)
|
|
12
|
+
|
|
13
|
+
// Determine the century based on the month encoding
|
|
14
|
+
if (month >= 1 && month <= 12) {
|
|
15
|
+
year += 1900
|
|
16
|
+
} else if (month >= 21 && month <= 32) {
|
|
17
|
+
year += 2000
|
|
18
|
+
month -= 20
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (!isValidDate(day, month, year)) {
|
|
22
|
+
return null
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return { day, month, year }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function isValidChecksum(code: string): boolean {
|
|
29
|
+
const weights = [1, 3, 7, 9, 1, 3, 7, 9, 1, 3]
|
|
30
|
+
const digits = code.slice(0, 10).split('').map(Number)
|
|
31
|
+
|
|
32
|
+
const sum = digits.reduce((acc, digit, index) => acc + digit * weights[index], 0)
|
|
33
|
+
|
|
34
|
+
const expectedChecksum = (10 - (sum % 10)) % 10
|
|
35
|
+
|
|
36
|
+
return expectedChecksum === parseInt(code[10], 10)
|
|
37
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { ParsedDate } from '../types'
|
|
2
|
+
import { isValidDate } from '../utils'
|
|
3
|
+
|
|
4
|
+
export default function swedenParser(code: string): ParsedDate | null {
|
|
5
|
+
code = code.replace('-', '')
|
|
6
|
+
|
|
7
|
+
if (!isValidChecksum(code)) {
|
|
8
|
+
return null
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
let year: number
|
|
12
|
+
let month: number
|
|
13
|
+
let day: number
|
|
14
|
+
|
|
15
|
+
if (code.length === 12) {
|
|
16
|
+
year = parseInt(code.slice(0, 4), 10)
|
|
17
|
+
month = parseInt(code.slice(4, 6), 10)
|
|
18
|
+
day = parseInt(code.slice(6, 8), 10)
|
|
19
|
+
} else {
|
|
20
|
+
year = parseInt(code.slice(0, 2), 10)
|
|
21
|
+
month = parseInt(code.slice(2, 4), 10)
|
|
22
|
+
day = parseInt(code.slice(4, 6), 10)
|
|
23
|
+
|
|
24
|
+
if (new Date().getFullYear() - (1900 + year) > 99) {
|
|
25
|
+
year += 2000
|
|
26
|
+
} else {
|
|
27
|
+
year += 1900
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (!isValidDate(day, month, year)) {
|
|
32
|
+
return null
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return { day, month, year }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function isValidChecksum(code: string) {
|
|
39
|
+
// Use the last 9 digits without checksum number
|
|
40
|
+
const digits = code.length === 12 ? code.slice(2, 11) : code.slice(0, 9)
|
|
41
|
+
|
|
42
|
+
let sum = 0
|
|
43
|
+
for (let i = 0; i < digits.length; i++) {
|
|
44
|
+
let digit = parseInt(digits[i])
|
|
45
|
+
|
|
46
|
+
if (i % 2 === 0) {
|
|
47
|
+
digit *= 2
|
|
48
|
+
|
|
49
|
+
if (digit > 9) {
|
|
50
|
+
digit -= 9
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
sum += digit
|
|
55
|
+
}
|
|
56
|
+
const expectedChecksum = (10 - (sum % 10)) % 10
|
|
57
|
+
|
|
58
|
+
return expectedChecksum === Number(code[code.length - 1])
|
|
59
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { ParsedDate } from '../types'
|
|
2
|
+
import { isValidDate } from '../utils'
|
|
3
|
+
|
|
4
|
+
export default function ukrainianParser(code: string): ParsedDate | null {
|
|
5
|
+
if (!isValidChecksum(code)) {
|
|
6
|
+
return null
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// The first 5 digits represent the number of days since 31-12-1899
|
|
10
|
+
const daysSinceBaseDate = parseInt(code.slice(0, 5), 10)
|
|
11
|
+
const baseDate = new Date(1899, 11, 31) // Dec 31, 1899
|
|
12
|
+
|
|
13
|
+
// Calculate the birthdate from the number of days since the base date
|
|
14
|
+
const birthDate = new Date(baseDate.getTime() + daysSinceBaseDate * 24 * 60 * 60 * 1000)
|
|
15
|
+
|
|
16
|
+
const day = birthDate.getDate()
|
|
17
|
+
const month = birthDate.getMonth() + 1 // getMonth is zero-based
|
|
18
|
+
const year = birthDate.getFullYear()
|
|
19
|
+
|
|
20
|
+
if (!isValidDate(day, month, year)) {
|
|
21
|
+
return null
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return { day, month, year }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function isValidChecksum(code: string): boolean {
|
|
28
|
+
const digits = code.slice(0, 9).split('').map(Number)
|
|
29
|
+
|
|
30
|
+
const weights = [-1, 5, 7, 9, 4, 6, 10, 5, 7]
|
|
31
|
+
const controlSum = digits.reduce((sum, digit, index) => sum + digit * weights[index], 0)
|
|
32
|
+
|
|
33
|
+
let controlNumber = controlSum % 11
|
|
34
|
+
|
|
35
|
+
if (controlNumber === 10) {
|
|
36
|
+
controlNumber = 0
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return controlNumber === parseInt(code[9], 10)
|
|
40
|
+
}
|
package/src/types.ts
ADDED
package/src/utils.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export function isValidDate(day: number, month: number, year: number): boolean {
|
|
2
|
+
const date = new Date(year, month - 1, day) // month is zero-indexed in JS Date
|
|
3
|
+
|
|
4
|
+
if (year < 1900 || year > new Date().getFullYear()) {
|
|
5
|
+
return false
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
return date.getFullYear() === year && date.getMonth() === month - 1 && date.getDate() === day
|
|
9
|
+
}
|
package/tsconfig.json
CHANGED
package/index.test.ts
DELETED
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import personalCodeToBirthday from './index'
|
|
2
|
-
|
|
3
|
-
describe('valid codes', () => {
|
|
4
|
-
test('Estonia', () => {
|
|
5
|
-
expect(personalCodeToBirthday('39405280299')).toEqual({ day: 28, month: 5, year: 1994 })
|
|
6
|
-
expect(personalCodeToBirthday('60105280299')).toEqual({ day: 28, month: 5, year: 2001 })
|
|
7
|
-
})
|
|
8
|
-
|
|
9
|
-
test('Latvia', () => {
|
|
10
|
-
expect(personalCodeToBirthday('050393-12344')).toEqual({ day: 5, month: 3, year: 1993 })
|
|
11
|
-
expect(personalCodeToBirthday('050303-22344')).toEqual({ day: 5, month: 3, year: 2003 })
|
|
12
|
-
})
|
|
13
|
-
|
|
14
|
-
test('Lithuania', () => {
|
|
15
|
-
expect(personalCodeToBirthday('39905280217')).toEqual({ day: 28, month: 5, year: 1999 })
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
test('Finland', () => {
|
|
19
|
-
expect(personalCodeToBirthday('101085-7001')).toEqual({ day: 10, month: 10, year: 1985 })
|
|
20
|
-
expect(personalCodeToBirthday('101085+789W')).toEqual({ day: 10, month: 10, year: 1885 })
|
|
21
|
-
expect(personalCodeToBirthday('150752-308N')).toEqual({ day: 15, month: 7, year: 1952 })
|
|
22
|
-
expect(personalCodeToBirthday('010100A123D')).toEqual({ day: 1, month: 1, year: 2000 })
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
test('Sweden', () => {
|
|
26
|
-
expect(personalCodeToBirthday('199001011234')).toEqual({ day: 1, month: 1, year: 1990 })
|
|
27
|
-
})
|
|
28
|
-
|
|
29
|
-
test('Norway', () => {
|
|
30
|
-
expect(personalCodeToBirthday('01020352345')).toEqual({ day: 1, month: 2, year: 2003 })
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
test('Denmark', () => {
|
|
34
|
-
expect(personalCodeToBirthday('0503054567')).toEqual({ day: 5, month: 3, year: 2005 })
|
|
35
|
-
expect(personalCodeToBirthday('0503841235')).toEqual({ day: 5, month: 3, year: 1984 })
|
|
36
|
-
expect(personalCodeToBirthday('050384-1235')).toEqual({ day: 5, month: 3, year: 1984 })
|
|
37
|
-
expect(personalCodeToBirthday('0503844567')).toEqual({ day: 5, month: 3, year: 1984 })
|
|
38
|
-
})
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
describe('invalid personal code', () => {
|
|
42
|
-
test('Estonia', () => {
|
|
43
|
-
const result = personalCodeToBirthday('39913025555')
|
|
44
|
-
expect(result).toBeNull()
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
test('Latvia', () => {
|
|
48
|
-
expect(personalCodeToBirthday('123456-12345')).toBeNull()
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
test('Lithuania', () => {
|
|
52
|
-
expect(personalCodeToBirthday('99905280217')).toBeNull()
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
test('Finland', () => {
|
|
56
|
-
expect(personalCodeToBirthday('999999A123T')).toBeNull()
|
|
57
|
-
expect(personalCodeToBirthday('131052Z3087')).toBeNull()
|
|
58
|
-
expect(personalCodeToBirthday('321052A3087')).toBeNull()
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
test('Sweden', () => {
|
|
62
|
-
expect(personalCodeToBirthday('1990010123456')).toBeNull()
|
|
63
|
-
expect(personalCodeToBirthday('199013011234')).toBeNull()
|
|
64
|
-
expect(personalCodeToBirthday('199001341234')).toBeNull()
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
test('Norway', () => {
|
|
68
|
-
expect(personalCodeToBirthday('32129999999')).toBeNull()
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
test('Denmark', () => {
|
|
72
|
-
expect(personalCodeToBirthday('9999991234')).toBeNull()
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
test('unsupported personal code format', () => {
|
|
76
|
-
const result = personalCodeToBirthday('abcd1234')
|
|
77
|
-
expect(result).toBeNull()
|
|
78
|
-
})
|
|
79
|
-
})
|
package/index.ts
DELETED
|
@@ -1,192 +0,0 @@
|
|
|
1
|
-
interface ParsedDate {
|
|
2
|
-
day: number
|
|
3
|
-
month: number
|
|
4
|
-
year: number
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export default function personalCodeToBirthday(code: string): ParsedDate | null {
|
|
8
|
-
if (/^\d{11}$/.test(code)) {
|
|
9
|
-
// Could be Estonia, Lithuania, or Norway
|
|
10
|
-
const birthday = estonianLithuanianParser(code)
|
|
11
|
-
if (birthday) {
|
|
12
|
-
return birthday
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
return norwayParser(code)
|
|
16
|
-
} else if (/^\d{6}-\d{5}$/.test(code)) {
|
|
17
|
-
return latvianParser(code)
|
|
18
|
-
} else if (/^\d{6}[+-A]\d{3}[A-Za-z]$/.test(code)) {
|
|
19
|
-
return finlandParser(code)
|
|
20
|
-
} else if (/^\d{6}[+-A]\d{4}$/.test(code)) {
|
|
21
|
-
// Could be Finland or Denmark
|
|
22
|
-
const birthday = finlandParser(code)
|
|
23
|
-
if (birthday) {
|
|
24
|
-
return birthday
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
return denmarkParser(code)
|
|
28
|
-
} else if (/^\d{12}$/.test(code)) {
|
|
29
|
-
return swedenParser(code)
|
|
30
|
-
} else if (/^\d{10}$/.test(code)) {
|
|
31
|
-
return denmarkParser(code)
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
return null
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// Estonia (EE) and Lithuanian (LT) Parser
|
|
38
|
-
function estonianLithuanianParser(code: string): ParsedDate | null {
|
|
39
|
-
const centuryCode = parseInt(code[0], 10)
|
|
40
|
-
const day = parseInt(code.slice(5, 7), 10)
|
|
41
|
-
const month = parseInt(code.slice(3, 5), 10)
|
|
42
|
-
let year = parseInt(code.slice(1, 3), 10)
|
|
43
|
-
|
|
44
|
-
if (centuryCode === 3 || centuryCode === 4) {
|
|
45
|
-
year += 1900
|
|
46
|
-
}
|
|
47
|
-
if (centuryCode === 5 || centuryCode === 6) {
|
|
48
|
-
year += 2000
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
if (!isValidDate(day, month, year)) {
|
|
52
|
-
return null
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
return { day, month, year }
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Latvia (LV) Parser
|
|
59
|
-
// Correct old school format is DDMMYY-CXXXX
|
|
60
|
-
function latvianParser(code: string): ParsedDate | null {
|
|
61
|
-
const day = parseInt(code.slice(0, 2), 10)
|
|
62
|
-
const month = parseInt(code.slice(2, 4), 10)
|
|
63
|
-
let year = parseInt(code.slice(4, 6), 10)
|
|
64
|
-
const centuryCode = parseInt(code[7], 10)
|
|
65
|
-
|
|
66
|
-
// Latvian codes are always from the 20th century, so add 1900
|
|
67
|
-
if (centuryCode === 2) {
|
|
68
|
-
year += 2000
|
|
69
|
-
} else {
|
|
70
|
-
year += 1900
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Validate the parsed date
|
|
74
|
-
if (!isValidDate(day, month, year)) {
|
|
75
|
-
return null
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
return { day, month, year }
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// Finland (FI) Parser
|
|
82
|
-
function finlandParser(code: string): ParsedDate | null {
|
|
83
|
-
const day = parseInt(code.slice(0, 2), 10)
|
|
84
|
-
const month = parseInt(code.slice(2, 4), 10)
|
|
85
|
-
const yearSuffix = parseInt(code.slice(4, 6), 10)
|
|
86
|
-
const centuryMarker = code[6]
|
|
87
|
-
|
|
88
|
-
let year = yearSuffix
|
|
89
|
-
|
|
90
|
-
// Handle the century based on the marker
|
|
91
|
-
if (centuryMarker === '+') {
|
|
92
|
-
year += 1800
|
|
93
|
-
} else if (centuryMarker === '-') {
|
|
94
|
-
year += 1900
|
|
95
|
-
} else if (centuryMarker === 'A') {
|
|
96
|
-
year += 2000
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// Validate the parsed date
|
|
100
|
-
if (!isValidDate(day, month, year)) {
|
|
101
|
-
return null
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
if (!isValidFinnishChecksum(code)) {
|
|
105
|
-
return null
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
return { day, month, year }
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
function isValidFinnishChecksum(code: string): boolean {
|
|
112
|
-
const validChars = '0123456789ABCDEFHJKLMNPRSTUVWXY'
|
|
113
|
-
|
|
114
|
-
const serial = code.slice(0, 6) + code.slice(7, 10)
|
|
115
|
-
const checksum = code[10]
|
|
116
|
-
const num = parseInt(serial, 10)
|
|
117
|
-
const remainder = num % 31
|
|
118
|
-
const expectedChecksum = validChars.charAt(remainder)
|
|
119
|
-
|
|
120
|
-
return expectedChecksum === checksum.toUpperCase()
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// Sweden (SE) Parser
|
|
124
|
-
function swedenParser(code: string): ParsedDate | null {
|
|
125
|
-
const year = parseInt(code.slice(0, 4), 10)
|
|
126
|
-
const month = parseInt(code.slice(4, 6), 10)
|
|
127
|
-
const day = parseInt(code.slice(6, 8), 10)
|
|
128
|
-
|
|
129
|
-
if (!isValidDate(day, month, year)) {
|
|
130
|
-
return null
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
return { day, month, year }
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// Norway (NO) Parser
|
|
137
|
-
function norwayParser(code: string): ParsedDate | null {
|
|
138
|
-
const day = parseInt(code.slice(0, 2), 10)
|
|
139
|
-
const month = parseInt(code.slice(2, 4), 10)
|
|
140
|
-
let year = parseInt(code.slice(4, 6), 10)
|
|
141
|
-
|
|
142
|
-
const individual = parseInt(code.slice(6, 9), 10)
|
|
143
|
-
|
|
144
|
-
// Determine century based on individual number and year
|
|
145
|
-
if (individual <= 499 || (individual >= 500 && year >= 40)) {
|
|
146
|
-
year += 1900
|
|
147
|
-
} else if (individual <= 999 && year <= 39) {
|
|
148
|
-
year += 2000
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// Now validate the date after adjusting the century
|
|
152
|
-
if (!isValidDate(day, month, year)) {
|
|
153
|
-
return null
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
return { day, month, year }
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// Denmark (DK) Parser
|
|
160
|
-
function denmarkParser(code: string): ParsedDate | null {
|
|
161
|
-
if (code.includes('-')) {
|
|
162
|
-
code = code.replace('-', '')
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
const day = parseInt(code.slice(0, 2), 10)
|
|
166
|
-
const month = parseInt(code.slice(2, 4), 10)
|
|
167
|
-
let year = parseInt(code.slice(4, 6), 10)
|
|
168
|
-
const centuryIndicator = parseInt(code.slice(6, 7), 10)
|
|
169
|
-
|
|
170
|
-
// Determine century based on the 7th digit and the year
|
|
171
|
-
if (centuryIndicator >= 0 && centuryIndicator <= 3) {
|
|
172
|
-
year += 1900
|
|
173
|
-
} else if (centuryIndicator >= 4 && centuryIndicator <= 9) {
|
|
174
|
-
if (year >= 0 && year <= 36) {
|
|
175
|
-
year += 2000
|
|
176
|
-
} else {
|
|
177
|
-
year += 1900
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
if (!isValidDate(day, month, year)) {
|
|
182
|
-
return null
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
return { day, month, year }
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
function isValidDate(day: number, month: number, year: number): boolean {
|
|
189
|
-
const date = new Date(year, month - 1, day) // month is zero-indexed in JS Date
|
|
190
|
-
|
|
191
|
-
return date.getFullYear() === year && date.getMonth() === month - 1 && date.getDate() === day
|
|
192
|
-
}
|
package/prettier.config.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
module.exports = require("@remato/prettier-config");
|