File Coverage

blib/lib/Business/PLZ.pm
Criterion Covered Total %
statement 41 41 100.0
branch 17 18 94.4
condition 7 11 63.6
subroutine 11 11 100.0
pod 3 4 75.0
total 79 85 92.9


line stmt bran cond sub pod time code
1 2     2   32638 use strict;
  2         5  
  2         73  
2 2     2   12 use warnings;
  2         3  
  2         118  
3             package Business::PLZ;
4             {
5             $Business::PLZ::VERSION = '0.11';
6             }
7             #ABSTRACT: Validate German postal codes and map them to states
8              
9 2     2   2138 use Tree::Binary::Search 1.0;
  2         13913  
  2         100  
10 2     2   1497 use overload '""' => sub { ${$_[0]} };
  2     117   1028  
  2         25  
  117         241  
  117         446  
11 2     2   155 use Carp 'croak';
  2         5  
  2         926  
12              
13             our $STATES;
14              
15             # http://web.archive.org/web/*/http://www.uni-koeln.de/~arcd2/3d.htm
16             BEGIN {
17 2     2   95 my %RANGES = (
18             BW => [qw(68000-68309 68520-68549 68700-69234 69240-69429 69435-69469
19             69489-69502 69510-69514 70000-76709 77600-79879 88000-88099
20             88180-89198 89300-89619 97860-97999)],
21             BY => [qw(63700-63939 80000-87490 87493-87561 87570-87789 88100-88179
22             89200-89299 90000-96489 97000-97859)],
23             BE => [qw(10000-12527 12531-14199)],
24             BB => [qw(01940-01998 03000-03253 04890-04938 12529
25             14400-16949 17260-17291 19340-19357)],
26             HB => [qw(27500-27580 28000-28779)],
27             HH => [qw(20000-21149 22000-22769 27499)],
28             HE => [qw(34000-34329 34356-34399 34440-36399 37195 37200-37299
29             55240-55252 59969 60000-63699 64200-65556 65583-65620 65627
30             65700-65936 68501-68519 68600-68649 69235-69239 69430-69434
31             69479-69488 69503-69509 69515-69518)],
32             MV => [qw(17000-17259 17300-19260 19280-19339 19360-19417 23920-23999)],
33             NI => [qw(19270-19273 21202-21449 21522 21600-21789 26000-27478 27607-27809
34             28784-29399 29430-31868 34330-34355 37000-37194 37197-37199
35             37400-37649 37689-37691 37697-38479 38500-38729 48442-48465
36             48478-48480 48486-48488 48497-48531 49000-49459 49550-49849)],
37             NW => [qw(32000-33829 34400-34439 37650-37688 37692-37696 40000-48432
38             48466-48477 48481-48485 48489-48496 48541-48739 49461-49549
39             50100-51597 51600-53359 53580-53604 53620-53949 57000-57489
40             58000-59968)],
41             RP => [qw(51598 53400-53579 53614-53619 54200-55239 55253-56869 57500-57648
42             65558-65582 65621-65626 65629 66460-66509 66840-67829 76710-76891)],
43             SL => [qw(66000-66459 66510-66839)],
44             SN => [qw(01000-01936 02600-02999 04000-04579 04640-04889 07917-07919
45             07951-07952 07982-07985 08000-09669)],
46             ST => [qw(06000-06548 06600-06928 29400-29416 38480-38489 38800-39649)],
47             SH => [qw(21450-21521 21524-21529 22801-23919 24000-25999 27483-27498)],
48             TH => [qw(04580-04639 06550-06578 07300-07907 07920-07950 07953-07980
49             07987-07989 36400-36469 37300-37359 96500-96529 98500-99998)],
50             8 => [qw(87567-87569)], # Kleinwalsertal, Vorarlberg
51             7 => [87491], # Jungholz, Tirol
52             );
53              
54 2         13 $STATES = Tree::Binary::Search->new;
55             $STATES->setComparisonFunction(sub {
56 3238         83931 my ($a1,$a2) = split '-', $_[0];
57 3238         6397 my ($b1,$b2) = split '-', $_[1];
58 3238 100       6378 $a2 = $a1 unless defined $a2;
59 3238 100       5330 $b2 = $b1 unless defined $b2;
60 3238 100       7196 return -1 if $a2 < $b1;
61 2721 100       8535 return +1 if $a1 > $b2;
62 12         32 return 0;
63 2         47 });
64 2         22 while (my ($state,$ranges) = each(%RANGES)) {
65 36         1127 foreach my $plz (@$ranges) {
66 280         7402 $STATES->insert($plz,$state);
67             }
68             }
69             }
70              
71             # TODO: see http://anchje.de/inv_rep2.htm for more expections
72             # 21039 SH and HH
73             # 37194 HE and NE
74             # 59969 HE and NW
75              
76             sub new {
77 17     17 0 3980 my ($class, $code) = @_;
78 17   33     76 $class = ref $class || $class;
79              
80 17 100 66     1026 croak 'invalid postal code' unless $code and $code =~ qr/^\d{5}$/;
81            
82 10         81 bless \$code, $class;
83             }
84              
85             sub state {
86 9     9 1 25 my $plz = shift;
87 9 100 66     60 $plz = Business::PLZ->new( $plz )
88             unless ref $plz and $plz->isa('Business::PLZ');
89             # Tree::Binary throws on exception if key does not exist :-(
90 9 100       36 return $STATES->exists($plz) ? $STATES->select($plz) : undef;
91             }
92              
93             sub exists {
94 2     2 1 295 my $state = state(shift);
95 2 100       42 return defined $state ? 1 : 0;
96             }
97              
98             sub iso_state {
99 3   100 3 1 995 my $state = state(shift) || return;
100 1 50       23 return ($state =~ /[A-Z][A-Z]/) ? "DE-$state" : "AT-$state";
101             }
102              
103             1;
104              
105              
106              
107             =pod
108              
109             =head1 NAME
110              
111             Business::PLZ - Validate German postal codes and map them to states
112              
113             =head1 VERSION
114              
115             version 0.11
116              
117             =head1 SYNOPSIS
118              
119             use Business::PLZ;
120              
121             my $plz = Business::PLZ->new('12345'); # croaks on invalid code
122              
123             print "$plz"; # stringify
124              
125             $plz->state; # state or undef if not exist
126             $plz->iso_state; # state as full ISO code
127              
128             =head1 DESCRIPTION
129              
130             This module validates German postal codes and maps them to states.
131              
132             =head1 METHODS
133              
134             =head2 state
135              
136             Returns the state ("Bundesland") of a postal code as ISO 3166-2 subdivision
137             code. The country prefix 'DE-' (or 'AT-') is not included. Some postal codes
138             belong to more than one state - in this case only one state is returned. A
139             future version of this module may also return multiple states.
140              
141             If no state was found (so the postal code likely does not exists), this
142             method returns undef. The method 'exists' is based on this lookup.
143              
144             To get more information about a state, you can use L:
145              
146             $state_code = $plz->state;
147             $state_name = Locale::SubCountry->new('DE')->full_name( $state_code );
148              
149             =head2 iso_state
150              
151             Returns the state of a postal code as ISO 3166-2 subdivision code, including
152             country prefix.
153              
154             =head2 exists
155              
156             Returns whether the postal code is assigned. This is exactely the case if
157             it can be mapped to a state.
158              
159             =head1 SEE ALSO
160              
161             There are some country-specific modules to handle postal codes, for instance
162             L and L. L contains
163             regular expressions for postal codes of almost every country.
164              
165             =head1 AUTHOR
166              
167             Jakob Voß
168              
169             =head1 COPYRIGHT AND LICENSE
170              
171             This software is copyright (c) 2013 by Jakob Voß.
172              
173             This is free software; you can redistribute it and/or modify it under
174             the same terms as the Perl 5 programming language system itself.
175              
176             =cut
177              
178              
179             __END__