Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 40 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
[![Build Status](https://travis-ci.org/cblanc/postcode.js.png)](https://travis-ci.org/cblanc/postcode.js)
[![Build Status](https://travis-ci.org/cblanc/postcode.js.png)](https://travis-ci.org/cblanc/postcode.js)
[![Dependency Status](https://gemnasium.com/cblanc/postcode.js.png)](https://gemnasium.com/cblanc/postcode.js)

# Postcodes

Utility methods for UK Postcodes.

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

## Getting Started

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

var postcode = new Postcode("po167gz");
var postcode = new Postcode("ec1v9lb");
```

Perform simple validations, parsing and normalisation

```javascript
postcode.valid() // => True

postcode.normalise() // => "PO16 7GZ"

postcode.outcode() // => "PO16"

postcode.incode() // => "7GZ"

postcode.area() // => "PO"

postcode.sector() // => "PO16 7"

postcode.unit() // => "GZ"
postcode.valid() // => true
postcode.normalise() // => "EC1V 9LB"

postcode.outcode() // => "EC1V"
postcode.incode() // => "9LB"
postcode.area() // => "EC"
postcode.district() // => "EC1"
postcode.subDistrict() // => "EC1V"
postcode.sector() // => "EC1V 9"
postcode.unit() // => "LB"
```

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

## Definitions

### Outcode (#outcode)
### Outcode

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".

### Inward Code (#inward)
### Incode

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".

### Postcode Area (#area)
### Area

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.

### Postcode Sector (#sector)
### District

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".

### Sub-District

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".

Note: for outcodes not ending with a letter, `subDistrict` will return `null`. For example:

```js
new Postcode("SW1A 1AA").subDistrict() // => SW1A
new Postcode("E1W 1LD").subDistrict() // => E1W
new Postcode("PO16 7GZ").subDistrict() // => null
new Postcode("B5 5NY").subDistrict() // => null
```

### Sector

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".

### Postcode Unit (#unit)
### Unit

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".

Source: [Wikipedia](http://en.wikipedia.org/wiki/Postcodes_in_the_United_Kingdom#Formatting)
Sources:

- https://en.wikipedia.org/wiki/Postcodes_in_the_United_Kingdom#Formatting
- https://en.wikipedia.org/wiki/London_postal_district#Numbered_divisions

## Testing

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

MIT

Contains Ordnance Survey Data © Crown Copyright & Database Right 2014
Contains Ordnance Survey Data © Crown Copyright & Database Right 2014
19 changes: 19 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ var validationRegex = /^[a-z0-9]{1,4}\s*?\d[a-z]{2}$/i,
incodeRegex = /\d[a-z]{2}$/i,
validOutcodeRegex = /^[a-z0-9]{1,4}$/i,
areaRegex = /^[a-z]{1,2}/i,
districtSplitRegex = /^([a-z]{1,2}\d)([a-z])$/i,
sectorRegex = /^[a-z0-9]{1,4}\s*?\d/i,
unitRegex = /[a-z]{2}$/i;

Expand Down Expand Up @@ -69,6 +70,24 @@ Postcode.prototype.area = function () {
return this._area;
}

Postcode.prototype.district = function () {
if (!this._valid) return null;
if (this._district) return this._district;
var outcode = this.outcode();
var split = outcode.match(districtSplitRegex);
this._district = split ? split[1] : outcode;
return this._district;
}

Postcode.prototype.subDistrict = function () {
if (!this._valid) return null;
if (this._subDistrict) return this._subDistrict;
var outcode = this.outcode();
var split = outcode.match(districtSplitRegex);
this._subDistrict = split ? outcode : null;
return this._subDistrict;
}

Postcode.prototype.sector = function () {
if (!this._valid) return null;
if (this._sector) return this._sector;
Expand Down
68 changes: 68 additions & 0 deletions tests/data/districts.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
{
"tests" : [
{
"base" : "L27 8XY",
"expected" : "L27"
},
{
"base" : "NR10 3EZ",
"expected" : "NR10"
},
{
"base" : "RG4 5AY",
"expected" : "RG4"
},
{
"base" : "NE69 7AW",
"expected" : "NE69"
},
{
"base" : "SE23 2NF",
"expected" : "SE23"
},
{
"base" : "BT35 8GE",
"expected" : "BT35"
},
{
"base" : "L278XY",
"expected" : "L27"
},
{
"base" : "NR103EZ",
"expected" : "NR10"
},
{
"base" : "RG45AY",
"expected" : "RG4"
},
{
"base" : "NE697AW",
"expected" : "NE69"
},
{
"base" : "SE232NF",
"expected" : "SE23"
},
{
"base" : "BT358GE",
"expected" : "BT35"
},
{
"base" : "EC1A 1BB",
"expected" : "EC1"
},
{
"base" : "W1A0AX",
"expected" : "W1"
},
{
"base" : "NW1W1AA",
"expected" : "NW1"
},
{
"base" : "N1C 0AB",
"expected" : "N1"
}
]
}
68 changes: 68 additions & 0 deletions tests/data/sub-districts.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
{
"tests" : [
{
"base" : "L27 8XY",
"expected" : null
},
{
"base" : "NR10 3EZ",
"expected" : null
},
{
"base" : "RG4 5AY",
"expected" : null
},
{
"base" : "NE69 7AW",
"expected" : null
},
{
"base" : "SE23 2NF",
"expected" : null
},
{
"base" : "BT35 8GE",
"expected" : null
},
{
"base" : "L278XY",
"expected" : null
},
{
"base" : "NR103EZ",
"expected" : null
},
{
"base" : "RG45AY",
"expected" : null
},
{
"base" : "NE697AW",
"expected" : null
},
{
"base" : "SE232NF",
"expected" : null
},
{
"base" : "BT358GE",
"expected" : null
},
{
"base" : "EC1A 1BB",
"expected" : "EC1A"
},
{
"base" : "W1A0AX",
"expected" : "W1A"
},
{
"base" : "NW1W1AA",
"expected" : "NW1W"
},
{
"base" : "N1C 0AB",
"expected" : "N1C"
}
]
}
48 changes: 48 additions & 0 deletions tests/exhaustive_unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,54 @@ describe("Exhaustive postcode test", function () {
done();
});
});
describe("District parsing", function () {
it("should return the correct district", function (done) {
this.timeout(60000);
testData.forEach(function (testPostcode) {
var pc = testPostcode[0],
postcode = new Postcode(pc),
downcasePostcode = new Postcode(pc.toLowerCase()),
unspacedPostcode = new Postcode(pc.replace(/\s/, "")),
testDistrict;
if (pc.length === 7) {
// Since this isn't normalised in dataset, best we can do is see if normalised data matches
assert.equal(postcode.district(), downcasePostcode.district());
assert.equal(postcode.district(), unspacedPostcode.district());
} else {
// Any space indicates incode/outcode
testDistrict = pc.match(/\s.*/)[0].replace(/\s/, "");
assert.equal(postcode.district(), testDistrict);
assert.equal(downcasePostcode.district(), testDistrict);
assert.equal(unspacedPostcode.district(), testDistrict);
}
});
done();
});
});
describe("Sub-district parsing", function () {
it("should return the correct sub-district", function (done) {
this.timeout(60000);
testData.forEach(function (testPostcode) {
var pc = testPostcode[0],
postcode = new Postcode(pc),
downcasePostcode = new Postcode(pc.toLowerCase()),
unspacedPostcode = new Postcode(pc.replace(/\s/, "")),
testSubDistrict;
if (pc.length === 7) {
// Since this isn't normalised in dataset, best we can do is see if normalised data matches
assert.equal(postcode.subDistrict(), downcasePostcode.subDistrict());
assert.equal(postcode.subDistrict(), unspacedPostcode.subDistrict());
} else {
// Any space indicates incode/outcode
testSubDistrict = pc.match(/\s.*/)[0].replace(/\s/, "");
assert.equal(postcode.subDistrict(), testSubDistrict);
assert.equal(downcasePostcode.subDistrict(), testSubDistrict);
assert.equal(unspacedPostcode.subDistrict(), testSubDistrict);
}
});
done();
});
});
describe("Sector parsing", function () {
it("should return the correct sector", function (done) {
this.timeout(60000);
Expand Down
38 changes: 38 additions & 0 deletions tests/unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,44 @@ describe("Area parsing", function () {
});
});

describe("District parsing", function () {
before(function (done) {
testData = fs.readFile(path.join(dataDir, "districts.json"), function (error, data) {
if (error) throw error;
testData = JSON.parse(data);
done();
});
});

it ("should correctly parse districts", function () {
testData.tests.forEach(function (elem) {
assert.equal(new Postcode(elem.base).district(), elem.expected);
});
});
it ("should return null if invalid postcode", function () {
assert.isNull(new Postcode("Definitely bogus").district());
});
});

describe("Sub-district parsing", function () {
before(function (done) {
testData = fs.readFile(path.join(dataDir, "sub-districts.json"), function (error, data) {
if (error) throw error;
testData = JSON.parse(data);
done();
});
});

it ("should correctly parse sub-districts", function () {
testData.tests.forEach(function (elem) {
assert.equal(new Postcode(elem.base).subDistrict(), elem.expected);
});
});
it ("should return null if invalid postcode", function () {
assert.isNull(new Postcode("Definitely bogus").subDistrict());
});
});

describe("Sector parsing", function () {
before(function (done) {
testData = fs.readFile(path.join(dataDir, "sectors.json"), function (error, data) {
Expand Down