@wener/common 1.0.3 → 1.0.4

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.
Files changed (97) hide show
  1. package/lib/cn/DivisionCode.js.map +1 -1
  2. package/lib/cn/Mod11Checksum.js.map +1 -1
  3. package/lib/cn/Mod31Checksum.js.map +1 -1
  4. package/lib/cn/ResidentIdentityCardNumber.js.map +1 -1
  5. package/lib/cn/UnifiedSocialCreditCode.js.map +1 -1
  6. package/lib/cn/formatDate.js.map +1 -1
  7. package/lib/cn/parseSex.js.map +1 -1
  8. package/lib/cn/types.d.js.map +1 -1
  9. package/lib/data/formatSort.js +15 -0
  10. package/lib/data/formatSort.js.map +1 -0
  11. package/lib/data/index.js +4 -0
  12. package/lib/data/index.js.map +1 -0
  13. package/lib/data/maybeNumber.js +22 -0
  14. package/lib/data/maybeNumber.js.map +1 -0
  15. package/lib/data/parseSort.js +95 -0
  16. package/lib/data/parseSort.js.map +1 -0
  17. package/lib/data/resolvePagination.js +36 -0
  18. package/lib/data/resolvePagination.js.map +1 -0
  19. package/lib/data/types.d.js +3 -0
  20. package/lib/data/types.d.js.map +1 -0
  21. package/lib/index.js +6 -2
  22. package/lib/index.js.map +1 -1
  23. package/lib/jsonschema/JsonSchema.js +4 -4
  24. package/lib/jsonschema/JsonSchema.js.map +1 -1
  25. package/lib/jsonschema/types.d.js.map +1 -1
  26. package/lib/meta/defineFileType.js.map +1 -1
  27. package/lib/meta/defineInit.js.map +1 -1
  28. package/lib/meta/defineMetadata.js.map +1 -1
  29. package/lib/password/PHC.js +8 -8
  30. package/lib/password/PHC.js.map +1 -1
  31. package/lib/password/Password.js.map +1 -1
  32. package/lib/password/createArgon2PasswordAlgorithm.js.map +1 -1
  33. package/lib/password/createBase64PasswordAlgorithm.js.map +1 -1
  34. package/lib/password/createBcryptPasswordAlgorithm.js.map +1 -1
  35. package/lib/password/createPBKDF2PasswordAlgorithm.js.map +1 -1
  36. package/lib/password/createScryptPasswordAlgorithm.js.map +1 -1
  37. package/lib/search/AdvanceSearch.js.map +1 -1
  38. package/lib/search/formatAdvanceSearch.js.map +1 -1
  39. package/lib/search/optimizeAdvanceSearch.js.map +1 -1
  40. package/lib/search/parseAdvanceSearch.js.map +1 -1
  41. package/lib/search/parser.d.js.map +1 -1
  42. package/lib/search/types.d.js.map +1 -1
  43. package/lib/tools/renderJsonSchemaToMarkdownDoc.js.map +1 -1
  44. package/package.json +10 -5
  45. package/src/cn/DivisionCode.test.ts +38 -45
  46. package/src/cn/DivisionCode.ts +140 -184
  47. package/src/cn/Mod11Checksum.ts +17 -17
  48. package/src/cn/Mod31Checksum.ts +25 -25
  49. package/src/cn/ResidentIdentityCardNumber.test.ts +12 -16
  50. package/src/cn/ResidentIdentityCardNumber.ts +82 -82
  51. package/src/cn/UnifiedSocialCreditCode.test.ts +11 -11
  52. package/src/cn/UnifiedSocialCreditCode.ts +115 -120
  53. package/src/cn/__snapshots__/ResidentIdentityCardNumber.test.ts.snap +1 -1
  54. package/src/cn/formatDate.ts +10 -10
  55. package/src/cn/parseSex.ts +11 -25
  56. package/src/cn/types.d.ts +43 -43
  57. package/src/data/formatSort.test.ts +13 -0
  58. package/src/data/formatSort.ts +18 -0
  59. package/src/data/index.ts +5 -0
  60. package/src/data/maybeNumber.ts +23 -0
  61. package/src/data/parseSort.test.ts +67 -0
  62. package/src/data/parseSort.ts +108 -0
  63. package/src/data/resolvePagination.test.ts +58 -0
  64. package/src/data/resolvePagination.ts +60 -0
  65. package/src/data/types.d.ts +33 -0
  66. package/src/index.ts +8 -2
  67. package/src/jsonschema/JsonSchema.test.ts +13 -22
  68. package/src/jsonschema/JsonSchema.ts +145 -177
  69. package/src/jsonschema/types.d.ts +151 -161
  70. package/src/meta/defineFileType.tsx +54 -54
  71. package/src/meta/defineInit.ts +32 -53
  72. package/src/meta/defineMetadata.test.ts +5 -7
  73. package/src/meta/defineMetadata.ts +28 -46
  74. package/src/password/PHC.test.ts +186 -277
  75. package/src/password/PHC.ts +243 -243
  76. package/src/password/Password.test.ts +38 -50
  77. package/src/password/Password.ts +73 -95
  78. package/src/password/createArgon2PasswordAlgorithm.ts +65 -69
  79. package/src/password/createBase64PasswordAlgorithm.ts +9 -9
  80. package/src/password/createBcryptPasswordAlgorithm.ts +20 -22
  81. package/src/password/createPBKDF2PasswordAlgorithm.ts +49 -61
  82. package/src/password/createScryptPasswordAlgorithm.ts +48 -59
  83. package/src/search/AdvanceSearch.test.ts +136 -143
  84. package/src/search/AdvanceSearch.ts +6 -6
  85. package/src/search/formatAdvanceSearch.ts +44 -53
  86. package/src/search/optimizeAdvanceSearch.ts +70 -83
  87. package/src/search/parseAdvanceSearch.ts +16 -19
  88. package/src/search/parser.d.ts +3 -3
  89. package/src/search/types.d.ts +28 -54
  90. package/src/tools/renderJsonSchemaToMarkdownDoc.ts +69 -69
  91. package/lib/normalizePagination.js +0 -14
  92. package/lib/normalizePagination.js.map +0 -1
  93. package/lib/parseSort.js +0 -106
  94. package/lib/parseSort.js.map +0 -1
  95. package/src/normalizePagination.ts +0 -25
  96. package/src/parseSort.test.ts +0 -42
  97. package/src/parseSort.ts +0 -133
