自動判斷時區(TimeZone)與時間轉換之IP address database

  • 30529
  • 0
  • .Net
  • 2012-01-04

前些日子,一個專案因為使用者分散在不同的國家中,所以有一個需求是必需要自動判斷使用者所在的時區,將時間轉換成當地時間,有找到幾個方案,其中使用IP address database是比較簡單的,有很多公司有推出IP對應的國家、地區、時區,大部分都要錢,不然就是免費版本的資料量不多,剛好有找到一家完全免費的IpInfoDB,本篇分享如何用此資料庫完成時區轉換。

前些日子,一個專案因為使用者分散在不同的國家中,所以有一個需求是必需要自動判斷使用者所在的時區,將時間轉換成當地時間,有找到幾個方案,其中使用IP address database是比較簡單的,有很多公司有推出IP對應的國家、地區、時區,大部分都要錢,不然就是免費版本的資料量不多,剛好有找到一家完全免費的IpInfoDB,本篇分享如何用此資料庫完成時區轉換。

 

新增資料庫

 image

網址:http://ipinfodb.com/

 

資料庫下載頁:http://ipinfodb.com/ip_database.php

image

有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.第一行是資料欄。

  image

 

匯入時預設的格式為String,有三個資料欄一定要轉換,不然計算會出錯。

  • ip_group_city.csv的ip_start(bigint)。
  • timezones.csv的start(bigint)gmtoff(int)

注:因為ip_start是無符號int,MS SQL沒有這種格式,所以用bigint。

image

 

資料格式說明

ip_group_city

image

ip_start ip的Int格式,你可以打開小算盤(Win7),來算一下值是什麼意思了。

image

latitude、longitude是經緯度。

查詢時只要取出比目前IP大的第一筆就可以了,SQL如下

SELECT TOP 1 * FROM ip_group_city where ip_start <= 1249717604 order by ip_start desc

 

fips_regions.csv

 image
從ip_group_city資料表可以取得Country_Code與Region_Code就可以判斷出timezone。
 
 

timezones_data.csv

image
start與gmtoff 的大小是秒數,start是MySql的格式,以1970/1/1時為基準,可以用下列SQL取得正確時間。
select dateadd(SS,339102000,'1970/1/1') --1980-09-29 19:00:00.000
abbreviation指是是時間格式
  • CST Central Standard Time - 美國中部標準時間
  • Central Daylight Time - 美國夏令時間,要有加減一小時,還沒完全搞懂,所以此範例沒有判斷此。

Select取start最大的一筆就可以了。

 

範例

image

這是一個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"];//如果有參數使用參數
}
 
2.將IP轉成uint
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]);

 
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資料必需手動自行建立。