OLD | NEW |
| (Empty) |
1 # -*- coding: utf-8 -*- | |
2 """ | |
3 Pure Python GeoIP API | |
4 | |
5 The API is based on MaxMind's C-based Python API, but the code itself is | |
6 ported from the Pure PHP GeoIP API by Jim Winstead and Hans Lellelid. | |
7 | |
8 @author: Jennifer Ennis <zaylea@gmail.com> | |
9 | |
10 @license: Copyright(C) 2004 MaxMind LLC | |
11 | |
12 This program is free software: you can redistribute it and/or modify | |
13 it under the terms of the GNU Lesser General Public License as published by | |
14 the Free Software Foundation, either version 3 of the License, or | |
15 (at your option) any later version. | |
16 | |
17 This program is distributed in the hope that it will be useful, | |
18 but WITHOUT ANY WARRANTY; without even the implied warranty of | |
19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
20 GNU General Public License for more details. | |
21 | |
22 You should have received a copy of the GNU Lesser General Public License | |
23 along with this program. If not, see <http://www.gnu.org/licenses/lgpl.txt>. | |
24 """ | |
25 | |
26 import os | |
27 import math | |
28 import socket | |
29 import mmap | |
30 import codecs | |
31 from threading import Lock | |
32 | |
33 try: | |
34 from StringIO import StringIO | |
35 except ImportError: | |
36 from io import StringIO, BytesIO | |
37 | |
38 from pygeoip import util, const | |
39 from pygeoip.const import PY2, PY3 | |
40 from pygeoip.timezone import time_zone_by_country_and_region | |
41 | |
42 | |
43 STANDARD = const.STANDARD | |
44 MMAP_CACHE = const.MMAP_CACHE | |
45 MEMORY_CACHE = const.MEMORY_CACHE | |
46 | |
47 ENCODING = const.ENCODING | |
48 | |
49 | |
50 class GeoIPError(Exception): | |
51 pass | |
52 | |
53 | |
54 class GeoIPMetaclass(type): | |
55 def __new__(cls, *args, **kwargs): | |
56 """ | |
57 Singleton method to gets an instance without reparsing the db. Unique | |
58 instances are instantiated based on the filename of the db. Flags are | |
59 ignored for this, i.e. if you initialize one with STANDARD | |
60 flag (default) and then try later to initialize with MEMORY_CACHE, it | |
61 will still return the STANDARD one. | |
62 """ | |
63 if not hasattr(cls, '_instances'): | |
64 cls._instances = {} | |
65 | |
66 if len(args) > 0: | |
67 filename = args[0] | |
68 elif 'filename' in kwargs: | |
69 filename = kwargs['filename'] | |
70 | |
71 if filename not in cls._instances: | |
72 cls._instances[filename] = type.__new__(cls, *args, **kwargs) | |
73 | |
74 return cls._instances[filename] | |
75 | |
76 | |
77 GeoIPBase = GeoIPMetaclass('GeoIPBase', (object,), {}) | |
78 | |
79 | |
80 class GeoIP(GeoIPBase): | |
81 def __init__(self, filename, flags=0): | |
82 """ | |
83 Initialize the class. | |
84 | |
85 @param filename: Path to a geoip database. | |
86 @type filename: str | |
87 @param flags: Flags that affect how the database is processed. | |
88 Currently supported flags are STANDARD (the default), | |
89 MEMORY_CACHE (preload the whole file into memory) and | |
90 MMAP_CACHE (access the file via mmap). | |
91 @type flags: int | |
92 """ | |
93 self._filename = filename | |
94 self._flags = flags | |
95 | |
96 if self._flags & const.MMAP_CACHE: | |
97 f = open(filename, 'rb') | |
98 access = mmap.ACCESS_READ | |
99 self._filehandle = mmap.mmap(f.fileno(), 0, access=access) | |
100 f.close() | |
101 | |
102 elif self._flags & const.MEMORY_CACHE: | |
103 f = open(filename, 'rb') | |
104 self._memoryBuffer = f.read() | |
105 iohandle = BytesIO if PY3 else StringIO | |
106 self._filehandle = iohandle(self._memoryBuffer) | |
107 f.close() | |
108 | |
109 else: | |
110 self._filehandle = codecs.open(filename, 'rb', ENCODING) | |
111 | |
112 self._lock = Lock() | |
113 self._setup_segments() | |
114 | |
115 def _setup_segments(self): | |
116 """ | |
117 Parses the database file to determine what kind of database is | |
118 being used and setup segment sizes and start points that will | |
119 be used by the seek*() methods later. | |
120 | |
121 Supported databases: | |
122 | |
123 * COUNTRY_EDITION | |
124 * COUNTRY_EDITION_V6 | |
125 * REGION_EDITION_REV0 | |
126 * REGION_EDITION_REV1 | |
127 * CITY_EDITION_REV0 | |
128 * CITY_EDITION_REV1 | |
129 * CITY_EDITION_REV1_V6 | |
130 * ORG_EDITION | |
131 * ISP_EDITION | |
132 * ASNUM_EDITION | |
133 * ASNUM_EDITION_V6 | |
134 | |
135 """ | |
136 self._databaseType = const.COUNTRY_EDITION | |
137 self._recordLength = const.STANDARD_RECORD_LENGTH | |
138 self._databaseSegments = const.COUNTRY_BEGIN | |
139 | |
140 self._lock.acquire() | |
141 filepos = self._filehandle.tell() | |
142 self._filehandle.seek(-3, os.SEEK_END) | |
143 | |
144 for i in range(const.STRUCTURE_INFO_MAX_SIZE): | |
145 chars = chr(255) * 3 | |
146 delim = self._filehandle.read(3) | |
147 | |
148 if PY3 and type(delim) is bytes: | |
149 delim = delim.decode(ENCODING) | |
150 | |
151 if PY2: | |
152 chars = chars.decode(ENCODING) | |
153 if type(delim) is str: | |
154 delim = delim.decode(ENCODING) | |
155 | |
156 if delim == chars: | |
157 byte = self._filehandle.read(1) | |
158 self._databaseType = ord(byte) | |
159 | |
160 # Compatibility with databases from April 2003 and earlier | |
161 if (self._databaseType >= 106): | |
162 self._databaseType -= 105 | |
163 | |
164 if self._databaseType == const.REGION_EDITION_REV0: | |
165 self._databaseSegments = const.STATE_BEGIN_REV0 | |
166 | |
167 elif self._databaseType == const.REGION_EDITION_REV1: | |
168 self._databaseSegments = const.STATE_BEGIN_REV1 | |
169 | |
170 elif self._databaseType in (const.CITY_EDITION_REV0, | |
171 const.CITY_EDITION_REV1, | |
172 const.CITY_EDITION_REV1_V6, | |
173 const.ORG_EDITION, | |
174 const.ISP_EDITION, | |
175 const.ASNUM_EDITION, | |
176 const.ASNUM_EDITION_V6): | |
177 self._databaseSegments = 0 | |
178 buf = self._filehandle.read(const.SEGMENT_RECORD_LENGTH) | |
179 | |
180 if PY3 and type(buf) is bytes: | |
181 buf = buf.decode(ENCODING) | |
182 | |
183 for j in range(const.SEGMENT_RECORD_LENGTH): | |
184 self._databaseSegments += (ord(buf[j]) << (j * 8)) | |
185 | |
186 LONG_RECORDS = (const.ORG_EDITION, const.ISP_EDITION) | |
187 if self._databaseType in LONG_RECORDS: | |
188 self._recordLength = const.ORG_RECORD_LENGTH | |
189 break | |
190 else: | |
191 self._filehandle.seek(-4, os.SEEK_CUR) | |
192 | |
193 self._filehandle.seek(filepos, os.SEEK_SET) | |
194 self._lock.release() | |
195 | |
196 def _seek_country(self, ipnum): | |
197 """ | |
198 Using the record length and appropriate start points, seek to the | |
199 country that corresponds to the converted IP address integer. | |
200 | |
201 @param ipnum: result of ip2long conversion | |
202 @type ipnum: int | |
203 @return: offset of start of record | |
204 @rtype: int | |
205 """ | |
206 try: | |
207 offset = 0 | |
208 seek_depth = 127 if len(str(ipnum)) > 10 else 31 | |
209 | |
210 for depth in range(seek_depth, -1, -1): | |
211 if self._flags & const.MEMORY_CACHE: | |
212 startIndex = 2 * self._recordLength * offset | |
213 endIndex = startIndex + (2 * self._recordLength) | |
214 buf = self._memoryBuffer[startIndex:endIndex] | |
215 else: | |
216 startIndex = 2 * self._recordLength * offset | |
217 readLength = 2 * self._recordLength | |
218 self._lock.acquire() | |
219 self._filehandle.seek(startIndex, os.SEEK_SET) | |
220 buf = self._filehandle.read(readLength) | |
221 self._lock.release() | |
222 | |
223 if PY3 and type(buf) is bytes: | |
224 buf = buf.decode(ENCODING) | |
225 | |
226 x = [0, 0] | |
227 for i in range(2): | |
228 for j in range(self._recordLength): | |
229 byte = buf[self._recordLength * i + j] | |
230 x[i] += ord(byte) << (j * 8) | |
231 if ipnum & (1 << depth): | |
232 if x[1] >= self._databaseSegments: | |
233 return x[1] | |
234 offset = x[1] | |
235 else: | |
236 if x[0] >= self._databaseSegments: | |
237 return x[0] | |
238 offset = x[0] | |
239 except: | |
240 pass | |
241 | |
242 raise GeoIPError('Corrupt database') | |
243 | |
244 def _get_org(self, ipnum): | |
245 """ | |
246 Seek and return organization or ISP name for ipnum. | |
247 @param ipnum: Converted IP address | |
248 @type ipnum: int | |
249 @return: org/isp name | |
250 @rtype: str | |
251 """ | |
252 seek_org = self._seek_country(ipnum) | |
253 if seek_org == self._databaseSegments: | |
254 return None | |
255 | |
256 read_length = (2 * self._recordLength - 1) * self._databaseSegments | |
257 self._lock.acquire() | |
258 self._filehandle.seek(seek_org + read_length, os.SEEK_SET) | |
259 buf = self._filehandle.read(const.MAX_ORG_RECORD_LENGTH) | |
260 self._lock.release() | |
261 | |
262 if PY3 and type(buf) is bytes: | |
263 buf = buf.decode(ENCODING) | |
264 | |
265 return buf[:buf.index(chr(0))] | |
266 | |
267 def _get_region(self, ipnum): | |
268 """ | |
269 Seek and return the region info (dict containing country_code | |
270 and region_name). | |
271 | |
272 @param ipnum: Converted IP address | |
273 @type ipnum: int | |
274 @return: dict containing country_code and region_name | |
275 @rtype: dict | |
276 """ | |
277 region = '' | |
278 country_code = '' | |
279 seek_country = self._seek_country(ipnum) | |
280 | |
281 def get_region_name(offset): | |
282 region1 = chr(offset // 26 + 65) | |
283 region2 = chr(offset % 26 + 65) | |
284 return ''.join([region1, region2]) | |
285 | |
286 if self._databaseType == const.REGION_EDITION_REV0: | |
287 seek_region = seek_country - const.STATE_BEGIN_REV0 | |
288 if seek_region >= 1000: | |
289 country_code = 'US' | |
290 region = get_region_name(seek_region - 1000) | |
291 else: | |
292 country_code = const.COUNTRY_CODES[seek_region] | |
293 elif self._databaseType == const.REGION_EDITION_REV1: | |
294 seek_region = seek_country - const.STATE_BEGIN_REV1 | |
295 if seek_region < const.US_OFFSET: | |
296 pass | |
297 elif seek_region < const.CANADA_OFFSET: | |
298 country_code = 'US' | |
299 region = get_region_name(seek_region - const.US_OFFSET) | |
300 elif seek_region < const.WORLD_OFFSET: | |
301 country_code = 'CA' | |
302 region = get_region_name(seek_region - const.CANADA_OFFSET) | |
303 else: | |
304 index = (seek_region - const.WORLD_OFFSET) // const.FIPS_RANGE | |
305 if index in const.COUNTRY_CODES: | |
306 country_code = const.COUNTRY_CODES[index] | |
307 elif self._databaseType in const.CITY_EDITIONS: | |
308 rec = self._get_record(ipnum) | |
309 region = rec.get('region_name', '') | |
310 country_code = rec.get('country_code', '') | |
311 | |
312 return {'country_code': country_code, 'region_name': region} | |
313 | |
314 def _get_record(self, ipnum): | |
315 """ | |
316 Populate location dict for converted IP. | |
317 | |
318 @param ipnum: Converted IP address | |
319 @type ipnum: int | |
320 @return: dict with country_code, country_code3, country_name, | |
321 region, city, postal_code, latitude, longitude, | |
322 dma_code, metro_code, area_code, region_name, time_zone | |
323 @rtype: dict | |
324 """ | |
325 seek_country = self._seek_country(ipnum) | |
326 if seek_country == self._databaseSegments: | |
327 return {} | |
328 | |
329 read_length = (2 * self._recordLength - 1) * self._databaseSegments | |
330 self._lock.acquire() | |
331 self._filehandle.seek(seek_country + read_length, os.SEEK_SET) | |
332 buf = self._filehandle.read(const.FULL_RECORD_LENGTH) | |
333 self._lock.release() | |
334 | |
335 if PY3 and type(buf) is bytes: | |
336 buf = buf.decode(ENCODING) | |
337 | |
338 record = { | |
339 'dma_code': 0, | |
340 'area_code': 0, | |
341 'metro_code': '', | |
342 'postal_code': '' | |
343 } | |
344 | |
345 latitude = 0 | |
346 longitude = 0 | |
347 buf_pos = 0 | |
348 | |
349 # Get country | |
350 char = ord(buf[buf_pos]) | |
351 record['country_code'] = const.COUNTRY_CODES[char] | |
352 record['country_code3'] = const.COUNTRY_CODES3[char] | |
353 record['country_name'] = const.COUNTRY_NAMES[char] | |
354 record['continent'] = const.CONTINENT_NAMES[char] | |
355 | |
356 buf_pos += 1 | |
357 def get_data(buf, buf_pos): | |
358 offset = buf_pos | |
359 char = ord(buf[offset]) | |
360 while (char != 0): | |
361 offset += 1 | |
362 char = ord(buf[offset]) | |
363 if offset > buf_pos: | |
364 return (offset, buf[buf_pos:offset]) | |
365 return (offset, '') | |
366 | |
367 offset, record['region_name'] = get_data(buf, buf_pos) | |
368 offset, record['city'] = get_data(buf, offset + 1) | |
369 offset, record['postal_code'] = get_data(buf, offset + 1) | |
370 buf_pos = offset + 1 | |
371 | |
372 for j in range(3): | |
373 char = ord(buf[buf_pos]) | |
374 buf_pos += 1 | |
375 latitude += (char << (j * 8)) | |
376 | |
377 for j in range(3): | |
378 char = ord(buf[buf_pos]) | |
379 buf_pos += 1 | |
380 longitude += (char << (j * 8)) | |
381 | |
382 record['latitude'] = (latitude / 10000.0) - 180.0 | |
383 record['longitude'] = (longitude / 10000.0) - 180.0 | |
384 | |
385 if self._databaseType in (const.CITY_EDITION_REV1, const.CITY_EDITION_RE
V1_V6): | |
386 dmaarea_combo = 0 | |
387 if record['country_code'] == 'US': | |
388 for j in range(3): | |
389 char = ord(buf[buf_pos]) | |
390 dmaarea_combo += (char << (j * 8)) | |
391 buf_pos += 1 | |
392 | |
393 record['dma_code'] = int(math.floor(dmaarea_combo / 1000)) | |
394 record['area_code'] = dmaarea_combo % 1000 | |
395 | |
396 record['metro_code'] = const.DMA_MAP.get(record['dma_code']) | |
397 params = (record['country_code'], record['region_name']) | |
398 record['time_zone'] = time_zone_by_country_and_region(*params) | |
399 | |
400 return record | |
401 | |
402 def _gethostbyname(self, hostname): | |
403 if self._databaseType in const.IPV6_EDITIONS: | |
404 try: | |
405 response = socket.getaddrinfo(hostname, 0, socket.AF_INET6) | |
406 family, socktype, proto, canonname, sockaddr = response[0] | |
407 address, port, flow, scope = sockaddr | |
408 return address | |
409 except socket.gaierror: | |
410 return '' | |
411 else: | |
412 return socket.gethostbyname(hostname) | |
413 | |
414 def id_by_addr(self, addr): | |
415 """ | |
416 Get the country index. | |
417 Looks up the index for the country which is the key for | |
418 the code and name. | |
419 | |
420 @param addr: The IP address | |
421 @type addr: str | |
422 @return: network byte order 32-bit integer | |
423 @rtype: int | |
424 """ | |
425 ipnum = util.ip2long(addr) | |
426 if not ipnum: | |
427 raise ValueError("Invalid IP address: %s" % addr) | |
428 | |
429 COUNTY_EDITIONS = (const.COUNTRY_EDITION, const.COUNTRY_EDITION_V6) | |
430 if self._databaseType not in COUNTY_EDITIONS: | |
431 message = 'Invalid database type, expected Country' | |
432 raise GeoIPError(message) | |
433 | |
434 return self._seek_country(ipnum) - const.COUNTRY_BEGIN | |
435 | |
436 def country_code_by_addr(self, addr): | |
437 """ | |
438 Returns 2-letter country code (e.g. 'US') for specified IP address. | |
439 Use this method if you have a Country, Region, or City database. | |
440 | |
441 @param addr: IP address | |
442 @type addr: str | |
443 @return: 2-letter country code | |
444 @rtype: str | |
445 """ | |
446 try: | |
447 VALID_EDITIONS = (const.COUNTRY_EDITION, const.COUNTRY_EDITION_V6) | |
448 if self._databaseType in VALID_EDITIONS: | |
449 ipv = 6 if addr.find(':') >= 0 else 4 | |
450 | |
451 if ipv == 4 and self._databaseType != const.COUNTRY_EDITION: | |
452 message = 'Invalid database type; expected IPv6 address' | |
453 raise ValueError(message) | |
454 if ipv == 6 and self._databaseType != const.COUNTRY_EDITION_V6: | |
455 message = 'Invalid database type; expected IPv4 address' | |
456 raise ValueError(message) | |
457 | |
458 country_id = self.id_by_addr(addr) | |
459 return const.COUNTRY_CODES[country_id] | |
460 elif self._databaseType in const.REGION_CITY_EDITIONS: | |
461 return self.region_by_addr(addr).get('country_code') | |
462 | |
463 message = 'Invalid database type, expected Country, City or Region' | |
464 raise GeoIPError(message) | |
465 except ValueError: | |
466 raise GeoIPError('Failed to lookup address %s' % addr) | |
467 | |
468 def country_code_by_name(self, hostname): | |
469 """ | |
470 Returns 2-letter country code (e.g. 'US') for specified hostname. | |
471 Use this method if you have a Country, Region, or City database. | |
472 | |
473 @param hostname: Hostname | |
474 @type hostname: str | |
475 @return: 2-letter country code | |
476 @rtype: str | |
477 """ | |
478 addr = self._gethostbyname(hostname) | |
479 return self.country_code_by_addr(addr) | |
480 | |
481 def country_name_by_addr(self, addr): | |
482 """ | |
483 Returns full country name for specified IP address. | |
484 Use this method if you have a Country or City database. | |
485 | |
486 @param addr: IP address | |
487 @type addr: str | |
488 @return: country name | |
489 @rtype: str | |
490 """ | |
491 try: | |
492 VALID_EDITIONS = (const.COUNTRY_EDITION, const.COUNTRY_EDITION_V6) | |
493 if self._databaseType in VALID_EDITIONS: | |
494 country_id = self.id_by_addr(addr) | |
495 return const.COUNTRY_NAMES[country_id] | |
496 elif self._databaseType in const.CITY_EDITIONS: | |
497 return self.record_by_addr(addr).get('country_name') | |
498 else: | |
499 message = 'Invalid database type, expected Country or City' | |
500 raise GeoIPError(message) | |
501 except ValueError: | |
502 raise GeoIPError('Failed to lookup address %s' % addr) | |
503 | |
504 def country_name_by_name(self, hostname): | |
505 """ | |
506 Returns full country name for specified hostname. | |
507 Use this method if you have a Country database. | |
508 | |
509 @param hostname: Hostname | |
510 @type hostname: str | |
511 @return: country name | |
512 @rtype: str | |
513 """ | |
514 addr = self._gethostbyname(hostname) | |
515 return self.country_name_by_addr(addr) | |
516 | |
517 def org_by_addr(self, addr): | |
518 """ | |
519 Lookup Organization, ISP or ASNum for given IP address. | |
520 Use this method if you have an Organization, ISP or ASNum database. | |
521 | |
522 @param addr: IP address | |
523 @type addr: str | |
524 @return: organization or ISP name | |
525 @rtype: str | |
526 """ | |
527 try: | |
528 ipnum = util.ip2long(addr) | |
529 if not ipnum: | |
530 raise ValueError('Invalid IP address') | |
531 | |
532 valid = (const.ORG_EDITION, const.ISP_EDITION, const.ASNUM_EDITION,
const.ASNUM_EDITION_V6) | |
533 if self._databaseType not in valid: | |
534 message = 'Invalid database type, expected Org, ISP or ASNum' | |
535 raise GeoIPError(message) | |
536 | |
537 return self._get_org(ipnum) | |
538 except ValueError: | |
539 raise GeoIPError('Failed to lookup address %s' % addr) | |
540 | |
541 def org_by_name(self, hostname): | |
542 """ | |
543 Lookup the organization (or ISP) for hostname. | |
544 Use this method if you have an Organization/ISP database. | |
545 | |
546 @param hostname: Hostname | |
547 @type hostname: str | |
548 @return: Organization or ISP name | |
549 @rtype: str | |
550 """ | |
551 addr = self._gethostbyname(hostname) | |
552 return self.org_by_addr(addr) | |
553 | |
554 def record_by_addr(self, addr): | |
555 """ | |
556 Look up the record for a given IP address. | |
557 Use this method if you have a City database. | |
558 | |
559 @param addr: IP address | |
560 @type addr: str | |
561 @return: Dictionary with country_code, country_code3, country_name, | |
562 region, city, postal_code, latitude, longitude, dma_code, | |
563 metro_code, area_code, region_name, time_zone | |
564 @rtype: dict | |
565 """ | |
566 try: | |
567 ipnum = util.ip2long(addr) | |
568 if not ipnum: | |
569 raise ValueError('Invalid IP address') | |
570 | |
571 if self._databaseType not in const.CITY_EDITIONS: | |
572 message = 'Invalid database type, expected City' | |
573 raise GeoIPError(message) | |
574 | |
575 rec = self._get_record(ipnum) | |
576 if not rec: | |
577 return None | |
578 | |
579 return rec | |
580 except ValueError: | |
581 raise GeoIPError('Failed to lookup address %s' % addr) | |
582 | |
583 def record_by_name(self, hostname): | |
584 """ | |
585 Look up the record for a given hostname. | |
586 Use this method if you have a City database. | |
587 | |
588 @param hostname: Hostname | |
589 @type hostname: str | |
590 @return: Dictionary with country_code, country_code3, country_name, | |
591 region, city, postal_code, latitude, longitude, dma_code, | |
592 metro_code, area_code, region_name, time_zone | |
593 @rtype: dict | |
594 """ | |
595 addr = self._gethostbyname(hostname) | |
596 return self.record_by_addr(addr) | |
597 | |
598 def region_by_addr(self, addr): | |
599 """ | |
600 Lookup the region for given IP address. | |
601 Use this method if you have a Region database. | |
602 | |
603 @param addr: IP address | |
604 @type addr: str | |
605 @return: Dictionary containing country_code, region and region_name | |
606 @rtype: dict | |
607 """ | |
608 try: | |
609 ipnum = util.ip2long(addr) | |
610 if not ipnum: | |
611 raise ValueError('Invalid IP address') | |
612 | |
613 if self._databaseType not in const.REGION_CITY_EDITIONS: | |
614 message = 'Invalid database type, expected Region or City' | |
615 raise GeoIPError(message) | |
616 | |
617 return self._get_region(ipnum) | |
618 except ValueError: | |
619 raise GeoIPError('Failed to lookup address %s' % addr) | |
620 | |
621 def region_by_name(self, hostname): | |
622 """ | |
623 Lookup the region for given hostname. | |
624 Use this method if you have a Region database. | |
625 | |
626 @param hostname: Hostname | |
627 @type hostname: str | |
628 @return: Dictionary containing country_code, region, and region_name | |
629 @rtype: dict | |
630 """ | |
631 addr = self._gethostbyname(hostname) | |
632 return self.region_by_addr(addr) | |
633 | |
634 def time_zone_by_addr(self, addr): | |
635 """ | |
636 Look up the time zone for a given IP address. | |
637 Use this method if you have a Region or City database. | |
638 | |
639 @param addr: IP address | |
640 @type addr: str | |
641 @return: Time zone | |
642 @rtype: str | |
643 """ | |
644 try: | |
645 ipnum = util.ip2long(addr) | |
646 if not ipnum: | |
647 raise ValueError('Invalid IP address') | |
648 | |
649 if self._databaseType not in const.CITY_EDITIONS: | |
650 message = 'Invalid database type, expected City' | |
651 raise GeoIPError(message) | |
652 | |
653 return self._get_record(ipnum).get('time_zone') | |
654 except ValueError: | |
655 raise GeoIPError('Failed to lookup address %s' % addr) | |
656 | |
657 def time_zone_by_name(self, hostname): | |
658 """ | |
659 Look up the time zone for a given hostname. | |
660 Use this method if you have a Region or City database. | |
661 | |
662 @param hostname: Hostname | |
663 @type hostname: str | |
664 @return: Time zone | |
665 @rtype: str | |
666 """ | |
667 addr = self._gethostbyname(hostname) | |
668 return self.time_zone_by_addr(addr) | |
OLD | NEW |