前些日子,一個專案因為使用者分散在不同的國家中,所以有一個需求是必需要自動判斷使用者所在的時區,將時間轉換成當地時間,有找到幾個方案,其中使用IP address database是比較簡單的,有很多公司有推出IP對應的國家、地區、時區,大部分都要錢,不然就是免費版本的資料量不多,剛好有找到一家完全免費的IpInfoDB,本篇分享如何用此資料庫完成時區轉換。
前些日子,一個專案因為使用者分散在不同的國家中,所以有一個需求是必需要自動判斷使用者所在的時區,將時間轉換成當地時間,有找到幾個方案,其中使用IP address database是比較簡單的,有很多公司有推出IP對應的國家、地區、時區,大部分都要錢,不然就是免費版本的資料量不多,剛好有找到一家完全免費的IpInfoDB,本篇分享如何用此資料庫完成時區轉換。
新增資料庫
資料庫下載頁:http://ipinfodb.com/ip_database.php
有City與Country二種資料庫,像台灣這小地方,不同的城市都是相同的時區,但像美國或加拿大這些地大的國家,不同的城市有不同的時區,所以如果只下載Country的資料庫,沒有辦法得知正確的時區,而他City又分Small或Complete二種,如果只是測試下載Small應該足夠,因為我覺得Select時一個table比較省事,所以我用One DataTables格式。
時區下載頁:http://ipinfodb.com/timezonedatabase.php
格式有 SQL、CSV二種,SQL是MySql的格式,不是標準的SQL,如果你的資料庫引擎不是MySql,請不要下載,而我是用MS SQL所以我下載CSV檔。
二個壓縮檔解開,裡面共有6個檔案
- ip_group_country.csv 不新增 IP對應的國家,因為City資料表就包含國家資訊了,所以用不到這個資料表。
- ip_group_city.csv 新增 IP對應的城市,包含的國家資訊與座標。
- iso3166_countries.csv 不新增 縮寫對應的國家,如TW=Taiwan,資訊在City中包含。
- fips_regions.csv 新增 Federal Information Processing Standards(聯邦資料處理標準)地區代碼,城市對應的時區。
- timezones.csv 選項 時區的名稱,如Asia/Taipei,轉換時區用不到,不過可以用來顯示資訊。
- timezones_data.csv 新增 時區對應的UTC Offset。
我是用MS SQL的Import and Export Wizard來完成資料的匯入,在匯入過程中有遇到一些小問題,各位要注意。
1.檔案的換行是{LF}。
2.文字有用雙引號"分隔。
3.第一行是資料欄。
匯入時預設的格式為String,有三個資料欄一定要轉換,不然計算會出錯。
- ip_group_city.csv的ip_start(bigint)。
- timezones.csv的start(bigint)、gmtoff(int)。
注:因為ip_start是無符號int,MS SQL沒有這種格式,所以用bigint。
資料格式說明
ip_group_city
ip_start ip的Int格式,你可以打開小算盤(Win7),來算一下值是什麼意思了。
latitude、longitude是經緯度。
查詢時只要取出比目前IP大的第一筆就可以了,SQL如下
SELECT TOP 1 * FROM ip_group_city where ip_start <= 1249717604 order by ip_start desc
fips_regions.csv
timezones_data.csv
select dateadd(SS,339102000,'1970/1/1') --1980-09-29 19:00:00.000
- CST Central Standard Time - 美國中部標準時間
- Central Daylight Time - 美國夏令時間,要有加減一小時,還沒完全搞懂,所以此範例沒有判斷此。
Select取start最大的一筆就可以了。
範例
這是一個IP的測試範例,包含了受測IP、所在地、時區等資訊。
1.首先要取得IP
string ip;
if (string.IsNullOrEmpty(this.Request.QueryString["ip"]))
{
ip = this.Request.UserHostAddress;//UserHostAddress可以取得用戶IP
}
else
{
ip = this.Request.QueryString["ip"];//如果有參數使用參數
}
string[] temp = ip.Split('.');
//ip沒有正負號,所以用uint
uint ipInt = (uint.Parse(temp[0]) << 24) + (uint.Parse(temp[1]) << 16) + (uint.Parse(temp[2]) << 8) + uint.Parse(temp[3]);
//使用Linq取得資訊。
IPInfoDataContext ipInfo = new IPInfoDataContext();
//城市資訊
var city = ipInfo.ip_group_cities
.Where(x => x.ip_start >= ipInt)
.OrderBy(x => x.ip_start)
.First();
//區域資訊
var region = ipInfo.fips_regions
.Where(x => x.country_code == city.country_code && x.code == city.region_code)
.First();
//時區名稱
var timezone = ipInfo.timezones
.Where(x => x.id == region.timezone)
.First();
//時區資料
var timezoneData = ipInfo.timezones_datas
.Where(x => x.timezone == region.timezone)
.OrderByDescending(x => x.start)
.First();
4.顯示資訊
this.ZoneLabel.InnerText = timezone.name;
this.LocalLabel.InnerText = string.Format("{0}({1})", city.region_name, city.country_name); ;
this.OffsetLabel.InnerText = (timezoneData.gmtoff / 3600).ToString();
this.LocalTimeLabel.InnerText = DateTime.UtcNow.AddSeconds((double)timezoneData.gmtoff).ToString();
this.Map.Attributes.Add("src", string.Format("http://maps.google.com.tw/maps?f=q&source=s_q&q={0}+{1}&z=7&output=embed", city.latitude, city.longitude));
結語
使用這個方案,有幾個缺點就是
- IP的地區如果變動不會知道,如果是買的,可能還有售後服務,免費的要資料是最新的可能有點難。
- VPN資料必需手動自行建立。