One of the best options is the Maxmind.com GeoIP . The solution provided here applies to geolocate an IP address to get the timezone, country, city etc
Local or Remote
For better results I recommend to use their web service to geolocate a city : http://dev.maxmind.com/geoip/geoip2/web-services .The service is not free but you can pay 20$ for 50K resolutions. Fair enough.
I write a new post on how to query the web service.
Another option is to resolve the geolocation locally. Maxmind provides a free database that is updated every month. As a free product, it is less accurate than the web service but gives good results. http://dev.maxmind.com/geoip/legacy/geolite/
Local set up
We need:- download the database
- include the `geoip` gem, that will read the database
- our code to query the geoip gem
- link back to Maxmind
Download database
Read http://dev.maxmind.com/geoip/legacy/install/city/ for instructions. It boils down to:cd /tmp
wget -N http://geolite.maxmind.com/download/geoip/database/GeoLiteCity.dat.gz
gunzip GeoLiteCity.dat.gz
mv GeoLiteCity.dat /usr/local/share/GeoIP/
I recommend you to make a bash script with these commands and run a cron once a month
Required gem
To read the legacy GeoIP database I recommend the pure ruby geoip gem. Just include in your Gemfile
gem 'geoip'
or
gem install geoip
Boilerplate
The interface of GeoiP gem is really simple however, as we are using a database with less than 100% accuracy, we should account for cases where timezone is not available.When a ip resolves to a location without timezone, the best trick I found was to try with 'enclosing' networks (bigger supernets) until a result has timezone. That is: first try a perfect match, then try for the network with the last bit of the address masked, then mask 2 bits ... https://en.wikipedia.org/wiki/Subnetwork#Subnet_and_host_counts
class Ip2Geo DB_PATH = '/usr/local/share/GeoIP/GeoLiteCity.dat' def timezone(ipv4) if ipv4.present? addr = IPAddr.new(ipv4.to_s.strip) 32.downto(16).map do |mask| #if an address fails, test with enclosing network X.X.X.X , X.X.X.0 , X.X.0.0 ... network = addr.mask(mask).to_i c = db.city(network) if c && c.timezone return c.timezone end end end nil end #a similar pattern can be re-used def country_code(ipv4) if ipv4.present? candidate = ipv4.to_s.strip addr = IPAddr.new(ipv4.to_s.strip) 32.downto(16).map do |mask| network = addr.mask(mask).to_i c = db.city(network) if c && c.country_code2 return c.country_code2 end end end nil end def location(ipv4) if ipv4.present? candidate = ipv4.to_s.strip addr = IPAddr.new(ipv4.to_s.strip) 32.downto(16).map do |mask| network = addr.mask(mask).to_i c = db.city(network) return c if c end end nil end def db @db ||= GeoIP.new(DB_PATH) end end
To resolve the ip
locator = Ip2Geo.new
locator.timezone('193.110.128.199')
Attribution
Remember that the use of the free database requires an attribution: The attribution requirement may be met by including the following in all advertising and documentation mentioning features of or use of this databaseThis product includes GeoLite data created by MaxMind, available from http://www.maxmind.com