@@ -1,45 +1,9 @@
1
- const DivisionCodeLevels: Array<{
2
- level: number;
3
- code: string;
4
- length: number;
5
- size: number;
6
- label: string;
7
- }> = [
8
- {
9
- level: 1,
10
- code: 'Province',
11
- length: 2,
12
- size: 2,
13
- label: '省',
14
- },
15
- {
16
- level: 2,
17
- code: 'City',
18
- length: 4,
19
- size: 2,
20
- label: '市',
21
- },
22
- {
23
- level: 3,
24
- code: 'County',
25
- length: 6,
26
- size: 2,
27
- label: '区县',
28
- },
29
- {
30
- level: 4,
31
- code: 'Town',
32
- length: 9,
33
- size: 3,
34
- label: '乡镇',
35
- },
36
- {
37
- level: 5,
38
- code: 'Village',
39
- length: 12,
40
- size: 3,
41
- label: '村',
42
- },
1
+ const DivisionCodeLevels: Array<{ level: number; code: string; length: number; size: number; label: string }> = [
2
+ { level: 1, code: 'Province', length: 2, size: 2, label: '省' },
3
+ { level: 2, code: 'City', length: 4, size: 2, label: '市' },
4
+ { level: 3, code: 'County', length: 6, size: 2, label: '区县' },
5
+ { level: 4, code: 'Town', length: 9, size: 3, label: '乡镇' },
6
+ { level: 5, code: 'Village', length: 12, size: 3, label: '村' },
43
7
  ] as const;
44
8
 
45
9
  // String(Number.MAX_SAFE_INTEGER).length=16
@@ -51,128 +15,120 @@ const DivisionCodeLevels: Array<{
51
15
  * @see https://zh.wikipedia.org/wiki/GB/T_2260 中华人民共和国行政区划代码
52
16
  */
53
17
  export namespace DivisionCode {
54
- enum DivisionCodeLevel {
55
- Province = 1,
56
- City = 2,
57
- County = 3,
58
- Town = 4,
59
- Village = 5,
60
- }
61
-
62
- export const levels = DivisionCodeLevels;
63
-
64
- export const regex = /^(?<province>\d{2})(?<city>\d{2})?(?<county>\d{2})?(?<town>\d{3})?(?<village>\d{3})?$/;
65
-
66
- const root: CodeValue[] = [
67
- { value: '11', label: '北京市' },
68
- { value: '12', label: '天津市' },
69
- { value: '13', label: '河北省' },
70
- { value: '14', label: '山西省' },
71
- { value: '15', label: '内蒙古自治区' },
72
- { value: '21', label: '辽宁省' },
73
- { value: '22', label: '吉林省' },
74
- { value: '23', label: '黑龙江省' },
75
- { value: '31', label: '上海市' },
76
- { value: '32', label: '江苏省' },
77
- { value: '33', label: '浙江省' },
78
- { value: '34', label: '安徽省' },
79
- { value: '35', label: '福建省' },
80
- { value: '36', label: '江西省' },
81
- { value: '37', label: '山东省' },
82
- { value: '41', label: '河南省' },
83
- { value: '42', label: '湖北省' },
84
- { value: '43', label: '湖南省' },
85
- { value: '44', label: '广东省' },
86
- { value: '45', label: '广西壮族自治区' },
87
- { value: '46', label: '海南省' },
88
- { value: '50', label: '重庆市' },
89
- { value: '51', label: '四川省' },
90
- { value: '52', label: '贵州省' },
91
- { value: '53', label: '云南省' },
92
- { value: '54', label: '西藏自治区' },
93
- { value: '61', label: '陕西省' },
94
- { value: '62', label: '甘肃省' },
95
- { value: '63', label: '青海省' },
96
- { value: '64', label: '宁夏回族自治区' },
97
- { value: '65', label: '新疆维吾尔自治区' },
98
- { value: '71', label: '台湾省' },
99
- { value: '81', label: '香港特别行政区' },
100
- { value: '82', label: '澳门特别行政区' },
101
- // 9 国外
102
- ];
103
-
104
- export type ParsedCode = {
105
- province: string;
106
- city?: string;
107
- county?: string;
108
- town?: string;
109
- village?: string;
110
- codes: string[];
111
- level: DivisionCodeLevel;
112
- labels: string[];
113
- };
114
-
115
- export function parse(code: string | undefined | null | number): ParsedCode | undefined {
116
- if (!code) return;
117
- code = String(code);
118
- const match = regex.exec(code);
119
- if (!match) return;
120
- const { province, city, county, town, village } = match.groups ?? {};
121
- if (!province) return;
122
-
123
- let codes = [province, city, county, town, village].filter(Boolean);
124
- return {
125
- province,
126
- city,
127
- county,
128
- town,
129
- village,
130
- codes: codes,
131
- level: codes.length as DivisionCodeLevel,
132
- };
133
- }
134
-
135
- export function format({
136
- province,
137
- city,
138
- county,
139
- town,
140
- village,
141
- }: {
142
- province: string | number;
143
- city?: string | number;
144
- county?: string | number;
145
- town?: string | number;
146
- village?: string | number;
147
- }): string {
148
- const codes: string[] = [];
149
- for (let i = 0; i < [province, city, county, town, village].length; i++) {
150
- let x = [province, city, county, town, village][i];
151
- if (x === undefined || x === null || x === '') {
152
- break;
153
- }
154
- let len = levels[i].size;
155
- codes.push(String(x).padStart(len, '0').slice(0, len));
156
- }
157
- return codes.join('');
158
- }
159
-
160
- // export function random(level: DivisionCodeLevel = 'County'): string {
161
- // const l = DivisionCodeLevels.find((v) => v.code === level) || DivisionCodeLevels[2];
162
- // const l1 = randomPick(provinces);
163
- // if (l.level === 1) {
164
- // return String(l1[0]);
165
- // }
166
- // return l1 + String(Math.floor(Math.random() * parseFloat(`1e${l.length - 2}`) - 1));
167
- // }
18
+ enum DivisionCodeLevel {
19
+ Province = 1,
20
+ City = 2,
21
+ County = 3,
22
+ Town = 4,
23
+ Village = 5,
24
+ }
25
+
26
+ export const levels = DivisionCodeLevels;
27
+
28
+ export const regex = /^(?<province>\d{2})(?<city>\d{2})?(?<county>\d{2})?(?<town>\d{3})?(?<village>\d{3})?$/;
29
+
30
+ const root: CodeValue[] = [
31
+ { value: '11', label: '北京市' },
32
+ { value: '12', label: '天津市' },
33
+ { value: '13', label: '河北省' },
34
+ { value: '14', label: '山西省' },
35
+ { value: '15', label: '内蒙古自治区' },
36
+ { value: '21', label: '辽宁省' },
37
+ { value: '22', label: '吉林省' },
38
+ { value: '23', label: '黑龙江省' },
39
+ { value: '31', label: '上海市' },
40
+ { value: '32', label: '江苏省' },
41
+ { value: '33', label: '浙江省' },
42
+ { value: '34', label: '安徽省' },
43
+ { value: '35', label: '福建省' },
44
+ { value: '36', label: '江西省' },
45
+ { value: '37', label: '山东省' },
46
+ { value: '41', label: '河南省' },
47
+ { value: '42', label: '湖北省' },
48
+ { value: '43', label: '湖南省' },
49
+ { value: '44', label: '广东省' },
50
+ { value: '45', label: '广西壮族自治区' },
51
+ { value: '46', label: '海南省' },
52
+ { value: '50', label: '重庆市' },
53
+ { value: '51', label: '四川省' },
54
+ { value: '52', label: '贵州省' },
55
+ { value: '53', label: '云南省' },
56
+ { value: '54', label: '西藏自治区' },
57
+ { value: '61', label: '陕西省' },
58
+ { value: '62', label: '甘肃省' },
59
+ { value: '63', label: '青海省' },
60
+ { value: '64', label: '宁夏回族自治区' },
61
+ { value: '65', label: '新疆维吾尔自治区' },
62
+ { value: '71', label: '台湾省' },
63
+ { value: '81', label: '香港特别行政区' },
64
+ { value: '82', label: '澳门特别行政区' },
65
+ // 9 国外
66
+ ];
67
+
68
+ export type ParsedCode = {
69
+ province: string;
70
+ city?: string;
71
+ county?: string;
72
+ town?: string;
73
+ village?: string;
74
+ codes: string[];
75
+ level: DivisionCodeLevel;
76
+ labels: string[];
77
+ };
78
+
79
+ export function parse(code: string | undefined | null | number): ParsedCode | undefined {
80
+ if (!code) return;
81
+ code = String(code);
82
+ const match = regex.exec(code);
83
+ if (!match) return;
84
+ const { province, city, county, town, village } = match.groups ?? {};
85
+ if (!province) return;
86
+
87
+ let codes = [province, city, county, town, village].filter(Boolean);
88
+ return { province, city, county, town, village, codes: codes, level: codes.length as DivisionCodeLevel };
89
+ }
90
+
91
+ export function format({
92
+ province,
93
+ city,
94
+ county,
95
+ town,
96
+ village,
97
+ }: {
98
+ province: string | number;
99
+ city?: string | number;
100
+ county?: string | number;
101
+ town?: string | number;
102
+ village?: string | number;
103
+ }): string {
104
+ const codes: string[] = [];
105
+ for (let i = 0; i < [province, city, county, town, village].length; i++) {
106
+ let x = [province, city, county, town, village][i];
107
+ if (x === undefined || x === null || x === '') {
108
+ break;
109
+ }
110
+ let len = levels[i].size;
111
+ codes.push(String(x).padStart(len, '0').slice(0, len));
112
+ }
113
+ return codes.join('');
114
+ }
115
+
116
+ // export function random(level: DivisionCodeLevel = 'County'): string {
117
+ // const l = DivisionCodeLevels.find((v) => v.code === level) || DivisionCodeLevels[2];
118
+ // const l1 = randomPick(provinces);
119
+ // if (l.level === 1) {
120
+ // return String(l1[0]);
121
+ // }
122
+ // return l1 + String(Math.floor(Math.random() * parseFloat(`1e${l.length - 2}`) - 1));
123
+ // }
168
124
  }
169
125
 
170
126
  interface DivisionTreeNode {
171
- sub: string; // sub code
172
- children?: Record<string, DivisionTreeNode>;
127
+ sub: string; // sub code
128
+ children?: Record<string, DivisionTreeNode>;
173
129
 
174
- code: string; // full code
175
- name?: string; // name of division
130
+ code: string; // full code
131
+ name?: string; // name of division
176
132
  }
177
133
 
178
134
  // export type DivisionCodeLevel = 'Village' | 'Town' | 'County' | 'City' | 'Province';
@@ -220,34 +176,34 @@ interface DivisionTreeNode {
220
176
  // }
221
177
 
222
178
  export function randomPick<T>(s: T[]) {
223
- return s[Math.floor(Math.random() * s.length)];
179
+ return s[Math.floor(Math.random() * s.length)];
224
180
  }
225
181
 
226
182
  function lookup(opts: { values: string[]; root: CodeValue[] }): { found: CodeValue[] } {
227
- const { values, root } = opts;
228
- const found: CodeValue[] = [];
229
- let currentLevel = root;
230
-
231
- for (const v of values) {
232
- const node = currentLevel.find((n) => n.value === v);
233
- if (!node) {
234
- break;
235
- }
236
-
237
- found.push(node);
238
-
239
- if (node.children) {
240
- currentLevel = node.children;
241
- } else {
242
- break;
243
- }
244
- }
245
-
246
- return { found };
183
+ const { values, root } = opts;
184
+ const found: CodeValue[] = [];
185
+ let currentLevel = root;
186
+
187
+ for (const v of values) {
188
+ const node = currentLevel.find((n) => n.value === v);
189
+ if (!node) {
190
+ break;
191
+ }
192
+
193
+ found.push(node);
194
+
195
+ if (node.children) {
196
+ currentLevel = node.children;
197
+ } else {
198
+ break;
199
+ }
200
+ }
201
+
202
+ return { found };
247
203
  }
248
204
 
249
205
  interface CodeValue {
250
- value: string;
251
- label: string;
252
- children?: Array<CodeValue>;
206
+ value: string;
207
+ label: string;
208
+ children?: Array<CodeValue>;
253
209
  }
@@ -2,23 +2,23 @@
2
2
  * ISO 7064:1983, MOD 11-2.
3
3
  */
4
4
  export class Mod11Checksum {
5
- weights = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2, 1];
5
+ weights = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2, 1];
6
6
 
7
- validate(s: string) {
8
- return s.at(-1) === this.compute(s.slice(0, s.length - 1));
9
- }
7
+ validate(s: string) {
8
+ return s.at(-1) === this.compute(s.slice(0, s.length - 1));
9
+ }
10
10
 
11
- compute(s: string) {
12
- const { weights } = this;
13
- let sum = 0;
14
- for (let i = 0; i < s.length; i++) {
15
- sum += parseInt(s[i]) * weights[i];
16
- }
17
- const num = (12 - (sum % 11)) % 11;
18
- if (num < 10) {
19
- return num.toString();
20
- } else {
21
- return 'X';
22
- }
23
- }
11
+ compute(s: string) {
12
+ const { weights } = this;
13
+ let sum = 0;
14
+ for (let i = 0; i < s.length; i++) {
15
+ sum += parseInt(s[i]) * weights[i];
16
+ }
17
+ const num = (12 - (sum % 11)) % 11;
18
+ if (num < 10) {
19
+ return num.toString();
20
+ } else {
21
+ return 'X';
22
+ }
23
+ }
24
24
  }
@@ -4,33 +4,33 @@
4
4
  * Mod31-3
5
5
  */
6
6
  export class Mod31Checksum {
7
- weights = [1, 3, 9, 27, 19, 26, 16, 17, 20, 29, 25, 13, 8, 24, 10, 30, 28];
8
- chars = '0123456789ABCDEFGHJKLMNPQRTUWXY';
9
- numbers: Record<string, number> = this.chars.split('').reduce(
10
- (acc, cur, i) => {
11
- acc[cur] = i;
12
- return acc;
13
- },
14
- {} as Record<string, number>,
15
- );
7
+ weights = [1, 3, 9, 27, 19, 26, 16, 17, 20, 29, 25, 13, 8, 24, 10, 30, 28];
8
+ chars = '0123456789ABCDEFGHJKLMNPQRTUWXY';
9
+ numbers: Record<string, number> = this.chars.split('').reduce(
10
+ (acc, cur, i) => {
11
+ acc[cur] = i;
12
+ return acc;
13
+ },
14
+ {} as Record<string, number>,
15
+ );
16
16
 
17
- toChar(n: number) {
18
- return this.chars[n];
19
- }
17
+ toChar(n: number) {
18
+ return this.chars[n];
19
+ }
20
20
 
21
- toNumber(c: string) {
22
- return this.numbers[c];
23
- }
21
+ toNumber(c: string) {
22
+ return this.numbers[c];
23
+ }
24
24
 
25
- validate(s: string) {
26
- return s.at(-1) === this.compute(s.slice(0, s.length - 1));
27
- }
25
+ validate(s: string) {
26
+ return s.at(-1) === this.compute(s.slice(0, s.length - 1));
27
+ }
28
28
 
29
- compute(s: string) {
30
- let sum = 0;
31
- for (let i = 0; i < s.length; i++) {
32
- sum += this.numbers[s[i]] * this.weights[i];
33
- }
34
- return this.toChar(31 - (sum % 31));
35
- }
29
+ compute(s: string) {
30
+ let sum = 0;
31
+ for (let i = 0; i < s.length; i++) {
32
+ sum += this.numbers[s[i]] * this.weights[i];
33
+ }
34
+ return this.toChar(31 - (sum % 31));
35
+ }
36
36
  }
@@ -2,20 +2,16 @@ import { describe, expect, it } from 'vitest';
2
2
  import { ResidentIdentityCardNumber } from './ResidentIdentityCardNumber';
3
3
 
4
4
  describe('ResidentIdentityCardNumber', () => {
5
- it('should parse', () => {
6
- for (const a of ['11010519491231002X']) {
7
- let out = ResidentIdentityCardNumber.parse(a);
8
- expect(ResidentIdentityCardNumber.format(out)).toBe(a);
9
- expect(out).toMatchSnapshot();
10
- }
11
- });
12
- it('should format with partial', () => {
13
- expect(
14
- ResidentIdentityCardNumber.format({
15
- division: '110105',
16
- birthDate: new Date('1949-12-31'),
17
- sequence: 2,
18
- }),
19
- ).toBe('11010519491231002X');
20
- });
5
+ it('should parse', () => {
6
+ for (const a of ['11010519491231002X']) {
7
+ let out = ResidentIdentityCardNumber.parse(a);
8
+ expect(ResidentIdentityCardNumber.format(out)).toBe(a);
9
+ expect(out).toMatchSnapshot();
10
+ }
11
+ });
12
+ it('should format with partial', () => {
13
+ expect(
14
+ ResidentIdentityCardNumber.format({ division: '110105', birthDate: new Date('1949-12-31'), sequence: 2 }),
15
+ ).toBe('11010519491231002X');
16
+ });
21
17
  });
@@ -2,95 +2,95 @@ import { formatDate } from './formatDate';
2
2
  import { Mod11Checksum } from './Mod11Checksum';
3
3
 
4
4
  export namespace ResidentIdentityCardNumber {
5
- export const Checksum = new Mod11Checksum();
5
+ export const Checksum = new Mod11Checksum();
6
6
 
7
- /*
7
+ /*
8
8
  export const length = 18;
9
9
  export const pattern = /^\d{6}(18|19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[0-9Xx]$/;
10
10
  */
11
11
 
12
- export function parse(s: string): ParsedResidentIdentityCardNumber {
13
- const division = s.slice(0, 6);
14
- const birthDate = s.slice(6, 14);
15
- const sequence = parseInt(s.slice(14, 17), 10);
16
- const checksum = s.slice(17, 18);
17
- const valid = Checksum.validate(s);
18
- const sex = sequence % 2 === 1 ? 'Male' : 'Female';
19
- return {
20
- division,
21
- birthDate,
22
- sequence,
23
- checksum,
24
- valid,
25
- sex,
26
- male: sex === 'Male',
27
- female: sex === 'Female',
28
- age: new Date().getFullYear() - parseInt(birthDate.slice(0, 4), 10),
29
- };
30
- }
12
+ export function parse(s: string): ParsedResidentIdentityCardNumber {
13
+ const division = s.slice(0, 6);
14
+ const birthDate = s.slice(6, 14);
15
+ const sequence = parseInt(s.slice(14, 17), 10);
16
+ const checksum = s.slice(17, 18);
17
+ const valid = Checksum.validate(s);
18
+ const sex = sequence % 2 === 1 ? 'Male' : 'Female';
19
+ return {
20
+ division,
21
+ birthDate,
22
+ sequence,
23
+ checksum,
24
+ valid,
25
+ sex,
26
+ male: sex === 'Male',
27
+ female: sex === 'Female',
28
+ age: new Date().getFullYear() - parseInt(birthDate.slice(0, 4), 10),
29
+ };
30
+ }
31
31
 
32
- export function format({
33
- division,
34
- birthDate,
35
- sequence,
36
- checksum,
37
- }: {
38
- division: string;
39
- birthDate: string | Date;
40
- sequence: number | string;
41
- checksum?: string;
42
- }) {
43
- if (typeof birthDate !== 'string') {
44
- birthDate = formatDate(birthDate, 'yyyyMMDD');
45
- }
46
- if (birthDate.includes('-')) {
47
- birthDate = birthDate.replace(/-/g, '');
48
- }
49
- typeof sequence === 'number' && (sequence = sequence.toString());
50
- sequence = sequence.padStart(3, '0');
51
- const base = [division, birthDate, sequence].join('');
52
- if (base.length !== 17) throw new Error('Invalid params');
53
- checksum ||= Checksum.compute(base);
54
- return base + checksum;
55
- }
32
+ export function format({
33
+ division,
34
+ birthDate,
35
+ sequence,
36
+ checksum,
37
+ }: {
38
+ division: string;
39
+ birthDate: string | Date;
40
+ sequence: number | string;
41
+ checksum?: string;
42
+ }) {
43
+ if (typeof birthDate !== 'string') {
44
+ birthDate = formatDate(birthDate, 'yyyyMMDD');
45
+ }
46
+ if (birthDate.includes('-')) {
47
+ birthDate = birthDate.replace(/-/g, '');
48
+ }
49
+ typeof sequence === 'number' && (sequence = sequence.toString());
50
+ sequence = sequence.padStart(3, '0');
51
+ const base = [division, birthDate, sequence].join('');
52
+ if (base.length !== 17) throw new Error('Invalid params');
53
+ checksum ||= Checksum.compute(base);
54
+ return base + checksum;
55
+ }
56
56
  }
57
57
  export interface ParsedResidentIdentityCardNumber {
58
- /**
59
- * @title 行政区划代码
60
- */
61
- division: string;
62
- /**
63
- * @title 出生日期
64
- * 格式 yyyyMMDD
65
- */
66
- birthDate: string;
67
- /**
68
- * @title 顺序码
69
- */
70
- sequence: number;
71
- /**
72
- * @title 校验位
73
- */
74
- checksum: string;
58
+ /**
59
+ * @title 行政区划代码
60
+ */
61
+ division: string;
62
+ /**
63
+ * @title 出生日期
64
+ * 格式 yyyyMMDD
65
+ */
66
+ birthDate: string;
67
+ /**
68
+ * @title 顺序码
69
+ */
70
+ sequence: number;
71
+ /**
72
+ * @title 校验位
73
+ */
74
+ checksum: string;
75
75
 
76
- /**
77
- * @title 是否有效
78
- */
79
- valid: boolean;
80
- /**
81
- * @title 是否男性
82
- */
83
- male: boolean;
84
- /**
85
- * @title 是否女性
86
- */
87
- female: boolean;
88
- /**
89
- * @title 性别
90
- */
91
- sex: 'Male' | 'Female';
92
- /**
93
- * @title 年龄
94
- */
95
- age: number;
76
+ /**
77
+ * @title 是否有效
78
+ */
79
+ valid: boolean;
80
+ /**
81
+ * @title 是否男性
82
+ */
83
+ male: boolean;
84
+ /**
85
+ * @title 是否女性
86
+ */
87
+ female: boolean;
88
+ /**
89
+ * @title 性别
90
+ */
91
+ sex: 'Male' | 'Female';
92
+ /**
93
+ * @title 年龄
94
+ */
95
+ age: number;
96
96
  }