Skip to content

Commit e94aba0

Browse files
committed
Merge pull request #4 from billinghamj/districts
Fixes #3 - added district/subDistrict
2 parents f72d2e6 + 04da335 commit e94aba0

6 files changed

Lines changed: 281 additions & 23 deletions

File tree

README.md

Lines changed: 40 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
[![Build Status](https://travis-ci.org/cblanc/postcode.js.png)](https://travis-ci.org/cblanc/postcode.js)
1+
[![Build Status](https://travis-ci.org/cblanc/postcode.js.png)](https://travis-ci.org/cblanc/postcode.js)
22
[![Dependency Status](https://gemnasium.com/cblanc/postcode.js.png)](https://gemnasium.com/cblanc/postcode.js)
33

44
# Postcodes
55

66
Utility methods for UK Postcodes.
77

8-
Included is a test suite that tests against all postcodes listed in the Ordnance Survey's postcode dataset as of January 2014.
8+
Included is a test suite that tests against all postcodes listed in the Ordnance Survey's postcode dataset as of January 2014.
99

1010
## Getting Started
1111

@@ -16,25 +16,22 @@ Create an instance of Postcode to perform utility methods, like so
1616
```javascript
1717
var Postcode = require("postcode");
1818

19-
var postcode = new Postcode("po167gz");
19+
var postcode = new Postcode("ec1v9lb");
2020
```
2121

2222
Perform simple validations, parsing and normalisation
2323

2424
```javascript
25-
postcode.valid() // => True
26-
27-
postcode.normalise() // => "PO16 7GZ"
28-
29-
postcode.outcode() // => "PO16"
30-
31-
postcode.incode() // => "7GZ"
32-
33-
postcode.area() // => "PO"
34-
35-
postcode.sector() // => "PO16 7"
36-
37-
postcode.unit() // => "GZ"
25+
postcode.valid() // => true
26+
postcode.normalise() // => "EC1V 9LB"
27+
28+
postcode.outcode() // => "EC1V"
29+
postcode.incode() // => "9LB"
30+
postcode.area() // => "EC"
31+
postcode.district() // => "EC1"
32+
postcode.subDistrict() // => "EC1V"
33+
postcode.sector() // => "EC1V 9"
34+
postcode.unit() // => "LB"
3835
```
3936

4037
Misc. Class Methods include
@@ -45,27 +42,47 @@ Postcode.validOutcode(outcode)
4542

4643
## Definitions
4744

48-
### Outcode (#outcode)
45+
### Outcode
4946

5047
The outward code is the part of the postcode before the single space in the middle. It is between two and four characters long. A few outward codes are non-geographic, not divulging where mail is to be sent. Examples of outward codes include "L1", "W1A", "RH1", "RH10" or "SE1P".
5148

52-
### Inward Code (#inward)
49+
### Incode
5350

5451
The inward part is the part of the postcode after the single space in the middle. It is three characters long. The inward code assists in the delivery of post within a postal district. Examples of inward codes include "0NY", "7GZ", "7HF", or "8JQ".
5552

56-
### Postcode Area (#area)
53+
### Area
5754

5855
The postcode area is part of the outward code. The postcode area is between one and two characters long and is all letters. Examples of postcode areas include "L" for Liverpool, "RH" for Redhill and "EH" Edinburgh. A postal area may cover a wide area, for example "RH" covers north Sussex, (which has little to do with Redhill historically apart from the railway links), and "BT" (Belfast) covers the whole of Northern Ireland.
5956

60-
### Postcode Sector (#sector)
57+
### District
58+
59+
The district code is part of the outward code. It is between two and four characters long. It does not include the trailing letter found in some outcodes. Examples of district codes include "L1", "W1", "RH1", "RH10" or "SE1".
60+
61+
### Sub-District
62+
63+
The sub-district code is part of the outward code. It is often not present, only existing in particularly high density London districts. It is between three and four characters long. It does include the trailing letter omitted from the district. Examples of sub-district codes include "W1A", "EC1A", "NW1W", "E1W" or "SE1P".
64+
65+
Note: for outcodes not ending with a letter, `subDistrict` will return `null`. For example:
66+
67+
```js
68+
new Postcode("SW1A 1AA").subDistrict() // => SW1A
69+
new Postcode("E1W 1LD").subDistrict() // => E1W
70+
new Postcode("PO16 7GZ").subDistrict() // => null
71+
new Postcode("B5 5NY").subDistrict() // => null
72+
```
73+
74+
### Sector
6175

6276
The postcode sector is made up of the postcode district, the single space, and the first character of the inward code. It is between four and six characters long (including the single space). Examples of postcode sectors include "SW1W 0", "PO16 7", "GU16 7", or "L1 8", "CV1 4".
6377

64-
### Postcode Unit (#unit)
78+
### Unit
6579

6680
The postcode unit is two characters added to the end of the postcode sector. Each postcode unit generally represents a street, part of a street, a single address, a group of properties, a single property, a sub-section of the property, an individual organisation or (for instance Driver and Vehicle Licensing Agency) a subsection of the organisation. The level of discrimination is often based on the amount of mail received by the premises or business. Examples of postcode units include "SW1W 0NY", "PO16 7GZ", "GU16 7HF", or "L1 8JQ".
6781

68-
Source: [Wikipedia](http://en.wikipedia.org/wiki/Postcodes_in_the_United_Kingdom#Formatting)
82+
Sources:
83+
84+
- https://en.wikipedia.org/wiki/Postcodes_in_the_United_Kingdom#Formatting
85+
- https://en.wikipedia.org/wiki/London_postal_district#Numbered_divisions
6986

7087
## Testing
7188

@@ -81,4 +98,4 @@ A complete list of Postcodes can be obtained from the ONS Postcode Directory, wh
8198

8299
MIT
83100

84-
Contains Ordnance Survey Data © Crown Copyright & Database Right 2014
101+
Contains Ordnance Survey Data © Crown Copyright & Database Right 2014

index.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ var validationRegex = /^[a-z0-9]{1,4}\s*?\d[a-z]{2}$/i,
44
incodeRegex = /\d[a-z]{2}$/i,
55
validOutcodeRegex = /^[a-z0-9]{1,4}$/i,
66
areaRegex = /^[a-z]{1,2}/i,
7+
districtSplitRegex = /^([a-z]{1,2}\d)([a-z])$/i,
78
sectorRegex = /^[a-z0-9]{1,4}\s*?\d/i,
89
unitRegex = /[a-z]{2}$/i;
910

@@ -69,6 +70,24 @@ Postcode.prototype.area = function () {
6970
return this._area;
7071
}
7172

73+
Postcode.prototype.district = function () {
74+
if (!this._valid) return null;
75+
if (this._district) return this._district;
76+
var outcode = this.outcode();
77+
var split = outcode.match(districtSplitRegex);
78+
this._district = split ? split[1] : outcode;
79+
return this._district;
80+
}
81+
82+
Postcode.prototype.subDistrict = function () {
83+
if (!this._valid) return null;
84+
if (this._subDistrict) return this._subDistrict;
85+
var outcode = this.outcode();
86+
var split = outcode.match(districtSplitRegex);
87+
this._subDistrict = split ? outcode : null;
88+
return this._subDistrict;
89+
}
90+
7291
Postcode.prototype.sector = function () {
7392
if (!this._valid) return null;
7493
if (this._sector) return this._sector;

tests/data/districts.json

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
{
2+
"tests" : [
3+
{
4+
"base" : "L27 8XY",
5+
"expected" : "L27"
6+
},
7+
{
8+
"base" : "NR10 3EZ",
9+
"expected" : "NR10"
10+
},
11+
{
12+
"base" : "RG4 5AY",
13+
"expected" : "RG4"
14+
},
15+
{
16+
"base" : "NE69 7AW",
17+
"expected" : "NE69"
18+
},
19+
{
20+
"base" : "SE23 2NF",
21+
"expected" : "SE23"
22+
},
23+
{
24+
"base" : "BT35 8GE",
25+
"expected" : "BT35"
26+
},
27+
{
28+
"base" : "L278XY",
29+
"expected" : "L27"
30+
},
31+
{
32+
"base" : "NR103EZ",
33+
"expected" : "NR10"
34+
},
35+
{
36+
"base" : "RG45AY",
37+
"expected" : "RG4"
38+
},
39+
{
40+
"base" : "NE697AW",
41+
"expected" : "NE69"
42+
},
43+
{
44+
"base" : "SE232NF",
45+
"expected" : "SE23"
46+
},
47+
{
48+
"base" : "BT358GE",
49+
"expected" : "BT35"
50+
},
51+
{
52+
"base" : "EC1A 1BB",
53+
"expected" : "EC1"
54+
},
55+
{
56+
"base" : "W1A0AX",
57+
"expected" : "W1"
58+
},
59+
{
60+
"base" : "NW1W1AA",
61+
"expected" : "NW1"
62+
},
63+
{
64+
"base" : "N1C 0AB",
65+
"expected" : "N1"
66+
}
67+
]
68+
}

tests/data/sub-districts.json

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
{
2+
"tests" : [
3+
{
4+
"base" : "L27 8XY",
5+
"expected" : null
6+
},
7+
{
8+
"base" : "NR10 3EZ",
9+
"expected" : null
10+
},
11+
{
12+
"base" : "RG4 5AY",
13+
"expected" : null
14+
},
15+
{
16+
"base" : "NE69 7AW",
17+
"expected" : null
18+
},
19+
{
20+
"base" : "SE23 2NF",
21+
"expected" : null
22+
},
23+
{
24+
"base" : "BT35 8GE",
25+
"expected" : null
26+
},
27+
{
28+
"base" : "L278XY",
29+
"expected" : null
30+
},
31+
{
32+
"base" : "NR103EZ",
33+
"expected" : null
34+
},
35+
{
36+
"base" : "RG45AY",
37+
"expected" : null
38+
},
39+
{
40+
"base" : "NE697AW",
41+
"expected" : null
42+
},
43+
{
44+
"base" : "SE232NF",
45+
"expected" : null
46+
},
47+
{
48+
"base" : "BT358GE",
49+
"expected" : null
50+
},
51+
{
52+
"base" : "EC1A 1BB",
53+
"expected" : "EC1A"
54+
},
55+
{
56+
"base" : "W1A0AX",
57+
"expected" : "W1A"
58+
},
59+
{
60+
"base" : "NW1W1AA",
61+
"expected" : "NW1W"
62+
},
63+
{
64+
"base" : "N1C 0AB",
65+
"expected" : "N1C"
66+
}
67+
]
68+
}

tests/exhaustive_unit.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,54 @@ describe("Exhaustive postcode test", function () {
135135
done();
136136
});
137137
});
138+
describe("District parsing", function () {
139+
it("should return the correct district", function (done) {
140+
this.timeout(60000);
141+
testData.forEach(function (testPostcode) {
142+
var pc = testPostcode[0],
143+
postcode = new Postcode(pc),
144+
downcasePostcode = new Postcode(pc.toLowerCase()),
145+
unspacedPostcode = new Postcode(pc.replace(/\s/, "")),
146+
testDistrict;
147+
if (pc.length === 7) {
148+
// Since this isn't normalised in dataset, best we can do is see if normalised data matches
149+
assert.equal(postcode.district(), downcasePostcode.district());
150+
assert.equal(postcode.district(), unspacedPostcode.district());
151+
} else {
152+
// Any space indicates incode/outcode
153+
testDistrict = pc.match(/\s.*/)[0].replace(/\s/, "");
154+
assert.equal(postcode.district(), testDistrict);
155+
assert.equal(downcasePostcode.district(), testDistrict);
156+
assert.equal(unspacedPostcode.district(), testDistrict);
157+
}
158+
});
159+
done();
160+
});
161+
});
162+
describe("Sub-district parsing", function () {
163+
it("should return the correct sub-district", function (done) {
164+
this.timeout(60000);
165+
testData.forEach(function (testPostcode) {
166+
var pc = testPostcode[0],
167+
postcode = new Postcode(pc),
168+
downcasePostcode = new Postcode(pc.toLowerCase()),
169+
unspacedPostcode = new Postcode(pc.replace(/\s/, "")),
170+
testSubDistrict;
171+
if (pc.length === 7) {
172+
// Since this isn't normalised in dataset, best we can do is see if normalised data matches
173+
assert.equal(postcode.subDistrict(), downcasePostcode.subDistrict());
174+
assert.equal(postcode.subDistrict(), unspacedPostcode.subDistrict());
175+
} else {
176+
// Any space indicates incode/outcode
177+
testSubDistrict = pc.match(/\s.*/)[0].replace(/\s/, "");
178+
assert.equal(postcode.subDistrict(), testSubDistrict);
179+
assert.equal(downcasePostcode.subDistrict(), testSubDistrict);
180+
assert.equal(unspacedPostcode.subDistrict(), testSubDistrict);
181+
}
182+
});
183+
done();
184+
});
185+
});
138186
describe("Sector parsing", function () {
139187
it("should return the correct sector", function (done) {
140188
this.timeout(60000);

tests/unit.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,44 @@ describe("Area parsing", function () {
119119
});
120120
});
121121

122+
describe("District parsing", function () {
123+
before(function (done) {
124+
testData = fs.readFile(path.join(dataDir, "districts.json"), function (error, data) {
125+
if (error) throw error;
126+
testData = JSON.parse(data);
127+
done();
128+
});
129+
});
130+
131+
it ("should correctly parse districts", function () {
132+
testData.tests.forEach(function (elem) {
133+
assert.equal(new Postcode(elem.base).district(), elem.expected);
134+
});
135+
});
136+
it ("should return null if invalid postcode", function () {
137+
assert.isNull(new Postcode("Definitely bogus").district());
138+
});
139+
});
140+
141+
describe("Sub-district parsing", function () {
142+
before(function (done) {
143+
testData = fs.readFile(path.join(dataDir, "sub-districts.json"), function (error, data) {
144+
if (error) throw error;
145+
testData = JSON.parse(data);
146+
done();
147+
});
148+
});
149+
150+
it ("should correctly parse sub-districts", function () {
151+
testData.tests.forEach(function (elem) {
152+
assert.equal(new Postcode(elem.base).subDistrict(), elem.expected);
153+
});
154+
});
155+
it ("should return null if invalid postcode", function () {
156+
assert.isNull(new Postcode("Definitely bogus").subDistrict());
157+
});
158+
});
159+
122160
describe("Sector parsing", function () {
123161
before(function (done) {
124162
testData = fs.readFile(path.join(dataDir, "sectors.json"), function (error, data) {

0 commit comments

Comments
 (0)