eu_vat_rates_data 2026.3.30 → 2026.3.31

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1aa8c1e73bfff75f7bfc71c45045e28046e27b74a0bbbfe3379bc9af017a3c48
4
- data.tar.gz: dd3c4d519da5941cf04c764fc7c1deab569df162572623ebaa6f12a445ec3710
3
+ metadata.gz: 9aa5cfb0609e948804ee35b2314921d7f07a8561b015a35fde1b24465e6eac39
4
+ data.tar.gz: 909f7e7e7a454a4c83f10030c48e1bdcebdf2dffd96d00dfe47ef8dc017533f4
5
5
  SHA512:
6
- metadata.gz: 6b705bd46c7576224e9140296600e7d4644f02cb6acd3651a49430dad32acfac963e29dd41338badfd8130844f1be1e06dd59f4f1d409e8d41b45ecd5db0224f
7
- data.tar.gz: 140db6e9b3c1e05078040d26df4631851a19dc57c48c66d7e6a5bb9f3102bd4a1fd77cd54f8c2b9de9ca55847aa0920dbaf997157f6a1ccf47ab0b671193051c
6
+ metadata.gz: 47f2770e396b04e9140dd3aacae12c3980eb4a0906d9fd2d6e9a5bde6ca3b75f23a29e349f4d866b5cb4065a907def92c4d4184beb580703b06e206f984cdfe0
7
+ data.tar.gz: cc8cca83cf408f8a816440b86323caeb3e4f451de7c59da4ab647a4453f5f79309ac83b6cceca282b8838e9a417687b17315aec60defbf05750605e4b209b594
data/README.md CHANGED
@@ -10,6 +10,8 @@ VAT rates for **44 European countries** — EU-27 plus Norway, Switzerland, UK,
10
10
  - `eu_member` flag on every country — `true` for EU-27, `false` for non-EU
11
11
  - `vat_name` — official name of the VAT tax in the country's primary official language
12
12
  - `vat_abbr` — short abbreviation used locally (e.g. "ALV", "MwSt", "TVA")
13
+ - **`format` — human-readable VAT number format (e.g. `"ATU + 8 digits"`)** — unique to this package
14
+ - **`pattern` — regex for VAT number validation + built-in `valid_format?()` — free, no API key needed** — unique to this package
13
15
  - No dependencies — pure Ruby 3.0+
14
16
  - Data bundled in the gem — works offline, no network calls
15
17
  - EU rates checked daily via GitHub Actions, new version published only when rates change
@@ -67,6 +69,16 @@ end
67
69
 
68
70
  # When were EU rates last fetched?
69
71
  puts EuVatRatesData.data_version # e.g. "2026-03-27"
