How to detect visitor’s country using his IP address
Software77 is perhaps the first company which created IP to country database. On their website you can grab the DB in CSV and GeoIP format with a few example scripts in various languages to use the DB. I couldn’t find anything in PHP to operate on their GeoIP format DB, so I converted the CSV version to PHP arrays and created a simple script to handle this new format.
Usage example:
$i = new Ip2Country;
//run below function once only. It will parse IpToCountry.csv
//file into PHP files and save them into php_db directory
$i->parseCSV();
//to display countryCode:
echo $i->load('24.24.24.24')->countryCode;
//to display country and countryCode:
$i->load('24.24.24.24');
echo $i->countryCode;
echo $i->country;
Download link to the DB in CSV format:
PHP class download:
Update (04.02.2010):
Function parseCSV() is storing over 99 000 entries in a memory table before saving to files. Each table entry in PHP is taking a lot of space, so the script needs more than 40 MB of RAM. So I added the parseCSV2() function, which does exactly the same, but requires less than 1 MB of RAM. New function is a bit slower, as it is doing more disk operations.
Update (01.06.2010):
Fixed a bug: IPs are now compared as floats not strings. Thanks EdwardRF for spotting the bug!

February 2nd, 2010 at 1:21 pm
Hi. Where do you store .csv file in server? And where are you creating php_db directory?
February 2nd, 2010 at 3:11 pm
Put them in the same directory as the file that includes the class.
You can also set output directory this way:
$i = new Ip2Country;
$i->dir = ‘mydirectory/subdirectory’;
To set another path to CSV file:
$i->parseCSV(‘otherdir/IpToCountry.csv’);
After you run parseCSV() and have php_db directory filled with files you can delete CSV file.
Where you are unsure what is your current directory you can run this on Windows:
print_r(system(‘dir’));
or this on Linux:
print_r(system(‘pwd’));
This will print you the path.
February 4th, 2010 at 4:09 pm
I keep getting the following error when trying to run the class:
Fatal error: Allowed memory size of 33554432 bytes exhausted (tried to allocate 12 bytes) in /home/labdirec/public_html/ip2country/Ip2Country.php on line 289
Could you help me out?
February 4th, 2010 at 4:43 pm
It seems there’s not enough RAM for the script.
Please add this before calling parseCSV():
ini_set(‘memory_limit’, ‘48M’);
It will set memory limit to 48 MB, which is enough.
After you run parseCSV() and have all the small PHP files created you can even set:
ini_set(‘memory_limit’, ‘4M’);
for the script.
I will try to update the script, so it will need less RAM for parseCSV().
February 4th, 2010 at 4:56 pm
I’m still getting basically the same error after adding that line.
February 4th, 2010 at 4:59 pm
Below is the code
parseCSV();
//to display countryCode:
echo $i->load(‘24.24.24.24′)->countryCode;
//to display country and countryCode:
$i->load(‘24.24.24.24′);
echo $i->countryCode;
echo $i->country;
?>
February 4th, 2010 at 5:00 pm
Here is my code, sorry, the above must have pulled out the parts it didn’t like:
require ‘Ip2Country.php’;
$i = new Ip2Country;
ini_set(‘memory_limit’, ‘48M’);
//run below function once only. It will parse IpToCountry.csv
//file into PHP files and save them into php_db directory
$i->parseCSV();
//to display countryCode:
echo $i->load(‘24.24.24.24′)->countryCode;
//to display country and countryCode:
$i->load(‘24.24.24.24′);
echo $i->countryCode;
echo $i->country;
February 4th, 2010 at 5:16 pm
Wow, I got it. Moved it to 120M, to be safe. Went straight through. I don’t know what the magic number is, but 120M is safe.
Thanks.
February 4th, 2010 at 5:37 pm
I updated the class. It now contains new function- parseCSV2(). You can see details in the post above.
February 24th, 2010 at 12:01 pm
hello admin, I have .csv – file this format and use function parseCSV2.
16777216,”33554431″,”AU”,”AUS”,”AUSTRALIA”
What should be the format .csv-file ?
Thanks
February 24th, 2010 at 12:26 pm
if I send you ftp-access, can you install script ?
Thanks,
February 24th, 2010 at 2:07 pm
Victor: Your CSV file is incompatible with this script.
You can get CSV file compatible with this script using the link in this article.
The file should have lines like this:
“1159262208″,”1159266303″,”arin”,”1247788800″,”US”,”USA”,”United States”
(IpFrom, IpTo, X, X, 2LetterCountryCode, X, CountryName)
Values from columns marked X are ignored, but they have to be there.
You can edit the script and change this line (#368):
list($from, $to, $a, $b, $code, $c, $country) = $temp;
to:
list($from, $to, $code, $a, $country) = $temp;
And it should work with your CSV file.
March 5th, 2010 at 4:03 pm
How to use the updated class??
after i did parseCSV2() it says
Warning: fopen(php_db/0.php)xxxxxxx
i get million lines of errors like this…
what do i do?
March 5th, 2010 at 4:18 pm
Does php_db directory exist and is writable? Can you see any files created in this directory?
March 10th, 2010 at 7:45 pm
Thanks for this. I will try it on my on.
March 27th, 2010 at 2:43 pm
I don’t usually post but I enjoyed your blog a lot, thanks alot for the great read.
April 16th, 2010 at 7:24 pm
tanks very much for this info
May 31st, 2010 at 9:37 am
Thanks a lot for this handy tool. But I’ve realize there is a bug in the script. The load function is comparing the IP addresses as string, this normally works well, but there are cases this would fail.
e.g. “419.php”
<?php $entries = array(
array('419430400','436207615','GB'),
array('4194304000','4211081215','ZZ'),
);
If the IP int is 4194304100, it would return you GB instead of ZZ.
I think the simple fix is to use intval
if (intval($e[0]) = intval($ip))
June 1st, 2010 at 2:24 pm
Yes, there is a bug, but please note that:
intval(‘4194304100′) == intval(‘4194304000′)
so your code will not solve the bug.
However floatval() does the trick, so I updated the code. Thanks!
June 2nd, 2010 at 2:33 am
I didn’t notice that, Thanks, i’ll correct the code too.
July 12th, 2010 at 4:05 am
Great website. Thanks!
September 9th, 2010 at 2:24 am
Can’t we use database instead of using CSV file?
Will be there any problem in using the database?
September 16th, 2010 at 9:55 am
Suresh N: You can use database, but I am not sure if it won’t be slower..
October 25th, 2010 at 3:18 pm
There is a bug:
Parse error: syntax error, unexpected ‘,’ in /www/php_db/133.php on line 309
Which is:
***************************************
array(‘1337982976′,’1342177279′,’DE’),
);array(‘1330642944′,’1331691519′,’FR’),
***************************************
(the comma before the parenthesis)
Your function parseCSV2() needs to be fixed.
Thanks.
October 25th, 2010 at 7:12 pm
********
To have it working in the meantime, do a replace in files for “);array” to “array”
October 25th, 2010 at 7:36 pm
Apart from the little bug in parseCSV2() this peace of code is reqlly good. Thank you very much!
October 27th, 2010 at 8:14 pm
Thanks Olivier for pointing the bug and providing a solution. I’ll try to fix the class, but I am terribly busy these days.
November 7th, 2010 at 1:05 pm
I found a bug.
If you parse 78.21.104.246 it will convert to 1310025974.
However 78.21.104.246 is in block 78.21.0.0/19
(1309933568 – 1310195711), so parseCSV2 only add’s it in 130.php.
Result if you do a lookup the IP isn’t found.
Solution:
If the first 3 digits of the to-IP is not the same 3 digits from the from-IP, just put the array also in the to-array.
This is the change code in parseCSV2 (++ is added)
$piece = substr($from, 0, 3);
++ $topiece = substr($to, 0, 3);
$db[$piece][] = array($from, $to, $code);
$dbSize++;
++ if ($topiece > $piece)
++ {
++ $db[$topiece][] = array($from, $to, $code);
++ $dbSize++;
++ }
November 11th, 2010 at 2:37 am
Thanks, Pascal!
February 18th, 2011 at 2:14 pm
Oliver, Admin:
FIND:
REPLACE:
March 28th, 2011 at 12:40 am
Or try:
public function parseCSV($filename = 'IpToCountry.csv') { $f = fopen($filename, 'r'); $db = array(); //parse into array while (!feof($f)) { $s = fgets($f); if (substr($s, 0, 1) == '#') continue; $temp = explode(',', $s); if (count($temp)<7) continue; list($from, $to, $a, $b, $code, $c, $country) = $temp; $from = trim($from, '"'); $to = trim($to, '"'); $code = trim($code, '"'); $piece = substr($from, 0, 3); $db[$piece][] = array($from, $to, $code); } fclose($f); //dump array into many PHP files foreach ($db AS $piece=>$entries) { $f = fopen($this->dir . $piece . '.php', 'w'); fputs($f, '<?php $entries = array(' . "\n"); foreach ($entries AS $from=>$entry) { fputs($f, "array('" . $entry[0] . "','" . $entry[1] . "','" . $entry[2] . "'),\n"); } fputs($f, ');'); fclose($f); } }April 15th, 2011 at 6:07 pm
DOgi’s fix above worked fine, from what I can tell.
For any newcomers: Just replace the parseCSV function in the Ip2Country.php with DOgi’s updated one, and then run the example.php to build the database. You might need to create the php_db folder manually if it doesn’t exist in the same directory.
Thanks to the author for making a simple solution that does not require advanced database lookups, and thanks to everyone who contributed in the thread. You made my day today.
)
May 27th, 2011 at 11:08 pm
This represents a really cost-effective method of reaching potential prospects.
June 23rd, 2011 at 5:28 pm
I get an error : “Parse error: syntax error, unexpected T_STRING, expecting T_OLD_FUNCTION or T_FUNCTION or T_VAR or ‘}’ in Ip2Country.php on line 12″
And I cannot find any probleme in the file. I just donwloaded it and put it on the webserver, no modifications at all …
Can you help me please ?
July 23rd, 2011 at 4:26 pm
Any plans to update this to use the IPv6 database?
August 13th, 2011 at 5:02 am
Thanks man! your code was super-helpful!
I made my own version hacking your code, of course, but you save me a lot of hours. Mainly i copy from your code the idea of the database splitting using the 3 first digits! great!
Cheers, from Hermosillo, Mexico.
SERGI
December 10th, 2011 at 8:19 am
A similar project
Ip2Country without Mysql
http://rob72.net78.net
Demo:
http://rob72.net78.net?demo
March 15th, 2012 at 11:40 pm
file downloaded : “http://software77.net/geo-ip/?DL=1″
[quote]
# IP FROM IP TO REGISTRY ASSIGNED CTRY CNTRY COUNTRY
# “1346797568″,”1346801663″,”ripencc”,”20010601″,”il”,”isr”,”Israel”
[/quote]
1346797568 ?
is 134.679.756.8
or 1.346.797.568
i dont understand, i think max ip is 255 also its grouped into 4 -> like -> 128.123.200.240
[quote=@line 275]
“16777216″,”16777471″,”apnic”,”1313020800″,”AU”,”AUS”,”Australia”
[/quote]
16777216=16.777.216 ?
i really dont understand
please explain it.
thank u.
April 9th, 2012 at 8:01 pm
Yesterday everything worked fine, but now went back to get the syntax error by ); review the files of the arrays and turns out I doubled the lines of code, do not understand why? can anyone help?
May 6th, 2012 at 6:24 pm
This is 32bit notation. Instead of 4 bytes: A.B.C.D you have 1 32bit Integer = A*256*256*256+B*256*256+C*256+D
May 6th, 2012 at 6:29 pm
Such editors can’t work from textarea. Please copy html from textarea to a div, use the editor and after you are done with editing- copy it back to textarea.
May 6th, 2012 at 6:30 pm
Delete cache files (PHPs created from CSV) and run the script again to generate new PHP files.
June 20th, 2012 at 8:35 pm
Hi,
I worked with the script / class and was not satisfied with the import duration. I recoded the import and added one function. Reworked class imports geo-data slightly faster and has a new function to change the data path. The code is here: http://pastebin.com/UfmqGwBa
Hope, you’ll like it.
Thanks & best regards.
September 6th, 2012 at 8:26 am
BUG BUG BUG!!!
I discovered this after noticing many IPS not working.
Example IP: 98.247.53.116
It will register as ? (unknown using this script)
Reason:
The IP4 value is 1660368244
It checks the file 166.php which does exist properly, but has no array of values that contains the interval.
BUT when I checked it on http://software77.net/geo-ip/ it was registering as a US IP. So I debugged further.
File 165.php did have the proper interval, but it spread out from 165xxxxxxxxx to 166xxxxxxxxxx.
Solution, replace the code in ParseCVS with the following.
$pieceFrom = substr($from, 0, 3);
$pieceTo = substr($to, 0, 3);
for ($i=$pieceFrom; $i<=$pieceTo; $i++)
{
$db[$i][] = array($from, $to, $code);
}
October 3rd, 2012 at 5:42 am
None of the fixes quite get it right. For example, try this IP address: 50.196.142.177 The problem is that the range of decimal IPs covered can be quite large. The above IP falls into a range with over 8,000,000 IP addresses. So just puttig it in 1 or 2 of the php_db files is not sufficient. This fix corrects that, but it does create a few extra files for IP addresses that will never occur.
*** Ip2Country.php 2012-10-03 05:33:42.412162893 +0000
— /var/www/html/Ip2Country.php 2012-10-03 03:36:24.535563936 +0000
***************
*** 307,353 ****
fclose($f);
}
! public function parseCSV($filename = ‘IpToCountry.csv’)
{
! $f = fopen($filename, ‘r’);
! $db = array();
!
! //parse into array
! while (!feof($f))
{
! $s = fgets($f);
! if (substr($s, 0, 1) == ‘#’) continue;
!
! $temp = explode(‘,’, $s);
! if (count($temp)$entries)
- {
- $f = fopen($this->dir . $piece . ‘.php’, ‘w’);
- fputs($f, ‘$entry)
- {
- fputs($f, “array(‘” . $entry[0] . “‘,’” . $entry[1] . “‘,’” . $entry[2] . “‘),\n”);
- }
-
- fputs($f, ‘);’);
- fclose($f);
- }
}
public function parseCSV2($filename = ‘IpToCountry.csv’)
{
— 307,356 —-
fclose($f);
}
! public function parseCSV($filename = ‘IpToCountry.csv’)
! {
! $f = fopen($filename, ‘r’);
! $db = array();
!
! //parse into array
! while (!feof($f))
{
! $s = fgets($f);
! if (substr($s, 0, 1) == ‘#’) continue;
!
! $temp = explode(‘,’, $s);
! if (count($temp)<7) continue;
!
! list($from, $to, $a, $b, $code, $c, $country) = $temp;
!
! $from = trim($from, '"');
! $to = trim($to, '"');
! $code = trim($code, '"');
! $fromPiece = substr($from, 0, 3);
! $toPiece = substr($to, 0, 3);
!
! for ($piece = $fromPiece; $piece $entries)
! {
! $f = fopen($this->dir . $piece . ‘.php’, ‘w’);
! fputs($f, ‘$entry)
{
! fputs($f, “array(‘” . $entry[0] . “‘,’” . $entry[1] . “‘,’” . $entry[2] . “‘),\n”);
}
+
+ fputs($f, ‘);’);
fclose($f);
}
+ }
+
public function parseCSV2($filename = ‘IpToCountry.csv’)
{
***************
*** 370,387 ****
$from = trim($from, ‘”‘);
$to = trim($to, ‘”‘);
$code = trim($code, ‘”‘);
!
! $piece = substr($from, 0, 3);
! $topiece = substr($to, 0, 3);
!
! $db[$piece][] = array($from, $to, $code);
! $dbSize++;
!
! if ($topiece > $piece) {
! $db[$topiece][] = array($from, $to, $code);
$dbSize++;
}
!
if ($dbSize>100)
{
$this->appendToFile($db);
— 373,386 —-
$from = trim($from, ‘”‘);
$to = trim($to, ‘”‘);
$code = trim($code, ‘”‘);
! $fromPiece = substr($from, 0, 3);
! $toPiece = substr($to, 0, 3);
!
! for ($piece = $fromPiece; $piece 100)
{
$this->appendToFile($db);
***************
*** 467,470 ****
return false;
}
! }
October 11th, 2012 at 10:57 am
Thanks. My code method might not be perfect but still it’s quite reliable and no method is 100% perfect.
January 14th, 2013 at 8:09 pm
It is not a good code. Look at the no of files you are creating in the process. A better solution would be to 1) to pick entries from excel and post it in a database
2)To use xml file.
But anyways you have shown the way. Good Work
January 14th, 2013 at 8:23 pm
1) If you want to use a database then there are other solutions. This is file-based one. And I think it can be faster than DB-based solution
2) XML files are terribly, terribly slow.
January 14th, 2013 at 8:25 pm
No, sorry. IPv6 is not popular yet.
January 14th, 2013 at 8:25 pm
The files work just fine. Maybe your PHP is too old?