diff --git a/pygeoip/__init__.py b/pygeoip/__init__.py
index 4237ee3..3e89422 100644
--- a/pygeoip/__init__.py
+++ b/pygeoip/__init__.py
@@ -1,641 +1,647 @@
-"""
-Pure Python GeoIP API. The API is based off of U{MaxMind's C-based Python API<http://www.maxmind.com/app/python>},
-but the code itself is based on the U{pure PHP5 API<http://pear.php.net/package/Net_GeoIP/>}
-by Jim Winstead and Hans Lellelid.
-
-It is mostly a drop-in replacement, except the
-C{new} and C{open} methods are gone. You should instantiate the L{GeoIP} class yourself:
-
-C{gi = GeoIP('/path/to/GeoIP.dat', pygeoip.MEMORY_CACHE)}
-
-@author: Jennifer Ennis <zaylea at gmail dot com>
-
-@license:
-Copyright(C) 2004 MaxMind LLC
-
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License
-along with this program.  If not, see <http://www.gnu.org/licenses/lgpl.txt>.
-"""
-
-from __future__ import with_statement, absolute_import, division
-import os
-import math
-import socket
-import mmap
-import gzip
-import codecs
-
-from . import const
-from .util import ip2long
-from .timezone import time_zone_by_country_and_region
-
-import six
-
-MMAP_CACHE = const.MMAP_CACHE
-MEMORY_CACHE = const.MEMORY_CACHE
-STANDARD = const.STANDARD
-
-class GeoIPError(Exception):
-    pass
-
-class GeoIPMetaclass(type):
-
-    def __new__(cls, *args, **kwargs):
-        """
-        Singleton method to gets an instance without reparsing the db. Unique
-        instances are instantiated based on the filename of the db. Flags are
-        ignored for this, i.e. if you initialize one with STANDARD flag (default)
-        and then try later to initialize with MEMORY_CACHE, it will still
-        return the STANDARD one.
-        """
-
-        if not hasattr(cls, '_instances'):
-            cls._instances = {}
-
-        if len(args) > 0:
-            filename = args[0]
-        elif 'filename' in kwargs:
-            filename = kwargs['filename']
-
-        if not filename in cls._instances:
-            cls._instances[filename] = type.__new__(cls, *args, **kwargs)
-
-        return cls._instances[filename]
-
-GeoIPBase = GeoIPMetaclass('GeoIPBase', (object,), {})
-
-class GeoIP(GeoIPBase):
-
-    def __init__(self, filename, flags=0):
-        """
-        Initialize the class.
-
-        @param filename: path to a geoip database. If MEMORY_CACHE is used,
-            the file can be gzipped.
-        @type filename: str
-        @param flags: flags that affect how the database is processed.
-            Currently the only supported flags are STANDARD (the default),
-            MEMORY_CACHE (preload the whole file into memory), and
-            MMAP_CACHE (access the file via mmap).
-        @type flags: int
-        """
-        self._filename = filename
-        self._flags = flags
-
-        if self._flags & const.MMAP_CACHE:
-            with open(filename, 'rb') as f:
-                self._filehandle = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)
-
-        elif self._flags & const.MEMORY_CACHE:
-            self._filehandle = gzip.open(filename, 'rb')
-            self._memoryBuffer = self._filehandle.read()
-
-        else:
-            self._filehandle = codecs.open(filename, 'rb','latin_1')
-
-        self._setup_segments()
-
-    def _setup_segments(self):
-        """
-        Parses the database file to determine what kind of database is being used and setup
-        segment sizes and start points that will be used by the seek*() methods later.
-        """
-        self._databaseType = const.COUNTRY_EDITION
-        self._recordLength = const.STANDARD_RECORD_LENGTH
-
-        filepos = self._filehandle.tell()
-        self._filehandle.seek(-3, os.SEEK_END)
-
-        for i in range(const.STRUCTURE_INFO_MAX_SIZE):
-            delim = self._filehandle.read(3)
-
-            if delim == six.u(chr(255) * 3):
-                self._databaseType = ord(self._filehandle.read(1))
-
-                if (self._databaseType >= 106):
-                    # backwards compatibility with databases from April 2003 and earlier
-                    self._databaseType -= 105
-
-                if self._databaseType == const.REGION_EDITION_REV0:
-                    self._databaseSegments = const.STATE_BEGIN_REV0
-
-                elif self._databaseType == const.REGION_EDITION_REV1:
-                    self._databaseSegments = const.STATE_BEGIN_REV1
-
-                elif self._databaseType in (const.CITY_EDITION_REV0,
-                                            const.CITY_EDITION_REV1,
-                                            const.ORG_EDITION,
-                                            const.ISP_EDITION,
-                                            const.ASNUM_EDITION):
-                    self._databaseSegments = 0
-                    buf = self._filehandle.read(const.SEGMENT_RECORD_LENGTH)
-
-                    for j in range(const.SEGMENT_RECORD_LENGTH):
-                        self._databaseSegments += (ord(buf[j]) << (j * 8))
-
-                    if self._databaseType in (const.ORG_EDITION, const.ISP_EDITION):
-                        self._recordLength = const.ORG_RECORD_LENGTH
-
-                break
-            else:
-                self._filehandle.seek(-4, os.SEEK_CUR)
-
-        if self._databaseType == const.COUNTRY_EDITION:
-            self._databaseSegments = const.COUNTRY_BEGIN
-
-        self._filehandle.seek(filepos, os.SEEK_SET)
-
-    def _lookup_country_id(self, addr):
-        """
-        Get the country index.
-
-        This method is called by the _lookupCountryCode and _lookupCountryName
-        methods. It looks up the index ('id') for the country which is the key
-        for the code and name.
-
-        @param addr: The IP address
-        @type addr: str
-        @return: network byte order 32-bit integer
-        @rtype: int
-        """
-
-        ipnum = ip2long(addr)
-
-        if not ipnum:
-            raise ValueError("Invalid IP address: %s" % addr)
-
-        if self._databaseType != const.COUNTRY_EDITION:
-            raise GeoIPError('Invalid database type; country_* methods expect '\
-                             'Country database')
-
-        return self._seek_country(ipnum) - const.COUNTRY_BEGIN
-
-    def _seek_country(self, ipnum):
-        """
-        Using the record length and appropriate start points, seek to the
-        country that corresponds to the converted IP address integer.
-
-        @param ipnum: result of ip2long conversion
-        @type ipnum: int
-        @return: offset of start of record
-        @rtype: int
-        """
-        offset = 0
-
-        for depth in range(31, -1, -1):
-
-            if self._flags & const.MEMORY_CACHE:
-                startIndex = 2 * self._recordLength * offset
-                length = 2 * self._recordLength
-                endIndex = startIndex + length
-                buf = self._memoryBuffer[startIndex:endIndex]
-            else:
-                self._filehandle.seek(2 * self._recordLength * offset, os.SEEK_SET)
-                buf = self._filehandle.read(2 * self._recordLength)
-
-            x = [0,0]
-
-            for i in range(2):
-                for j in range(self._recordLength):
-                    x[i] += ord(buf[self._recordLength * i + j]) << (j * 8)
-
-            if ipnum & (1 << depth):
-
-                if x[1] >= self._databaseSegments:
-                    return x[1]
-
-                offset = x[1]
-
-            else:
-
-                if x[0] >= self._databaseSegments:
-                    return x[0]
-
-                offset = x[0]
-
-
-        raise Exception('Error traversing database - perhaps it is corrupt?')
-
-    def _get_org(self, ipnum):
-        """
-        Seek and return organization (or ISP) name for converted IP addr.
-        @param ipnum: Converted IP address
-        @type ipnum: int
-        @return: org/isp name
-        @rtype: str
-        """
-
-        seek_org = self._seek_country(ipnum)
-        if seek_org == self._databaseSegments:
-            return None
-
-        record_pointer = seek_org + (2 * self._recordLength - 1) * self._databaseSegments
-
-        self._filehandle.seek(record_pointer, os.SEEK_SET)
-
-        org_buf = self._filehandle.read(const.MAX_ORG_RECORD_LENGTH)
-
-        return org_buf[:org_buf.index(chr(0))]
-
-    def _get_region(self, ipnum):
-        """
-        Seek and return the region info (dict containing country_code and region_name).
-
-        @param ipnum: converted IP address
-        @type ipnum: int
-        @return: dict containing country_code and region_name
-        @rtype: dict
-        """
-        country_code = ''
-        region = ''
-
-        if self._databaseType == const.REGION_EDITION_REV0:
-            seek_country = self._seek_country(ipnum)
-            seek_region = seek_country - const.STATE_BEGIN_REV0
-            if seek_region >= 1000:
-                country_code = 'US'
-                region = ''.join([chr((seek_region // 1000) // 26 + 65), chr((seek_region // 1000) % 26 + 65)])
-            else:
-                country_code = const.COUNTRY_CODES[seek_region]
-                region = ''
-        elif self._databaseType == const.REGION_EDITION_REV1:
-            seek_country = self._seek_country(ipnum)
-            seek_region = seek_country - const.STATE_BEGIN_REV1
-            if seek_region < const.US_OFFSET:
-                country_code = '';
-                region = ''
-            elif seek_region < const.CANADA_OFFSET:
-                country_code = 'US'
-                region = ''.join([chr((seek_region - const.US_OFFSET) // 26 + 65), chr((seek_region - const.US_OFFSET) % 26 + 65)])
-            elif seek_region  < const.WORLD_OFFSET:
-                country_code = 'CA'
-                region = ''.join([chr((seek_region - const.CANADA_OFFSET) // 26 + 65), chr((seek_region - const.CANADA_OFFSET) % 26 + 65)])
-            else:
-                i = (seek_region - const.WORLD_OFFSET) // const.FIPS_RANGE
-                if i < len(const.COUNTRY_CODES):
-                    #country_code = const.COUNTRY_CODES[(seek_region - const.WORLD_OFFSET) // const.FIPS_RANGE]
-                    country_code = const.COUNTRY_CODES[i]
-                else:
-                    country_code = ''
-                region = ''
-
-        elif self._databaseType in (const.CITY_EDITION_REV0, const.CITY_EDITION_REV1):
-            rec = self._get_record(ipnum)
-            country_code = rec['country_code'] if 'country_code' in rec else ''
-            region = rec['region_name'] if 'region_name' in rec else ''
-
-        return {'country_code' : country_code, 'region_name' : region }
-
-    def _get_record(self, ipnum):
-        """
-        Populate location dict for converted IP.
-
-        @param ipnum: converted IP address
-        @type ipnum: int
-        @return: dict with country_code, country_code3, country_name,
-            region, city, postal_code, latitude, longitude,
-            dma_code, metro_code, area_code, region_name, time_zone
-        @rtype: dict
-        """
-        seek_country = self._seek_country(ipnum)
-        if seek_country == self._databaseSegments:
-            return None
-
-        record_pointer = seek_country + (2 * self._recordLength - 1) * self._databaseSegments
-
-        self._filehandle.seek(record_pointer, os.SEEK_SET)
-        record_buf = self._filehandle.read(const.FULL_RECORD_LENGTH)
-
-        record = {}
-
-        record_buf_pos = 0
-        char = ord(record_buf[record_buf_pos])
-        #char = record_buf[record_buf_pos] if six.PY3 else ord(record_buf[record_buf_pos])
-        record['country_code'] = const.COUNTRY_CODES[char]
-        record['country_code3'] = const.COUNTRY_CODES3[char]
-        record['country_name'] = const.COUNTRY_NAMES[char]
-        record_buf_pos += 1
-        str_length = 0
-
-        # get region
-        char = ord(record_buf[record_buf_pos+str_length])
-        while (char != 0):
-            str_length += 1
-            char = ord(record_buf[record_buf_pos+str_length])
-
-        if str_length > 0:
-            record['region_name'] = record_buf[record_buf_pos:record_buf_pos+str_length]
-
-        record_buf_pos += str_length + 1
-        str_length = 0
-
-        # get city
-        char = ord(record_buf[record_buf_pos+str_length])
-        while (char != 0):
-            str_length += 1
-            char = ord(record_buf[record_buf_pos+str_length])
-
-        if str_length > 0:
-            record['city'] = record_buf[record_buf_pos:record_buf_pos+str_length]
-        else:
-            record['city'] = ''
-
-        record_buf_pos += str_length + 1
-        str_length = 0
-
-        # get the postal code
-        char = ord(record_buf[record_buf_pos+str_length])
-        while (char != 0):
-            str_length += 1
-            char = ord(record_buf[record_buf_pos+str_length])
-
-        if str_length > 0:
-            record['postal_code'] = record_buf[record_buf_pos:record_buf_pos+str_length]
-        else:
-            record['postal_code'] = None
-
-        record_buf_pos += str_length + 1
-        str_length = 0
-
-        latitude = 0
-        longitude = 0
-        for j in range(3):
-            char = ord(record_buf[record_buf_pos])
-            record_buf_pos += 1
-            latitude += (char << (j * 8))
-
-        record['latitude'] = (latitude/10000.0) - 180.0
-
-        for j in range(3):
-            char = ord(record_buf[record_buf_pos])
-            record_buf_pos += 1
-            longitude += (char << (j * 8))
-
-        record['longitude'] = (longitude/10000.0) - 180.0
-
-        if self._databaseType == const.CITY_EDITION_REV1:
-            dmaarea_combo = 0
-            if record['country_code'] == 'US':
-                for j in range(3):
-                    char = ord(record_buf[record_buf_pos])
-                    record_buf_pos += 1
-                    dmaarea_combo += (char << (j*8))
-
-                record['dma_code'] = int(math.floor(dmaarea_combo/1000))
-                record['area_code'] = dmaarea_combo%1000
-        else:
-            record['dma_code'] = 0
-            record['area_code'] = 0
-
-        if 'dma_code' in record and record['dma_code'] in const.DMA_MAP:
-            record['metro_code'] = const.DMA_MAP[record['dma_code']]
-        else:
-            record['metro_code'] = ''
-
-        if 'country_code' in record:
-            record['time_zone'] = time_zone_by_country_and_region(
-                record['country_code'], record.get('region_name')) or ''
-        else:
-            record['time_zone'] = ''
-
-        return record
-
-    def country_code_by_addr(self, addr):
-        """
-        Returns 2-letter country code (e.g. 'US') for specified IP address.
-        Use this method if you have a Country, Region, or City database.
-
-        @param addr: IP address
-        @type addr: str
-        @return: 2-letter country code
-        @rtype: str
-        """
-        try:
-            if self._databaseType == const.COUNTRY_EDITION:
-                country_id = self._lookup_country_id(addr)
-                return const.COUNTRY_CODES[country_id]
-            elif self._databaseType in (const.REGION_EDITION_REV0, const.REGION_EDITION_REV1,
-                                          const.CITY_EDITION_REV0, const.CITY_EDITION_REV1):
-                return self.region_by_addr(addr)['country_code']
-            else:
-                raise GeoIPError('Invalid database type; country_* methods expect '\
-                                 'Country, City, or Region database')
-
-        except ValueError:
-            raise GeoIPError('*_by_addr methods only accept IP addresses. Use *_by_name for hostnames. (Address: %s)' % addr)
-
-    def country_code_by_name(self, hostname):
-        """
-        Returns 2-letter country code (e.g. 'US') for specified hostname.
-        Use this method if you have a Country, Region, or City database.
-
-        @param hostname: host name
-        @type hostname: str
-        @return: 2-letter country code
-        @rtype: str
-        """
-        addr = socket.gethostbyname(hostname)
-
-        return self.country_code_by_addr(addr)
-
-    def country_name_by_addr(self, addr):
-        """
-        Returns full country name for specified IP address.
-        Use this method if you have a Country or City database.
-
-        @param addr: IP address
-        @type addr: str
-        @return: country name
-        @rtype: str
-        """
-        try:
-            if self._databaseType == const.COUNTRY_EDITION:
-                country_id = self._lookup_country_id(addr)
-                return const.COUNTRY_NAMES[country_id]
-            elif self._databaseType in (const.CITY_EDITION_REV0, const.CITY_EDITION_REV1):
-                return self.record_by_addr(addr)['country_name']
-            else:
-                raise GeoIPError('Invalid database type; country_* methods expect '\
-                                 'Country or City database')
-        except ValueError:
-            raise GeoIPError('*_by_addr methods only accept IP addresses. Use *_by_name for hostnames. (Address: %s)' % addr)
-
-    def country_name_by_name(self, hostname):
-        """
-        Returns full country name for specified hostname.
-        Use this method if you have a Country database.
-
-        @param hostname: host name
-        @type hostname: str
-        @return: country name
-        @rtype: str
-        """
-        addr = socket.gethostbyname(hostname)
-        return self.country_name_by_addr(addr)
-
-    def org_by_addr(self, addr):
-        """
-        Lookup the organization (or ISP) for given IP address.
-        Use this method if you have an Organization/ISP database.
-
-        @param addr: IP address
-        @type addr: str
-        @return: organization or ISP name
-        @rtype: str
-        """
-        try:
-            ipnum = ip2long(addr)
-
-            if not ipnum:
-                raise ValueError("Invalid IP address: %s" % addr)
-
-            if self._databaseType not in (const.ORG_EDITION, const.ISP_EDITION, const.ASNUM_EDITION):
-                raise GeoIPError('Invalid database type; org_* methods expect '\
-                                 'Org/ISP database')
-
-            return self._get_org(ipnum)
-        except ValueError:
-            raise GeoIPError('*_by_addr methods only accept IP addresses. Use *_by_name for hostnames. (Address: %s)' % addr)
-
-    def org_by_name(self, hostname):
-        """
-        Lookup the organization (or ISP) for hostname.
-        Use this method if you have an Organization/ISP database.
-
-        @param hostname: host name
-        @type hostname: str
-        @return: organization or ISP name
-        @rtype: str
-        """
-        addr = socket.gethostbyname(hostname)
-
-        return self.org_by_addr(addr)
-
-    def record_by_addr(self, addr):
-        """
-        Look up the record for a given IP address.
-        Use this method if you have a City database.
-
-        @param addr: IP address
-        @type addr: str
-        @return: dict with country_code, country_code3, country_name,
-            region, city, postal_code, latitude, longitude,
-            dma_code, metro_code, area_code, region_name, time_zone
-        @rtype: dict
-        """
-        try:
-            ipnum = ip2long(addr)
-
-            if not ipnum:
-                raise ValueError("Invalid IP address: %s" % addr)
-
-            if not self._databaseType in (const.CITY_EDITION_REV0, const.CITY_EDITION_REV1):
-                raise GeoIPError('Invalid database type; record_* methods expect City database')
-
-            return self._get_record(ipnum)
-        except ValueError:
-            raise GeoIPError('*_by_addr methods only accept IP addresses. Use *_by_name for hostnames. (Address: %s)' % addr)
-
-    def record_by_name(self, hostname):
-        """
-        Look up the record for a given hostname.
-        Use this method if you have a City database.
-
-        @param hostname: host name
-        @type hostname: str
-        @return: dict with country_code, country_code3, country_name,
-            region, city, postal_code, latitude, longitude,
-            dma_code, metro_code, area_code, region_name, time_zone
-        @rtype: dict
-        """
-        addr = socket.gethostbyname(hostname)
-
-        return self.record_by_addr(addr)
-
-    def region_by_addr(self, addr):
-        """
-        Lookup the region for given IP address.
-        Use this method if you have a Region database.
-
-        @param addr: IP address
-        @type addr: str
-        @return: dict containing country_code, region,
-            and region_name
-        @rtype: dict
-        """
-        try:
-            ipnum = ip2long(addr)
-
-            if not ipnum:
-                raise ValueError("Invalid IP address: %s" % addr)
-
-            if not self._databaseType in (const.REGION_EDITION_REV0, const.REGION_EDITION_REV1,
-                                          const.CITY_EDITION_REV0, const.CITY_EDITION_REV1):
-                raise GeoIPError('Invalid database type; region_* methods expect '\
-                                 'Region or City database')
-
-            return self._get_region(ipnum)
-        except ValueError:
-            raise GeoIPError('*_by_addr methods only accept IP addresses. Use *_by_name for hostnames. (Address: %s)' % addr)
-
-    def region_by_name(self, hostname):
-        """
-        Lookup the region for given hostname.
-        Use this method if you have a Region database.
-
-        @param hostname: host name
-        @type hostname: str
-        @return: dict containing country_code, region,
-            and region_name
-        @rtype: dict
-        """
-        addr = socket.gethostbyname(hostname)
-        return self.region_by_addr(addr)
-
-    def time_zone_by_addr(self, addr):
-        """
-        Look up the time zone for a given IP address.
-        Use this method if you have a Region or City database.
-
-        @param hostname: IP address
-        @type hostname: str
-        @return: Time zone
-        @rtype: str
-        """
-        try:
-            ipnum = ip2long(addr)
-
-            if not ipnum:
-                raise ValueError("Invalid IP address: %s" % addr)
-
-            if not self._databaseType in (const.REGION_EDITION_REV0, const.REGION_EDITION_REV1,
-                                          const.CITY_EDITION_REV0, const.CITY_EDITION_REV1):
-                raise GeoIPError('Invalid database type; region_* methods expect '\
-                                 'Region or City database')
-
-            return self._get_record(ipnum)['time_zone']
-        except ValueError:
-            raise GeoIPError('*_by_addr methods only accept IP addresses. Use *_by_name for hostnames. (Address: %s)' % addr)
-
-    def time_zone_by_name(self, hostname):
-        """
-        Look up the time zone for a given hostname.
-        Use this method if you have a Region or City database.
-
-        @param hostname: host name
-        @type hostname: str
-        @return: Time zone
-        @rtype: str
-        """
-        addr = socket.gethostbyname(hostname)
-        return self.time_zone_by_addr(addr)
+"""
+Pure Python GeoIP API. The API is based off of U{MaxMind's C-based Python API<http://www.maxmind.com/app/python>},
+but the code itself is based on the U{pure PHP5 API<http://pear.php.net/package/Net_GeoIP/>}
+by Jim Winstead and Hans Lellelid.
+
+It is mostly a drop-in replacement, except the
+C{new} and C{open} methods are gone. You should instantiate the L{GeoIP} class yourself:
+
+C{gi = GeoIP('/path/to/GeoIP.dat', pygeoip.MEMORY_CACHE)}
+
+@author: Jennifer Ennis <zaylea at gmail dot com>
+
+@license:
+Copyright(C) 2004 MaxMind LLC
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/lgpl.txt>.
+"""
+
+from __future__ import with_statement, absolute_import, division
+import os
+import math
+import socket
+import mmap
+import gzip
+import codecs
+from StringIO import StringIO
+
+from . import const
+from .util import ip2long
+from .timezone import time_zone_by_country_and_region
+
+import six
+
+MMAP_CACHE = const.MMAP_CACHE
+MEMORY_CACHE = const.MEMORY_CACHE
+STANDARD = const.STANDARD
+
+class GeoIPError(Exception):
+    pass
+
+class GeoIPMetaclass(type):
+
+    def __new__(cls, *args, **kwargs):
+        """
+        Singleton method to gets an instance without reparsing the db. Unique
+        instances are instantiated based on the filename of the db. Flags are
+        ignored for this, i.e. if you initialize one with STANDARD flag (default)
+        and then try later to initialize with MEMORY_CACHE, it will still
+        return the STANDARD one.
+        """
+
+        if not hasattr(cls, '_instances'):
+            cls._instances = {}
+
+        if len(args) > 0:
+            filename = args[0]
+        elif 'filename' in kwargs:
+            filename = kwargs['filename']
+
+        if not filename in cls._instances:
+            cls._instances[filename] = type.__new__(cls, *args, **kwargs)
+
+        return cls._instances[filename]
+
+GeoIPBase = GeoIPMetaclass('GeoIPBase', (object,), {})
+
+class GeoIP(GeoIPBase):
+
+    def __init__(self, filename, flags=0):
+        """
+        Initialize the class.
+
+        @param filename: path to a geoip database. If MEMORY_CACHE is used,
+            the file can be gzipped.
+        @type filename: str
+        @param flags: flags that affect how the database is processed.
+            Currently the only supported flags are STANDARD (the default),
+            MEMORY_CACHE (preload the whole file into memory), and
+            MMAP_CACHE (access the file via mmap).
+        @type flags: int
+        """
+        self._filename = filename
+        self._flags = flags
+
+        if self._flags & const.MMAP_CACHE:
+            with open(filename, 'rb') as f:
+                self._filehandle = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)
+
+        elif self._flags & const.MEMORY_CACHE:
+            if filename.endswith('.gz'):
+                opener = gzip.open
+            else:
+                opener = open
+
+            with opener(filename, 'rb') as f:
+                self._memoryBuffer = f.read()
+                self._filehandle = StringIO(self._memoryBuffer)
+        else:
+            self._filehandle = codecs.open(filename, 'rb','latin_1')
+
+        self._setup_segments()
+
+    def _setup_segments(self):
+        """
+        Parses the database file to determine what kind of database is being used and setup
+        segment sizes and start points that will be used by the seek*() methods later.
+        """
+        self._databaseType = const.COUNTRY_EDITION
+        self._recordLength = const.STANDARD_RECORD_LENGTH
+
+        filepos = self._filehandle.tell()
+        self._filehandle.seek(-3, os.SEEK_END)
+
+        for i in range(const.STRUCTURE_INFO_MAX_SIZE):
+            delim = self._filehandle.read(3)
+
+            if delim == six.u(chr(255) * 3):
+                self._databaseType = ord(self._filehandle.read(1))
+
+                if (self._databaseType >= 106):
+                    # backwards compatibility with databases from April 2003 and earlier
+                    self._databaseType -= 105
+
+                if self._databaseType == const.REGION_EDITION_REV0:
+                    self._databaseSegments = const.STATE_BEGIN_REV0
+
+                elif self._databaseType == const.REGION_EDITION_REV1:
+                    self._databaseSegments = const.STATE_BEGIN_REV1
+
+                elif self._databaseType in (const.CITY_EDITION_REV0,
+                                            const.CITY_EDITION_REV1,
+                                            const.ORG_EDITION,
+                                            const.ISP_EDITION,
+                                            const.ASNUM_EDITION):
+                    self._databaseSegments = 0
+                    buf = self._filehandle.read(const.SEGMENT_RECORD_LENGTH)
+
+                    for j in range(const.SEGMENT_RECORD_LENGTH):
+                        self._databaseSegments += (ord(buf[j]) << (j * 8))
+
+                    if self._databaseType in (const.ORG_EDITION, const.ISP_EDITION):
+                        self._recordLength = const.ORG_RECORD_LENGTH
+
+                break
+            else:
+                self._filehandle.seek(-4, os.SEEK_CUR)
+
+        if self._databaseType == const.COUNTRY_EDITION:
+            self._databaseSegments = const.COUNTRY_BEGIN
+
+        self._filehandle.seek(filepos, os.SEEK_SET)
+
+    def _lookup_country_id(self, addr):
+        """
+        Get the country index.
+
+        This method is called by the _lookupCountryCode and _lookupCountryName
+        methods. It looks up the index ('id') for the country which is the key
+        for the code and name.
+
+        @param addr: The IP address
+        @type addr: str
+        @return: network byte order 32-bit integer
+        @rtype: int
+        """
+
+        ipnum = ip2long(addr)
+
+        if not ipnum:
+            raise ValueError("Invalid IP address: %s" % addr)
+
+        if self._databaseType != const.COUNTRY_EDITION:
+            raise GeoIPError('Invalid database type; country_* methods expect '\
+                             'Country database')
+
+        return self._seek_country(ipnum) - const.COUNTRY_BEGIN
+
+    def _seek_country(self, ipnum):
+        """
+        Using the record length and appropriate start points, seek to the
+        country that corresponds to the converted IP address integer.
+
+        @param ipnum: result of ip2long conversion
+        @type ipnum: int
+        @return: offset of start of record
+        @rtype: int
+        """
+        offset = 0
+
+        for depth in range(31, -1, -1):
+
+            if self._flags & const.MEMORY_CACHE:
+                startIndex = 2 * self._recordLength * offset
+                length = 2 * self._recordLength
+                endIndex = startIndex + length
+                buf = self._memoryBuffer[startIndex:endIndex]
+            else:
+                self._filehandle.seek(2 * self._recordLength * offset, os.SEEK_SET)
+                buf = self._filehandle.read(2 * self._recordLength)
+
+            x = [0,0]
+
+            for i in range(2):
+                for j in range(self._recordLength):
+                    x[i] += ord(buf[self._recordLength * i + j]) << (j * 8)
+
+            if ipnum & (1 << depth):
+
+                if x[1] >= self._databaseSegments:
+                    return x[1]
+
+                offset = x[1]
+
+            else:
+
+                if x[0] >= self._databaseSegments:
+                    return x[0]
+
+                offset = x[0]
+
+
+        raise Exception('Error traversing database - perhaps it is corrupt?')
+
+    def _get_org(self, ipnum):
+        """
+        Seek and return organization (or ISP) name for converted IP addr.
+        @param ipnum: Converted IP address
+        @type ipnum: int
+        @return: org/isp name
+        @rtype: str
+        """
+
+        seek_org = self._seek_country(ipnum)
+        if seek_org == self._databaseSegments:
+            return None
+
+        record_pointer = seek_org + (2 * self._recordLength - 1) * self._databaseSegments
+
+        self._filehandle.seek(record_pointer, os.SEEK_SET)
+
+        org_buf = self._filehandle.read(const.MAX_ORG_RECORD_LENGTH)
+
+        return org_buf[:org_buf.index(chr(0))]
+
+    def _get_region(self, ipnum):
+        """
+        Seek and return the region info (dict containing country_code and region_name).
+
+        @param ipnum: converted IP address
+        @type ipnum: int
+        @return: dict containing country_code and region_name
+        @rtype: dict
+        """
+        country_code = ''
+        region = ''
+
+        if self._databaseType == const.REGION_EDITION_REV0:
+            seek_country = self._seek_country(ipnum)
+            seek_region = seek_country - const.STATE_BEGIN_REV0
+            if seek_region >= 1000:
+                country_code = 'US'
+                region = ''.join([chr((seek_region // 1000) // 26 + 65), chr((seek_region // 1000) % 26 + 65)])
+            else:
+                country_code = const.COUNTRY_CODES[seek_region]
+                region = ''
+        elif self._databaseType == const.REGION_EDITION_REV1:
+            seek_country = self._seek_country(ipnum)
+            seek_region = seek_country - const.STATE_BEGIN_REV1
+            if seek_region < const.US_OFFSET:
+                country_code = '';
+                region = ''
+            elif seek_region < const.CANADA_OFFSET:
+                country_code = 'US'
+                region = ''.join([chr((seek_region - const.US_OFFSET) // 26 + 65), chr((seek_region - const.US_OFFSET) % 26 + 65)])
+            elif seek_region  < const.WORLD_OFFSET:
+                country_code = 'CA'
+                region = ''.join([chr((seek_region - const.CANADA_OFFSET) // 26 + 65), chr((seek_region - const.CANADA_OFFSET) % 26 + 65)])
+            else:
+                i = (seek_region - const.WORLD_OFFSET) // const.FIPS_RANGE
+                if i < len(const.COUNTRY_CODES):
+                    #country_code = const.COUNTRY_CODES[(seek_region - const.WORLD_OFFSET) // const.FIPS_RANGE]
+                    country_code = const.COUNTRY_CODES[i]
+                else:
+                    country_code = ''
+                region = ''
+
+        elif self._databaseType in (const.CITY_EDITION_REV0, const.CITY_EDITION_REV1):
+            rec = self._get_record(ipnum)
+            country_code = rec['country_code'] if 'country_code' in rec else ''
+            region = rec['region_name'] if 'region_name' in rec else ''
+
+        return {'country_code' : country_code, 'region_name' : region }
+
+    def _get_record(self, ipnum):
+        """
+        Populate location dict for converted IP.
+
+        @param ipnum: converted IP address
+        @type ipnum: int
+        @return: dict with country_code, country_code3, country_name,
+            region, city, postal_code, latitude, longitude,
+            dma_code, metro_code, area_code, region_name, time_zone
+        @rtype: dict
+        """
+        seek_country = self._seek_country(ipnum)
+        if seek_country == self._databaseSegments:
+            return None
+
+        record_pointer = seek_country + (2 * self._recordLength - 1) * self._databaseSegments
+
+        self._filehandle.seek(record_pointer, os.SEEK_SET)
+        record_buf = self._filehandle.read(const.FULL_RECORD_LENGTH)
+
+        record = {}
+
+        record_buf_pos = 0
+        char = ord(record_buf[record_buf_pos])
+        #char = record_buf[record_buf_pos] if six.PY3 else ord(record_buf[record_buf_pos])
+        record['country_code'] = const.COUNTRY_CODES[char]
+        record['country_code3'] = const.COUNTRY_CODES3[char]
+        record['country_name'] = const.COUNTRY_NAMES[char]
+        record_buf_pos += 1
+        str_length = 0
+
+        # get region
+        char = ord(record_buf[record_buf_pos+str_length])
+        while (char != 0):
+            str_length += 1
+            char = ord(record_buf[record_buf_pos+str_length])
+
+        if str_length > 0:
+            record['region_name'] = record_buf[record_buf_pos:record_buf_pos+str_length]
+
+        record_buf_pos += str_length + 1
+        str_length = 0
+
+        # get city
+        char = ord(record_buf[record_buf_pos+str_length])
+        while (char != 0):
+            str_length += 1
+            char = ord(record_buf[record_buf_pos+str_length])
+
+        if str_length > 0:
+            record['city'] = record_buf[record_buf_pos:record_buf_pos+str_length]
+        else:
+            record['city'] = ''
+
+        record_buf_pos += str_length + 1
+        str_length = 0
+
+        # get the postal code
+        char = ord(record_buf[record_buf_pos+str_length])
+        while (char != 0):
+            str_length += 1
+            char = ord(record_buf[record_buf_pos+str_length])
+
+        if str_length > 0:
+            record['postal_code'] = record_buf[record_buf_pos:record_buf_pos+str_length]
+        else:
+            record['postal_code'] = None
+
+        record_buf_pos += str_length + 1
+        str_length = 0
+
+        latitude = 0
+        longitude = 0
+        for j in range(3):
+            char = ord(record_buf[record_buf_pos])
+            record_buf_pos += 1
+            latitude += (char << (j * 8))
+
+        record['latitude'] = (latitude/10000.0) - 180.0
+
+        for j in range(3):
+            char = ord(record_buf[record_buf_pos])
+            record_buf_pos += 1
+            longitude += (char << (j * 8))
+
+        record['longitude'] = (longitude/10000.0) - 180.0
+
+        if self._databaseType == const.CITY_EDITION_REV1:
+            dmaarea_combo = 0
+            if record['country_code'] == 'US':
+                for j in range(3):
+                    char = ord(record_buf[record_buf_pos])
+                    record_buf_pos += 1
+                    dmaarea_combo += (char << (j*8))
+
+                record['dma_code'] = int(math.floor(dmaarea_combo/1000))
+                record['area_code'] = dmaarea_combo%1000
+        else:
+            record['dma_code'] = 0
+            record['area_code'] = 0
+
+        if 'dma_code' in record and record['dma_code'] in const.DMA_MAP:
+            record['metro_code'] = const.DMA_MAP[record['dma_code']]
+        else:
+            record['metro_code'] = ''
+
+        if 'country_code' in record:
+            record['time_zone'] = time_zone_by_country_and_region(
+                record['country_code'], record.get('region_name')) or ''
+        else:
+            record['time_zone'] = ''
+
+        return record
+
+    def country_code_by_addr(self, addr):
+        """
+        Returns 2-letter country code (e.g. 'US') for specified IP address.
+        Use this method if you have a Country, Region, or City database.
+
+        @param addr: IP address
+        @type addr: str
+        @return: 2-letter country code
+        @rtype: str
+        """
+        try:
+            if self._databaseType == const.COUNTRY_EDITION:
+                country_id = self._lookup_country_id(addr)
+                return const.COUNTRY_CODES[country_id]
+            elif self._databaseType in (const.REGION_EDITION_REV0, const.REGION_EDITION_REV1,
+                                          const.CITY_EDITION_REV0, const.CITY_EDITION_REV1):
+                return self.region_by_addr(addr)['country_code']
+            else:
+                raise GeoIPError('Invalid database type; country_* methods expect '\
+                                 'Country, City, or Region database')
+
+        except ValueError:
+            raise GeoIPError('*_by_addr methods only accept IP addresses. Use *_by_name for hostnames. (Address: %s)' % addr)
+
+    def country_code_by_name(self, hostname):
+        """
+        Returns 2-letter country code (e.g. 'US') for specified hostname.
+        Use this method if you have a Country, Region, or City database.
+
+        @param hostname: host name
+        @type hostname: str
+        @return: 2-letter country code
+        @rtype: str
+        """
+        addr = socket.gethostbyname(hostname)
+
+        return self.country_code_by_addr(addr)
+
+    def country_name_by_addr(self, addr):
+        """
+        Returns full country name for specified IP address.
+        Use this method if you have a Country or City database.
+
+        @param addr: IP address
+        @type addr: str
+        @return: country name
+        @rtype: str
+        """
+        try:
+            if self._databaseType == const.COUNTRY_EDITION:
+                country_id = self._lookup_country_id(addr)
+                return const.COUNTRY_NAMES[country_id]
+            elif self._databaseType in (const.CITY_EDITION_REV0, const.CITY_EDITION_REV1):
+                return self.record_by_addr(addr)['country_name']
+            else:
+                raise GeoIPError('Invalid database type; country_* methods expect '\
+                                 'Country or City database')
+        except ValueError:
+            raise GeoIPError('*_by_addr methods only accept IP addresses. Use *_by_name for hostnames. (Address: %s)' % addr)
+
+    def country_name_by_name(self, hostname):
+        """
+        Returns full country name for specified hostname.
+        Use this method if you have a Country database.
+
+        @param hostname: host name
+        @type hostname: str
+        @return: country name
+        @rtype: str
+        """
+        addr = socket.gethostbyname(hostname)
+        return self.country_name_by_addr(addr)
+
+    def org_by_addr(self, addr):
+        """
+        Lookup the organization (or ISP) for given IP address.
+        Use this method if you have an Organization/ISP database.
+
+        @param addr: IP address
+        @type addr: str
+        @return: organization or ISP name
+        @rtype: str
+        """
+        try:
+            ipnum = ip2long(addr)
+
+            if not ipnum:
+                raise ValueError("Invalid IP address: %s" % addr)
+
+            if self._databaseType not in (const.ORG_EDITION, const.ISP_EDITION, const.ASNUM_EDITION):
+                raise GeoIPError('Invalid database type; org_* methods expect '\
+                                 'Org/ISP database')
+
+            return self._get_org(ipnum)
+        except ValueError:
+            raise GeoIPError('*_by_addr methods only accept IP addresses. Use *_by_name for hostnames. (Address: %s)' % addr)
+
+    def org_by_name(self, hostname):
+        """
+        Lookup the organization (or ISP) for hostname.
+        Use this method if you have an Organization/ISP database.
+
+        @param hostname: host name
+        @type hostname: str
+        @return: organization or ISP name
+        @rtype: str
+        """
+        addr = socket.gethostbyname(hostname)
+
+        return self.org_by_addr(addr)
+
+    def record_by_addr(self, addr):
+        """
+        Look up the record for a given IP address.
+        Use this method if you have a City database.
+
+        @param addr: IP address
+        @type addr: str
+        @return: dict with country_code, country_code3, country_name,
+            region, city, postal_code, latitude, longitude,
+            dma_code, metro_code, area_code, region_name, time_zone
+        @rtype: dict
+        """
+        try:
+            ipnum = ip2long(addr)
+
+            if not ipnum:
+                raise ValueError("Invalid IP address: %s" % addr)
+
+            if not self._databaseType in (const.CITY_EDITION_REV0, const.CITY_EDITION_REV1):
+                raise GeoIPError('Invalid database type; record_* methods expect City database')
+
+            return self._get_record(ipnum)
+        except ValueError:
+            raise GeoIPError('*_by_addr methods only accept IP addresses. Use *_by_name for hostnames. (Address: %s)' % addr)
+
+    def record_by_name(self, hostname):
+        """
+        Look up the record for a given hostname.
+        Use this method if you have a City database.
+
+        @param hostname: host name
+        @type hostname: str
+        @return: dict with country_code, country_code3, country_name,
+            region, city, postal_code, latitude, longitude,
+            dma_code, metro_code, area_code, region_name, time_zone
+        @rtype: dict
+        """
+        addr = socket.gethostbyname(hostname)
+
+        return self.record_by_addr(addr)
+
+    def region_by_addr(self, addr):
+        """
+        Lookup the region for given IP address.
+        Use this method if you have a Region database.
+
+        @param addr: IP address
+        @type addr: str
+        @return: dict containing country_code, region,
+            and region_name
+        @rtype: dict
+        """
+        try:
+            ipnum = ip2long(addr)
+
+            if not ipnum:
+                raise ValueError("Invalid IP address: %s" % addr)
+
+            if not self._databaseType in (const.REGION_EDITION_REV0, const.REGION_EDITION_REV1,
+                                          const.CITY_EDITION_REV0, const.CITY_EDITION_REV1):
+                raise GeoIPError('Invalid database type; region_* methods expect '\
+                                 'Region or City database')
+
+            return self._get_region(ipnum)
+        except ValueError:
+            raise GeoIPError('*_by_addr methods only accept IP addresses. Use *_by_name for hostnames. (Address: %s)' % addr)
+
+    def region_by_name(self, hostname):
+        """
+        Lookup the region for given hostname.
+        Use this method if you have a Region database.
+
+        @param hostname: host name
+        @type hostname: str
+        @return: dict containing country_code, region,
+            and region_name
+        @rtype: dict
+        """
+        addr = socket.gethostbyname(hostname)
+        return self.region_by_addr(addr)
+
+    def time_zone_by_addr(self, addr):
+        """
+        Look up the time zone for a given IP address.
+        Use this method if you have a Region or City database.
+
+        @param hostname: IP address
+        @type hostname: str
+        @return: Time zone
+        @rtype: str
+        """
+        try:
+            ipnum = ip2long(addr)
+
+            if not ipnum:
+                raise ValueError("Invalid IP address: %s" % addr)
+
+            if not self._databaseType in (const.REGION_EDITION_REV0, const.REGION_EDITION_REV1,
+                                          const.CITY_EDITION_REV0, const.CITY_EDITION_REV1):
+                raise GeoIPError('Invalid database type; region_* methods expect '\
+                                 'Region or City database')
+
+            return self._get_record(ipnum)['time_zone']
+        except ValueError:
+            raise GeoIPError('*_by_addr methods only accept IP addresses. Use *_by_name for hostnames. (Address: %s)' % addr)
+
+    def time_zone_by_name(self, hostname):
+        """
+        Look up the time zone for a given hostname.
+        Use this method if you have a Region or City database.
+
+        @param hostname: host name
+        @type hostname: str
+        @return: Time zone
+        @rtype: str
+        """
+        addr = socket.gethostbyname(hostname)
+        return self.time_zone_by_addr(addr)