thai_id_utils 0.1.1 → 0.2.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/CHANGELOG.md +41 -0
- data/LICENSE +21 -0
- data/README.md +5 -4
- data/lib/thai_id_utils/version.rb +1 -1
- data/lib/thai_id_utils.rb +132 -13
- metadata +11 -7
- data/Gemfile +0 -5
- data/Rakefile +0 -9
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a190bd99a1b5194f6dd8b8fb9b54ff187f8612acef24e17d86fbbe058f404e49
|
|
4
|
+
data.tar.gz: a13c400601b9ae3f4426fe8dfb619403cbd52ae511552b37c5617f0ae0518c8f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fbfe9afb6a1f1d13f4b8baa45e04287ba46b2c28abdf192fff6a7dd6debc745f55fefc7ea332fd601df7d9e3fdc41194e90da539648eb8038c19e4c72ede7232
|
|
7
|
+
data.tar.gz: 3503c0c47a6b022689f4a46958b03caf7e269b18131422aa2e6db107ce64d8c51fb9da675084844ef27aee650af22beecd8313ba679d81e92db0960c76eaf13c
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.2.0] - 2025-06-15
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- Province code lookup (`PROVINCE_CODES`) mapping all 77 Thai provinces
|
|
12
|
+
- `province_name(code)` — return province name from 2-digit code
|
|
13
|
+
- Laser ID validation (`laser_id_valid?`) using format `XXN-NNNNNNN-NN`
|
|
14
|
+
- Laser ID decoding (`laser_id_decode`) into hardware version, box ID, and position
|
|
15
|
+
- Buddhist Era conversion: `be_to_ce(year)` and `ce_to_be(year)`
|
|
16
|
+
- `province_name` field included in `decode` output hash
|
|
17
|
+
|
|
18
|
+
## [0.1.2] - 2025-06-15
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
- Minor internal cleanup; no public API changes
|
|
22
|
+
|
|
23
|
+
## [0.1.1] - 2025-06-15
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
- Gemspec corrections and metadata updates
|
|
27
|
+
|
|
28
|
+
## [0.1.0] - 2025-06-15
|
|
29
|
+
|
|
30
|
+
### Added
|
|
31
|
+
- Initial release
|
|
32
|
+
- `valid?(id)` — checksum validation using Thailand's modulus-11 algorithm
|
|
33
|
+
- `decode(id)` — decode category, office code, province code, district code, sequence, and registration code
|
|
34
|
+
- `generate(...)` — generate a random valid 13-digit Thai national ID with optional overrides
|
|
35
|
+
- `category_description(category)` — human-readable description of ID category codes (0–8)
|
|
36
|
+
- `InvalidIDError` — raised on invalid IDs passed to `decode`
|
|
37
|
+
|
|
38
|
+
[0.2.0]: https://github.com/chayuto/thai_id_utils/compare/v0.1.2...v0.2.0
|
|
39
|
+
[0.1.2]: https://github.com/chayuto/thai_id_utils/compare/v0.1.1...v0.1.2
|
|
40
|
+
[0.1.1]: https://github.com/chayuto/thai_id_utils/compare/v0.1.0...v0.1.1
|
|
41
|
+
[0.1.0]: https://github.com/chayuto/thai_id_utils/releases/tag/v0.1.0
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Chayut Orapinpatipat
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
CHANGED
|
@@ -11,23 +11,24 @@ require "thai_id_utils"
|
|
|
11
11
|
|
|
12
12
|
id = "3012304567082"
|
|
13
13
|
|
|
14
|
-
#
|
|
14
|
+
# Validate checksum / ตรวจสอบความถูกต้องของ checksum
|
|
15
15
|
if ThaiIdUtils.valid?(id)
|
|
16
16
|
puts "Valid!"
|
|
17
17
|
else
|
|
18
18
|
puts "Invalid ID"
|
|
19
19
|
end
|
|
20
20
|
|
|
21
|
-
#
|
|
21
|
+
# Decode components / ถอดรหัสส่วนประกอบ
|
|
22
22
|
info = ThaiIdUtils.decode(id)
|
|
23
23
|
# => { category: 1, office_code: "6099", district_code: "99", sequence: "00257" }
|
|
24
24
|
puts info.inspect
|
|
25
25
|
|
|
26
|
-
#
|
|
26
|
+
# Get category description / คำอธิบายประเภท
|
|
27
27
|
desc = ThaiIdUtils.category_description(info[:category])
|
|
28
28
|
# => "Thai nationals who were born after 1 January 1984 and had their birth notified within the given deadline (15 days)."
|
|
29
29
|
puts desc
|
|
30
30
|
|
|
31
|
-
#
|
|
31
|
+
# Generate a new random valid ID / สร้างหมายเลขบัตรประชาชนใหม่แบบสุ่มที่ถูกต้อง
|
|
32
32
|
new_id = ThaiIdUtils.generate
|
|
33
33
|
puts new_id # => e.g. "3601205234518"
|
|
34
|
+
```
|
data/lib/thai_id_utils.rb
CHANGED
|
@@ -21,9 +21,58 @@ module ThaiIdUtils
|
|
|
21
21
|
7 => 'Children of people of category 6 who were born in Thailand',
|
|
22
22
|
8 => 'Foreign nationals who are living in Thailand permanently or Thai nationals by naturalization'
|
|
23
23
|
}.freeze
|
|
24
|
+
|
|
25
|
+
# Mapping of 2-digit province codes (digits 2-3 of the ID) to province names
|
|
26
|
+
PROVINCE_CODES = {
|
|
27
|
+
'10' => 'Bangkok', '11' => 'Samut Prakan',
|
|
28
|
+
'12' => 'Nonthaburi', '13' => 'Pathum Thani',
|
|
29
|
+
'14' => 'Phra Nakhon Si Ayutthaya', '15' => 'Ang Thong',
|
|
30
|
+
'16' => 'Lopburi', '17' => 'Sing Buri',
|
|
31
|
+
'18' => 'Chainat', '19' => 'Saraburi',
|
|
32
|
+
'20' => 'Chonburi', '21' => 'Rayong',
|
|
33
|
+
'22' => 'Chanthaburi', '23' => 'Trat',
|
|
34
|
+
'24' => 'Chachoengsao', '25' => 'Prachin Buri',
|
|
35
|
+
'26' => 'Nakhon Nayok', '27' => 'Sa Kaeo',
|
|
36
|
+
'30' => 'Nakhon Ratchasima', '31' => 'Buri Ram',
|
|
37
|
+
'32' => 'Surin', '33' => 'Si Sa Ket',
|
|
38
|
+
'34' => 'Ubon Ratchathani', '35' => 'Yasothon',
|
|
39
|
+
'36' => 'Chaiyaphum', '37' => 'Amnat Charoen',
|
|
40
|
+
'38' => 'Bueng Kan', '39' => 'Nong Bua Lamphu',
|
|
41
|
+
'40' => 'Khon Kaen', '41' => 'Udon Thani',
|
|
42
|
+
'42' => 'Loei', '43' => 'Nong Khai',
|
|
43
|
+
'44' => 'Maha Sarakham', '45' => 'Roi Et',
|
|
44
|
+
'46' => 'Kalasin', '47' => 'Sakon Nakhon',
|
|
45
|
+
'48' => 'Nakhon Phanom', '49' => 'Mukdahan',
|
|
46
|
+
'50' => 'Chiang Mai', '51' => 'Lamphun',
|
|
47
|
+
'52' => 'Lampang', '53' => 'Uttaradit',
|
|
48
|
+
'54' => 'Phrae', '55' => 'Nan',
|
|
49
|
+
'56' => 'Phayao', '57' => 'Chiang Rai',
|
|
50
|
+
'58' => 'Mae Hong Son',
|
|
51
|
+
'60' => 'Nakhon Sawan', '61' => 'Uthai Thani',
|
|
52
|
+
'62' => 'Kamphaeng Phet', '63' => 'Tak',
|
|
53
|
+
'64' => 'Sukhothai', '65' => 'Phitsanulok',
|
|
54
|
+
'66' => 'Phichit', '67' => 'Phetchabun',
|
|
55
|
+
'70' => 'Ratchaburi', '71' => 'Kanchanaburi',
|
|
56
|
+
'72' => 'Suphanburi', '73' => 'Nakhon Pathom',
|
|
57
|
+
'74' => 'Samut Sakhon', '75' => 'Samut Songkhram',
|
|
58
|
+
'76' => 'Phetchaburi', '77' => 'Prachuap Khiri Khan',
|
|
59
|
+
'80' => 'Nakhon Si Thammarat', '81' => 'Krabi',
|
|
60
|
+
'82' => 'Phangnga', '83' => 'Phuket',
|
|
61
|
+
'84' => 'Surat Thani', '85' => 'Ranong',
|
|
62
|
+
'86' => 'Chumphon',
|
|
63
|
+
'90' => 'Songkhla', '91' => 'Satun',
|
|
64
|
+
'92' => 'Trang', '93' => 'Phatthalung',
|
|
65
|
+
'94' => 'Pattani', '95' => 'Yala',
|
|
66
|
+
'96' => 'Narathiwat'
|
|
67
|
+
}.freeze
|
|
24
68
|
# rubocop:enable Layout/LineLength
|
|
25
69
|
|
|
26
|
-
|
|
70
|
+
LASER_ID_FORMAT = /\A[A-Z]{2}\d-\d{7}-\d{2}\z/.freeze
|
|
71
|
+
|
|
72
|
+
# Validate a Thai national ID using Thailand’s modulus-11 checksum algorithm.
|
|
73
|
+
#
|
|
74
|
+
# @param id [String, Integer] 13-digit Thai national ID number
|
|
75
|
+
# @return [Boolean] true if the checksum is valid, false otherwise
|
|
27
76
|
def self.valid?(id)
|
|
28
77
|
digits = id.to_s.chars.map(&:to_i)
|
|
29
78
|
return false unless digits.size == 13
|
|
@@ -34,12 +83,18 @@ module ThaiIdUtils
|
|
|
34
83
|
false
|
|
35
84
|
end
|
|
36
85
|
|
|
37
|
-
#
|
|
38
|
-
#
|
|
39
|
-
#
|
|
40
|
-
#
|
|
41
|
-
#
|
|
42
|
-
#
|
|
86
|
+
# Decode the components encoded in a Thai national ID number.
|
|
87
|
+
#
|
|
88
|
+
# @param id [String, Integer] 13-digit Thai national ID number
|
|
89
|
+
# @return [Hash] decoded fields:
|
|
90
|
+
# - `:category` [Integer] — registration category (0–8)
|
|
91
|
+
# - `:office_code` [String] — 4-digit registrar code (province + district)
|
|
92
|
+
# - `:province_code` [String] — first 2 digits of office_code
|
|
93
|
+
# - `:province_name` [String, nil] — province name, or nil if unknown
|
|
94
|
+
# - `:district_code` [String] — last 2 digits of office_code
|
|
95
|
+
# - `:sequence` [String] — 5-digit personal sequence number
|
|
96
|
+
# - `:registration_code` [String] — 2-digit chronological sequence marker
|
|
97
|
+
# @raise [InvalidIDError] if the ID fails checksum validation
|
|
43
98
|
def self.decode(id)
|
|
44
99
|
raise InvalidIDError, 'Invalid ID' unless valid?(id)
|
|
45
100
|
|
|
@@ -48,14 +103,23 @@ module ThaiIdUtils
|
|
|
48
103
|
{
|
|
49
104
|
category: d[0].to_i,
|
|
50
105
|
office_code: d[1..4].join,
|
|
106
|
+
province_code: d[1..2].join,
|
|
107
|
+
province_name: PROVINCE_CODES[d[1..2].join],
|
|
51
108
|
district_code: d[3..4].join,
|
|
52
|
-
sequence: d[5..9].join
|
|
109
|
+
sequence: d[5..9].join,
|
|
110
|
+
registration_code: d[10..11].join
|
|
53
111
|
}
|
|
54
112
|
end
|
|
55
113
|
|
|
56
|
-
#
|
|
57
|
-
#
|
|
58
|
-
#
|
|
114
|
+
# Generate a random, valid 13-digit Thai national ID.
|
|
115
|
+
# Any component can be overridden; the rest is randomized and the checksum is computed.
|
|
116
|
+
#
|
|
117
|
+
# @param category [Integer] ID category (1–8), default: random 1–6
|
|
118
|
+
# @param office_code [Integer, String, nil] 4-digit registrar code, default: random
|
|
119
|
+
# @param district_code [String, nil] 2-digit district override within office_code
|
|
120
|
+
# @param sequence [Integer, String, nil] 5-digit personal sequence, default: random
|
|
121
|
+
# @return [String] a valid 13-digit Thai national ID
|
|
122
|
+
# rubocop:disable Metrics/AbcSize
|
|
59
123
|
def self.generate(category: rand(1..6),
|
|
60
124
|
office_code: nil,
|
|
61
125
|
district_code: nil,
|
|
@@ -80,10 +144,65 @@ module ThaiIdUtils
|
|
|
80
144
|
|
|
81
145
|
(digits + [check]).join
|
|
82
146
|
end
|
|
83
|
-
# rubocop:enable Metrics/AbcSize
|
|
147
|
+
# rubocop:enable Metrics/AbcSize
|
|
84
148
|
|
|
85
|
-
#
|
|
149
|
+
# Return the human-readable description for a Thai ID category code.
|
|
150
|
+
#
|
|
151
|
+
# @param category [Integer, String] category digit (0–8)
|
|
152
|
+
# @return [String] description, or "Unknown category" if not found
|
|
86
153
|
def self.category_description(category)
|
|
87
154
|
CATEGORY_DESCRIPTIONS[category.to_i] || 'Unknown category'
|
|
88
155
|
end
|
|
156
|
+
|
|
157
|
+
# Return the province name for a 2-digit province code.
|
|
158
|
+
#
|
|
159
|
+
# @param code [String] 2-digit province code (e.g., "10" for Bangkok)
|
|
160
|
+
# @return [String, nil] province name, or nil if the code is not recognized
|
|
161
|
+
def self.province_name(code)
|
|
162
|
+
PROVINCE_CODES[code.to_s]
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# Convert a Buddhist Era year to Common Era (subtract 543).
|
|
166
|
+
#
|
|
167
|
+
# @param year [Integer, String] Buddhist Era year (e.g., 2567)
|
|
168
|
+
# @return [Integer] Common Era year (e.g., 2024)
|
|
169
|
+
def self.be_to_ce(year)
|
|
170
|
+
year.to_i - 543
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# Convert a Common Era year to Buddhist Era (add 543).
|
|
174
|
+
#
|
|
175
|
+
# @param year [Integer, String] Common Era year (e.g., 2024)
|
|
176
|
+
# @return [Integer] Buddhist Era year (e.g., 2567)
|
|
177
|
+
def self.ce_to_be(year)
|
|
178
|
+
year.to_i + 543
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# Validate the format of a Thai ID card laser ID (printed on the card back).
|
|
182
|
+
# Expected format: XXN-NNNNNNN-NN (e.g., JC1-0002507-15)
|
|
183
|
+
#
|
|
184
|
+
# @param laser_id [String] the laser ID string to validate
|
|
185
|
+
# @return [Boolean] true if format matches, false otherwise
|
|
186
|
+
def self.laser_id_valid?(laser_id)
|
|
187
|
+
LASER_ID_FORMAT.match?(laser_id.to_s)
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# Decode a Thai ID card laser ID into its components.
|
|
191
|
+
#
|
|
192
|
+
# @param laser_id [String] laser ID string (e.g., "JC1-0002507-15")
|
|
193
|
+
# @return [Hash] decoded fields:
|
|
194
|
+
# - `:hardware_version` [String] — chip generation code (e.g., "JC1")
|
|
195
|
+
# - `:box_id` [String] — distribution box number (e.g., "0002507")
|
|
196
|
+
# - `:position` [String] — slot within the box (e.g., "15")
|
|
197
|
+
# @raise [InvalidIDError] if the laser ID format is invalid
|
|
198
|
+
def self.laser_id_decode(laser_id)
|
|
199
|
+
raise InvalidIDError, 'Invalid laser ID' unless laser_id_valid?(laser_id)
|
|
200
|
+
|
|
201
|
+
parts = laser_id.to_s.split('-')
|
|
202
|
+
{
|
|
203
|
+
hardware_version: parts[0],
|
|
204
|
+
box_id: parts[1],
|
|
205
|
+
position: parts[2]
|
|
206
|
+
}
|
|
207
|
+
end
|
|
89
208
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: thai_id_utils
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Chayut Orapinpatipat
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-03-10 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: minitest
|
|
@@ -36,15 +36,19 @@ executables: []
|
|
|
36
36
|
extensions: []
|
|
37
37
|
extra_rdoc_files: []
|
|
38
38
|
files:
|
|
39
|
-
-
|
|
39
|
+
- CHANGELOG.md
|
|
40
|
+
- LICENSE
|
|
40
41
|
- README.md
|
|
41
|
-
- Rakefile
|
|
42
42
|
- lib/thai_id_utils.rb
|
|
43
43
|
- lib/thai_id_utils/version.rb
|
|
44
|
-
homepage: https://github.com/chayuto/
|
|
44
|
+
homepage: https://github.com/chayuto/thai_id_utils
|
|
45
45
|
licenses:
|
|
46
46
|
- MIT
|
|
47
|
-
metadata:
|
|
47
|
+
metadata:
|
|
48
|
+
documentation_uri: https://rubydoc.info/gems/thai_id_utils
|
|
49
|
+
source_code_uri: https://github.com/chayuto/thai_id_utils
|
|
50
|
+
changelog_uri: https://github.com/chayuto/thai_id_utils/blob/main/CHANGELOG.md
|
|
51
|
+
rubygems_mfa_required: 'true'
|
|
48
52
|
post_install_message:
|
|
49
53
|
rdoc_options: []
|
|
50
54
|
require_paths:
|
|
@@ -60,7 +64,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
60
64
|
- !ruby/object:Gem::Version
|
|
61
65
|
version: '0'
|
|
62
66
|
requirements: []
|
|
63
|
-
rubygems_version: 3.5.
|
|
67
|
+
rubygems_version: 3.5.22
|
|
64
68
|
signing_key:
|
|
65
69
|
specification_version: 4
|
|
66
70
|
summary: Validate and decode Thai national ID numbers
|
data/Gemfile
DELETED