72
+
73
+ # VAT number format validation — no API key, no network call
74
+ EuVatRatesData.valid_format?("ATU12345678") # => true
75
+ EuVatRatesData.valid_format?("DE123456789") # => true
76
+ EuVatRatesData.valid_format?("INVALID") # => false
77
+
78
+ # Access format metadata directly
79
+ at = EuVatRatesData.get_rate("AT")
80
+ puts at["format"] # "ATU + 8 digits"
81
+ puts at["pattern"] # "^ATU\\d{8}$"
70
82
  ```
71
83
 
72
84
  ---
@@ -79,6 +91,23 @@ puts EuVatRatesData.data_version # e.g. "2026-03-27"
79
91
 
80
92
  ---
81
93
 
94
+
95
+ ## Keeping rates current
96
+
97
+ Rates are bundled at install time. A new package version is published automatically whenever rates change — but your installed version will not update itself.
98
+
99
+ **Recommended:** add [Renovate](https://renovatebot.com) or [Dependabot](https://docs.github.com/en/code-security/dependabot) to your repo. They detect new versions and open a PR automatically whenever rates change — no manual update commands needed.
100
+
101
+ **Need real-time accuracy?** Fetch the always-current JSON directly:
102
+
103
+ ```
104
+ https://cdn.jsdelivr.net/gh/vatnode/eu-vat-rates-data@main/data/eu-vat-rates-data.json
105
+ ```
106
+
107
+ No package needed — parse it with a single `fetch()` / `http.get()` / `file_get_contents()` call and cache locally.
108
+
109
+ ---
110
+
82
111
  ## Covered countries
83
112
 
84
113
  **EU-27** (daily auto-updates via EC TEDB):
@@ -14,7 +14,9 @@
14
14
  2.5
15
15
  ],
16
16
  "super_reduced": null,
17
- "parking": null
17
+ "parking": null,
18
+ "format": "AD + alphanumeric",
19
+ "pattern": null
18
20
  },
19
21
  "AL": {
20
22
  "country": "Albania",
@@ -28,7 +30,9 @@
28
30
  10.0
29
31
  ],
30
32
  "super_reduced": null,
31
- "parking": null
33
+ "parking": null,
34
+ "format": "AL + 1 letter + 8 digits + 1 letter",
35
+ "pattern": "^AL[A-Z]\\d{8}[A-Z]$"
32
36
  },
33
37
  "AT": {
34
38
  "country": "Austria",
@@ -43,7 +47,9 @@
43
47
  19.0
44
48
  ],
45
49
  "super_reduced": null,
46
- "parking": null
50
+ "parking": null,
51
+ "format": "ATU + 8 digits",
52
+ "pattern": "^ATU\\d{8}$"
47
53
  },
48
54
  "BA": {
49
55
  "country": "Bosnia and Herzegovina",
@@ -54,7 +60,9 @@
54
60
  "standard": 17.0,
55
61
  "reduced": [],
56
62
  "super_reduced": null,
57
- "parking": null
63
+ "parking": null,
64
+ "format": "BA + alphanumeric",
65
+ "pattern": null
58
66
  },
59
67
  "BE": {
60
68
  "country": "Belgium",
@@ -68,7 +76,9 @@
68
76
  12.0
69
77
  ],
70
78
  "super_reduced": null,
71
- "parking": 12.0
79
+ "parking": 12.0,
80
+ "format": "BE + 0/1 + 9 digits",
81
+ "pattern": "^BE[01]\\d{9}$"
72
82
  },
73
83
  "BG": {
74
84
  "country": "Bulgaria",
@@ -81,7 +91,9 @@
81
91
  9.0
82
92
  ],
83
93
  "super_reduced": null,
84
- "parking": null
94
+ "parking": null,
95
+ "format": "BG + 9–10 digits",
96
+ "pattern": "^BG\\d{9,10}$"
85
97
  },
86
98
  "CH": {
87
99
  "country": "Switzerland",
@@ -95,7 +107,9 @@
95
107
  3.8
96
108
  ],
97
109
  "super_reduced": null,
98
- "parking": null
110
+ "parking": null,
111
+ "format": "CHE + 9 digits (+ MWST/TVA/IVA)",
112
+ "pattern": "^CHE\\d{9}(MWST|TVA|IVA)?$"
99
113
  },
100
114
  "CY": {
101
115
  "country": "Cyprus",
@@ -109,7 +123,9 @@
109
123
  9.0
110
124
  ],
111
125
  "super_reduced": 3.0,
112
- "parking": null
126
+ "parking": null,
127
+ "format": "CY + 8 digits + 1 letter",
128
+ "pattern": "^CY\\d{8}[A-Z]$"
113
129
  },
114
130
  "CZ": {
115
131
  "country": "Czech Republic",
@@ -122,7 +138,9 @@
122
138
  12.0
123
139
  ],
124
140
  "super_reduced": null,
125
- "parking": null
141
+ "parking": null,
142
+ "format": "CZ + 8–10 digits",
143
+ "pattern": "^CZ\\d{8,10}$"
126
144
  },
127
145
  "DE": {
128
146
  "country": "Germany",
@@ -135,7 +153,9 @@
135
153
  7.0
136
154
  ],
137
155
  "super_reduced": null,
138
- "parking": null
156
+ "parking": null,
157
+ "format": "DE + 9 digits",
158
+ "pattern": "^DE\\d{9}$"
139
159
  },
140
160
  "DK": {
141
161
  "country": "Denmark",
@@ -146,7 +166,9 @@
146
166
  "standard": 25.0,
147
167
  "reduced": [],
148
168
  "super_reduced": null,
149
- "parking": null
169
+ "parking": null,
170
+ "format": "DK + 8 digits",
171
+ "pattern": "^DK\\d{8}$"
150
172
  },
151
173
  "EE": {
152
174
  "country": "Estonia",
@@ -160,7 +182,9 @@
160
182
  13.0
161
183
  ],
162
184
  "super_reduced": null,
163
- "parking": null
185
+ "parking": null,
186
+ "format": "EE + 9 digits",
187
+ "pattern": "^EE\\d{9}$"
164
188
  },
165
189
  "ES": {
166
190
  "country": "Spain",
@@ -173,7 +197,9 @@
173
197
  10.0
174
198
  ],
175
199
  "super_reduced": 4.0,
176
- "parking": null
200
+ "parking": null,
201
+ "format": "ES + letter/digit + 7 digits + letter/digit",
202
+ "pattern": "^ES[A-Z0-9]\\d{7}[A-Z0-9]$"
177
203
  },
178
204
  "FI": {
179
205
  "country": "Finland",
@@ -187,7 +213,9 @@
187
213
  13.5
188
214
  ],
189
215
  "super_reduced": null,
190
- "parking": null
216
+ "parking": null,
217
+ "format": "FI + 8 digits",
218
+ "pattern": "^FI\\d{8}$"
191
219
  },
192
220
  "FR": {
193
221
  "country": "France",
@@ -205,7 +233,9 @@
205
233
  13.0
206
234
  ],
207
235
  "super_reduced": 2.1,
208
- "parking": null
236
+ "parking": null,
237
+ "format": "FR + 2 alphanumeric + 9 digits",
238
+ "pattern": "^FR[A-HJ-NP-Z0-9]{2}\\d{9}$"
209
239
  },
210
240
  "GB": {
211
241
  "country": "United Kingdom",
@@ -218,7 +248,9 @@
218
248
  5.0
219
249
  ],
220
250
  "super_reduced": null,
221
- "parking": null
251
+ "parking": null,
252
+ "format": "GB + 9 or 12 digits (or GD/HA + 3 digits)",
253
+ "pattern": "^GB(\\d{9}|\\d{12}|GD\\d{3}|HA\\d{3})$"
222
254
  },
223
255
  "GE": {
224
256
  "country": "Georgia",
@@ -229,7 +261,9 @@
229
261
  "standard": 18.0,
230
262
  "reduced": [],
231
263
  "super_reduced": null,
232
- "parking": null
264
+ "parking": null,
265
+ "format": "GE + alphanumeric",
266
+ "pattern": null
233
267
  },
234
268
  "GR": {
235
269
  "country": "Greece",
@@ -244,7 +278,9 @@
244
278
  17.0
245
279
  ],
246
280
  "super_reduced": 4.0,
247
- "parking": 13.0
281
+ "parking": 13.0,
282
+ "format": "EL + 9 digits",
283
+ "pattern": "^EL\\d{9}$"
248
284
  },
249
285
  "HR": {
250
286
  "country": "Croatia",
@@ -258,7 +294,9 @@
258
294
  13.0
259
295
  ],
260
296
  "super_reduced": null,
261
- "parking": null
297
+ "parking": null,
298
+ "format": "HR + 11 digits",
299
+ "pattern": "^HR\\d{11}$"
262
300
  },
263
301
  "HU": {
264
302
  "country": "Hungary",
@@ -272,7 +310,9 @@
272
310
  18.0
273
311
  ],
274
312
  "super_reduced": null,
275
- "parking": null
313
+ "parking": null,
314
+ "format": "HU + 8 digits",
315
+ "pattern": "^HU\\d{8}$"
276
316
  },
277
317
  "IE": {
278
318
  "country": "Ireland",
@@ -286,7 +326,9 @@
286
326
  13.5
287
327
  ],
288
328
  "super_reduced": null,
289
- "parking": null
329
+ "parking": null,
330
+ "format": "IE + 7 digits + 1–2 letters",
331
+ "pattern": "^IE\\d{7}[A-W][A-IW]?$|^IE\\d[A-Z+*]\\d{5}[A-W]$"
290
332
  },
291
333
  "IS": {
292
334
  "country": "Iceland",
@@ -299,7 +341,9 @@
299
341
  11.0
300
342
  ],
301
343
  "super_reduced": null,
302
- "parking": null
344
+ "parking": null,
345
+ "format": "IS + 5–6 digits",
346
+ "pattern": "^IS\\d{5,6}$"
303
347
  },
304
348
  "IT": {
305
349
  "country": "Italy",
@@ -313,7 +357,9 @@
313
357
  10.0
314
358
  ],
315
359
  "super_reduced": 4.0,
316
- "parking": null
360
+ "parking": null,
361
+ "format": "IT + 11 digits",
362
+ "pattern": "^IT\\d{11}$"
317
363
  },
318
364
  "LI": {
319
365
  "country": "Liechtenstein",
@@ -327,7 +373,9 @@
327
373
  3.8
328
374
  ],
329
375
  "super_reduced": null,
330
- "parking": null
376
+ "parking": null,
377
+ "format": "LI + alphanumeric",
378
+ "pattern": null
331
379
  },
332
380
  "LT": {
333
381
  "country": "Lithuania",
@@ -341,7 +389,9 @@
341
389
  12.0
342
390
  ],
343
391
  "super_reduced": null,
344
- "parking": null
392
+ "parking": null,
393
+ "format": "LT + 9 or 12 digits",
394
+ "pattern": "^LT(\\d{9}|\\d{12})$"
345
395
  },
346
396
  "LU": {
347
397
  "country": "Luxembourg",
@@ -355,7 +405,9 @@
355
405
  14.0
356
406
  ],
357
407
  "super_reduced": 3.0,
358
- "parking": 14.0
408
+ "parking": 14.0,
409
+ "format": "LU + 8 digits",
410
+ "pattern": "^LU\\d{8}$"
359
411
  },
360
412
  "LV": {
361
413
  "country": "Latvia",
@@ -369,7 +421,9 @@
369
421
  12.0
370
422
  ],
371
423
  "super_reduced": null,
372
- "parking": null
424
+ "parking": null,
425
+ "format": "LV + 11 digits",
426
+ "pattern": "^LV\\d{11}$"
373
427
  },
374
428
  "MC": {
375
429
  "country": "Monaco",
@@ -383,7 +437,9 @@
383
437
  10.0
384
438
  ],
385
439
  "super_reduced": 2.1,
386
- "parking": null
440
+ "parking": null,
441
+ "format": "MC + alphanumeric",
442
+ "pattern": null
387
443
  },
388
444
  "MD": {
389
445
  "country": "Moldova",
@@ -396,7 +452,9 @@
396
452
  8.0
397
453
  ],
398
454
  "super_reduced": null,
399
- "parking": null
455
+ "parking": null,
456
+ "format": "MD + alphanumeric",
457
+ "pattern": null
400
458
  },
401
459
  "ME": {
402
460
  "country": "Montenegro",
@@ -410,7 +468,9 @@
410
468
  15.0
411
469
  ],
412
470
  "super_reduced": null,
413
- "parking": null
471
+ "parking": null,
472
+ "format": "ME + 8 digits",
473
+ "pattern": "^ME\\d{8}$"
414
474
  },
415
475
  "MK": {
416
476
  "country": "North Macedonia",
@@ -424,7 +484,9 @@
424
484
  10.0
425
485
  ],
426
486
  "super_reduced": null,
427
- "parking": null
487
+ "parking": null,
488
+ "format": "MK + 13 digits",
489
+ "pattern": "^MK\\d{13}$"
428
490
  },
429
491
  "MT": {
430
492
  "country": "Malta",
@@ -438,7 +500,9 @@
438
500
  7.0
439
501
  ],
440
502
  "super_reduced": null,
441
- "parking": 12.0
503
+ "parking": 12.0,
504
+ "format": "MT + 8 digits",
505
+ "pattern": "^MT\\d{8}$"
442
506
  },
443
507
  "NL": {
444
508
  "country": "Netherlands",
@@ -451,7 +515,9 @@
451
515
  9.0
452
516
  ],
453
517
  "super_reduced": null,
454
- "parking": null
518
+ "parking": null,
519
+ "format": "NL + 9 digits + B + 2 digits",
520
+ "pattern": "^NL\\d{9}B\\d{2}$"
455
521
  },
456
522
  "NO": {
457
523
  "country": "Norway",
@@ -465,7 +531,9 @@
465
531
  15.0
466
532
  ],
467
533
  "super_reduced": null,
468
- "parking": null
534
+ "parking": null,
535
+ "format": "NO + 9 digits + MVA",
536
+ "pattern": "^NO\\d{9}MVA$"
469
537
  },
470
538
  "PL": {
471
539
  "country": "Poland",
@@ -479,7 +547,9 @@
479
547
  8.0
480
548
  ],
481
549
  "super_reduced": 8.0,
482
- "parking": null
550
+ "parking": null,
551
+ "format": "PL + 10 digits",
552
+ "pattern": "^PL\\d{10}$"
483
553
  },
484
554
  "PT": {
485
555
  "country": "Portugal",
@@ -495,7 +565,9 @@
495
565
  22.0
496
566
  ],
497
567
  "super_reduced": 6.0,
498
- "parking": 13.0
568
+ "parking": 13.0,
569
+ "format": "PT + 9 digits",
570
+ "pattern": "^PT\\d{9}$"
499
571
  },
500
572
  "RO": {
501
573
  "country": "Romania",
@@ -508,7 +580,9 @@
508
580
  11.0
509
581
  ],
510
582
  "super_reduced": null,
511
- "parking": null
583
+ "parking": null,
584
+ "format": "RO + 2–10 digits",
585
+ "pattern": "^RO\\d{2,10}$"
512
586
  },
513
587
  "RS": {
514
588
  "country": "Serbia",
@@ -521,7 +595,9 @@
521
595
  10.0
522
596
  ],
523
597
  "super_reduced": null,
524
- "parking": null
598
+ "parking": null,
599
+ "format": "RS + 9 digits",
600
+ "pattern": "^RS\\d{9}$"
525
601
  },
526
602
  "SE": {
527
603
  "country": "Sweden",
@@ -535,7 +611,9 @@
535
611
  12.0
536
612
  ],
537
613
  "super_reduced": null,
538
- "parking": null
614
+ "parking": null,
615
+ "format": "SE + 12 digits",
616
+ "pattern": "^SE\\d{12}$"
539
617
  },
540
618
  "SI": {
541
619
  "country": "Slovenia",
@@ -549,7 +627,9 @@
549
627
  9.5
550
628
  ],
551
629
  "super_reduced": null,
552
- "parking": null
630
+ "parking": null,
631
+ "format": "SI + 8 digits",
632
+ "pattern": "^SI\\d{8}$"
553
633
  },
554
634
  "SK": {
555
635
  "country": "Slovakia",
@@ -563,7 +643,9 @@
563
643
  19.0
564
644
  ],
565
645
  "super_reduced": null,
566
- "parking": null
646
+ "parking": null,
647
+ "format": "SK + 10 digits",
648
+ "pattern": "^SK\\d{10}$"
567
649
  },
568
650
  "TR": {
569
651
  "country": "Turkey",
@@ -577,7 +659,9 @@
577
659
  10.0
578
660
  ],
579
661
  "super_reduced": null,
580
- "parking": null
662
+ "parking": null,
663
+ "format": "TR + 10 digits",
664
+ "pattern": "^TR\\d{10}$"
581
665
  },
582
666
  "UA": {
583
667
  "country": "Ukraine",
@@ -591,7 +675,9 @@
591
675
  14.0
592
676
  ],
593
677
  "super_reduced": null,
594
- "parking": null
678
+ "parking": null,
679
+ "format": "UA + 12 digits",
680
+ "pattern": "^UA\\d{12}$"
595
681
  },
596
682
  "XK": {
597
683
  "country": "Kosovo",
@@ -604,7 +690,9 @@
604
690
  8.0
605
691
  ],
606
692
  "super_reduced": null,
607
- "parking": null
693
+ "parking": null,
694
+ "format": "XK + alphanumeric",
695
+ "pattern": null
608
696
  }
609
697
  }
610
698
  }
@@ -1,3 +1,3 @@
1
1
  module EuVatRatesData
2
- VERSION = "2026.3.30"
2
+ VERSION = "2026.3.31"
3
3
  end
@@ -68,7 +68,20 @@ module EuVatRatesData
68
68
  rates.key?(country_code.upcase)
69
69
  end
70
70
 
71
- # Return the ISO 8601 date when EU data was last fetched from EC TEDB.
71
+ # Return true if vat_id matches the expected format for its country.
72
+ # Input must include the country code prefix (e.g. "ATU12345678").
73
+ # Returns false when the country has no standardised format or the ID does not match.
74
+ # Note: Greece uses the "EL" prefix, not "GR".
75
+ # @param vat_id [String] VAT number string including country code prefix
76
+ # @return [Boolean]
77
+ def self.valid_format?(vat_id)
78
+ code = vat_id[0, 2].upcase
79
+ rate = get_rate(code)
80
+ return false unless rate && rate["pattern"]
81
+ !!(vat_id.upcase =~ /\A#{rate["pattern"]}\z/)
82
+ end
83
+
84
+ # Return the ISO 8601 date when EU data was last fetched from EC TEDB.
72
85
  # @return [String] e.g. "2026-03-18"
73
86
  def self.data_version
74
87
  dataset["version"]
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: eu_vat_rates_data
3
3
  version: !ruby/object:Gem::Version
4
- version: 2026.3.30
4
+ version: 2026.3.31
5
5
  platform: ruby
6
6
  authors:
7
7
  - Iurii Rogulia